Wikilivres frwikibooks https://fr.wikibooks.org/wiki/Accueil MediaWiki 1.46.0-wmf.24 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 Philosophie/Histoire de la philosophie 0 615 764694 764341 2026-04-23T19:29:16Z PandaMystique 119061 764694 wikitext text/x-wiki <!-- ═══════════════════════════════════════════════════════════════════ NAVIGATION PAR PÉRIODE ═══════════════════════════════════════════════════════════════════ --> <div style="background: #f8f9fa; border: 1px solid #e0e0e0; border-radius: 6px; padding: 14px 20px; margin-bottom: 25px; text-align: center; font-size: 0.9em;"> <span style="color: #666; margin-right: 8px;">Naviguer :</span> [[#Antique|<span style="color: #8a5a30;">● Antiquité</span>]] &nbsp;•&nbsp; [[#Moderne|<span style="color: #4a6a8a;">● Époque moderne</span>]] &nbsp;•&nbsp; [[#Contemporaine|<span style="color: #5a3d7a;">● Contemporaine</span>]] &nbsp;•&nbsp; [[#Bibliographie|<span style="color: #555;">● Bibliographie</span>]] </div> <!-- ═══════════════════════════════════════════════════════════════════ FRISE CHRONOLOGIQUE SIMPLIFIÉE ═══════════════════════════════════════════════════════════════════ --> <table style="width: 100%; border-collapse: collapse; margin-bottom: 30px;"> <tr> <td style="width: 25%; background: linear-gradient(180deg, #f5efe5, #ebe3d5); border: 1px solid #c9b896; border-radius: 6px 0 0 6px; padding: 12px 15px; text-align: center;"> <div style="font-size: 0.75em; color: #8a6a40; text-transform: uppercase; letter-spacing: 0.08em;">Antiquité</div> <div style="font-size: 0.85em; color: #5a4a30; margin-top: 4px;">VIᵉ s. av. J.-C. → Vᵉ s.</div> </td> <td style="width: 25%; background: linear-gradient(180deg, #e8f0f5, #dae6ef); border: 1px solid #a0b8c8; padding: 12px 15px; text-align: center;"> <div style="font-size: 0.75em; color: #4a6a85; text-transform: uppercase; letter-spacing: 0.08em;">Moyen Âge</div> <div style="font-size: 0.85em; color: #3a5065; margin-top: 4px;">Vᵉ → XVᵉ s.</div> </td> <td style="width: 25%; background: linear-gradient(180deg, #eef5f0, #e0ede5); border: 1px solid #7aa088; padding: 12px 15px; text-align: center;"> <div style="font-size: 0.75em; color: #4a7a5a; text-transform: uppercase; letter-spacing: 0.08em;">Époque moderne</div> <div style="font-size: 0.85em; color: #3a5a45; margin-top: 4px;">XVIᵉ → XVIIIᵉ s.</div> </td> <td style="width: 25%; background: linear-gradient(180deg, #f0eaf5, #e4daf0); border: 1px solid #9080a8; border-radius: 0 6px 6px 0; padding: 12px 15px; text-align: center;"> <div style="font-size: 0.75em; color: #6a5080; text-transform: uppercase; letter-spacing: 0.08em;">Contemporaine</div> <div style="font-size: 0.85em; color: #4a3860; margin-top: 4px;">XIXᵉ → XXIᵉ s.</div> </td> </tr> </table> <!-- ═══════════════════════════════════════════════════════════════════ SECTION : PHILOSOPHIE ANTIQUE ═══════════════════════════════════════════════════════════════════ --> <div id="Antique" style="margin: 30px 0 15px 0; padding-bottom: 6px; border-bottom: 2px solid #c9a860;"> <span style="font-size: 1.15em; font-weight: 700; color: #6a5020; letter-spacing: 0.02em;">PHILOSOPHIE ANTIQUE</span> <span style="font-size: 0.85em; color: #999; margin-left: 12px;">VIᵉ siècle av. J.-C. — Vᵉ siècle</span> </div> <table style="width:100%; border-collapse:separate; border-spacing:12px;"> <tr> <!-- PRÉSOCRATIQUES --> <td style="width:33%; vertical-align:top; position:relative; background:linear-gradient(180deg, #faf6ed, #f2ebdc); border:1px solid #d4c090; border-radius:8px; padding:16px 18px; overflow:hidden;"> <div style="font-weight:bold; font-size:1.05em; color:#5a4a20; margin-bottom:8px;">🌅 [[Philosophie/Présocratiques|Présocratiques]]</div> <div style="font-size:0.88em; color:#555; line-height:1.6; margin-bottom:10px;">Les premiers penseurs grecs cherchent le principe (''archè'') de toutes choses.</div> <div style="font-size:0.85em; color:#666; border-top:1px dashed #d4c090; padding-top:8px;"> → [[Philosophie/Thalès de Milet|Thalès de Milet]]<br/> → [[Philosophie/Anaximandre de Milet|Anaximandre]]<br/> → [[Dictionnaire de philosophie/Anaxagore|Anaxagore]] </div> </td> <!-- PLATON --> <td style="width:33%; vertical-align:top; position:relative; background:linear-gradient(180deg, #fdf8ed, #f6eedc); border:1px solid #d4b860; border-radius:8px; padding:16px 18px; overflow:hidden;"> <div style="font-weight:bold; font-size:1.05em; color:#5a4800; margin-bottom:8px;">📜 [[Pour lire Platon|Platon]]</div> <div style="font-size:0.88em; color:#555; line-height:1.6; margin-bottom:10px;">Théorie des Idées, dialectique et fondation de l'Académie.</div> <div style="font-size:0.85em; color:#666; border-top:1px dashed #d4b860; padding-top:8px;"> → [[Pour lire Platon/Guide des dialogues/Apologie de Socrate|Apologie de Socrate]]<br/> → [[Pour lire Platon/Guide des dialogues/Ion|Ion]]<br/> → [[Pour lire Platon/Vocabulaire|Vocabulaire platonicien]] </div> </td> <!-- STOÏCIENS --> <td style="width:33%; vertical-align:top; position:relative; background:linear-gradient(180deg, #f8f4e8, #f0e8d8); border:1px solid #c8b070; border-radius:8px; padding:16px 18px; overflow:hidden;"> <div style="font-weight:bold; font-size:1.05em; color:#5a4820; margin-bottom:8px;">🏛️ Stoïcisme</div> <div style="font-size:0.88em; color:#555; line-height:1.6; margin-bottom:10px;">Vivre selon la nature, maîtriser ses passions, accepter le destin.</div> <div style="font-size:0.85em; color:#666; border-top:1px dashed #c8b070; padding-top:8px;"> → [[Les Stoïciens : Épictète Le poignard à la main|Épictète]] </div> </td> </tr> </table> <!-- ═══════════════════════════════════════════════════════════════════ SECTION : PHILOSOPHIE MODERNE ═══════════════════════════════════════════════════════════════════ --> <div id="Moderne" style="margin: 35px 0 15px 0; padding-bottom: 6px; border-bottom: 2px solid #4a90b8;"> <span style="font-size: 1.15em; font-weight: 700; color: #2a5a7a; letter-spacing: 0.02em;">PHILOSOPHIE MODERNE</span> <span style="font-size: 0.85em; color: #999; margin-left: 12px;">XVIᵉ — XVIIIᵉ siècle</span> </div> <!-- Sous-section : XVIIe siècle --> <div style="font-size: 0.9em; font-weight: 600; color: #4a6a85; margin: 20px 0 12px 0; padding-left: 10px; border-left: 3px solid #a0c0d8;">XVIIᵉ siècle — L'âge classique</div> <table style="width:100%; border-collapse:separate; border-spacing:12px;"> <tr> <!-- DESCARTES --> <td style="width:33%; vertical-align:top; position:relative; background:linear-gradient(180deg, #eef4f8, #e0ecf4); border:1px solid #6090b8; border-radius:8px; padding:16px 18px; overflow:hidden;"> <div style="font-weight:bold; font-size:1.05em; color:#1a4a6a; margin-bottom:4px;">[[Dictionnaire de philosophie/René Descartes|René Descartes]]</div> <div style="font-size:0.8em; color:#888; margin-bottom:8px;">1596 — 1650</div> <div style="font-size:0.88em; color:#555; line-height:1.6; margin-bottom:10px;">Le ''cogito'', le doute méthodique et la fondation de la philosophie moderne.</div> <div style="font-size:0.85em; color:#666; border-top:1px dashed #6090b8; padding-top:8px;"> → [[Méditations métaphysiques]] </div> </td> <!-- SPINOZA --> <td style="width:33%; vertical-align:top; position:relative; background:linear-gradient(180deg, #edf5f2, #dfeee8); border:1px solid #5a9878; border-radius:8px; padding:16px 18px; overflow:hidden;"> <div style="font-weight:bold; font-size:1.05em; color:#1a5038; margin-bottom:4px;">Baruch Spinoza</div> <div style="font-size:0.8em; color:#888; margin-bottom:8px;">1632 — 1677</div> <div style="font-size:0.88em; color:#555; line-height:1.6; margin-bottom:10px;">Substance unique, déterminisme et béatitude par la connaissance.</div> <div style="font-size:0.85em; color:#666; border-top:1px dashed #5a9878; padding-top:8px;"> → [[Commentaire de l'Éthique]] </div> </td> <!-- PASCAL --> <td style="width:33%; vertical-align:top; position:relative; background:linear-gradient(180deg, #f5f0f8, #ebe4f2); border:1px solid #8878a8; border-radius:8px; padding:16px 18px; overflow:hidden;"> <div style="font-weight:bold; font-size:1.05em; color:#3a2860; margin-bottom:4px;">Blaise Pascal</div> <div style="font-size:0.8em; color:#888; margin-bottom:8px;">1623 — 1662</div> <div style="font-size:0.88em; color:#555; line-height:1.6; margin-bottom:10px;">Misère et grandeur de l'homme, le pari, la condition humaine.</div> <div style="font-size:0.85em; color:#666; border-top:1px dashed #8878a8; padding-top:8px;"> → [[Philosophie/Commentaire du passage à propos de l'Homme esclave du divertissement|Le divertissement]]<br/> → [[Philosophie/Commentaire du passage à propos des deux infinis|Les deux infinis]] </div> </td> </tr> </table> <!-- Sous-section : XVIIIe siècle --> <div style="font-size: 0.9em; font-weight: 600; color: #4a6a85; margin: 25px 0 12px 0; padding-left: 10px; border-left: 3px solid #a0c0d8;">XVIIIᵉ siècle — Les Lumières</div> <table style="width:100%; border-collapse:separate; border-spacing:12px;"> <tr> <!-- HUME --> <td style="width:50%; vertical-align:top; position:relative; background:linear-gradient(180deg, #f0f5f8, #e2eef5); border:1px solid #5088b0; border-radius:8px; padding:16px 18px; overflow:hidden;"> <div style="font-weight:bold; font-size:1.05em; color:#1a4a68; margin-bottom:4px;">David Hume</div> <div style="font-size:0.8em; color:#888; margin-bottom:8px;">1711 — 1776</div> <div style="font-size:0.88em; color:#555; line-height:1.6; margin-bottom:10px;">Empirisme radical, critique de la causalité et scepticisme modéré.</div> <div style="font-size:0.85em; color:#666; border-top:1px dashed #5088b0; padding-top:8px;"> → [[Philosophie/Vocabulaire/David Hume|Vocabulaire]] </div> </td> <!-- KANT --> <td style="width:50%; vertical-align:top; position:relative; background:linear-gradient(180deg, #eef2f8, #e0e8f2); border:1px solid #6080a8; border-radius:8px; padding:16px 18px; overflow:hidden;"> <div style="font-weight:bold; font-size:1.05em; color:#2a3860; margin-bottom:4px;">Emmanuel Kant</div> <div style="font-size:0.8em; color:#888; margin-bottom:8px;">1724 — 1804</div> <div style="font-size:0.88em; color:#555; line-height:1.6; margin-bottom:10px;">Révolution copernicienne, critique de la raison et impératif catégorique.</div> <div style="font-size:0.85em; color:#666; border-top:1px dashed #6080a8; padding-top:8px;"> → [[Philosophie/Vocabulaire/Kant|Vocabulaire]]<br/> → [[Commentaire de la Fondation de la métaphysique des mœurs|Fondation de la métaphysique des mœurs]] </div> </td> </tr> </table> <!-- ═══════════════════════════════════════════════════════════════════ SECTION : PHILOSOPHIE CONTEMPORAINE ═══════════════════════════════════════════════════════════════════ --> <div id="Contemporaine" style="margin: 35px 0 15px 0; padding-bottom: 6px; border-bottom: 2px solid #9070b0;"> <span style="font-size: 1.15em; font-weight: 700; color: #5a3d7a; letter-spacing: 0.02em;">PHILOSOPHIE CONTEMPORAINE</span> <span style="font-size: 0.85em; color: #999; margin-left: 12px;">XIXᵉ — XXIᵉ siècle</span> </div> <!-- Sous-section : XIXe siècle --> <div style="font-size: 0.9em; font-weight: 600; color: #6a5080; margin: 20px 0 12px 0; padding-left: 10px; border-left: 3px solid #c0a8d0;">XIXᵉ siècle</div> <table style="width:100%; border-collapse:separate; border-spacing:12px;"> <tr> <!-- NIETZSCHE --> <td style="width:50%; vertical-align:top; position:relative; background:linear-gradient(180deg, #f8f0f2, #f0e4e8); border:1px solid #a87080; border-radius:8px; padding:16px 18px; overflow:hidden;"> <div style="font-weight:bold; font-size:1.05em; color:#5a2838; margin-bottom:4px;">[[Philosophie/Nietzsche|Friedrich Nietzsche]]</div> <div style="font-size:0.8em; color:#888; margin-bottom:8px;">1844 — 1900</div> <div style="font-size:0.88em; color:#555; line-height:1.6;">Critique de la morale, volonté de puissance, mort de Dieu et éternel retour.</div> </td> <!-- Placeholder pour futurs philosophes du XIXe --> <td style="width:50%; vertical-align:top; background: #fafafa; border: 1px dashed #ccc; border-radius:8px; padding:16px 18px; color: #999; font-size: 0.9em; text-align: center;"> <div style="margin: 15px 0;">''À venir : Hegel, Marx, Kierkegaard…''</div> </td> </tr> </table> <!-- Sous-section : XXe siècle --> <div style="font-size: 0.9em; font-weight: 600; color: #6a5080; margin: 25px 0 12px 0; padding-left: 10px; border-left: 3px solid #c0a8d0;">XXᵉ siècle</div> <table style="width:100%; border-collapse:separate; border-spacing:12px;"> <tr> <!-- HEIDEGGER --> <td style="width:50%; vertical-align:top; position:relative; background:linear-gradient(180deg, #f3eef8, #e8e0f2); border:1px solid #8068a0; border-radius:8px; padding:16px 18px; overflow:hidden;"> <div style="font-weight:bold; font-size:1.05em; color:#3a2858; margin-bottom:4px;">Martin Heidegger</div> <div style="font-size:0.8em; color:#888; margin-bottom:8px;">1889 — 1976</div> <div style="font-size:0.88em; color:#555; line-height:1.6; margin-bottom:10px;">Question de l'être, ''Dasein'', temporalité et critique de la métaphysique.</div> <div style="font-size:0.85em; color:#666; border-top:1px dashed #8068a0; padding-top:8px;"> → [[Être et Temps]] </div> </td> <!-- Placeholder pour futurs philosophes du XXe --> <td style="width:50%; vertical-align:top; background: #fafafa; border: 1px dashed #ccc; border-radius:8px; padding:16px 18px; color: #999; font-size: 0.9em; text-align: center;"> <div style="margin: 15px 0;">''À venir : Sartre, Merleau-Ponty, Wittgenstein…''</div> </td> </tr> </table> <!-- Sous-section : Courants --> <div style="font-size: 0.9em; font-weight: 600; color: #6a5080; margin: 25px 0 12px 0; padding-left: 10px; border-left: 3px solid #c0a8d0;">Courants philosophiques</div> <table style="width:100%; border-collapse:separate; border-spacing:12px;"> <tr> <!-- PHILOSOPHIE ANALYTIQUE --> <td style="width:33%; vertical-align:top; position:relative; background:linear-gradient(180deg, #f0f2f8, #e5e8f2); border:1px solid #7080a0; border-radius:8px; padding:14px 16px; overflow:hidden;"> <div style="font-weight:bold; font-size:1em; color:#2a3860; margin-bottom:6px;">🔬 [[Philosophie analytique]]</div> <div style="font-size:0.85em; color:#555; line-height:1.5; margin-bottom:10px;">Analyse logique du langage et clarification des concepts.</div> <div style="font-size:0.85em; color:#666; border-top:1px dashed #8068a0; padding-top:8px;"> → [[Ignorance: A Case for Scepticism]] </div> </td> <!-- Placeholders pour futurs courants --> <td style="width:33%; vertical-align:top; background: #fafafa; border: 1px dashed #ccc; border-radius:8px; padding:14px 16px; color: #999; font-size: 0.85em; text-align: center;"> ''À venir : Phénoménologie'' </td> <td style="width:33%; vertical-align:top; background: #fafafa; border: 1px dashed #ccc; border-radius:8px; padding:14px 16px; color: #999; font-size: 0.85em; text-align: center;"> ''À venir : Existentialisme'' </td> </tr> </table> <!-- ═══════════════════════════════════════════════════════════════════ SECTION : BIBLIOGRAPHIE ═══════════════════════════════════════════════════════════════════ --> <div id="Bibliographie" style="margin: 35px 0 15px 0; padding-bottom: 6px; border-bottom: 2px solid #888;"> <span style="font-size: 1.15em; font-weight: 700; color: #444; letter-spacing: 0.02em;">BIBLIOGRAPHIE</span> <span style="font-size: 0.85em; color: #999; margin-left: 12px;">Ouvrages de référence</span> </div> <div style="background: linear-gradient(180deg, #fafafa, #f5f5f5); border: 1px solid #e0e0e0; border-radius: 8px; padding: 18px 22px; font-size: 0.92em; line-height: 1.8; color: #444;"> <div style="margin-bottom: 8px;">📚 <strong>PRADEAU</strong>, Jean-François (dir.), ''Histoire de la philosophie'', Éditions du Seuil, 2009</div> <div style="margin-bottom: 8px;">📚 <strong>CHÂTELET</strong>, François, ''Histoire de la philosophie'', 8 tomes, Paris, Hachette-Pluriel, 1999-2000</div> <div style="margin-bottom: 8px;">📚 <strong>BRÉHIER</strong>, Émile, ''Histoire de la philosophie'', PUF, Quadrige, 2004, {{ISBN|2-13054-396-0}} — [http://classiques.uqac.ca/classiques/brehier_emile/brehier_emile.html Texte en ligne]</div> <div>📚 <strong>ZARADER</strong>, Jean-Pierre (dir.), ''Le vocabulaire des philosophes'', 5 tomes, Paris, Ellipses, 2002-2006</div> </div> <!-- ═══════════════════════════════════════════════════════════════════ FOOTER : RESSOURCES ═══════════════════════════════════════════════════════════════════ --> <div style="margin-top: 25px; background: linear-gradient(180deg, #f5f5f3, #eaeae8); border: 1px solid #d8d8d5; border-radius: 8px; padding: 16px 20px;"> <div style="font-size: 0.8em; font-weight: 700; color: #555; text-transform: uppercase; letter-spacing: 0.08em; margin-bottom: 10px;">Voir aussi</div> <div style="font-size: 0.9em; color: #444; line-height: 1.7;"> [[Philosophie|Portail Philosophie]] &nbsp;•&nbsp; [[Dictionnaire de philosophie|Dictionnaire des concepts]] &nbsp;•&nbsp; [[Manuel de terminale de philosophie|Manuel de Terminale]] &nbsp;•&nbsp; [[w:Histoire de la philosophie|Wikipédia]] </div> </div> [[Catégorie:Discipline philosophique]] [[Catégorie:Histoire|Histoire de la philosophie]] [[Catégorie:Histoire de la philosophie|*]] 811cdiwtlb69hgogj0asxsgpqrhtcgx 764695 764694 2026-04-23T19:29:45Z PandaMystique 119061 764695 wikitext text/x-wiki <!-- ═══════════════════════════════════════════════════════════════════ NAVIGATION PAR PÉRIODE ═══════════════════════════════════════════════════════════════════ --> <div style="background: #f8f9fa; border: 1px solid #e0e0e0; border-radius: 6px; padding: 14px 20px; margin-bottom: 25px; text-align: center; font-size: 0.9em;"> <span style="color: #666; margin-right: 8px;">Naviguer :</span> [[#Antique|<span style="color: #8a5a30;">● Antiquité</span>]] &nbsp;•&nbsp; [[#Moderne|<span style="color: #4a6a8a;">● Époque moderne</span>]] &nbsp;•&nbsp; [[#Contemporaine|<span style="color: #5a3d7a;">● Contemporaine</span>]] &nbsp;•&nbsp; [[#Bibliographie|<span style="color: #555;">● Bibliographie</span>]] </div> <!-- ═══════════════════════════════════════════════════════════════════ SECTION : PHILOSOPHIE ANTIQUE ═══════════════════════════════════════════════════════════════════ --> <div id="Antique" style="margin: 30px 0 15px 0; padding-bottom: 6px; border-bottom: 2px solid #c9a860;"> <span style="font-size: 1.15em; font-weight: 700; color: #6a5020; letter-spacing: 0.02em;">PHILOSOPHIE ANTIQUE</span> <span style="font-size: 0.85em; color: #999; margin-left: 12px;">VIᵉ siècle av. J.-C. — Vᵉ siècle</span> </div> <table style="width:100%; border-collapse:separate; border-spacing:12px;"> <tr> <!-- PRÉSOCRATIQUES --> <td style="width:33%; vertical-align:top; position:relative; background:linear-gradient(180deg, #faf6ed, #f2ebdc); border:1px solid #d4c090; border-radius:8px; padding:16px 18px; overflow:hidden;"> <div style="font-weight:bold; font-size:1.05em; color:#5a4a20; margin-bottom:8px;">🌅 [[Philosophie/Présocratiques|Présocratiques]]</div> <div style="font-size:0.88em; color:#555; line-height:1.6; margin-bottom:10px;">Les premiers penseurs grecs cherchent le principe (''archè'') de toutes choses.</div> <div style="font-size:0.85em; color:#666; border-top:1px dashed #d4c090; padding-top:8px;"> → [[Philosophie/Thalès de Milet|Thalès de Milet]]<br/> → [[Philosophie/Anaximandre de Milet|Anaximandre]]<br/> → [[Dictionnaire de philosophie/Anaxagore|Anaxagore]] </div> </td> <!-- PLATON --> <td style="width:33%; vertical-align:top; position:relative; background:linear-gradient(180deg, #fdf8ed, #f6eedc); border:1px solid #d4b860; border-radius:8px; padding:16px 18px; overflow:hidden;"> <div style="font-weight:bold; font-size:1.05em; color:#5a4800; margin-bottom:8px;">📜 [[Pour lire Platon|Platon]]</div> <div style="font-size:0.88em; color:#555; line-height:1.6; margin-bottom:10px;">Théorie des Idées, dialectique et fondation de l'Académie.</div> <div style="font-size:0.85em; color:#666; border-top:1px dashed #d4b860; padding-top:8px;"> → [[Pour lire Platon/Guide des dialogues/Apologie de Socrate|Apologie de Socrate]]<br/> → [[Pour lire Platon/Guide des dialogues/Ion|Ion]]<br/> → [[Pour lire Platon/Vocabulaire|Vocabulaire platonicien]] </div> </td> <!-- STOÏCIENS --> <td style="width:33%; vertical-align:top; position:relative; background:linear-gradient(180deg, #f8f4e8, #f0e8d8); border:1px solid #c8b070; border-radius:8px; padding:16px 18px; overflow:hidden;"> <div style="font-weight:bold; font-size:1.05em; color:#5a4820; margin-bottom:8px;">🏛️ Stoïcisme</div> <div style="font-size:0.88em; color:#555; line-height:1.6; margin-bottom:10px;">Vivre selon la nature, maîtriser ses passions, accepter le destin.</div> <div style="font-size:0.85em; color:#666; border-top:1px dashed #c8b070; padding-top:8px;"> → [[Les Stoïciens : Épictète Le poignard à la main|Épictète]] </div> </td> </tr> </table> <!-- ═══════════════════════════════════════════════════════════════════ SECTION : PHILOSOPHIE MODERNE ═══════════════════════════════════════════════════════════════════ --> <div id="Moderne" style="margin: 35px 0 15px 0; padding-bottom: 6px; border-bottom: 2px solid #4a90b8;"> <span style="font-size: 1.15em; font-weight: 700; color: #2a5a7a; letter-spacing: 0.02em;">PHILOSOPHIE MODERNE</span> <span style="font-size: 0.85em; color: #999; margin-left: 12px;">XVIᵉ — XVIIIᵉ siècle</span> </div> <!-- Sous-section : XVIIe siècle --> <div style="font-size: 0.9em; font-weight: 600; color: #4a6a85; margin: 20px 0 12px 0; padding-left: 10px; border-left: 3px solid #a0c0d8;">XVIIᵉ siècle — L'âge classique</div> <table style="width:100%; border-collapse:separate; border-spacing:12px;"> <tr> <!-- DESCARTES --> <td style="width:33%; vertical-align:top; position:relative; background:linear-gradient(180deg, #eef4f8, #e0ecf4); border:1px solid #6090b8; border-radius:8px; padding:16px 18px; overflow:hidden;"> <div style="font-weight:bold; font-size:1.05em; color:#1a4a6a; margin-bottom:4px;">[[Dictionnaire de philosophie/René Descartes|René Descartes]]</div> <div style="font-size:0.8em; color:#888; margin-bottom:8px;">1596 — 1650</div> <div style="font-size:0.88em; color:#555; line-height:1.6; margin-bottom:10px;">Le ''cogito'', le doute méthodique et la fondation de la philosophie moderne.</div> <div style="font-size:0.85em; color:#666; border-top:1px dashed #6090b8; padding-top:8px;"> → [[Méditations métaphysiques]] </div> </td> <!-- SPINOZA --> <td style="width:33%; vertical-align:top; position:relative; background:linear-gradient(180deg, #edf5f2, #dfeee8); border:1px solid #5a9878; border-radius:8px; padding:16px 18px; overflow:hidden;"> <div style="font-weight:bold; font-size:1.05em; color:#1a5038; margin-bottom:4px;">Baruch Spinoza</div> <div style="font-size:0.8em; color:#888; margin-bottom:8px;">1632 — 1677</div> <div style="font-size:0.88em; color:#555; line-height:1.6; margin-bottom:10px;">Substance unique, déterminisme et béatitude par la connaissance.</div> <div style="font-size:0.85em; color:#666; border-top:1px dashed #5a9878; padding-top:8px;"> → [[Commentaire de l'Éthique]] </div> </td> <!-- PASCAL --> <td style="width:33%; vertical-align:top; position:relative; background:linear-gradient(180deg, #f5f0f8, #ebe4f2); border:1px solid #8878a8; border-radius:8px; padding:16px 18px; overflow:hidden;"> <div style="font-weight:bold; font-size:1.05em; color:#3a2860; margin-bottom:4px;">Blaise Pascal</div> <div style="font-size:0.8em; color:#888; margin-bottom:8px;">1623 — 1662</div> <div style="font-size:0.88em; color:#555; line-height:1.6; margin-bottom:10px;">Misère et grandeur de l'homme, le pari, la condition humaine.</div> <div style="font-size:0.85em; color:#666; border-top:1px dashed #8878a8; padding-top:8px;"> → [[Philosophie/Commentaire du passage à propos de l'Homme esclave du divertissement|Le divertissement]]<br/> → [[Philosophie/Commentaire du passage à propos des deux infinis|Les deux infinis]] </div> </td> </tr> </table> <!-- Sous-section : XVIIIe siècle --> <div style="font-size: 0.9em; font-weight: 600; color: #4a6a85; margin: 25px 0 12px 0; padding-left: 10px; border-left: 3px solid #a0c0d8;">XVIIIᵉ siècle — Les Lumières</div> <table style="width:100%; border-collapse:separate; border-spacing:12px;"> <tr> <!-- HUME --> <td style="width:50%; vertical-align:top; position:relative; background:linear-gradient(180deg, #f0f5f8, #e2eef5); border:1px solid #5088b0; border-radius:8px; padding:16px 18px; overflow:hidden;"> <div style="font-weight:bold; font-size:1.05em; color:#1a4a68; margin-bottom:4px;">David Hume</div> <div style="font-size:0.8em; color:#888; margin-bottom:8px;">1711 — 1776</div> <div style="font-size:0.88em; color:#555; line-height:1.6; margin-bottom:10px;">Empirisme radical, critique de la causalité et scepticisme modéré.</div> <div style="font-size:0.85em; color:#666; border-top:1px dashed #5088b0; padding-top:8px;"> → [[Philosophie/Vocabulaire/David Hume|Vocabulaire]] </div> </td> <!-- KANT --> <td style="width:50%; vertical-align:top; position:relative; background:linear-gradient(180deg, #eef2f8, #e0e8f2); border:1px solid #6080a8; border-radius:8px; padding:16px 18px; overflow:hidden;"> <div style="font-weight:bold; font-size:1.05em; color:#2a3860; margin-bottom:4px;">Emmanuel Kant</div> <div style="font-size:0.8em; color:#888; margin-bottom:8px;">1724 — 1804</div> <div style="font-size:0.88em; color:#555; line-height:1.6; margin-bottom:10px;">Révolution copernicienne, critique de la raison et impératif catégorique.</div> <div style="font-size:0.85em; color:#666; border-top:1px dashed #6080a8; padding-top:8px;"> → [[Philosophie/Vocabulaire/Kant|Vocabulaire]]<br/> → [[Commentaire de la Fondation de la métaphysique des mœurs|Fondation de la métaphysique des mœurs]] </div> </td> </tr> </table> <!-- ═══════════════════════════════════════════════════════════════════ SECTION : PHILOSOPHIE CONTEMPORAINE ═══════════════════════════════════════════════════════════════════ --> <div id="Contemporaine" style="margin: 35px 0 15px 0; padding-bottom: 6px; border-bottom: 2px solid #9070b0;"> <span style="font-size: 1.15em; font-weight: 700; color: #5a3d7a; letter-spacing: 0.02em;">PHILOSOPHIE CONTEMPORAINE</span> <span style="font-size: 0.85em; color: #999; margin-left: 12px;">XIXᵉ — XXIᵉ siècle</span> </div> <!-- Sous-section : XIXe siècle --> <div style="font-size: 0.9em; font-weight: 600; color: #6a5080; margin: 20px 0 12px 0; padding-left: 10px; border-left: 3px solid #c0a8d0;">XIXᵉ siècle</div> <table style="width:100%; border-collapse:separate; border-spacing:12px;"> <tr> <!-- NIETZSCHE --> <td style="width:50%; vertical-align:top; position:relative; background:linear-gradient(180deg, #f8f0f2, #f0e4e8); border:1px solid #a87080; border-radius:8px; padding:16px 18px; overflow:hidden;"> <div style="font-weight:bold; font-size:1.05em; color:#5a2838; margin-bottom:4px;">[[Philosophie/Nietzsche|Friedrich Nietzsche]]</div> <div style="font-size:0.8em; color:#888; margin-bottom:8px;">1844 — 1900</div> <div style="font-size:0.88em; color:#555; line-height:1.6;">Critique de la morale, volonté de puissance, mort de Dieu et éternel retour.</div> </td> <!-- Placeholder pour futurs philosophes du XIXe --> <td style="width:50%; vertical-align:top; background: #fafafa; border: 1px dashed #ccc; border-radius:8px; padding:16px 18px; color: #999; font-size: 0.9em; text-align: center;"> <div style="margin: 15px 0;">''À venir : Hegel, Marx, Kierkegaard…''</div> </td> </tr> </table> <!-- Sous-section : XXe siècle --> <div style="font-size: 0.9em; font-weight: 600; color: #6a5080; margin: 25px 0 12px 0; padding-left: 10px; border-left: 3px solid #c0a8d0;">XXᵉ siècle</div> <table style="width:100%; border-collapse:separate; border-spacing:12px;"> <tr> <!-- HEIDEGGER --> <td style="width:50%; vertical-align:top; position:relative; background:linear-gradient(180deg, #f3eef8, #e8e0f2); border:1px solid #8068a0; border-radius:8px; padding:16px 18px; overflow:hidden;"> <div style="font-weight:bold; font-size:1.05em; color:#3a2858; margin-bottom:4px;">Martin Heidegger</div> <div style="font-size:0.8em; color:#888; margin-bottom:8px;">1889 — 1976</div> <div style="font-size:0.88em; color:#555; line-height:1.6; margin-bottom:10px;">Question de l'être, ''Dasein'', temporalité et critique de la métaphysique.</div> <div style="font-size:0.85em; color:#666; border-top:1px dashed #8068a0; padding-top:8px;"> → [[Être et Temps]] </div> </td> <!-- Placeholder pour futurs philosophes du XXe --> <td style="width:50%; vertical-align:top; background: #fafafa; border: 1px dashed #ccc; border-radius:8px; padding:16px 18px; color: #999; font-size: 0.9em; text-align: center;"> <div style="margin: 15px 0;">''À venir : Sartre, Merleau-Ponty, Wittgenstein…''</div> </td> </tr> </table> <!-- Sous-section : Courants --> <div style="font-size: 0.9em; font-weight: 600; color: #6a5080; margin: 25px 0 12px 0; padding-left: 10px; border-left: 3px solid #c0a8d0;">Courants philosophiques</div> <table style="width:100%; border-collapse:separate; border-spacing:12px;"> <tr> <!-- PHILOSOPHIE ANALYTIQUE --> <td style="width:33%; vertical-align:top; position:relative; background:linear-gradient(180deg, #f0f2f8, #e5e8f2); border:1px solid #7080a0; border-radius:8px; padding:14px 16px; overflow:hidden;"> <div style="font-weight:bold; font-size:1em; color:#2a3860; margin-bottom:6px;">🔬 [[Philosophie analytique]]</div> <div style="font-size:0.85em; color:#555; line-height:1.5; margin-bottom:10px;">Analyse logique du langage et clarification des concepts.</div> <div style="font-size:0.85em; color:#666; border-top:1px dashed #8068a0; padding-top:8px;"> → [[Ignorance: A Case for Scepticism]] </div> </td> <!-- Placeholders pour futurs courants --> <td style="width:33%; vertical-align:top; background: #fafafa; border: 1px dashed #ccc; border-radius:8px; padding:14px 16px; color: #999; font-size: 0.85em; text-align: center;"> ''À venir : Phénoménologie'' </td> <td style="width:33%; vertical-align:top; background: #fafafa; border: 1px dashed #ccc; border-radius:8px; padding:14px 16px; color: #999; font-size: 0.85em; text-align: center;"> ''À venir : Existentialisme'' </td> </tr> </table> <!-- ═══════════════════════════════════════════════════════════════════ SECTION : BIBLIOGRAPHIE ═══════════════════════════════════════════════════════════════════ --> <div id="Bibliographie" style="margin: 35px 0 15px 0; padding-bottom: 6px; border-bottom: 2px solid #888;"> <span style="font-size: 1.15em; font-weight: 700; color: #444; letter-spacing: 0.02em;">BIBLIOGRAPHIE</span> <span style="font-size: 0.85em; color: #999; margin-left: 12px;">Ouvrages de référence</span> </div> <div style="background: linear-gradient(180deg, #fafafa, #f5f5f5); border: 1px solid #e0e0e0; border-radius: 8px; padding: 18px 22px; font-size: 0.92em; line-height: 1.8; color: #444;"> <div style="margin-bottom: 8px;">📚 <strong>PRADEAU</strong>, Jean-François (dir.), ''Histoire de la philosophie'', Éditions du Seuil, 2009</div> <div style="margin-bottom: 8px;">📚 <strong>CHÂTELET</strong>, François, ''Histoire de la philosophie'', 8 tomes, Paris, Hachette-Pluriel, 1999-2000</div> <div style="margin-bottom: 8px;">📚 <strong>BRÉHIER</strong>, Émile, ''Histoire de la philosophie'', PUF, Quadrige, 2004, {{ISBN|2-13054-396-0}} — [http://classiques.uqac.ca/classiques/brehier_emile/brehier_emile.html Texte en ligne]</div> <div>📚 <strong>ZARADER</strong>, Jean-Pierre (dir.), ''Le vocabulaire des philosophes'', 5 tomes, Paris, Ellipses, 2002-2006</div> </div> <!-- ═══════════════════════════════════════════════════════════════════ FOOTER : RESSOURCES ═══════════════════════════════════════════════════════════════════ --> <div style="margin-top: 25px; background: linear-gradient(180deg, #f5f5f3, #eaeae8); border: 1px solid #d8d8d5; border-radius: 8px; padding: 16px 20px;"> <div style="font-size: 0.8em; font-weight: 700; color: #555; text-transform: uppercase; letter-spacing: 0.08em; margin-bottom: 10px;">Voir aussi</div> <div style="font-size: 0.9em; color: #444; line-height: 1.7;"> [[Philosophie|Portail Philosophie]] &nbsp;•&nbsp; [[Dictionnaire de philosophie|Dictionnaire des concepts]] &nbsp;•&nbsp; [[Manuel de terminale de philosophie|Manuel de Terminale]] &nbsp;•&nbsp; [[w:Histoire de la philosophie|Wikipédia]] </div> </div> [[Catégorie:Discipline philosophique]] [[Catégorie:Histoire|Histoire de la philosophie]] [[Catégorie:Histoire de la philosophie|*]] amkf4a2ut3ax3mai52fudnt8kceuxo5 764697 764695 2026-04-23T19:29:59Z PandaMystique 119061 764697 wikitext text/x-wiki <!-- ═══════════════════════════════════════════════════════════════════ SECTION : PHILOSOPHIE ANTIQUE ═══════════════════════════════════════════════════════════════════ --> <div id="Antique" style="margin: 30px 0 15px 0; padding-bottom: 6px; border-bottom: 2px solid #c9a860;"> <span style="font-size: 1.15em; font-weight: 700; color: #6a5020; letter-spacing: 0.02em;">PHILOSOPHIE ANTIQUE</span> <span style="font-size: 0.85em; color: #999; margin-left: 12px;">VIᵉ siècle av. J.-C. — Vᵉ siècle</span> </div> <table style="width:100%; border-collapse:separate; border-spacing:12px;"> <tr> <!-- PRÉSOCRATIQUES --> <td style="width:33%; vertical-align:top; position:relative; background:linear-gradient(180deg, #faf6ed, #f2ebdc); border:1px solid #d4c090; border-radius:8px; padding:16px 18px; overflow:hidden;"> <div style="font-weight:bold; font-size:1.05em; color:#5a4a20; margin-bottom:8px;">🌅 [[Philosophie/Présocratiques|Présocratiques]]</div> <div style="font-size:0.88em; color:#555; line-height:1.6; margin-bottom:10px;">Les premiers penseurs grecs cherchent le principe (''archè'') de toutes choses.</div> <div style="font-size:0.85em; color:#666; border-top:1px dashed #d4c090; padding-top:8px;"> → [[Philosophie/Thalès de Milet|Thalès de Milet]]<br/> → [[Philosophie/Anaximandre de Milet|Anaximandre]]<br/> → [[Dictionnaire de philosophie/Anaxagore|Anaxagore]] </div> </td> <!-- PLATON --> <td style="width:33%; vertical-align:top; position:relative; background:linear-gradient(180deg, #fdf8ed, #f6eedc); border:1px solid #d4b860; border-radius:8px; padding:16px 18px; overflow:hidden;"> <div style="font-weight:bold; font-size:1.05em; color:#5a4800; margin-bottom:8px;">📜 [[Pour lire Platon|Platon]]</div> <div style="font-size:0.88em; color:#555; line-height:1.6; margin-bottom:10px;">Théorie des Idées, dialectique et fondation de l'Académie.</div> <div style="font-size:0.85em; color:#666; border-top:1px dashed #d4b860; padding-top:8px;"> → [[Pour lire Platon/Guide des dialogues/Apologie de Socrate|Apologie de Socrate]]<br/> → [[Pour lire Platon/Guide des dialogues/Ion|Ion]]<br/> → [[Pour lire Platon/Vocabulaire|Vocabulaire platonicien]] </div> </td> <!-- STOÏCIENS --> <td style="width:33%; vertical-align:top; position:relative; background:linear-gradient(180deg, #f8f4e8, #f0e8d8); border:1px solid #c8b070; border-radius:8px; padding:16px 18px; overflow:hidden;"> <div style="font-weight:bold; font-size:1.05em; color:#5a4820; margin-bottom:8px;">🏛️ Stoïcisme</div> <div style="font-size:0.88em; color:#555; line-height:1.6; margin-bottom:10px;">Vivre selon la nature, maîtriser ses passions, accepter le destin.</div> <div style="font-size:0.85em; color:#666; border-top:1px dashed #c8b070; padding-top:8px;"> → [[Les Stoïciens : Épictète Le poignard à la main|Épictète]] </div> </td> </tr> </table> <!-- ═══════════════════════════════════════════════════════════════════ SECTION : PHILOSOPHIE MODERNE ═══════════════════════════════════════════════════════════════════ --> <div id="Moderne" style="margin: 35px 0 15px 0; padding-bottom: 6px; border-bottom: 2px solid #4a90b8;"> <span style="font-size: 1.15em; font-weight: 700; color: #2a5a7a; letter-spacing: 0.02em;">PHILOSOPHIE MODERNE</span> <span style="font-size: 0.85em; color: #999; margin-left: 12px;">XVIᵉ — XVIIIᵉ siècle</span> </div> <!-- Sous-section : XVIIe siècle --> <div style="font-size: 0.9em; font-weight: 600; color: #4a6a85; margin: 20px 0 12px 0; padding-left: 10px; border-left: 3px solid #a0c0d8;">XVIIᵉ siècle — L'âge classique</div> <table style="width:100%; border-collapse:separate; border-spacing:12px;"> <tr> <!-- DESCARTES --> <td style="width:33%; vertical-align:top; position:relative; background:linear-gradient(180deg, #eef4f8, #e0ecf4); border:1px solid #6090b8; border-radius:8px; padding:16px 18px; overflow:hidden;"> <div style="font-weight:bold; font-size:1.05em; color:#1a4a6a; margin-bottom:4px;">[[Dictionnaire de philosophie/René Descartes|René Descartes]]</div> <div style="font-size:0.8em; color:#888; margin-bottom:8px;">1596 — 1650</div> <div style="font-size:0.88em; color:#555; line-height:1.6; margin-bottom:10px;">Le ''cogito'', le doute méthodique et la fondation de la philosophie moderne.</div> <div style="font-size:0.85em; color:#666; border-top:1px dashed #6090b8; padding-top:8px;"> → [[Méditations métaphysiques]] </div> </td> <!-- SPINOZA --> <td style="width:33%; vertical-align:top; position:relative; background:linear-gradient(180deg, #edf5f2, #dfeee8); border:1px solid #5a9878; border-radius:8px; padding:16px 18px; overflow:hidden;"> <div style="font-weight:bold; font-size:1.05em; color:#1a5038; margin-bottom:4px;">Baruch Spinoza</div> <div style="font-size:0.8em; color:#888; margin-bottom:8px;">1632 — 1677</div> <div style="font-size:0.88em; color:#555; line-height:1.6; margin-bottom:10px;">Substance unique, déterminisme et béatitude par la connaissance.</div> <div style="font-size:0.85em; color:#666; border-top:1px dashed #5a9878; padding-top:8px;"> → [[Commentaire de l'Éthique]] </div> </td> <!-- PASCAL --> <td style="width:33%; vertical-align:top; position:relative; background:linear-gradient(180deg, #f5f0f8, #ebe4f2); border:1px solid #8878a8; border-radius:8px; padding:16px 18px; overflow:hidden;"> <div style="font-weight:bold; font-size:1.05em; color:#3a2860; margin-bottom:4px;">Blaise Pascal</div> <div style="font-size:0.8em; color:#888; margin-bottom:8px;">1623 — 1662</div> <div style="font-size:0.88em; color:#555; line-height:1.6; margin-bottom:10px;">Misère et grandeur de l'homme, le pari, la condition humaine.</div> <div style="font-size:0.85em; color:#666; border-top:1px dashed #8878a8; padding-top:8px;"> → [[Philosophie/Commentaire du passage à propos de l'Homme esclave du divertissement|Le divertissement]]<br/> → [[Philosophie/Commentaire du passage à propos des deux infinis|Les deux infinis]] </div> </td> </tr> </table> <!-- Sous-section : XVIIIe siècle --> <div style="font-size: 0.9em; font-weight: 600; color: #4a6a85; margin: 25px 0 12px 0; padding-left: 10px; border-left: 3px solid #a0c0d8;">XVIIIᵉ siècle — Les Lumières</div> <table style="width:100%; border-collapse:separate; border-spacing:12px;"> <tr> <!-- HUME --> <td style="width:50%; vertical-align:top; position:relative; background:linear-gradient(180deg, #f0f5f8, #e2eef5); border:1px solid #5088b0; border-radius:8px; padding:16px 18px; overflow:hidden;"> <div style="font-weight:bold; font-size:1.05em; color:#1a4a68; margin-bottom:4px;">David Hume</div> <div style="font-size:0.8em; color:#888; margin-bottom:8px;">1711 — 1776</div> <div style="font-size:0.88em; color:#555; line-height:1.6; margin-bottom:10px;">Empirisme radical, critique de la causalité et scepticisme modéré.</div> <div style="font-size:0.85em; color:#666; border-top:1px dashed #5088b0; padding-top:8px;"> → [[Philosophie/Vocabulaire/David Hume|Vocabulaire]] </div> </td> <!-- KANT --> <td style="width:50%; vertical-align:top; position:relative; background:linear-gradient(180deg, #eef2f8, #e0e8f2); border:1px solid #6080a8; border-radius:8px; padding:16px 18px; overflow:hidden;"> <div style="font-weight:bold; font-size:1.05em; color:#2a3860; margin-bottom:4px;">Emmanuel Kant</div> <div style="font-size:0.8em; color:#888; margin-bottom:8px;">1724 — 1804</div> <div style="font-size:0.88em; color:#555; line-height:1.6; margin-bottom:10px;">Révolution copernicienne, critique de la raison et impératif catégorique.</div> <div style="font-size:0.85em; color:#666; border-top:1px dashed #6080a8; padding-top:8px;"> → [[Philosophie/Vocabulaire/Kant|Vocabulaire]]<br/> → [[Commentaire de la Fondation de la métaphysique des mœurs|Fondation de la métaphysique des mœurs]] </div> </td> </tr> </table> <!-- ═══════════════════════════════════════════════════════════════════ SECTION : PHILOSOPHIE CONTEMPORAINE ═══════════════════════════════════════════════════════════════════ --> <div id="Contemporaine" style="margin: 35px 0 15px 0; padding-bottom: 6px; border-bottom: 2px solid #9070b0;"> <span style="font-size: 1.15em; font-weight: 700; color: #5a3d7a; letter-spacing: 0.02em;">PHILOSOPHIE CONTEMPORAINE</span> <span style="font-size: 0.85em; color: #999; margin-left: 12px;">XIXᵉ — XXIᵉ siècle</span> </div> <!-- Sous-section : XIXe siècle --> <div style="font-size: 0.9em; font-weight: 600; color: #6a5080; margin: 20px 0 12px 0; padding-left: 10px; border-left: 3px solid #c0a8d0;">XIXᵉ siècle</div> <table style="width:100%; border-collapse:separate; border-spacing:12px;"> <tr> <!-- NIETZSCHE --> <td style="width:50%; vertical-align:top; position:relative; background:linear-gradient(180deg, #f8f0f2, #f0e4e8); border:1px solid #a87080; border-radius:8px; padding:16px 18px; overflow:hidden;"> <div style="font-weight:bold; font-size:1.05em; color:#5a2838; margin-bottom:4px;">[[Philosophie/Nietzsche|Friedrich Nietzsche]]</div> <div style="font-size:0.8em; color:#888; margin-bottom:8px;">1844 — 1900</div> <div style="font-size:0.88em; color:#555; line-height:1.6;">Critique de la morale, volonté de puissance, mort de Dieu et éternel retour.</div> </td> <!-- Placeholder pour futurs philosophes du XIXe --> <td style="width:50%; vertical-align:top; background: #fafafa; border: 1px dashed #ccc; border-radius:8px; padding:16px 18px; color: #999; font-size: 0.9em; text-align: center;"> <div style="margin: 15px 0;">''À venir : Hegel, Marx, Kierkegaard…''</div> </td> </tr> </table> <!-- Sous-section : XXe siècle --> <div style="font-size: 0.9em; font-weight: 600; color: #6a5080; margin: 25px 0 12px 0; padding-left: 10px; border-left: 3px solid #c0a8d0;">XXᵉ siècle</div> <table style="width:100%; border-collapse:separate; border-spacing:12px;"> <tr> <!-- HEIDEGGER --> <td style="width:50%; vertical-align:top; position:relative; background:linear-gradient(180deg, #f3eef8, #e8e0f2); border:1px solid #8068a0; border-radius:8px; padding:16px 18px; overflow:hidden;"> <div style="font-weight:bold; font-size:1.05em; color:#3a2858; margin-bottom:4px;">Martin Heidegger</div> <div style="font-size:0.8em; color:#888; margin-bottom:8px;">1889 — 1976</div> <div style="font-size:0.88em; color:#555; line-height:1.6; margin-bottom:10px;">Question de l'être, ''Dasein'', temporalité et critique de la métaphysique.</div> <div style="font-size:0.85em; color:#666; border-top:1px dashed #8068a0; padding-top:8px;"> → [[Être et Temps]] </div> </td> <!-- Placeholder pour futurs philosophes du XXe --> <td style="width:50%; vertical-align:top; background: #fafafa; border: 1px dashed #ccc; border-radius:8px; padding:16px 18px; color: #999; font-size: 0.9em; text-align: center;"> <div style="margin: 15px 0;">''À venir : Sartre, Merleau-Ponty, Wittgenstein…''</div> </td> </tr> </table> <!-- Sous-section : Courants --> <div style="font-size: 0.9em; font-weight: 600; color: #6a5080; margin: 25px 0 12px 0; padding-left: 10px; border-left: 3px solid #c0a8d0;">Courants philosophiques</div> <table style="width:100%; border-collapse:separate; border-spacing:12px;"> <tr> <!-- PHILOSOPHIE ANALYTIQUE --> <td style="width:33%; vertical-align:top; position:relative; background:linear-gradient(180deg, #f0f2f8, #e5e8f2); border:1px solid #7080a0; border-radius:8px; padding:14px 16px; overflow:hidden;"> <div style="font-weight:bold; font-size:1em; color:#2a3860; margin-bottom:6px;">🔬 [[Philosophie analytique]]</div> <div style="font-size:0.85em; color:#555; line-height:1.5; margin-bottom:10px;">Analyse logique du langage et clarification des concepts.</div> <div style="font-size:0.85em; color:#666; border-top:1px dashed #8068a0; padding-top:8px;"> → [[Ignorance: A Case for Scepticism]] </div> </td> <!-- Placeholders pour futurs courants --> <td style="width:33%; vertical-align:top; background: #fafafa; border: 1px dashed #ccc; border-radius:8px; padding:14px 16px; color: #999; font-size: 0.85em; text-align: center;"> ''À venir : Phénoménologie'' </td> <td style="width:33%; vertical-align:top; background: #fafafa; border: 1px dashed #ccc; border-radius:8px; padding:14px 16px; color: #999; font-size: 0.85em; text-align: center;"> ''À venir : Existentialisme'' </td> </tr> </table> <!-- ═══════════════════════════════════════════════════════════════════ SECTION : BIBLIOGRAPHIE ═══════════════════════════════════════════════════════════════════ --> <div id="Bibliographie" style="margin: 35px 0 15px 0; padding-bottom: 6px; border-bottom: 2px solid #888;"> <span style="font-size: 1.15em; font-weight: 700; color: #444; letter-spacing: 0.02em;">BIBLIOGRAPHIE</span> <span style="font-size: 0.85em; color: #999; margin-left: 12px;">Ouvrages de référence</span> </div> <div style="background: linear-gradient(180deg, #fafafa, #f5f5f5); border: 1px solid #e0e0e0; border-radius: 8px; padding: 18px 22px; font-size: 0.92em; line-height: 1.8; color: #444;"> <div style="margin-bottom: 8px;">📚 <strong>PRADEAU</strong>, Jean-François (dir.), ''Histoire de la philosophie'', Éditions du Seuil, 2009</div> <div style="margin-bottom: 8px;">📚 <strong>CHÂTELET</strong>, François, ''Histoire de la philosophie'', 8 tomes, Paris, Hachette-Pluriel, 1999-2000</div> <div style="margin-bottom: 8px;">📚 <strong>BRÉHIER</strong>, Émile, ''Histoire de la philosophie'', PUF, Quadrige, 2004, {{ISBN|2-13054-396-0}} — [http://classiques.uqac.ca/classiques/brehier_emile/brehier_emile.html Texte en ligne]</div> <div>📚 <strong>ZARADER</strong>, Jean-Pierre (dir.), ''Le vocabulaire des philosophes'', 5 tomes, Paris, Ellipses, 2002-2006</div> </div> <!-- ═══════════════════════════════════════════════════════════════════ FOOTER : RESSOURCES ═══════════════════════════════════════════════════════════════════ --> <div style="margin-top: 25px; background: linear-gradient(180deg, #f5f5f3, #eaeae8); border: 1px solid #d8d8d5; border-radius: 8px; padding: 16px 20px;"> <div style="font-size: 0.8em; font-weight: 700; color: #555; text-transform: uppercase; letter-spacing: 0.08em; margin-bottom: 10px;">Voir aussi</div> <div style="font-size: 0.9em; color: #444; line-height: 1.7;"> [[Philosophie|Portail Philosophie]] &nbsp;•&nbsp; [[Dictionnaire de philosophie|Dictionnaire des concepts]] &nbsp;•&nbsp; [[Manuel de terminale de philosophie|Manuel de Terminale]] &nbsp;•&nbsp; [[w:Histoire de la philosophie|Wikipédia]] </div> </div> [[Catégorie:Discipline philosophique]] [[Catégorie:Histoire|Histoire de la philosophie]] [[Catégorie:Histoire de la philosophie|*]] dg6ic46chcmvylcficug1n1fegia98u 764752 764697 2026-04-24T04:49:48Z PandaMystique 119061 764752 wikitext text/x-wiki <!-- ═══════════════════════════════════════════════════════════════════ SECTION : PHILOSOPHIE ANTIQUE ═══════════════════════════════════════════════════════════════════ --> <div id="Antique" style="margin: 30px 0 15px 0; padding-bottom: 6px; border-bottom: 2px solid #c9a860;"> <span style="font-size: 1.15em; font-weight: 700; color: #6a5020; letter-spacing: 0.02em;">PHILOSOPHIE ANTIQUE</span> <span style="font-size: 0.85em; color: #999; margin-left: 12px;">VIᵉ siècle av. J.-C. — Vᵉ siècle</span> </div> <table style="width:100%; border-collapse:separate; border-spacing:12px;"> <tr> <!-- PRÉSOCRATIQUES --> <td style="width:33%; vertical-align:top; position:relative; background:linear-gradient(180deg, #faf6ed, #f2ebdc); border:1px solid #d4c090; border-radius:8px; padding:16px 18px; overflow:hidden;"> <div style="font-weight:bold; font-size:1.05em; color:#5a4a20; margin-bottom:8px;">🌅 [[Philosophie/Présocratiques|Présocratiques]]</div> <div style="font-size:0.88em; color:#555; line-height:1.6; margin-bottom:10px;">Les premiers penseurs grecs cherchent le principe (''archè'') de toutes choses.</div> <div style="font-size:0.85em; color:#666; border-top:1px dashed #d4c090; padding-top:8px;"> → [[Philosophie/Thalès de Milet|Thalès de Milet]]<br/> → [[Philosophie/Anaximandre de Milet|Anaximandre]]<br/> → [[Dictionnaire de philosophie/Empédocle|Empédocle]]<br> → [[Dictionnaire de philosophie/Anaxagore|Anaxagore]] </div> </td> <!-- PLATON --> <td style="width:33%; vertical-align:top; position:relative; background:linear-gradient(180deg, #fdf8ed, #f6eedc); border:1px solid #d4b860; border-radius:8px; padding:16px 18px; overflow:hidden;"> <div style="font-weight:bold; font-size:1.05em; color:#5a4800; margin-bottom:8px;">📜 [[Pour lire Platon|Platon]]</div> <div style="font-size:0.88em; color:#555; line-height:1.6; margin-bottom:10px;">Théorie des Idées, dialectique et fondation de l'Académie.</div> <div style="font-size:0.85em; color:#666; border-top:1px dashed #d4b860; padding-top:8px;"> → [[Pour lire Platon/Guide des dialogues/Apologie de Socrate|Apologie de Socrate]]<br/> → [[Pour lire Platon/Guide des dialogues/Ion|Ion]]<br/> → [[Pour lire Platon/Vocabulaire|Vocabulaire platonicien]] </div> </td> <!-- STOÏCIENS --> <td style="width:33%; vertical-align:top; position:relative; background:linear-gradient(180deg, #f8f4e8, #f0e8d8); border:1px solid #c8b070; border-radius:8px; padding:16px 18px; overflow:hidden;"> <div style="font-weight:bold; font-size:1.05em; color:#5a4820; margin-bottom:8px;">🏛️ Stoïcisme</div> <div style="font-size:0.88em; color:#555; line-height:1.6; margin-bottom:10px;">Vivre selon la nature, maîtriser ses passions, accepter le destin.</div> <div style="font-size:0.85em; color:#666; border-top:1px dashed #c8b070; padding-top:8px;"> → [[Les Stoïciens : Épictète Le poignard à la main|Épictète]] </div> </td> </tr> </table> <!-- ═══════════════════════════════════════════════════════════════════ SECTION : PHILOSOPHIE MODERNE ═══════════════════════════════════════════════════════════════════ --> <div id="Moderne" style="margin: 35px 0 15px 0; padding-bottom: 6px; border-bottom: 2px solid #4a90b8;"> <span style="font-size: 1.15em; font-weight: 700; color: #2a5a7a; letter-spacing: 0.02em;">PHILOSOPHIE MODERNE</span> <span style="font-size: 0.85em; color: #999; margin-left: 12px;">XVIᵉ — XVIIIᵉ siècle</span> </div> <!-- Sous-section : XVIIe siècle --> <div style="font-size: 0.9em; font-weight: 600; color: #4a6a85; margin: 20px 0 12px 0; padding-left: 10px; border-left: 3px solid #a0c0d8;">XVIIᵉ siècle — L'âge classique</div> <table style="width:100%; border-collapse:separate; border-spacing:12px;"> <tr> <!-- DESCARTES --> <td style="width:33%; vertical-align:top; position:relative; background:linear-gradient(180deg, #eef4f8, #e0ecf4); border:1px solid #6090b8; border-radius:8px; padding:16px 18px; overflow:hidden;"> <div style="font-weight:bold; font-size:1.05em; color:#1a4a6a; margin-bottom:4px;">[[Dictionnaire de philosophie/René Descartes|René Descartes]]</div> <div style="font-size:0.8em; color:#888; margin-bottom:8px;">1596 — 1650</div> <div style="font-size:0.88em; color:#555; line-height:1.6; margin-bottom:10px;">Le ''cogito'', le doute méthodique et la fondation de la philosophie moderne.</div> <div style="font-size:0.85em; color:#666; border-top:1px dashed #6090b8; padding-top:8px;"> → [[Méditations métaphysiques]] </div> </td> <!-- SPINOZA --> <td style="width:33%; vertical-align:top; position:relative; background:linear-gradient(180deg, #edf5f2, #dfeee8); border:1px solid #5a9878; border-radius:8px; padding:16px 18px; overflow:hidden;"> <div style="font-weight:bold; font-size:1.05em; color:#1a5038; margin-bottom:4px;">Baruch Spinoza</div> <div style="font-size:0.8em; color:#888; margin-bottom:8px;">1632 — 1677</div> <div style="font-size:0.88em; color:#555; line-height:1.6; margin-bottom:10px;">Substance unique, déterminisme et béatitude par la connaissance.</div> <div style="font-size:0.85em; color:#666; border-top:1px dashed #5a9878; padding-top:8px;"> → [[Commentaire de l'Éthique]] </div> </td> <!-- PASCAL --> <td style="width:33%; vertical-align:top; position:relative; background:linear-gradient(180deg, #f5f0f8, #ebe4f2); border:1px solid #8878a8; border-radius:8px; padding:16px 18px; overflow:hidden;"> <div style="font-weight:bold; font-size:1.05em; color:#3a2860; margin-bottom:4px;">Blaise Pascal</div> <div style="font-size:0.8em; color:#888; margin-bottom:8px;">1623 — 1662</div> <div style="font-size:0.88em; color:#555; line-height:1.6; margin-bottom:10px;">Misère et grandeur de l'homme, le pari, la condition humaine.</div> <div style="font-size:0.85em; color:#666; border-top:1px dashed #8878a8; padding-top:8px;"> → [[Philosophie/Commentaire du passage à propos de l'Homme esclave du divertissement|Le divertissement]]<br/> → [[Philosophie/Commentaire du passage à propos des deux infinis|Les deux infinis]] </div> </td> </tr> </table> <!-- Sous-section : XVIIIe siècle --> <div style="font-size: 0.9em; font-weight: 600; color: #4a6a85; margin: 25px 0 12px 0; padding-left: 10px; border-left: 3px solid #a0c0d8;">XVIIIᵉ siècle — Les Lumières</div> <table style="width:100%; border-collapse:separate; border-spacing:12px;"> <tr> <!-- HUME --> <td style="width:50%; vertical-align:top; position:relative; background:linear-gradient(180deg, #f0f5f8, #e2eef5); border:1px solid #5088b0; border-radius:8px; padding:16px 18px; overflow:hidden;"> <div style="font-weight:bold; font-size:1.05em; color:#1a4a68; margin-bottom:4px;">David Hume</div> <div style="font-size:0.8em; color:#888; margin-bottom:8px;">1711 — 1776</div> <div style="font-size:0.88em; color:#555; line-height:1.6; margin-bottom:10px;">Empirisme radical, critique de la causalité et scepticisme modéré.</div> <div style="font-size:0.85em; color:#666; border-top:1px dashed #5088b0; padding-top:8px;"> → [[Philosophie/Vocabulaire/David Hume|Vocabulaire]] </div> </td> <!-- KANT --> <td style="width:50%; vertical-align:top; position:relative; background:linear-gradient(180deg, #eef2f8, #e0e8f2); border:1px solid #6080a8; border-radius:8px; padding:16px 18px; overflow:hidden;"> <div style="font-weight:bold; font-size:1.05em; color:#2a3860; margin-bottom:4px;">Emmanuel Kant</div> <div style="font-size:0.8em; color:#888; margin-bottom:8px;">1724 — 1804</div> <div style="font-size:0.88em; color:#555; line-height:1.6; margin-bottom:10px;">Révolution copernicienne, critique de la raison et impératif catégorique.</div> <div style="font-size:0.85em; color:#666; border-top:1px dashed #6080a8; padding-top:8px;"> → [[Philosophie/Vocabulaire/Kant|Vocabulaire]]<br/> → [[Commentaire de la Fondation de la métaphysique des mœurs|Fondation de la métaphysique des mœurs]] </div> </td> </tr> </table> <!-- ═══════════════════════════════════════════════════════════════════ SECTION : PHILOSOPHIE CONTEMPORAINE ═══════════════════════════════════════════════════════════════════ --> <div id="Contemporaine" style="margin: 35px 0 15px 0; padding-bottom: 6px; border-bottom: 2px solid #9070b0;"> <span style="font-size: 1.15em; font-weight: 700; color: #5a3d7a; letter-spacing: 0.02em;">PHILOSOPHIE CONTEMPORAINE</span> <span style="font-size: 0.85em; color: #999; margin-left: 12px;">XIXᵉ — XXIᵉ siècle</span> </div> <!-- Sous-section : XIXe siècle --> <div style="font-size: 0.9em; font-weight: 600; color: #6a5080; margin: 20px 0 12px 0; padding-left: 10px; border-left: 3px solid #c0a8d0;">XIXᵉ siècle</div> <table style="width:100%; border-collapse:separate; border-spacing:12px;"> <tr> <!-- NIETZSCHE --> <td style="width:50%; vertical-align:top; position:relative; background:linear-gradient(180deg, #f8f0f2, #f0e4e8); border:1px solid #a87080; border-radius:8px; padding:16px 18px; overflow:hidden;"> <div style="font-weight:bold; font-size:1.05em; color:#5a2838; margin-bottom:4px;">[[Philosophie/Nietzsche|Friedrich Nietzsche]]</div> <div style="font-size:0.8em; color:#888; margin-bottom:8px;">1844 — 1900</div> <div style="font-size:0.88em; color:#555; line-height:1.6;">Critique de la morale, volonté de puissance, mort de Dieu et éternel retour.</div> </td> <!-- Placeholder pour futurs philosophes du XIXe --> <td style="width:50%; vertical-align:top; background: #fafafa; border: 1px dashed #ccc; border-radius:8px; padding:16px 18px; color: #999; font-size: 0.9em; text-align: center;"> <div style="margin: 15px 0;">''À venir : Hegel, Marx, Kierkegaard…''</div> </td> </tr> </table> <!-- Sous-section : XXe siècle --> <div style="font-size: 0.9em; font-weight: 600; color: #6a5080; margin: 25px 0 12px 0; padding-left: 10px; border-left: 3px solid #c0a8d0;">XXᵉ siècle</div> <table style="width:100%; border-collapse:separate; border-spacing:12px;"> <tr> <!-- HEIDEGGER --> <td style="width:50%; vertical-align:top; position:relative; background:linear-gradient(180deg, #f3eef8, #e8e0f2); border:1px solid #8068a0; border-radius:8px; padding:16px 18px; overflow:hidden;"> <div style="font-weight:bold; font-size:1.05em; color:#3a2858; margin-bottom:4px;">Martin Heidegger</div> <div style="font-size:0.8em; color:#888; margin-bottom:8px;">1889 — 1976</div> <div style="font-size:0.88em; color:#555; line-height:1.6; margin-bottom:10px;">Question de l'être, ''Dasein'', temporalité et critique de la métaphysique.</div> <div style="font-size:0.85em; color:#666; border-top:1px dashed #8068a0; padding-top:8px;"> → [[Être et Temps]] </div> </td> <!-- Placeholder pour futurs philosophes du XXe --> <td style="width:50%; vertical-align:top; background: #fafafa; border: 1px dashed #ccc; border-radius:8px; padding:16px 18px; color: #999; font-size: 0.9em; text-align: center;"> <div style="margin: 15px 0;">''À venir : Sartre, Merleau-Ponty, Wittgenstein…''</div> </td> </tr> </table> <!-- Sous-section : Courants --> <div style="font-size: 0.9em; font-weight: 600; color: #6a5080; margin: 25px 0 12px 0; padding-left: 10px; border-left: 3px solid #c0a8d0;">Courants philosophiques</div> <table style="width:100%; border-collapse:separate; border-spacing:12px;"> <tr> <!-- PHILOSOPHIE ANALYTIQUE --> <td style="width:33%; vertical-align:top; position:relative; background:linear-gradient(180deg, #f0f2f8, #e5e8f2); border:1px solid #7080a0; border-radius:8px; padding:14px 16px; overflow:hidden;"> <div style="font-weight:bold; font-size:1em; color:#2a3860; margin-bottom:6px;">🔬 [[Philosophie analytique]]</div> <div style="font-size:0.85em; color:#555; line-height:1.5; margin-bottom:10px;">Analyse logique du langage et clarification des concepts.</div> <div style="font-size:0.85em; color:#666; border-top:1px dashed #8068a0; padding-top:8px;"> → [[Ignorance: A Case for Scepticism]] </div> </td> <!-- Placeholders pour futurs courants --> <td style="width:33%; vertical-align:top; background: #fafafa; border: 1px dashed #ccc; border-radius:8px; padding:14px 16px; color: #999; font-size: 0.85em; text-align: center;"> ''À venir : Phénoménologie'' </td> <td style="width:33%; vertical-align:top; background: #fafafa; border: 1px dashed #ccc; border-radius:8px; padding:14px 16px; color: #999; font-size: 0.85em; text-align: center;"> ''À venir : Existentialisme'' </td> </tr> </table> <!-- ═══════════════════════════════════════════════════════════════════ SECTION : BIBLIOGRAPHIE ═══════════════════════════════════════════════════════════════════ --> <div id="Bibliographie" style="margin: 35px 0 15px 0; padding-bottom: 6px; border-bottom: 2px solid #888;"> <span style="font-size: 1.15em; font-weight: 700; color: #444; letter-spacing: 0.02em;">BIBLIOGRAPHIE</span> <span style="font-size: 0.85em; color: #999; margin-left: 12px;">Ouvrages de référence</span> </div> <div style="background: linear-gradient(180deg, #fafafa, #f5f5f5); border: 1px solid #e0e0e0; border-radius: 8px; padding: 18px 22px; font-size: 0.92em; line-height: 1.8; color: #444;"> <div style="margin-bottom: 8px;">📚 <strong>PRADEAU</strong>, Jean-François (dir.), ''Histoire de la philosophie'', Éditions du Seuil, 2009</div> <div style="margin-bottom: 8px;">📚 <strong>CHÂTELET</strong>, François, ''Histoire de la philosophie'', 8 tomes, Paris, Hachette-Pluriel, 1999-2000</div> <div style="margin-bottom: 8px;">📚 <strong>BRÉHIER</strong>, Émile, ''Histoire de la philosophie'', PUF, Quadrige, 2004, {{ISBN|2-13054-396-0}} — [http://classiques.uqac.ca/classiques/brehier_emile/brehier_emile.html Texte en ligne]</div> <div>📚 <strong>ZARADER</strong>, Jean-Pierre (dir.), ''Le vocabulaire des philosophes'', 5 tomes, Paris, Ellipses, 2002-2006</div> </div> <!-- ═══════════════════════════════════════════════════════════════════ FOOTER : RESSOURCES ═══════════════════════════════════════════════════════════════════ --> <div style="margin-top: 25px; background: linear-gradient(180deg, #f5f5f3, #eaeae8); border: 1px solid #d8d8d5; border-radius: 8px; padding: 16px 20px;"> <div style="font-size: 0.8em; font-weight: 700; color: #555; text-transform: uppercase; letter-spacing: 0.08em; margin-bottom: 10px;">Voir aussi</div> <div style="font-size: 0.9em; color: #444; line-height: 1.7;"> [[Philosophie|Portail Philosophie]] &nbsp;•&nbsp; [[Dictionnaire de philosophie|Dictionnaire des concepts]] &nbsp;•&nbsp; [[Manuel de terminale de philosophie|Manuel de Terminale]] &nbsp;•&nbsp; [[w:Histoire de la philosophie|Wikipédia]] </div> </div> [[Catégorie:Discipline philosophique]] [[Catégorie:Histoire|Histoire de la philosophie]] [[Catégorie:Histoire de la philosophie|*]] p0k4znb4388nnu8f5cm1c3r57i4p8ey Philosophie 0 5270 764699 764634 2026-04-23T19:33:22Z PandaMystique 119061 764699 wikitext text/x-wiki {{PhiloRecherche}} <!-- ══════════════════════════════════════════════ EXPLORER ══════════════════════════════════════════════ --> <table style="width: 100%; border-collapse: collapse; margin: 10px 0 6px;"><tr><td style="white-space: nowrap; padding-right: 10px; font-size: 0.82em; font-weight: 700; letter-spacing: 0.3em; text-transform: uppercase; color: #4e4232; vertical-align: middle; width: 1%;">Explorer</td><td style="vertical-align: middle;"><div style="height: 1px; background: #b8ae9c; font-size: 0; line-height: 0; margin: 0; padding: 0;">&#32;</div></td></tr></table> <table style="width: 100%; border-collapse: separate; border-spacing: 10px 0;"> <tr> <!-- Disciplines --> <td style="width: 33.33%; vertical-align: top; background: #bcd3bc; border-radius: 6px; border-top: 3px solid #1f4a2e; padding: 16px 18px;"> <div style="font-size: 1em; font-weight: 700; color: #14321c; margin-bottom: 8px; line-height: 1.3;"> <span style="color: #1f4a2e; margin-right: 5px;">◇</span>[[:Catégorie:Discipline philosophique|Les disciplines]] </div> <div style="font-size: 0.9em; color: #1e1a12; line-height: 1.65; margin-bottom: 10px;"> Les branches de la pensée&nbsp;: métaphysique, épistémologie, éthique, logique, esthétique, philosophie politique. </div> <div style="height: 1px; background: #8eb08e; margin-bottom: 10px;"></div> <div style="font-size: 0.9em; color: #14321c; line-height: 1.9;"> → [[Philosophie/Théorie de la connaissance|Théorie de la connaissance]]<br/> → [[:Catégorie:Philosophie de l'esprit|Philosophie de l'esprit]] </div> </td> <!-- Œuvres commentées --> <td style="width: 33.33%; vertical-align: top; background: #d9bc9c; border-radius: 6px; border-top: 3px solid #6a2e1e; padding: 16px 18px;"> <div style="font-size: 1em; font-weight: 700; color: #3f160a; margin-bottom: 10px; line-height: 1.3;"> <span style="color: #6a2e1e; margin-right: 5px;">❧</span>[[Philosophie/Histoire de la philosophie|Œuvres commentées]] </div> <div style="font-size: 0.9em; color: #1e1a12; line-height: 1.9;"> → [[Commentaire philosophique/Lettre à Ménécée|Lettre à Ménécée]] <span style="color: #5a3e28; font-size: 0.82em; letter-spacing: 0.08em; text-transform: uppercase; font-weight: 600;">Épicure</span><br/> → [[Méditations métaphysiques|Méditations métaphysiques]] <span style="color: #5a3e28; font-size: 0.82em; letter-spacing: 0.08em; text-transform: uppercase; font-weight: 600;">Descartes</span><br/> → [[Commentaire de l'Éthique|Éthique]] <span style="color: #5a3e28; font-size: 0.82em; letter-spacing: 0.08em; text-transform: uppercase; font-weight: 600;">Spinoza</span><br/> → [[Philosophie/Commentaire du passage à propos de l'Homme esclave du divertissement|Le divertissement]] <span style="color: #5a3e28; font-size: 0.82em; letter-spacing: 0.08em; text-transform: uppercase; font-weight: 600;">Pascal</span> </div> <div style="height: 1px; background: #b0906e; margin: 10px 0 8px;"></div> <div style="font-size: 0.86em; color: #6a2e1e; font-weight: 500;">[[Philosophie/Histoire de la philosophie|Tous les commentaires →]]</div> </td> <!-- Philosophes --> <td style="width: 33.33%; vertical-align: top; background: #aebad6; border-radius: 6px; border-top: 3px solid #233558; padding: 16px 18px;"> <div style="font-size: 1em; font-weight: 700; color: #141e32; margin-bottom: 10px; line-height: 1.3;"> <span style="color: #233558; margin-right: 5px;">☉</span>[[:Catégorie:Philosophe|Philosophes]] </div> <div style="font-size: 0.9em; color: #1e1a12; line-height: 1.9;"> → [[Philosophie/Thalès de Milet|Thalès de Milet]] <span style="color: #2f3e5a; font-size: 0.86em;">v. 625 av. J.-C.</span><br/> → [[Dictionnaire de philosophie/Anaxagore|Anaxagore]] <span style="color: #2f3e5a; font-size: 0.86em;">v. 500 av. J.-C.</span><br/> → [[Pour lire Platon|Platon]] <span style="color: #2f3e5a; font-size: 0.86em;">428 av. J.-C.</span><br/> → [[Philosophie/Nietzsche|Nietzsche]] <span style="color: #2f3e5a; font-size: 0.86em;">1844</span> </div> <div style="height: 1px; background: #7a8aae; margin: 10px 0 8px;"></div> <div style="font-size: 0.86em; color: #233558; font-weight: 500;">[[:Catégorie:Philosophe|Tous les philosophes →]]</div> </td> </tr> </table> <!-- ══════════════════════════════════════════════ APPRENDRE ══════════════════════════════════════════════ --> <table style="width: 100%; border-collapse: collapse; margin: 10px 0 6px;"><tr><td style="white-space: nowrap; padding-right: 10px; font-size: 0.82em; font-weight: 700; letter-spacing: 0.3em; text-transform: uppercase; color: #4e4232; vertical-align: middle; width: 1%;">Apprendre</td><td style="vertical-align: middle;"><div style="height: 1px; background: #b8ae9c; font-size: 0; line-height: 0; margin: 0; padding: 0;">&#32;</div></td></tr></table> <table style="width: 100%; border-collapse: separate; border-spacing: 10px 0;"> <tr> <td style="width: 50%; vertical-align: top; background: #e2d4b0; border-left: 3px solid #b8923e; border-radius: 0 5px 5px 0; padding: 14px 18px;"> <div style="font-size: 0.74em; font-weight: 700; letter-spacing: 0.3em; text-transform: uppercase; color: #5a4820; margin-bottom: 9px;">Manuels</div> <div style="font-size: 0.94em; color: #251e10; line-height: 1.95;"> → [[Manuel de terminale de philosophie|Manuel de terminale]]<br/> → [[Philosophie/Une brève introduction|Une brève introduction à la philosophie]] </div> </td> <td style="width: 50%; vertical-align: top; background: #e2d4b0; border-left: 3px solid #b8923e; border-radius: 0 5px 5px 0; padding: 14px 18px;"> <div style="font-size: 0.74em; font-weight: 700; letter-spacing: 0.3em; text-transform: uppercase; color: #5a4820; margin-bottom: 9px;">Vocabulaires</div> <div style="font-size: 0.94em; color: #251e10; line-height: 1.95;"> → [[Pour lire Platon/Vocabulaire|Platon]]<br/> → [[Philosophie/Vocabulaire/David Hume|David Hume]] &nbsp;&middot;&nbsp; [[Philosophie/Vocabulaire/Kant|Kant]] </div> </td> </tr> </table> <!-- ══════════════════════════════════════════════ DICTIONNAIRE ══════════════════════════════════════════════ --> <table style="width: 100%; border-collapse: collapse; margin: 10px 0 6px;"><tr><td style="white-space: nowrap; padding-right: 10px; font-size: 0.82em; font-weight: 700; letter-spacing: 0.3em; text-transform: uppercase; color: #4e4232; vertical-align: middle; width: 1%;">Dictionnaire</td><td style="vertical-align: middle;"><div style="height: 1px; background: #b8ae9c; font-size: 0; line-height: 0; margin: 0; padding: 0;">&#32;</div></td></tr></table> <div style="background: #17151f; border-radius: 8px; padding: 20px 26px;"> <table style="width: 100%; border-collapse: collapse;"> <tr> <td style="vertical-align: middle; padding-right: 26px; border-right: 1px solid #352e48; white-space: nowrap; width: 200px;"> <div style="font-size: 1em; color: #f4efe4; margin-bottom: 4px; line-height: 1.3; font-weight: 500;">[[Dictionnaire de philosophie|Dictionnaire de philosophie]]</div> <div style="font-size: 0.84em; color: #c8bca8; margin-bottom: 10px;">Concepts, termes et notions</div> <div style="width: 28px; height: 2px; background: #d4a84e;"></div> </td> <td style="vertical-align: middle; padding-left: 26px;"> <div style="font-size: 1.16em; color: #e4c888; letter-spacing: 0.08em; font-family: Georgia, 'Times New Roman', serif; line-height: 1.3; margin-bottom: 11px;"> [[Dictionnaire de philosophie/A|A]] &ensp; [[Dictionnaire de philosophie/B|B]] &ensp; [[Dictionnaire de philosophie/C|C]] &ensp; [[Dictionnaire de philosophie/D|D]] &ensp; [[Dictionnaire de philosophie/E|E]] &ensp; [[Dictionnaire de philosophie/F|F]] &ensp; [[Dictionnaire de philosophie/G|G]] &ensp; [[Dictionnaire de philosophie/H|H]] &ensp; [[Dictionnaire de philosophie/I|I]] &ensp; [[Dictionnaire de philosophie/J|J]] &ensp; [[Dictionnaire de philosophie/K|K]] &ensp; [[Dictionnaire de philosophie/L|L]] &ensp; [[Dictionnaire de philosophie/M|M]] </div> <div style="font-size: 1.16em; color: #e4c888; letter-spacing: 0.08em; font-family: Georgia, 'Times New Roman', serif; line-height: 1.3;"> [[Dictionnaire de philosophie/N|N]] &ensp; [[Dictionnaire de philosophie/O|O]] &ensp; [[Dictionnaire de philosophie/P|P]] &ensp; [[Dictionnaire de philosophie/Q|Q]] &ensp; [[Dictionnaire de philosophie/R|R]] &ensp; [[Dictionnaire de philosophie/S|S]] &ensp; [[Dictionnaire de philosophie/T|T]] &ensp; [[Dictionnaire de philosophie/U|U]] &ensp; [[Dictionnaire de philosophie/V|V]] &ensp; [[Dictionnaire de philosophie/W|W]] &ensp; [[Dictionnaire de philosophie/X|X]] &ensp; [[Dictionnaire de philosophie/Y|Y]] &ensp; [[Dictionnaire de philosophie/Z|Z]] </div> </td> </tr> </table> </div> <!-- ══════════════════════════════════════════════ PIED DE PAGE ══════════════════════════════════════════════ --> <div style="margin-top: 18px; padding: 10px 24px 8px; text-align: center; border-top: 1px solid #e8ddd0;"> <div style="font-size: 0.74em; letter-spacing: 0.35em; text-transform: uppercase; color: #5a4e3a; margin-bottom: 6px; font-weight: 600;">Autres projets Wikimedia</div> <div style="font-size: 0.92em; color: #3a3028;"> [[w:Philosophie|Wikipédia]] &nbsp;&middot;&nbsp; [[wikt:philosophie|Wiktionnaire]] &nbsp;&middot;&nbsp; [[v:Philosophie|Wikiversité]] &nbsp;&middot;&nbsp; [[s:Portail:Philosophie|Wikisource]] </div> <div style="margin-top: 6px; font-size: 0.82em; color: #8a7a60; letter-spacing: 0.4em;">✦</div> </div> </div> [[Catégorie:Philosophie|*]] glaxz5yo29zurma0tblqy3b1xqtzy3r 764708 764699 2026-04-23T21:07:58Z PandaMystique 119061 764708 wikitext text/x-wiki <!-- ══════════════════════════════════════════════ EXPLORER ══════════════════════════════════════════════ --> <table style="width: 100%; border-collapse: collapse; margin: 10px 0 6px;"><tr><td style="white-space: nowrap; padding-right: 10px; font-size: 0.82em; font-weight: 700; letter-spacing: 0.3em; text-transform: uppercase; color: #4e4232; vertical-align: middle; width: 1%;">Explorer</td><td style="vertical-align: middle;"><div style="height: 1px; background: #b8ae9c; font-size: 0; line-height: 0; margin: 0; padding: 0;">&#32;</div></td></tr></table> <table style="width: 100%; border-collapse: separate; border-spacing: 10px 0;"> <tr> <!-- Disciplines --> <td style="width: 33.33%; vertical-align: top; background: #bcd3bc; border-radius: 6px; border-top: 3px solid #1f4a2e; padding: 16px 18px;"> <div style="font-size: 1em; font-weight: 700; color: #14321c; margin-bottom: 8px; line-height: 1.3;"> <span style="color: #1f4a2e; margin-right: 5px;">◇</span>[[:Catégorie:Discipline philosophique|Les disciplines]] </div> <div style="font-size: 0.9em; color: #1e1a12; line-height: 1.65; margin-bottom: 10px;"> Les branches de la pensée&nbsp;: métaphysique, épistémologie, éthique, logique, esthétique, philosophie politique. </div> <div style="height: 1px; background: #8eb08e; margin-bottom: 10px;"></div> <div style="font-size: 0.9em; color: #14321c; line-height: 1.9;"> → [[Philosophie/Théorie de la connaissance|Théorie de la connaissance]]<br/> → [[:Catégorie:Philosophie de l'esprit|Philosophie de l'esprit]] </div> </td> <!-- Œuvres commentées --> <td style="width: 33.33%; vertical-align: top; background: #d9bc9c; border-radius: 6px; border-top: 3px solid #6a2e1e; padding: 16px 18px;"> <div style="font-size: 1em; font-weight: 700; color: #3f160a; margin-bottom: 10px; line-height: 1.3;"> <span style="color: #6a2e1e; margin-right: 5px;">❧</span>[[Philosophie/Histoire de la philosophie|Œuvres commentées]] </div> <div style="font-size: 0.9em; color: #1e1a12; line-height: 1.9;"> → [[Commentaire philosophique/Lettre à Ménécée|Lettre à Ménécée]] <span style="color: #5a3e28; font-size: 0.82em; letter-spacing: 0.08em; text-transform: uppercase; font-weight: 600;">Épicure</span><br/> → [[Méditations métaphysiques|Méditations métaphysiques]] <span style="color: #5a3e28; font-size: 0.82em; letter-spacing: 0.08em; text-transform: uppercase; font-weight: 600;">Descartes</span><br/> → [[Commentaire de l'Éthique|Éthique]] <span style="color: #5a3e28; font-size: 0.82em; letter-spacing: 0.08em; text-transform: uppercase; font-weight: 600;">Spinoza</span><br/> → [[Philosophie/Commentaire du passage à propos de l'Homme esclave du divertissement|Le divertissement]] <span style="color: #5a3e28; font-size: 0.82em; letter-spacing: 0.08em; text-transform: uppercase; font-weight: 600;">Pascal</span> </div> <div style="height: 1px; background: #b0906e; margin: 10px 0 8px;"></div> <div style="font-size: 0.86em; color: #6a2e1e; font-weight: 500;">[[Philosophie/Histoire de la philosophie|Tous les commentaires →]]</div> </td> <!-- Philosophes --> <td style="width: 33.33%; vertical-align: top; background: #aebad6; border-radius: 6px; border-top: 3px solid #233558; padding: 16px 18px;"> <div style="font-size: 1em; font-weight: 700; color: #141e32; margin-bottom: 10px; line-height: 1.3;"> <span style="color: #233558; margin-right: 5px;">☉</span>[[:Catégorie:Philosophe|Philosophes]] </div> <div style="font-size: 0.9em; color: #1e1a12; line-height: 1.9;"> → [[Philosophie/Thalès de Milet|Thalès de Milet]] <span style="color: #2f3e5a; font-size: 0.86em;">v. 625 av. J.-C.</span><br/> → [[Dictionnaire de philosophie/Anaxagore|Anaxagore]] <span style="color: #2f3e5a; font-size: 0.86em;">v. 500 av. J.-C.</span><br/> → [[Pour lire Platon|Platon]] <span style="color: #2f3e5a; font-size: 0.86em;">428 av. J.-C.</span><br/> → [[Philosophie/Nietzsche|Nietzsche]] <span style="color: #2f3e5a; font-size: 0.86em;">1844</span> </div> <div style="height: 1px; background: #7a8aae; margin: 10px 0 8px;"></div> <div style="font-size: 0.86em; color: #233558; font-weight: 500;">[[:Catégorie:Philosophe|Tous les philosophes →]]</div> </td> </tr> </table> <!-- ══════════════════════════════════════════════ APPRENDRE ══════════════════════════════════════════════ --> <table style="width: 100%; border-collapse: collapse; margin: 10px 0 6px;"><tr><td style="white-space: nowrap; padding-right: 10px; font-size: 0.82em; font-weight: 700; letter-spacing: 0.3em; text-transform: uppercase; color: #4e4232; vertical-align: middle; width: 1%;">Apprendre</td><td style="vertical-align: middle;"><div style="height: 1px; background: #b8ae9c; font-size: 0; line-height: 0; margin: 0; padding: 0;">&#32;</div></td></tr></table> <table style="width: 100%; border-collapse: separate; border-spacing: 10px 0;"> <tr> <td style="width: 50%; vertical-align: top; background: #e2d4b0; border-left: 3px solid #b8923e; border-radius: 0 5px 5px 0; padding: 14px 18px;"> <div style="font-size: 0.74em; font-weight: 700; letter-spacing: 0.3em; text-transform: uppercase; color: #5a4820; margin-bottom: 9px;">Manuels</div> <div style="font-size: 0.94em; color: #251e10; line-height: 1.95;"> → [[Manuel de terminale de philosophie|Manuel de terminale]]<br/> → [[Philosophie/Une brève introduction|Une brève introduction à la philosophie]] </div> </td> <td style="width: 50%; vertical-align: top; background: #e2d4b0; border-left: 3px solid #b8923e; border-radius: 0 5px 5px 0; padding: 14px 18px;"> <div style="font-size: 0.74em; font-weight: 700; letter-spacing: 0.3em; text-transform: uppercase; color: #5a4820; margin-bottom: 9px;">Vocabulaires</div> <div style="font-size: 0.94em; color: #251e10; line-height: 1.95;"> → [[Pour lire Platon/Vocabulaire|Platon]]<br/> → [[Philosophie/Vocabulaire/David Hume|David Hume]] &nbsp;&middot;&nbsp; [[Philosophie/Vocabulaire/Kant|Kant]] </div> </td> </tr> </table> <!-- ══════════════════════════════════════════════ DICTIONNAIRE ══════════════════════════════════════════════ --> <table style="width: 100%; border-collapse: collapse; margin: 10px 0 6px;"><tr><td style="white-space: nowrap; padding-right: 10px; font-size: 0.82em; font-weight: 700; letter-spacing: 0.3em; text-transform: uppercase; color: #4e4232; vertical-align: middle; width: 1%;">Dictionnaire</td><td style="vertical-align: middle;"><div style="height: 1px; background: #b8ae9c; font-size: 0; line-height: 0; margin: 0; padding: 0;">&#32;</div></td></tr></table> <div style="background: #17151f; border-radius: 8px; padding: 20px 26px;"> <table style="width: 100%; border-collapse: collapse;"> <tr> <td style="vertical-align: middle; padding-right: 26px; border-right: 1px solid #352e48; white-space: nowrap; width: 200px;"> <div style="font-size: 1em; color: #f4efe4; margin-bottom: 4px; line-height: 1.3; font-weight: 500;">[[Dictionnaire de philosophie|Dictionnaire de philosophie]]</div> <div style="font-size: 0.84em; color: #c8bca8; margin-bottom: 10px;">Concepts, termes et notions</div> <div style="width: 28px; height: 2px; background: #d4a84e;"></div> </td> <td style="vertical-align: middle; padding-left: 26px;"> <div style="font-size: 1.16em; color: #e4c888; letter-spacing: 0.08em; font-family: Georgia, 'Times New Roman', serif; line-height: 1.3; margin-bottom: 11px;"> [[Dictionnaire de philosophie/A|A]] &ensp; [[Dictionnaire de philosophie/B|B]] &ensp; [[Dictionnaire de philosophie/C|C]] &ensp; [[Dictionnaire de philosophie/D|D]] &ensp; [[Dictionnaire de philosophie/E|E]] &ensp; [[Dictionnaire de philosophie/F|F]] &ensp; [[Dictionnaire de philosophie/G|G]] &ensp; [[Dictionnaire de philosophie/H|H]] &ensp; [[Dictionnaire de philosophie/I|I]] &ensp; [[Dictionnaire de philosophie/J|J]] &ensp; [[Dictionnaire de philosophie/K|K]] &ensp; [[Dictionnaire de philosophie/L|L]] &ensp; [[Dictionnaire de philosophie/M|M]] </div> <div style="font-size: 1.16em; color: #e4c888; letter-spacing: 0.08em; font-family: Georgia, 'Times New Roman', serif; line-height: 1.3;"> [[Dictionnaire de philosophie/N|N]] &ensp; [[Dictionnaire de philosophie/O|O]] &ensp; [[Dictionnaire de philosophie/P|P]] &ensp; [[Dictionnaire de philosophie/Q|Q]] &ensp; [[Dictionnaire de philosophie/R|R]] &ensp; [[Dictionnaire de philosophie/S|S]] &ensp; [[Dictionnaire de philosophie/T|T]] &ensp; [[Dictionnaire de philosophie/U|U]] &ensp; [[Dictionnaire de philosophie/V|V]] &ensp; [[Dictionnaire de philosophie/W|W]] &ensp; [[Dictionnaire de philosophie/X|X]] &ensp; [[Dictionnaire de philosophie/Y|Y]] &ensp; [[Dictionnaire de philosophie/Z|Z]] </div> </td> </tr> </table> </div> {{PhiloRecherche}} <!-- ══════════════════════════════════════════════ PIED DE PAGE ══════════════════════════════════════════════ --> <div style="margin-top: 18px; padding: 10px 24px 8px; text-align: center; border-top: 1px solid #e8ddd0;"> <div style="font-size: 0.74em; letter-spacing: 0.35em; text-transform: uppercase; color: #5a4e3a; margin-bottom: 6px; font-weight: 600;">Autres projets Wikimedia</div> <div style="font-size: 0.92em; color: #3a3028;"> [[w:Philosophie|Wikipédia]] &nbsp;&middot;&nbsp; [[wikt:philosophie|Wiktionnaire]] &nbsp;&middot;&nbsp; [[v:Philosophie|Wikiversité]] &nbsp;&middot;&nbsp; [[s:Portail:Philosophie|Wikisource]] </div> <div style="margin-top: 6px; font-size: 0.82em; color: #8a7a60; letter-spacing: 0.4em;">✦</div> </div> </div> [[Catégorie:Philosophie|*]] 59xyk1mdwagcvjna33frobowzvpfdd0 764709 764708 2026-04-23T21:12:16Z PandaMystique 119061 764709 wikitext text/x-wiki <!-- ══════════════════════════════════════════════ EXPLORER ══════════════════════════════════════════════ --> <table style="width: 100%; border-collapse: collapse; margin: 10px 0 6px;"><tr><td style="white-space: nowrap; padding-right: 10px; font-size: 0.82em; font-weight: 700; letter-spacing: 0.3em; text-transform: uppercase; color: #4e4232; vertical-align: middle; width: 1%;">Explorer</td><td style="vertical-align: middle;"><div style="height: 1px; background: #b8ae9c; font-size: 0; line-height: 0; margin: 0; padding: 0;">&#32;</div></td></tr></table> <table style="width: 100%; border-collapse: separate; border-spacing: 10px 0;"> <tr> <!-- Disciplines --> <td style="width: 33.33%; vertical-align: top; background: #bcd3bc; border-radius: 6px; border-top: 3px solid #1f4a2e; padding: 16px 18px;"> <div style="font-size: 1em; font-weight: 700; color: #14321c; margin-bottom: 8px; line-height: 1.3;"> <span style="color: #1f4a2e; margin-right: 5px;">◇</span>[[:Catégorie:Discipline philosophique|Les disciplines]] </div> <div style="font-size: 0.9em; color: #1e1a12; line-height: 1.65; margin-bottom: 10px;"> Les branches de la pensée&nbsp;: [[:Catégorie:Métaphysique|métaphysique]], [[:Catégorie:Épistémologie|épistémologie]], éthique, logique, esthétique, philosophie politique. </div> <div style="height: 1px; background: #8eb08e; margin-bottom: 10px;"></div> <div style="font-size: 0.9em; color: #14321c; line-height: 1.9;"> → [[Philosophie/Théorie de la connaissance|Théorie de la connaissance]]<br/> → [[:Catégorie:Philosophie de l'esprit|Philosophie de l'esprit]] </div> </td> <!-- Œuvres commentées --> <td style="width: 33.33%; vertical-align: top; background: #d9bc9c; border-radius: 6px; border-top: 3px solid #6a2e1e; padding: 16px 18px;"> <div style="font-size: 1em; font-weight: 700; color: #3f160a; margin-bottom: 10px; line-height: 1.3;"> <span style="color: #6a2e1e; margin-right: 5px;">❧</span>[[Philosophie/Histoire de la philosophie|Œuvres commentées]] </div> <div style="font-size: 0.9em; color: #1e1a12; line-height: 1.9;"> → [[Commentaire philosophique/Lettre à Ménécée|Lettre à Ménécée]] <span style="color: #5a3e28; font-size: 0.82em; letter-spacing: 0.08em; text-transform: uppercase; font-weight: 600;">Épicure</span><br/> → [[Méditations métaphysiques|Méditations métaphysiques]] <span style="color: #5a3e28; font-size: 0.82em; letter-spacing: 0.08em; text-transform: uppercase; font-weight: 600;">Descartes</span><br/> → [[Commentaire de l'Éthique|Éthique]] <span style="color: #5a3e28; font-size: 0.82em; letter-spacing: 0.08em; text-transform: uppercase; font-weight: 600;">Spinoza</span><br/> → [[Philosophie/Commentaire du passage à propos de l'Homme esclave du divertissement|Le divertissement]] <span style="color: #5a3e28; font-size: 0.82em; letter-spacing: 0.08em; text-transform: uppercase; font-weight: 600;">Pascal</span> </div> <div style="height: 1px; background: #b0906e; margin: 10px 0 8px;"></div> <div style="font-size: 0.86em; color: #6a2e1e; font-weight: 500;">[[Philosophie/Histoire de la philosophie|Tous les commentaires →]]</div> </td> <!-- Philosophes --> <td style="width: 33.33%; vertical-align: top; background: #aebad6; border-radius: 6px; border-top: 3px solid #233558; padding: 16px 18px;"> <div style="font-size: 1em; font-weight: 700; color: #141e32; margin-bottom: 10px; line-height: 1.3;"> <span style="color: #233558; margin-right: 5px;">☉</span>[[:Catégorie:Philosophe|Philosophes]] </div> <div style="font-size: 0.9em; color: #1e1a12; line-height: 1.9;"> → [[Philosophie/Thalès de Milet|Thalès de Milet]] <span style="color: #2f3e5a; font-size: 0.86em;">v. 625 av. J.-C.</span><br/> → [[Dictionnaire de philosophie/Anaxagore|Anaxagore]] <span style="color: #2f3e5a; font-size: 0.86em;">v. 500 av. J.-C.</span><br/> → [[Pour lire Platon|Platon]] <span style="color: #2f3e5a; font-size: 0.86em;">428 av. J.-C.</span><br/> → [[Philosophie/Nietzsche|Nietzsche]] <span style="color: #2f3e5a; font-size: 0.86em;">1844</span> </div> <div style="height: 1px; background: #7a8aae; margin: 10px 0 8px;"></div> <div style="font-size: 0.86em; color: #233558; font-weight: 500;">[[:Catégorie:Philosophe|Tous les philosophes →]]</div> </td> </tr> </table> <!-- ══════════════════════════════════════════════ APPRENDRE ══════════════════════════════════════════════ --> <table style="width: 100%; border-collapse: collapse; margin: 10px 0 6px;"><tr><td style="white-space: nowrap; padding-right: 10px; font-size: 0.82em; font-weight: 700; letter-spacing: 0.3em; text-transform: uppercase; color: #4e4232; vertical-align: middle; width: 1%;">Apprendre</td><td style="vertical-align: middle;"><div style="height: 1px; background: #b8ae9c; font-size: 0; line-height: 0; margin: 0; padding: 0;">&#32;</div></td></tr></table> <table style="width: 100%; border-collapse: separate; border-spacing: 10px 0;"> <tr> <td style="width: 50%; vertical-align: top; background: #e2d4b0; border-left: 3px solid #b8923e; border-radius: 0 5px 5px 0; padding: 14px 18px;"> <div style="font-size: 0.74em; font-weight: 700; letter-spacing: 0.3em; text-transform: uppercase; color: #5a4820; margin-bottom: 9px;">Manuels</div> <div style="font-size: 0.94em; color: #251e10; line-height: 1.95;"> → [[Manuel de terminale de philosophie|Manuel de terminale]]<br/> → [[Philosophie/Une brève introduction|Une brève introduction à la philosophie]] </div> </td> <td style="width: 50%; vertical-align: top; background: #e2d4b0; border-left: 3px solid #b8923e; border-radius: 0 5px 5px 0; padding: 14px 18px;"> <div style="font-size: 0.74em; font-weight: 700; letter-spacing: 0.3em; text-transform: uppercase; color: #5a4820; margin-bottom: 9px;">Vocabulaires</div> <div style="font-size: 0.94em; color: #251e10; line-height: 1.95;"> → [[Pour lire Platon/Vocabulaire|Platon]]<br/> → [[Philosophie/Vocabulaire/David Hume|David Hume]] &nbsp;&middot;&nbsp; [[Philosophie/Vocabulaire/Kant|Kant]] </div> </td> </tr> </table> <!-- ══════════════════════════════════════════════ DICTIONNAIRE ══════════════════════════════════════════════ --> <table style="width: 100%; border-collapse: collapse; margin: 10px 0 6px;"><tr><td style="white-space: nowrap; padding-right: 10px; font-size: 0.82em; font-weight: 700; letter-spacing: 0.3em; text-transform: uppercase; color: #4e4232; vertical-align: middle; width: 1%;">Dictionnaire</td><td style="vertical-align: middle;"><div style="height: 1px; background: #b8ae9c; font-size: 0; line-height: 0; margin: 0; padding: 0;">&#32;</div></td></tr></table> <div style="background: #17151f; border-radius: 8px; padding: 20px 26px;"> <table style="width: 100%; border-collapse: collapse;"> <tr> <td style="vertical-align: middle; padding-right: 26px; border-right: 1px solid #352e48; white-space: nowrap; width: 200px;"> <div style="font-size: 1em; color: #f4efe4; margin-bottom: 4px; line-height: 1.3; font-weight: 500;">[[Dictionnaire de philosophie|Dictionnaire de philosophie]]</div> <div style="font-size: 0.84em; color: #c8bca8; margin-bottom: 10px;">Concepts, termes et notions</div> <div style="width: 28px; height: 2px; background: #d4a84e;"></div> </td> <td style="vertical-align: middle; padding-left: 26px;"> <div style="font-size: 1.16em; color: #e4c888; letter-spacing: 0.08em; font-family: Georgia, 'Times New Roman', serif; line-height: 1.3; margin-bottom: 11px;"> [[Dictionnaire de philosophie/A|A]] &ensp; [[Dictionnaire de philosophie/B|B]] &ensp; [[Dictionnaire de philosophie/C|C]] &ensp; [[Dictionnaire de philosophie/D|D]] &ensp; [[Dictionnaire de philosophie/E|E]] &ensp; [[Dictionnaire de philosophie/F|F]] &ensp; [[Dictionnaire de philosophie/G|G]] &ensp; [[Dictionnaire de philosophie/H|H]] &ensp; [[Dictionnaire de philosophie/I|I]] &ensp; [[Dictionnaire de philosophie/J|J]] &ensp; [[Dictionnaire de philosophie/K|K]] &ensp; [[Dictionnaire de philosophie/L|L]] &ensp; [[Dictionnaire de philosophie/M|M]] </div> <div style="font-size: 1.16em; color: #e4c888; letter-spacing: 0.08em; font-family: Georgia, 'Times New Roman', serif; line-height: 1.3;"> [[Dictionnaire de philosophie/N|N]] &ensp; [[Dictionnaire de philosophie/O|O]] &ensp; [[Dictionnaire de philosophie/P|P]] &ensp; [[Dictionnaire de philosophie/Q|Q]] &ensp; [[Dictionnaire de philosophie/R|R]] &ensp; [[Dictionnaire de philosophie/S|S]] &ensp; [[Dictionnaire de philosophie/T|T]] &ensp; [[Dictionnaire de philosophie/U|U]] &ensp; [[Dictionnaire de philosophie/V|V]] &ensp; [[Dictionnaire de philosophie/W|W]] &ensp; [[Dictionnaire de philosophie/X|X]] &ensp; [[Dictionnaire de philosophie/Y|Y]] &ensp; [[Dictionnaire de philosophie/Z|Z]] </div> </td> </tr> </table> </div> {{PhiloRecherche}} <!-- ══════════════════════════════════════════════ PIED DE PAGE ══════════════════════════════════════════════ --> <div style="margin-top: 18px; padding: 10px 24px 8px; text-align: center; border-top: 1px solid #e8ddd0;"> <div style="font-size: 0.74em; letter-spacing: 0.35em; text-transform: uppercase; color: #5a4e3a; margin-bottom: 6px; font-weight: 600;">Autres projets Wikimedia</div> <div style="font-size: 0.92em; color: #3a3028;"> [[w:Philosophie|Wikipédia]] &nbsp;&middot;&nbsp; [[wikt:philosophie|Wiktionnaire]] &nbsp;&middot;&nbsp; [[v:Philosophie|Wikiversité]] &nbsp;&middot;&nbsp; [[s:Portail:Philosophie|Wikisource]] </div> <div style="margin-top: 6px; font-size: 0.82em; color: #8a7a60; letter-spacing: 0.4em;">✦</div> </div> </div> [[Catégorie:Philosophie|*]] 46k5md55bt2on7pffysh9mtic3gyp2x 764718 764709 2026-04-23T21:18:29Z PandaMystique 119061 764718 wikitext text/x-wiki <!-- ══════════════════════════════════════════════ EXPLORER ══════════════════════════════════════════════ --> <table style="width: 100%; border-collapse: collapse; margin: 10px 0 6px;"><tr><td style="white-space: nowrap; padding-right: 10px; font-size: 0.82em; font-weight: 700; letter-spacing: 0.3em; text-transform: uppercase; color: #5a6470; vertical-align: middle; width: 1%;">Explorer</td><td style="vertical-align: middle;"><div style="height: 1px; background: #d8dde2; font-size: 0; line-height: 0; margin: 0; padding: 0;">&#32;</div></td></tr></table> <table style="width: 100%; border-collapse: separate; border-spacing: 14px 0;"> <tr> <!-- Disciplines --> <td style="width: 33.33%; vertical-align: top; background: #f3f6f9; border-radius: 10px; box-shadow: 0 3px 10px rgba(0,0,0,0.08); padding: 18px 20px;"> <div style="font-size: 1em; font-weight: 700; color: #1f2530; margin-bottom: 8px; line-height: 1.3;"> <span style="color: #5a6470; margin-right: 5px;">◇</span>[[:Catégorie:Discipline philosophique|Les disciplines]] </div> <div style="font-size: 0.9em; color: #1f2530; line-height: 1.65; margin-bottom: 10px;"> Les branches de la pensée&nbsp;: métaphysique, épistémologie, éthique, logique, esthétique, philosophie politique. </div> <div style="height: 1px; background: #d8dde2; margin-bottom: 10px;"></div> <div style="font-size: 0.9em; color: #1f2530; line-height: 1.9;"> → [[Philosophie/Théorie de la connaissance|Théorie de la connaissance]]<br/> → [[:Catégorie:Philosophie de l'esprit|Philosophie de l'esprit]] </div> </td> <!-- Œuvres commentées --> <td style="width: 33.33%; vertical-align: top; background: #f3f6f9; border-radius: 10px; box-shadow: 0 3px 10px rgba(0,0,0,0.08); padding: 18px 20px;"> <div style="font-size: 1em; font-weight: 700; color: #1f2530; margin-bottom: 10px; line-height: 1.3;"> <span style="color: #5a6470; margin-right: 5px;">❧</span>[[Philosophie/Histoire de la philosophie|Œuvres commentées]] </div> <div style="font-size: 0.9em; color: #1f2530; line-height: 1.9;"> → [[Commentaire philosophique/Lettre à Ménécée|Lettre à Ménécée]] <span style="color: #6e7885; font-size: 0.82em; letter-spacing: 0.08em; text-transform: uppercase; font-weight: 600;">Épicure</span><br/> → [[Méditations métaphysiques|Méditations métaphysiques]] <span style="color: #6e7885; font-size: 0.82em; letter-spacing: 0.08em; text-transform: uppercase; font-weight: 600;">Descartes</span><br/> → [[Commentaire de l'Éthique|Éthique]] <span style="color: #6e7885; font-size: 0.82em; letter-spacing: 0.08em; text-transform: uppercase; font-weight: 600;">Spinoza</span><br/> → [[Philosophie/Commentaire du passage à propos de l'Homme esclave du divertissement|Le divertissement]] <span style="color: #6e7885; font-size: 0.82em; letter-spacing: 0.08em; text-transform: uppercase; font-weight: 600;">Pascal</span> </div> <div style="height: 1px; background: #d8dde2; margin: 10px 0 8px;"></div> <div style="font-size: 0.86em; color: #5a6470; font-weight: 500;">[[Philosophie/Histoire de la philosophie|Tous les commentaires →]]</div> </td> <!-- Philosophes --> <td style="width: 33.33%; vertical-align: top; background: #f3f6f9; border-radius: 10px; box-shadow: 0 3px 10px rgba(0,0,0,0.08); padding: 18px 20px;"> <div style="font-size: 1em; font-weight: 700; color: #1f2530; margin-bottom: 10px; line-height: 1.3;"> <span style="color: #5a6470; margin-right: 5px;">☉</span>[[:Catégorie:Philosophe|Philosophes]] </div> <div style="font-size: 0.9em; color: #1f2530; line-height: 1.9;"> → [[Philosophie/Thalès de Milet|Thalès de Milet]] <span style="color: #6e7885; font-size: 0.86em;">v. 625 av. J.-C.</span><br/> → [[Dictionnaire de philosophie/Anaxagore|Anaxagore]] <span style="color: #6e7885; font-size: 0.86em;">v. 500 av. J.-C.</span><br/> → [[Pour lire Platon|Platon]] <span style="color: #6e7885; font-size: 0.86em;">428 av. J.-C.</span><br/> → [[Philosophie/Nietzsche|Nietzsche]] <span style="color: #6e7885; font-size: 0.86em;">1844</span> </div> <div style="height: 1px; background: #d8dde2; margin: 10px 0 8px;"></div> <div style="font-size: 0.86em; color: #5a6470; font-weight: 500;">[[:Catégorie:Philosophe|Tous les philosophes →]]</div> </td> </tr> </table> <!-- ══════════════════════════════════════════════ APPRENDRE ══════════════════════════════════════════════ --> <table style="width: 100%; border-collapse: collapse; margin: 14px 0 6px;"><tr><td style="white-space: nowrap; padding-right: 10px; font-size: 0.82em; font-weight: 700; letter-spacing: 0.3em; text-transform: uppercase; color: #5a6470; vertical-align: middle; width: 1%;">Apprendre</td><td style="vertical-align: middle;"><div style="height: 1px; background: #d8dde2; font-size: 0; line-height: 0; margin: 0; padding: 0;">&#32;</div></td></tr></table> <table style="width: 100%; border-collapse: separate; border-spacing: 14px 0;"> <tr> <td style="width: 50%; vertical-align: top; background: #f3f6f9; border-radius: 10px; box-shadow: 0 3px 10px rgba(0,0,0,0.08); padding: 16px 20px;"> <div style="font-size: 0.74em; font-weight: 700; letter-spacing: 0.3em; text-transform: uppercase; color: #5a6470; margin-bottom: 9px;">Manuels</div> <div style="font-size: 0.94em; color: #1f2530; line-height: 1.95;"> → [[Manuel de terminale de philosophie|Manuel de terminale]]<br/> → [[Philosophie/Une brève introduction|Une brève introduction à la philosophie]] </div> </td> <td style="width: 50%; vertical-align: top; background: #f3f6f9; border-radius: 10px; box-shadow: 0 3px 10px rgba(0,0,0,0.08); padding: 16px 20px;"> <div style="font-size: 0.74em; font-weight: 700; letter-spacing: 0.3em; text-transform: uppercase; color: #5a6470; margin-bottom: 9px;">Vocabulaires</div> <div style="font-size: 0.94em; color: #1f2530; line-height: 1.95;"> → [[Pour lire Platon/Vocabulaire|Platon]]<br/> → [[Philosophie/Vocabulaire/David Hume|David Hume]] &nbsp;&middot;&nbsp; [[Philosophie/Vocabulaire/Kant|Kant]] </div> </td> </tr> </table> <!-- ══════════════════════════════════════════════ DICTIONNAIRE ══════════════════════════════════════════════ --> <table style="width: 100%; border-collapse: collapse; margin: 14px 0 6px;"><tr><td style="white-space: nowrap; padding-right: 10px; font-size: 0.82em; font-weight: 700; letter-spacing: 0.3em; text-transform: uppercase; color: #5a6470; vertical-align: middle; width: 1%;">Dictionnaire</td><td style="vertical-align: middle;"><div style="height: 1px; background: #d8dde2; font-size: 0; line-height: 0; margin: 0; padding: 0;">&#32;</div></td></tr></table> <div style="background: #f3f6f9; border-radius: 10px; box-shadow: 0 3px 10px rgba(0,0,0,0.08); padding: 22px 28px;"> <table style="width: 100%; border-collapse: collapse;"> <tr> <td style="vertical-align: middle; padding-right: 26px; border-right: 1px solid #d8dde2; white-space: nowrap; width: 200px;"> <div style="font-size: 1em; color: #1f2530; margin-bottom: 4px; line-height: 1.3; font-weight: 500;">[[Dictionnaire de philosophie|Dictionnaire de philosophie]]</div> <div style="font-size: 0.84em; color: #5a6470; margin-bottom: 10px;">Concepts, termes et notions</div> <div style="width: 28px; height: 2px; background: #5a6470;"></div> </td> <td style="vertical-align: middle; padding-left: 26px;"> <div style="font-size: 1.16em; color: #2d3846; letter-spacing: 0.08em; font-family: Georgia, 'Times New Roman', serif; line-height: 1.3; margin-bottom: 11px;"> [[Dictionnaire de philosophie/A|A]] &ensp; [[Dictionnaire de philosophie/B|B]] &ensp; [[Dictionnaire de philosophie/C|C]] &ensp; [[Dictionnaire de philosophie/D|D]] &ensp; [[Dictionnaire de philosophie/E|E]] &ensp; [[Dictionnaire de philosophie/F|F]] &ensp; [[Dictionnaire de philosophie/G|G]] &ensp; [[Dictionnaire de philosophie/H|H]] &ensp; [[Dictionnaire de philosophie/I|I]] &ensp; [[Dictionnaire de philosophie/J|J]] &ensp; [[Dictionnaire de philosophie/K|K]] &ensp; [[Dictionnaire de philosophie/L|L]] &ensp; [[Dictionnaire de philosophie/M|M]] </div> <div style="font-size: 1.16em; color: #2d3846; letter-spacing: 0.08em; font-family: Georgia, 'Times New Roman', serif; line-height: 1.3;"> [[Dictionnaire de philosophie/N|N]] &ensp; [[Dictionnaire de philosophie/O|O]] &ensp; [[Dictionnaire de philosophie/P|P]] &ensp; [[Dictionnaire de philosophie/Q|Q]] &ensp; [[Dictionnaire de philosophie/R|R]] &ensp; [[Dictionnaire de philosophie/S|S]] &ensp; [[Dictionnaire de philosophie/T|T]] &ensp; [[Dictionnaire de philosophie/U|U]] &ensp; [[Dictionnaire de philosophie/V|V]] &ensp; [[Dictionnaire de philosophie/W|W]] &ensp; [[Dictionnaire de philosophie/X|X]] &ensp; [[Dictionnaire de philosophie/Y|Y]] &ensp; [[Dictionnaire de philosophie/Z|Z]] </div> </td> </tr> </table> </div> {{PhiloRecherche}} <!-- ══════════════════════════════════════════════ PIED DE PAGE ══════════════════════════════════════════════ --> <div style="margin-top: 18px; padding: 10px 24px 8px; text-align: center; border-top: 1px solid #d8dde2;"> <div style="font-size: 0.74em; letter-spacing: 0.35em; text-transform: uppercase; color: #5a6470; margin-bottom: 6px; font-weight: 600;">Autres projets Wikimedia</div> <div style="font-size: 0.92em; color: #1f2530;"> [[w:Philosophie|Wikipédia]] &nbsp;&middot;&nbsp; [[wikt:philosophie|Wiktionnaire]] &nbsp;&middot;&nbsp; [[v:Philosophie|Wikiversité]] &nbsp;&middot;&nbsp; [[s:Portail:Philosophie|Wikisource]] </div> <div style="margin-top: 6px; font-size: 0.82em; color: #6e7885; letter-spacing: 0.4em;">✦</div> </div> </div> [[Catégorie:Philosophie|*]] itia42itdja9t9vd7kvcq6vso3e26vy 764753 764718 2026-04-24T04:55:22Z PandaMystique 119061 764753 wikitext text/x-wiki <!-- ══════════════════════════════════════════════ EN-TÊTE ══════════════════════════════════════════════ --> <div style="padding: 6px 0 18px; border-bottom: 2px solid #1a2230; margin-bottom: 22px;"> <div style="font-size: 1.6em; font-weight: 600; color: #1a2230; line-height: 1.2; margin-bottom: 4px;">Classification de la philosophie</div> <div style="font-size: 0.9em; color: #555e6b; line-height: 1.5;">Portail raisonné des livres, manuels et dictionnaires de philosophie sur Wikilivres. Les entrées sont rangées en cinq grandes aires, subdivisées selon la taxonomie standard des champs philosophiques.</div> </div> {{PhiloRecherche}} <!-- ══════════════════════════════════════════════ CLUSTER 1 — MÉTAPHYSIQUE ET ÉPISTÉMOLOGIE ══════════════════════════════════════════════ --> <div style="margin: 26px 0 4px; padding: 6px 0 4px; border-bottom: 1px solid #1a2230;"> <div style="font-size: 1.15em; font-weight: 700; color: #1a2230; letter-spacing: 0.01em;">Métaphysique et épistémologie</div> </div> <table style="width: 100%; border-collapse: separate; border-spacing: 24px 0; margin: 8px 0 18px;"> <tr> <td style="width: 33.33%; vertical-align: top;"> <div style="font-size: 0.98em; font-weight: 600; color: #1a2230; margin-bottom: 4px;">[[Philosophie/Théorie de la connaissance|Théorie de la connaissance]] <span style="color: #8a94a3; font-weight: 400;">(6)</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75; padding-left: 12px;"> [[Philosophie/Théorie de la connaissance/Une définition traditionnelle|Définition traditionnelle]] <span style="color: #8a94a3;">(1)</span><br/> [[Philosophie/Théorie de la connaissance/Le Problème de Gettier|Problème de Gettier]] <span style="color: #8a94a3;">(1)</span><br/> [[Dictionnaire de philosophie/A priori|A priori]] <span style="color: #8a94a3;">(1)</span><br/> [[Dictionnaire de philosophie/Abduction|Abduction]] <span style="color: #8a94a3;">(1)</span><br/> [[Dictionnaire de philosophie/Agnosticisme|Agnosticisme]] <span style="color: #8a94a3;">(1)</span> </div> </td> <td style="width: 33.33%; vertical-align: top;"> <div style="font-size: 0.98em; font-weight: 600; color: #1a2230; margin-bottom: 4px;">Métaphysique <span style="color: #8a94a3; font-weight: 400;">(7)</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75; padding-left: 12px;"> [[Dictionnaire de philosophie/Absolu|Absolu]] <span style="color: #8a94a3;">(1)</span><br/> [[Philosophie/Acte/Puissance|Acte et Puissance]] <span style="color: #8a94a3;">(1)</span><br/> [[Dictionnaire de philosophie/Accident|Accident]] <span style="color: #8a94a3;">(1)</span><br/> [[Dictionnaire de philosophie/Attribut|Attribut]] <span style="color: #8a94a3;">(1)</span><br/> [[Dictionnaire de philosophie/Atomisme|Atomisme]] <span style="color: #8a94a3;">(1)</span> </div> </td> <td style="width: 33.33%; vertical-align: top;"> <div style="font-size: 0.98em; font-weight: 600; color: #1a2230; margin-bottom: 4px;">[[:Catégorie:Philosophie de l'esprit|Philosophie de l'esprit]] <span style="color: #8a94a3; font-weight: 400;">(5)</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75; padding-left: 12px;"> [[Philosophie de l'esprit/Argument de la connaissance|Argument de la connaissance]] <span style="color: #8a94a3;">(1)</span><br/> [[Manuel de terminale de philosophie/Conscience|Conscience]] <span style="color: #8a94a3;">(1)</span><br/> [[Manuel de terminale de philosophie/Inconscient|Inconscient]] <span style="color: #8a94a3;">(1)</span><br/> [[Dictionnaire de philosophie/Âme|Âme]] <span style="color: #8a94a3;">(1)</span><br/> [[Dictionnaire de philosophie/Intelligence animale|Intelligence animale]] <span style="color: #8a94a3;">(1)</span> </div> </td> </tr> <tr><td colspan="3" style="height: 14px;"></td></tr> <tr> <td style="width: 33.33%; vertical-align: top;"> <div style="font-size: 0.98em; font-weight: 600; color: #1a2230; margin-bottom: 4px;">Philosophie du langage <span style="color: #8a94a3; font-weight: 400;">(3)</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75; padding-left: 12px;"> [[Manuel de terminale de philosophie/Langage|Langage]] <span style="color: #8a94a3;">(1)</span><br/> [[Dictionnaire de philosophie/Analogie|Analogie]] <span style="color: #8a94a3;">(1)</span><br/> [[Dictionnaire de philosophie/Argument|Argument]] <span style="color: #8a94a3;">(1)</span> </div> </td> <td style="width: 33.33%; vertical-align: top;"> <div style="font-size: 0.98em; font-weight: 600; color: #1a2230; margin-bottom: 4px;">Philosophie de l'action <span style="color: #8a94a3; font-weight: 400;">(4)</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75; padding-left: 12px;"> [[Dictionnaire de philosophie/Action|Action]] <span style="color: #8a94a3;">(1)</span><br/> [[Dictionnaire de philosophie/Aboulie|Aboulie]] <span style="color: #8a94a3;">(1)</span><br/> [[Dictionnaire de philosophie/Altruisme|Altruisme]] <span style="color: #8a94a3;">(1)</span><br/> [[Dictionnaire de philosophie/Authenticité|Authenticité]] <span style="color: #8a94a3;">(1)</span> </div> </td> <td style="width: 33.33%; vertical-align: top;"> <div style="font-size: 0.98em; font-weight: 600; color: #1a2230; margin-bottom: 4px;">Philosophie de la religion <span style="color: #8a94a3; font-weight: 400;">(2)</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75; padding-left: 12px;"> [[Manuel de terminale de philosophie/Religion|Religion]] <span style="color: #8a94a3;">(1)</span><br/> [[Dictionnaire de philosophie/Athéisme|Athéisme]] <span style="color: #8a94a3;">(1)</span> </div> </td> </tr> </table> <!-- ══════════════════════════════════════════════ CLUSTER 2 — THÉORIE DE LA VALEUR ══════════════════════════════════════════════ --> <div style="margin: 26px 0 4px; padding: 6px 0 4px; border-bottom: 1px solid #1a2230;"> <div style="font-size: 1.15em; font-weight: 700; color: #1a2230; letter-spacing: 0.01em;">Théorie de la valeur</div> </div> <table style="width: 100%; border-collapse: separate; border-spacing: 24px 0; margin: 8px 0 18px;"> <tr> <td style="width: 33.33%; vertical-align: top;"> <div style="font-size: 0.98em; font-weight: 600; color: #1a2230; margin-bottom: 4px;">Éthique normative <span style="color: #8a94a3; font-weight: 400;">(7)</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75; padding-left: 12px;"> [[Manuel de terminale de philosophie/Devoir|Devoir]] <span style="color: #8a94a3;">(1)</span><br/> [[Manuel de terminale de philosophie/Bonheur|Bonheur]] <span style="color: #8a94a3;">(1)</span><br/> [[Manuel de terminale de philosophie/Liberté|Liberté]] <span style="color: #8a94a3;">(1)</span><br/> [[Philosophie/Ataraxie|Ataraxie]] <span style="color: #8a94a3;">(1)</span><br/> [[Dictionnaire de philosophie/Amour|Amour]] <span style="color: #8a94a3;">(1)</span><br/> [[Dictionnaire de philosophie/Amitié|Amitié]] <span style="color: #8a94a3;">(1)</span> </div> </td> <td style="width: 33.33%; vertical-align: top;"> <div style="font-size: 0.98em; font-weight: 600; color: #1a2230; margin-bottom: 4px;">Éthique appliquée <span style="color: #8a94a3; font-weight: 400;">(3)</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75; padding-left: 12px;"> [[Dictionnaire de philosophie/Avortement|Avortement (éthique)]] <span style="color: #8a94a3;">(1)</span><br/> [[Dictionnaire de philosophie/Animal|Animal (droits)]] <span style="color: #8a94a3;">(1)</span><br/> [[Dictionnaire de philosophie/Anthropocentrisme|Anthropocentrisme]] <span style="color: #8a94a3;">(1)</span> </div> </td> <td style="width: 33.33%; vertical-align: top;"> <div style="font-size: 0.98em; font-weight: 600; color: #1a2230; margin-bottom: 4px;">Esthétique <span style="color: #8a94a3; font-weight: 400;">(3)</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75; padding-left: 12px;"> [[Manuel de terminale de philosophie/Art|Art]] <span style="color: #8a94a3;">(1)</span><br/> [[Dictionnaire de philosophie/Art (introduction)|Art — introduction]] <span style="color: #8a94a3;">(1)</span><br/> [[Dictionnaire de philosophie/Art et Vérité|Art et vérité]] <span style="color: #8a94a3;">(1)</span> </div> </td> </tr> <tr><td colspan="3" style="height: 14px;"></td></tr> <tr> <td style="width: 33.33%; vertical-align: top;"> <div style="font-size: 0.98em; font-weight: 600; color: #1a2230; margin-bottom: 4px;">Philosophie sociale et politique <span style="color: #8a94a3; font-weight: 400;">(6)</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75; padding-left: 12px;"> [[Manuel de terminale de philosophie/État|État]] <span style="color: #8a94a3;">(1)</span><br/> [[Manuel de terminale de philosophie/Justice|Justice]] <span style="color: #8a94a3;">(1)</span><br/> [[Manuel de terminale de philosophie/Travail|Travail]] <span style="color: #8a94a3;">(1)</span><br/> [[Dictionnaire de philosophie/Absolutisme|Absolutisme]] <span style="color: #8a94a3;">(1)</span><br/> [[Dictionnaire de philosophie/Anarchisme|Anarchisme]] <span style="color: #8a94a3;">(1)</span><br/> [[Dictionnaire de philosophie/Aliénation|Aliénation]] <span style="color: #8a94a3;">(1)</span> </div> </td> <td style="width: 33.33%; vertical-align: top;"> <div style="font-size: 0.98em; font-weight: 600; color: #1a2230; margin-bottom: 4px;">Autrui et reconnaissance <span style="color: #8a94a3; font-weight: 400;">(4)</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75; padding-left: 12px;"> [[Philosophie/Autrui|Autrui]] <span style="color: #8a94a3;">(1)</span><br/> [[Dictionnaire de philosophie/Altérité|Altérité]] <span style="color: #8a94a3;">(1)</span><br/> [[Dictionnaire de philosophie/Autorité|Autorité]] <span style="color: #8a94a3;">(1)</span><br/> [[Dictionnaire de philosophie/Autonomie|Autonomie]] <span style="color: #8a94a3;">(1)</span> </div> </td> <td style="width: 33.33%; vertical-align: top;"> <div style="font-size: 0.98em; font-weight: 600; color: #1a2230; margin-bottom: 4px;">Métaéthique <span style="color: #8a94a3; font-weight: 400;">(3)</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75; padding-left: 12px;"> [[Dictionnaire de philosophie/Absurde|Absurde]] <span style="color: #8a94a3;">(1)</span><br/> [[Dictionnaire de philosophie/Affection|Affection]] <span style="color: #8a94a3;">(1)</span><br/> [[Dictionnaire de philosophie/Angoisse|Angoisse]] <span style="color: #8a94a3;">(1)</span> </div> </td> </tr> </table> <!-- ══════════════════════════════════════════════ CLUSTER 3 — SCIENCE, LOGIQUE ET MATHÉMATIQUES ══════════════════════════════════════════════ --> <div style="margin: 26px 0 4px; padding: 6px 0 4px; border-bottom: 1px solid #1a2230;"> <div style="font-size: 1.15em; font-weight: 700; color: #1a2230; letter-spacing: 0.01em;">Science, logique et mathématiques</div> </div> <table style="width: 100%; border-collapse: separate; border-spacing: 24px 0; margin: 8px 0 18px;"> <tr> <td style="width: 33.33%; vertical-align: top;"> <div style="font-size: 0.98em; font-weight: 600; color: #1a2230; margin-bottom: 4px;">Logique <span style="color: #8a94a3; font-weight: 400;">(6)</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75; padding-left: 12px;"> [[Dictionnaire de philosophie/A (logique)|A (logique)]] <span style="color: #8a94a3;">(1)</span><br/> [[Dictionnaire de philosophie/Abstraction|Abstraction]] <span style="color: #8a94a3;">(1)</span><br/> [[Dictionnaire de philosophie/Antinomie|Antinomie]] <span style="color: #8a94a3;">(1)</span><br/> [[Philosophie/Aporie|Aporie]] <span style="color: #8a94a3;">(1)</span><br/> [[Dictionnaire de philosophie/Axiome|Axiome]] <span style="color: #8a94a3;">(1)</span><br/> [[Dictionnaire de philosophie/Analyse|Analyse]] <span style="color: #8a94a3;">(1)</span> </div> </td> <td style="width: 33.33%; vertical-align: top;"> <div style="font-size: 0.98em; font-weight: 600; color: #1a2230; margin-bottom: 4px;">Philosophie des sciences <span style="color: #8a94a3; font-weight: 400;">(3)</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75; padding-left: 12px;"> [[Philosophie/Théorie et expérience|Théorie et expérience]] <span style="color: #8a94a3;">(1)</span><br/> [[Manuel de terminale de philosophie/Science|Science]] <span style="color: #8a94a3;">(1)</span><br/> [[Manuel de terminale de philosophie/Technique|Technique]] <span style="color: #8a94a3;">(1)</span> </div> </td> <td style="width: 33.33%; vertical-align: top;"> <div style="font-size: 0.98em; font-weight: 600; color: #1a2230; margin-bottom: 4px;">Philosophie du vivant <span style="color: #8a94a3; font-weight: 400;">(3)</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75; padding-left: 12px;"> [[Manuel de terminale de philosophie/Nature|Nature]] <span style="color: #8a94a3;">(1)</span><br/> [[Manuel de terminale de philosophie/Vivant|Vivant]] <span style="color: #8a94a3;">(1)</span><br/> [[Dictionnaire de philosophie/Animal|Animal]] <span style="color: #8a94a3;">(1)</span> </div> </td> </tr> </table> <!-- ══════════════════════════════════════════════ CLUSTER 4 — HISTOIRE DE LA PHILOSOPHIE ══════════════════════════════════════════════ --> <div style="margin: 26px 0 4px; padding: 6px 0 4px; border-bottom: 1px solid #1a2230;"> <div style="font-size: 1.15em; font-weight: 700; color: #1a2230; letter-spacing: 0.01em;">[[Philosophie/Histoire de la philosophie|Histoire de la philosophie]] <span style="color: #8a94a3; font-weight: 400; font-size: 0.92em;">(24)</span></div> </div> <table style="width: 100%; border-collapse: separate; border-spacing: 24px 0; margin: 8px 0 18px;"> <tr> <td style="width: 33.33%; vertical-align: top;"> <div style="font-size: 0.98em; font-weight: 600; color: #1a2230; margin-bottom: 4px;">Antiquité <span style="color: #8a94a3; font-weight: 400;">(10)</span> <span style="color: #8a94a3; font-weight: 400; font-size: 0.85em;">VI<sup>e</sup> av. — V<sup>e</sup> siècle</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75; padding-left: 12px;"> <span style="font-weight: 500;">Présocratiques</span> <span style="color: #8a94a3;">(5)</span><br/> <span style="padding-left: 10px;">[[Philosophie/Thalès de Milet|Thalès de Milet]] <span style="color: #8a94a3;">(1)</span></span><br/> <span style="padding-left: 10px;">[[Philosophie/Anaximandre de Milet|Anaximandre]] <span style="color: #8a94a3;">(4)</span></span><br/> <span style="padding-left: 10px;">[[Dictionnaire de philosophie/Anaxagore|Anaxagore]] <span style="color: #8a94a3;">(1)</span></span><br/> <span style="padding-left: 10px;">[[Philosophie/Présocratiques/Liste des Présocratiques|Liste complète]] <span style="color: #8a94a3;">→</span></span><br/> [[Pour lire Platon|Platon]] <span style="color: #8a94a3;">(2)</span><br/> [[Dictionnaire de philosophie/Aristote|Aristote]] <span style="color: #8a94a3;">(1)</span><br/> <span style="font-weight: 500;">Philosophie hellénistique</span> <span style="color: #8a94a3;">(2)</span><br/> <span style="padding-left: 10px;">[[Commentaire philosophique/Lettre à Ménécée|Épicure]] <span style="color: #8a94a3;">(1)</span></span><br/> <span style="padding-left: 10px;">[[Philosophie/Ataraxie|Stoïcisme]] <span style="color: #8a94a3;">(1)</span></span> </div> </td> <td style="width: 33.33%; vertical-align: top;"> <div style="font-size: 0.98em; font-weight: 600; color: #1a2230; margin-bottom: 4px;">Philosophie médiévale <span style="color: #8a94a3; font-weight: 400;">(0)</span> <span style="color: #8a94a3; font-weight: 400; font-size: 0.85em;">V<sup>e</sup> — XV<sup>e</sup></span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75; padding-left: 12px;"> <span style="color: #8a94a3; font-style: italic;">Augustin, Anselme, Thomas d'Aquin, Averroès — pages à rédiger.</span> </div> <div style="font-size: 0.98em; font-weight: 600; color: #1a2230; margin: 14px 0 4px;">Philosophie classique <span style="color: #8a94a3; font-weight: 400;">(6)</span> <span style="color: #8a94a3; font-weight: 400; font-size: 0.85em;">XVI<sup>e</sup> — XVIII<sup>e</sup></span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75; padding-left: 12px;"> [[Méditations métaphysiques|Descartes — Méditations]] <span style="color: #8a94a3;">(1)</span><br/> [[Philosophie/Commentaire du passage à propos de l'Homme esclave du divertissement|Pascal — Le divertissement]] <span style="color: #8a94a3;">(1)</span><br/> [[Commentaire de l'Éthique|Spinoza — Éthique]] <span style="color: #8a94a3;">(1)</span><br/> [[Philosophie/Vocabulaire/David Hume|Hume]] <span style="color: #8a94a3;">(1)</span><br/> [[Philosophie/Vocabulaire/Kant|Kant]] <span style="color: #8a94a3;">(1)</span><br/> [[Commentaire philosophique/Discours sur l'origine et les fondements de l'inégalité parmi les hommes|Rousseau — Inégalité]] <span style="color: #8a94a3;">(1)</span> </div> </td> <td style="width: 33.33%; vertical-align: top;"> <div style="font-size: 0.98em; font-weight: 600; color: #1a2230; margin-bottom: 4px;">Philosophie contemporaine <span style="color: #8a94a3; font-weight: 400;">(5)</span> <span style="color: #8a94a3; font-weight: 400; font-size: 0.85em;">XIX<sup>e</sup> — XXI<sup>e</sup></span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75; padding-left: 12px;"> [[Philosophie/Nietzsche|Nietzsche]] <span style="color: #8a94a3;">(4)</span><br/> <span style="padding-left: 10px;">[[Philosophie/Nietzsche/La culture grecque|Culture grecque]]</span><br/> <span style="padding-left: 10px;">[[Philosophie/Nietzsche/La culture moderne|Culture moderne]]</span><br/> <span style="padding-left: 10px;">[[Philosophie/Nietzsche/La métaphysique|Métaphysique]]</span><br/> <span style="padding-left: 10px;">[[Philosophie/Nietzsche/La moralité des mœurs|Moralité des mœurs]]</span><br/> [[Dictionnaire de philosophie/Argentine (Philosophie)|Argentine XX<sup>e</sup>]] <span style="color: #8a94a3;">(1)</span> </div> <div style="font-size: 0.98em; font-weight: 600; color: #1a2230; margin: 14px 0 4px;">Traditions non occidentales <span style="color: #8a94a3; font-weight: 400;">(1)</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75; padding-left: 12px;"> [[Dictionnaire de philosophie/Philosophie africaine|Philosophie africaine]] <span style="color: #8a94a3;">(1)</span> </div> </td> </tr> </table> <!-- ══════════════════════════════════════════════ CLUSTER 5 — OUVRAGES DE RÉFÉRENCE ══════════════════════════════════════════════ --> <div style="margin: 26px 0 4px; padding: 6px 0 4px; border-bottom: 1px solid #1a2230;"> <div style="font-size: 1.15em; font-weight: 700; color: #1a2230; letter-spacing: 0.01em;">Ouvrages de référence et pédagogiques</div> </div> <table style="width: 100%; border-collapse: separate; border-spacing: 24px 0; margin: 8px 0 18px;"> <tr> <td style="width: 33.33%; vertical-align: top;"> <div style="font-size: 0.98em; font-weight: 600; color: #1a2230; margin-bottom: 4px;">[[Dictionnaire de philosophie|Dictionnaire de philosophie]] <span style="color: #8a94a3; font-weight: 400;">(35)</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75; padding-left: 12px;"> [[Dictionnaire de philosophie/A|A]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/B|B]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/C|C]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/D|D]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/E|E]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/F|F]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/G|G]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/H|H]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/I|I]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/J|J]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/K|K]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/L|L]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/M|M]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/N|N]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/O|O]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/P|P]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/Q|Q]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/R|R]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/S|S]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/T|T]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/U|U]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/V|V]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/W|W]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/X|X]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/Y|Y]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/Z|Z]] </div> </td> <td style="width: 33.33%; vertical-align: top;"> <div style="font-size: 0.98em; font-weight: 600; color: #1a2230; margin-bottom: 4px;">[[Manuel de terminale de philosophie|Manuel de terminale]] <span style="color: #8a94a3; font-weight: 400;">(26)</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75; padding-left: 12px;"> <span style="font-weight: 500;">Éthique &amp; action</span> <span style="color: #8a94a3;">(4)</span><br/> <span style="font-weight: 500;">Esprit &amp; conscience</span> <span style="color: #8a94a3;">(4)</span><br/> <span style="font-weight: 500;">Connaissance &amp; vérité</span> <span style="color: #8a94a3;">(5)</span><br/> <span style="font-weight: 500;">Monde &amp; société</span> <span style="color: #8a94a3;">(4)</span><br/> <span style="font-weight: 500;">Culture &amp; sens</span> <span style="color: #8a94a3;">(6)</span><br/> <span style="font-weight: 500;">Méthode &amp; ressources</span> <span style="color: #8a94a3;">(3)</span> </div> <div style="font-size: 0.98em; font-weight: 600; color: #1a2230; margin: 14px 0 4px;">[[:Catégorie:Vocabulaire philosophique|Vocabulaires]] <span style="color: #8a94a3; font-weight: 400;">(4)</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75; padding-left: 12px;"> [[Pour lire Platon/Vocabulaire|Platon]] <span style="color: #8a94a3;">(1)</span><br/> [[Philosophie/Vocabulaire/David Hume|Hume]] <span style="color: #8a94a3;">(1)</span><br/> [[Philosophie/Vocabulaire/Kant|Kant]] <span style="color: #8a94a3;">(1)</span> </div> </td> <td style="width: 33.33%; vertical-align: top;"> <div style="font-size: 0.98em; font-weight: 600; color: #1a2230; margin-bottom: 4px;">[[:Catégorie:Commentaire philosophique|Commentaires d'œuvres]] <span style="color: #8a94a3; font-weight: 400;">(20)</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75; padding-left: 12px;"> [[Commentaire philosophique/Lettre à Ménécée|Lettre à Ménécée]] <span style="color: #8a94a3;">·</span> Épicure<br/> [[Méditations métaphysiques|Méditations métaphysiques]] <span style="color: #8a94a3;">·</span> Descartes<br/> [[Commentaire de l'Éthique|Éthique]] <span style="color: #8a94a3;">·</span> Spinoza<br/> [[Commentaire philosophique/Discours sur l'origine et les fondements de l'inégalité parmi les hommes|Discours sur l'inégalité]] <span style="color: #8a94a3;">·</span> Rousseau<br/> [[Commentaire philosophique/Étude de texte sur la différence entre l'homme et l'animal à l'état de nature|Homme &amp; animal]] <span style="color: #8a94a3;">·</span> Rousseau<br/> [[Philosophie/Commentaire du passage à propos de l'Homme esclave du divertissement|Le divertissement]] <span style="color: #8a94a3;">·</span> Pascal </div> <div style="font-size: 0.98em; font-weight: 600; color: #1a2230; margin: 14px 0 4px;">Introductions <span style="color: #8a94a3; font-weight: 400;">(1)</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75; padding-left: 12px;"> [[Philosophie/Une brève introduction|Une brève introduction à la philosophie]] </div> </td> </tr> </table> <!-- ══════════════════════════════════════════════ PIED DE PAGE ══════════════════════════════════════════════ --> <div style="margin-top: 32px; padding: 14px 0 4px; border-top: 1px solid #1a2230; font-size: 0.86em; color: #555e6b;"> <div style="margin-bottom: 6px;"><span style="font-weight: 600; color: #1a2230;">Consulter ailleurs</span> &nbsp;·&nbsp; [[w:Philosophie|Wikipédia]] &nbsp;·&nbsp; [[wikt:philosophie|Wiktionnaire]] &nbsp;·&nbsp; [[v:Philosophie|Wikiversité]] &nbsp;·&nbsp; [[s:Portail:Philosophie|Wikisource]]</div> <div><span style="font-weight: 600; color: #1a2230;">Catégories sources</span> &nbsp;·&nbsp; [[:Catégorie:Discipline philosophique|Disciplines]] &nbsp;·&nbsp; [[:Catégorie:Histoire de la philosophie|Histoire]] &nbsp;·&nbsp; [[:Catégorie:Philosophe|Philosophes]] &nbsp;·&nbsp; [[:Catégorie:Dictionnaire de philosophie (livre)|Dictionnaire]] &nbsp;·&nbsp; [[:Catégorie:Manuel de terminale de philosophie (livre)|Manuel]] &nbsp;·&nbsp; [[:Catégorie:Commentaire philosophique|Commentaires]]</div> </div> </div> [[Catégorie:Philosophie|*]] duhnghqp5j4ezsvhthfsw3fsjc641s5 764754 764753 2026-04-24T05:10:48Z PandaMystique 119061 764754 wikitext text/x-wiki <!-- ══════════════════════════════════════════════ EN-TÊTE ══════════════════════════════════════════════ --> <div style="padding: 6px 0 18px; border-bottom: 2px solid #1a2230; margin-bottom: 22px;"> <div style="font-size: 1.6em; font-weight: 600; color: #1a2230; line-height: 1.2; margin-bottom: 4px;">Classification de la philosophie</div> <div style="font-size: 0.9em; color: #555e6b; line-height: 1.5;">Portail raisonné des livres, manuels et dictionnaires de philosophie sur Wikilivres. Les entrées sont rangées en cinq grandes aires, subdivisées selon la taxonomie standard des champs philosophiques.</div> </div> {{PhiloRecherche}} <!-- ══════════════════════════════════════════════ CLUSTER 1 — MÉTAPHYSIQUE ET ÉPISTÉMOLOGIE ══════════════════════════════════════════════ --> <div style="margin: 26px 0 10px; padding: 6px 0 4px; border-bottom: 1px solid #1a2230;"> <div style="font-size: 1.15em; font-weight: 700; color: #1a2230;">Métaphysique et épistémologie</div> </div> <table style="width: 100%; border-collapse: separate; border-spacing: 14px 14px;"> <tr> <td style="width: 33.33%; vertical-align: top; background: #f8fafc; border-radius: 10px; padding: 14px 16px; box-shadow: 0 1px 3px rgba(0,0,0,0.06);"> <div style="font-size: 0.96em; font-weight: 600; color: #1a2230; margin-bottom: 6px;">[[Philosophie/Théorie de la connaissance|Théorie de la connaissance]] <span style="color: #8a94a3; font-weight: 400;">(6)</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Philosophie/Théorie de la connaissance/Une définition traditionnelle|Définition traditionnelle]]<br/> [[Philosophie/Théorie de la connaissance/Le Problème de Gettier|Problème de Gettier]]<br/> [[Dictionnaire de philosophie/A priori|A priori]]<br/> [[Dictionnaire de philosophie/Abduction|Abduction]]<br/> [[Dictionnaire de philosophie/Agnosticisme|Agnosticisme]] </div> </td> <td style="width: 33.33%; vertical-align: top; background: #f8fafc; border-radius: 10px; padding: 14px 16px; box-shadow: 0 1px 3px rgba(0,0,0,0.06);"> <div style="font-size: 0.96em; font-weight: 600; color: #1a2230; margin-bottom: 6px;">Métaphysique <span style="color: #8a94a3; font-weight: 400;">(7)</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Dictionnaire de philosophie/Absolu|Absolu]]<br/> [[Philosophie/Acte/Puissance|Acte et Puissance]]<br/> [[Dictionnaire de philosophie/Accident|Accident]]<br/> [[Dictionnaire de philosophie/Attribut|Attribut]]<br/> [[Dictionnaire de philosophie/Atomisme|Atomisme]] </div> </td> <td style="width: 33.33%; vertical-align: top; background: #f8fafc; border-radius: 10px; padding: 14px 16px; box-shadow: 0 1px 3px rgba(0,0,0,0.06);"> <div style="font-size: 0.96em; font-weight: 600; color: #1a2230; margin-bottom: 6px;">[[:Catégorie:Philosophie de l'esprit|Philosophie de l'esprit]] <span style="color: #8a94a3; font-weight: 400;">(5)</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Philosophie de l'esprit/Argument de la connaissance|Argument de la connaissance]]<br/> [[Manuel de terminale de philosophie/Conscience|Conscience]]<br/> [[Manuel de terminale de philosophie/Inconscient|Inconscient]]<br/> [[Dictionnaire de philosophie/Âme|Âme]]<br/> [[Dictionnaire de philosophie/Intelligence animale|Intelligence animale]] </div> </td> </tr> <tr> <td style="width: 33.33%; vertical-align: top; background: #f8fafc; border-radius: 10px; padding: 14px 16px; box-shadow: 0 1px 3px rgba(0,0,0,0.06);"> <div style="font-size: 0.96em; font-weight: 600; color: #1a2230; margin-bottom: 6px;">Philosophie du langage <span style="color: #8a94a3; font-weight: 400;">(3)</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Manuel de terminale de philosophie/Langage|Langage]]<br/> [[Dictionnaire de philosophie/Analogie|Analogie]]<br/> [[Dictionnaire de philosophie/Argument|Argument]] </div> </td> <td style="width: 33.33%; vertical-align: top; background: #f8fafc; border-radius: 10px; padding: 14px 16px; box-shadow: 0 1px 3px rgba(0,0,0,0.06);"> <div style="font-size: 0.96em; font-weight: 600; color: #1a2230; margin-bottom: 6px;">Philosophie de l'action <span style="color: #8a94a3; font-weight: 400;">(4)</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Dictionnaire de philosophie/Action|Action]]<br/> [[Dictionnaire de philosophie/Aboulie|Aboulie]]<br/> [[Dictionnaire de philosophie/Altruisme|Altruisme]]<br/> [[Dictionnaire de philosophie/Authenticité|Authenticité]] </div> </td> <td style="width: 33.33%; vertical-align: top; background: #f8fafc; border-radius: 10px; padding: 14px 16px; box-shadow: 0 1px 3px rgba(0,0,0,0.06);"> <div style="font-size: 0.96em; font-weight: 600; color: #1a2230; margin-bottom: 6px;">Philosophie de la religion <span style="color: #8a94a3; font-weight: 400;">(2)</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Manuel de terminale de philosophie/Religion|Religion]]<br/> [[Dictionnaire de philosophie/Athéisme|Athéisme]] </div> </td> </tr> </table> <!-- ══════════════════════════════════════════════ CLUSTER 2 — THÉORIE DE LA VALEUR ══════════════════════════════════════════════ --> <div style="margin: 22px 0 10px; padding: 6px 0 4px; border-bottom: 1px solid #1a2230;"> <div style="font-size: 1.15em; font-weight: 700; color: #1a2230;">Théorie de la valeur</div> </div> <table style="width: 100%; border-collapse: separate; border-spacing: 14px 14px;"> <tr> <td style="width: 33.33%; vertical-align: top; background: #f8fafc; border-radius: 10px; padding: 14px 16px; box-shadow: 0 1px 3px rgba(0,0,0,0.06);"> <div style="font-size: 0.96em; font-weight: 600; color: #1a2230; margin-bottom: 6px;">Éthique normative <span style="color: #8a94a3; font-weight: 400;">(6)</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Manuel de terminale de philosophie/Devoir|Devoir]]<br/> [[Manuel de terminale de philosophie/Bonheur|Bonheur]]<br/> [[Manuel de terminale de philosophie/Liberté|Liberté]]<br/> [[Philosophie/Ataraxie|Ataraxie]]<br/> [[Dictionnaire de philosophie/Amour|Amour]]<br/> [[Dictionnaire de philosophie/Amitié|Amitié]] </div> </td> <td style="width: 33.33%; vertical-align: top; background: #f8fafc; border-radius: 10px; padding: 14px 16px; box-shadow: 0 1px 3px rgba(0,0,0,0.06);"> <div style="font-size: 0.96em; font-weight: 600; color: #1a2230; margin-bottom: 6px;">Éthique appliquée <span style="color: #8a94a3; font-weight: 400;">(3)</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Dictionnaire de philosophie/Avortement|Avortement (éthique)]]<br/> [[Dictionnaire de philosophie/Animal|Animal (droits)]]<br/> [[Dictionnaire de philosophie/Anthropocentrisme|Anthropocentrisme]] </div> </td> <td style="width: 33.33%; vertical-align: top; background: #f8fafc; border-radius: 10px; padding: 14px 16px; box-shadow: 0 1px 3px rgba(0,0,0,0.06);"> <div style="font-size: 0.96em; font-weight: 600; color: #1a2230; margin-bottom: 6px;">Esthétique <span style="color: #8a94a3; font-weight: 400;">(3)</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Manuel de terminale de philosophie/Art|Art]]<br/> [[Dictionnaire de philosophie/Art (introduction)|Art — introduction]]<br/> [[Dictionnaire de philosophie/Art et Vérité|Art et vérité]] </div> </td> </tr> <tr> <td style="width: 33.33%; vertical-align: top; background: #f8fafc; border-radius: 10px; padding: 14px 16px; box-shadow: 0 1px 3px rgba(0,0,0,0.06);"> <div style="font-size: 0.96em; font-weight: 600; color: #1a2230; margin-bottom: 6px;">Philosophie sociale et politique <span style="color: #8a94a3; font-weight: 400;">(6)</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Manuel de terminale de philosophie/État|État]]<br/> [[Manuel de terminale de philosophie/Justice|Justice]]<br/> [[Manuel de terminale de philosophie/Travail|Travail]]<br/> [[Dictionnaire de philosophie/Absolutisme|Absolutisme]]<br/> [[Dictionnaire de philosophie/Anarchisme|Anarchisme]]<br/> [[Dictionnaire de philosophie/Aliénation|Aliénation]] </div> </td> <td style="width: 33.33%; vertical-align: top; background: #f8fafc; border-radius: 10px; padding: 14px 16px; box-shadow: 0 1px 3px rgba(0,0,0,0.06);"> <div style="font-size: 0.96em; font-weight: 600; color: #1a2230; margin-bottom: 6px;">Autrui et reconnaissance <span style="color: #8a94a3; font-weight: 400;">(4)</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Philosophie/Autrui|Autrui]]<br/> [[Dictionnaire de philosophie/Altérité|Altérité]]<br/> [[Dictionnaire de philosophie/Autorité|Autorité]]<br/> [[Dictionnaire de philosophie/Autonomie|Autonomie]] </div> </td> <td style="width: 33.33%; vertical-align: top; background: #f8fafc; border-radius: 10px; padding: 14px 16px; box-shadow: 0 1px 3px rgba(0,0,0,0.06);"> <div style="font-size: 0.96em; font-weight: 600; color: #1a2230; margin-bottom: 6px;">Métaéthique <span style="color: #8a94a3; font-weight: 400;">(3)</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Dictionnaire de philosophie/Absurde|Absurde]]<br/> [[Dictionnaire de philosophie/Affection|Affection]]<br/> [[Dictionnaire de philosophie/Angoisse|Angoisse]] </div> </td> </tr> </table> <!-- ══════════════════════════════════════════════ CLUSTER 3 — SCIENCE, LOGIQUE ET MATHÉMATIQUES ══════════════════════════════════════════════ --> <div style="margin: 22px 0 10px; padding: 6px 0 4px; border-bottom: 1px solid #1a2230;"> <div style="font-size: 1.15em; font-weight: 700; color: #1a2230;">Science, logique et mathématiques</div> </div> <table style="width: 100%; border-collapse: separate; border-spacing: 14px 14px;"> <tr> <td style="width: 33.33%; vertical-align: top; background: #f8fafc; border-radius: 10px; padding: 14px 16px; box-shadow: 0 1px 3px rgba(0,0,0,0.06);"> <div style="font-size: 0.96em; font-weight: 600; color: #1a2230; margin-bottom: 6px;">Logique <span style="color: #8a94a3; font-weight: 400;">(6)</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Dictionnaire de philosophie/A (logique)|A (logique)]]<br/> [[Dictionnaire de philosophie/Abstraction|Abstraction]]<br/> [[Dictionnaire de philosophie/Antinomie|Antinomie]]<br/> [[Philosophie/Aporie|Aporie]]<br/> [[Dictionnaire de philosophie/Axiome|Axiome]]<br/> [[Dictionnaire de philosophie/Analyse|Analyse]] </div> </td> <td style="width: 33.33%; vertical-align: top; background: #f8fafc; border-radius: 10px; padding: 14px 16px; box-shadow: 0 1px 3px rgba(0,0,0,0.06);"> <div style="font-size: 0.96em; font-weight: 600; color: #1a2230; margin-bottom: 6px;">Philosophie des sciences <span style="color: #8a94a3; font-weight: 400;">(3)</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Philosophie/Théorie et expérience|Théorie et expérience]]<br/> [[Manuel de terminale de philosophie/Science|Science]]<br/> [[Manuel de terminale de philosophie/Technique|Technique]] </div> </td> <td style="width: 33.33%; vertical-align: top; background: #f8fafc; border-radius: 10px; padding: 14px 16px; box-shadow: 0 1px 3px rgba(0,0,0,0.06);"> <div style="font-size: 0.96em; font-weight: 600; color: #1a2230; margin-bottom: 6px;">Philosophie du vivant <span style="color: #8a94a3; font-weight: 400;">(3)</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Manuel de terminale de philosophie/Nature|Nature]]<br/> [[Manuel de terminale de philosophie/Vivant|Vivant]]<br/> [[Dictionnaire de philosophie/Animal|Animal]] </div> </td> </tr> </table> <!-- ══════════════════════════════════════════════ CLUSTER 4 — HISTOIRE DE LA PHILOSOPHIE ══════════════════════════════════════════════ --> <div style="margin: 22px 0 10px; padding: 6px 0 4px; border-bottom: 1px solid #1a2230;"> <div style="font-size: 1.15em; font-weight: 700; color: #1a2230;">[[Philosophie/Histoire de la philosophie|Histoire de la philosophie]] <span style="color: #8a94a3; font-weight: 400; font-size: 0.92em;">(24)</span></div> </div> <table style="width: 100%; border-collapse: separate; border-spacing: 14px 14px;"> <tr> <td style="width: 33.33%; vertical-align: top; background: #f8fafc; border-radius: 10px; padding: 14px 16px; box-shadow: 0 1px 3px rgba(0,0,0,0.06);"> <div style="font-size: 0.96em; font-weight: 600; color: #1a2230; margin-bottom: 2px;">Antiquité <span style="color: #8a94a3; font-weight: 400;">(10)</span></div> <div style="font-size: 0.82em; color: #8a94a3; margin-bottom: 6px;">VI<sup>e</sup>&nbsp;av.&nbsp;— V<sup>e</sup>&nbsp;siècle</div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> <span style="font-weight: 500;">Présocratiques</span><br/> <span style="padding-left: 10px;">[[Philosophie/Thalès de Milet|Thalès de Milet]]</span><br/> <span style="padding-left: 10px;">[[Philosophie/Anaximandre de Milet|Anaximandre]]</span><br/> <span style="padding-left: 10px;">[[Dictionnaire de philosophie/Anaxagore|Anaxagore]]</span><br/> <span style="padding-left: 10px;">[[Philosophie/Présocratiques/Liste des Présocratiques|Liste complète]]</span><br/> [[Pour lire Platon|Platon]]<br/> [[Dictionnaire de philosophie/Aristote|Aristote]]<br/> <span style="font-weight: 500;">Philosophie hellénistique</span><br/> <span style="padding-left: 10px;">[[Commentaire philosophique/Lettre à Ménécée|Épicure]]</span><br/> <span style="padding-left: 10px;">[[Philosophie/Ataraxie|Stoïcisme]]</span> </div> </td> <td style="width: 33.33%; vertical-align: top; background: #f8fafc; border-radius: 10px; padding: 14px 16px; box-shadow: 0 1px 3px rgba(0,0,0,0.06);"> <div style="font-size: 0.96em; font-weight: 600; color: #1a2230; margin-bottom: 2px;">Philosophie médiévale <span style="color: #8a94a3; font-weight: 400;">(0)</span></div> <div style="font-size: 0.82em; color: #8a94a3; margin-bottom: 6px;">V<sup>e</sup>&nbsp;— XV<sup>e</sup>&nbsp;siècle</div> <div style="font-size: 0.88em; color: #8a94a3; line-height: 1.75; font-style: italic;"> Augustin, Anselme, Thomas d'Aquin, Averroès — pages à rédiger. </div> </td> <td style="width: 33.33%; vertical-align: top; background: #f8fafc; border-radius: 10px; padding: 14px 16px; box-shadow: 0 1px 3px rgba(0,0,0,0.06);"> <div style="font-size: 0.96em; font-weight: 600; color: #1a2230; margin-bottom: 2px;">Philosophie classique <span style="color: #8a94a3; font-weight: 400;">(6)</span></div> <div style="font-size: 0.82em; color: #8a94a3; margin-bottom: 6px;">XVI<sup>e</sup>&nbsp;— XVIII<sup>e</sup>&nbsp;siècle</div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Méditations métaphysiques|Descartes — Méditations]]<br/> [[Philosophie/Commentaire du passage à propos de l'Homme esclave du divertissement|Pascal — Divertissement]]<br/> [[Commentaire de l'Éthique|Spinoza — Éthique]]<br/> [[Philosophie/Vocabulaire/David Hume|Hume]]<br/> [[Philosophie/Vocabulaire/Kant|Kant]]<br/> [[Commentaire philosophique/Discours sur l'origine et les fondements de l'inégalité parmi les hommes|Rousseau — Inégalité]] </div> </td> </tr> <tr> <td style="width: 33.33%; vertical-align: top; background: #f8fafc; border-radius: 10px; padding: 14px 16px; box-shadow: 0 1px 3px rgba(0,0,0,0.06);"> <div style="font-size: 0.96em; font-weight: 600; color: #1a2230; margin-bottom: 2px;">Philosophie contemporaine <span style="color: #8a94a3; font-weight: 400;">(5)</span></div> <div style="font-size: 0.82em; color: #8a94a3; margin-bottom: 6px;">XIX<sup>e</sup>&nbsp;— XXI<sup>e</sup>&nbsp;siècle</div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Philosophie/Nietzsche|Nietzsche]]<br/> <span style="padding-left: 10px;">[[Philosophie/Nietzsche/La culture grecque|Culture grecque]]</span><br/> <span style="padding-left: 10px;">[[Philosophie/Nietzsche/La culture moderne|Culture moderne]]</span><br/> <span style="padding-left: 10px;">[[Philosophie/Nietzsche/La métaphysique|Métaphysique]]</span><br/> <span style="padding-left: 10px;">[[Philosophie/Nietzsche/La moralité des mœurs|Moralité des mœurs]]</span> </div> </td> <td style="width: 33.33%; vertical-align: top; background: #f8fafc; border-radius: 10px; padding: 14px 16px; box-shadow: 0 1px 3px rgba(0,0,0,0.06);"> <div style="font-size: 0.96em; font-weight: 600; color: #1a2230; margin-bottom: 2px;">Traditions non occidentales <span style="color: #8a94a3; font-weight: 400;">(2)</span></div> <div style="font-size: 0.82em; color: #8a94a3; margin-bottom: 6px;">Afrique, Amériques</div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Dictionnaire de philosophie/Philosophie africaine|Philosophie africaine]]<br/> [[Dictionnaire de philosophie/Argentine (Philosophie)|Argentine — XX<sup>e</sup> siècle]] </div> </td> <td style="width: 33.33%; vertical-align: top;"></td> </tr> </table> <!-- ══════════════════════════════════════════════ CLUSTER 5 — OUVRAGES DE RÉFÉRENCE ══════════════════════════════════════════════ --> <div style="margin: 22px 0 10px; padding: 6px 0 4px; border-bottom: 1px solid #1a2230;"> <div style="font-size: 1.15em; font-weight: 700; color: #1a2230;">Ouvrages de référence et pédagogiques</div> </div> <!-- Dictionnaire — pleine largeur --> <div style="background: #f8fafc; border-radius: 10px; padding: 16px 20px; margin-bottom: 14px; box-shadow: 0 1px 3px rgba(0,0,0,0.06);"> <div style="font-size: 0.96em; font-weight: 600; color: #1a2230; margin-bottom: 8px;">[[Dictionnaire de philosophie|Dictionnaire de philosophie]] <span style="color: #8a94a3; font-weight: 400;">(35)</span></div> <div style="font-size: 0.92em; color: #1a2230; line-height: 1.7; letter-spacing: 0.04em;"> [[Dictionnaire de philosophie/A|A]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/B|B]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/C|C]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/D|D]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/E|E]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/F|F]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/G|G]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/H|H]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/I|I]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/J|J]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/K|K]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/L|L]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/M|M]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/N|N]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/O|O]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/P|P]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/Q|Q]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/R|R]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/S|S]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/T|T]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/U|U]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/V|V]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/W|W]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/X|X]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/Y|Y]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/Z|Z]] </div> </div> <!-- Autres ouvrages --> <table style="width: 100%; border-collapse: separate; border-spacing: 14px 14px;"> <tr> <td style="width: 33.33%; vertical-align: top; background: #f8fafc; border-radius: 10px; padding: 14px 16px; box-shadow: 0 1px 3px rgba(0,0,0,0.06);"> <div style="font-size: 0.96em; font-weight: 600; color: #1a2230; margin-bottom: 6px;">[[Manuel de terminale de philosophie|Manuel de terminale]] <span style="color: #8a94a3; font-weight: 400;">(26)</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> Éthique &amp; action <span style="color: #8a94a3;">(4)</span><br/> Esprit &amp; conscience <span style="color: #8a94a3;">(4)</span><br/> Connaissance &amp; vérité <span style="color: #8a94a3;">(5)</span><br/> Monde &amp; société <span style="color: #8a94a3;">(4)</span><br/> Culture &amp; sens <span style="color: #8a94a3;">(6)</span><br/> Méthode &amp; ressources <span style="color: #8a94a3;">(3)</span> </div> </td> <td style="width: 33.33%; vertical-align: top; background: #f8fafc; border-radius: 10px; padding: 14px 16px; box-shadow: 0 1px 3px rgba(0,0,0,0.06);"> <div style="font-size: 0.96em; font-weight: 600; color: #1a2230; margin-bottom: 6px;">[[:Catégorie:Commentaire philosophique|Commentaires d'œuvres]] <span style="color: #8a94a3; font-weight: 400;">(20)</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Commentaire philosophique/Lettre à Ménécée|Lettre à Ménécée]] <span style="color: #8a94a3;">·</span> Épicure<br/> [[Méditations métaphysiques|Méditations]] <span style="color: #8a94a3;">·</span> Descartes<br/> [[Commentaire de l'Éthique|Éthique]] <span style="color: #8a94a3;">·</span> Spinoza<br/> [[Commentaire philosophique/Discours sur l'origine et les fondements de l'inégalité parmi les hommes|Inégalité]] <span style="color: #8a94a3;">·</span> Rousseau<br/> [[Philosophie/Commentaire du passage à propos de l'Homme esclave du divertissement|Divertissement]] <span style="color: #8a94a3;">·</span> Pascal </div> </td> <td style="width: 33.33%; vertical-align: top; background: #f8fafc; border-radius: 10px; padding: 14px 16px; box-shadow: 0 1px 3px rgba(0,0,0,0.06);"> <div style="font-size: 0.96em; font-weight: 600; color: #1a2230; margin-bottom: 6px;">[[:Catégorie:Vocabulaire philosophique|Vocabulaires]] <span style="color: #8a94a3; font-weight: 400;">(4)</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Pour lire Platon/Vocabulaire|Platon]]<br/> [[Philosophie/Vocabulaire/David Hume|David Hume]]<br/> [[Philosophie/Vocabulaire/Kant|Kant]] </div> <div style="height: 1px; background: #e2e8ef; margin: 10px 0;"></div> <div style="font-size: 0.96em; font-weight: 600; color: #1a2230; margin-bottom: 6px;">Introductions <span style="color: #8a94a3; font-weight: 400;">(1)</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Philosophie/Une brève introduction|Une brève introduction à la philosophie]] </div> </td> </tr> </table> <!-- ══════════════════════════════════════════════ PIED DE PAGE ══════════════════════════════════════════════ --> <div style="margin-top: 24px; padding: 14px 0 4px; border-top: 1px solid #1a2230; font-size: 0.86em; color: #555e6b;"> <div style="margin-bottom: 6px;"><span style="font-weight: 600; color: #1a2230;">Consulter ailleurs</span> &nbsp;·&nbsp; [[w:Philosophie|Wikipédia]] &nbsp;·&nbsp; [[wikt:philosophie|Wiktionnaire]] &nbsp;·&nbsp; [[v:Philosophie|Wikiversité]] &nbsp;·&nbsp; [[s:Portail:Philosophie|Wikisource]]</div> <div><span style="font-weight: 600; color: #1a2230;">Catégories sources</span> &nbsp;·&nbsp; [[:Catégorie:Discipline philosophique|Disciplines]] &nbsp;·&nbsp; [[:Catégorie:Histoire de la philosophie|Histoire]] &nbsp;·&nbsp; [[:Catégorie:Philosophe|Philosophes]] &nbsp;·&nbsp; [[:Catégorie:Dictionnaire de philosophie (livre)|Dictionnaire]] &nbsp;·&nbsp; [[:Catégorie:Manuel de terminale de philosophie (livre)|Manuel]] &nbsp;·&nbsp; [[:Catégorie:Commentaire philosophique|Commentaires]]</div> </div> </div> [[Catégorie:Philosophie|*]] qalyqkv1iee01wiud8jp037kv5w7p34 764756 764754 2026-04-24T05:50:17Z PandaMystique 119061 764756 wikitext text/x-wiki <!-- ══════════════════════════════════════════════ EN-TÊTE ══════════════════════════════════════════════ --> <div style="padding: 6px 0 18px; border-bottom: 2px solid #1a2230; margin-bottom: 22px;"> <div style="font-size: 1.6em; font-weight: 600; color: #1a2230; line-height: 1.2; margin-bottom: 4px;">Classification de la philosophie</div> <div style="font-size: 0.9em; color: #555e6b; line-height: 1.5;">Portail raisonné des livres, manuels et dictionnaires de philosophie sur Wikilivres. Les entrées sont rangées en cinq grandes aires, subdivisées selon la taxonomie standard des champs philosophiques.</div> </div> {{PhiloRecherche}} <!-- ══════════════════════════════════════════════ CLUSTER 1 — MÉTAPHYSIQUE ET ÉPISTÉMOLOGIE ══════════════════════════════════════════════ --> <div style="margin: 26px 0 10px; padding: 6px 0 4px; border-bottom: 1px solid #1a2230;"> <div style="font-size: 1.15em; font-weight: 700; color: #1a2230;">Métaphysique et épistémologie</div> </div> <table style="width: 100%; border-collapse: separate; border-spacing: 14px 14px;"> <tr> <td style="width: 33.33%; vertical-align: top; background: #f8fafc; border-radius: 10px; padding: 14px 16px; box-shadow: 0 1px 3px rgba(0,0,0,0.06);"> <div style="font-size: 0.96em; font-weight: 600; color: #1a2230; margin-bottom: 6px;">[[Philosophie/Théorie de la connaissance|Théorie de la connaissance]] <span style="color: #8a94a3; font-weight: 400;">(8)</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Philosophie/Théorie de la connaissance/Une définition traditionnelle|Définition traditionnelle]]<br/> [[Philosophie/Théorie de la connaissance/Le Problème de Gettier|Problème de Gettier]]<br/> [[Dictionnaire de philosophie/Vérité|Vérité]]<br/> [[Dictionnaire de philosophie/Certitude|Certitude]]<br/> [[Dictionnaire de philosophie/A priori|A priori]]<br/> [[Dictionnaire de philosophie/Abduction|Abduction]]<br/> [[Dictionnaire de philosophie/Agnosticisme|Agnosticisme]] </div> </td> <td style="width: 33.33%; vertical-align: top; background: #f8fafc; border-radius: 10px; padding: 14px 16px; box-shadow: 0 1px 3px rgba(0,0,0,0.06);"> <div style="font-size: 0.96em; font-weight: 600; color: #1a2230; margin-bottom: 6px;">Métaphysique <span style="color: #8a94a3; font-weight: 400;">(12)</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Dictionnaire de philosophie/Absolu|Absolu]]<br/> [[Philosophie/Acte/Puissance|Acte et Puissance]]<br/> [[Dictionnaire de philosophie/Accident|Accident]]<br/> [[Dictionnaire de philosophie/Attribut|Attribut]]<br/> [[Dictionnaire de philosophie/Atomisme|Atomisme]]<br/> [[Dictionnaire de philosophie/Déterminisme|Déterminisme]] </div> </td> <td style="width: 33.33%; vertical-align: top; background: #f8fafc; border-radius: 10px; padding: 14px 16px; box-shadow: 0 1px 3px rgba(0,0,0,0.06);"> <div style="font-size: 0.96em; font-weight: 600; color: #1a2230; margin-bottom: 6px;">[[Philosophie/Philosophie de l'esprit|Philosophie de l'esprit]] <span style="color: #8a94a3; font-weight: 400;">(14)</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Philosophie/Philosophie de l'esprit/Introduction|Introduction]]<br/> [[Philosophie/Philosophie de l'esprit/Ce que Marie ne savait pas|Ce que Marie ne savait pas]]<br/> [[Dictionnaire de philosophie/Conscience|Conscience]]<br/> [[Dictionnaire de philosophie/Âme|Âme]] </div> </td> </tr> <tr> <td style="width: 33.33%; vertical-align: top; background: #f8fafc; border-radius: 10px; padding: 14px 16px; box-shadow: 0 1px 3px rgba(0,0,0,0.06);"> <div style="font-size: 0.96em; font-weight: 600; color: #1a2230; margin-bottom: 6px;">Philosophie du langage <span style="color: #8a94a3; font-weight: 400;">(3)</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Manuel de terminale de philosophie/Langage|Langage]]<br/> [[Dictionnaire de philosophie/Analogie|Analogie]]<br/> [[Dictionnaire de philosophie/Argument|Argument]] </div> </td> <td style="width: 33.33%; vertical-align: top; background: #f8fafc; border-radius: 10px; padding: 14px 16px; box-shadow: 0 1px 3px rgba(0,0,0,0.06);"> <div style="font-size: 0.96em; font-weight: 600; color: #1a2230; margin-bottom: 6px;">Philosophie de l'action <span style="color: #8a94a3; font-weight: 400;">(5)</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Dictionnaire de philosophie/Action|Action]]<br/> [[Dictionnaire de philosophie/Aboulie|Aboulie]]<br/> [[Dictionnaire de philosophie/Altruisme|Altruisme]]<br/> [[Dictionnaire de philosophie/Authenticité|Authenticité]]<br/> [[Philosophie/Liberté|Liberté]] </div> </td> <td style="width: 33.33%; vertical-align: top; background: #f8fafc; border-radius: 10px; padding: 14px 16px; box-shadow: 0 1px 3px rgba(0,0,0,0.06);"> <div style="font-size: 0.96em; font-weight: 600; color: #1a2230; margin-bottom: 6px;">Philosophie de la religion <span style="color: #8a94a3; font-weight: 400;">(5)</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Manuel de terminale de philosophie/Religion|Religion]]<br/> [[Philosophie/Athéisme|Athéisme]]<br/> [[Philosophie/Déisme|Déisme]]<br/> [[Philosophie/Monothéisme|Monothéisme]]<br/> [[Dictionnaire de philosophie/Panthéisme|Panthéisme]] </div> </td> </tr> </table> <!-- ══════════════════════════════════════════════ CLUSTER 2 — THÉORIE DE LA VALEUR ══════════════════════════════════════════════ --> <div style="margin: 22px 0 10px; padding: 6px 0 4px; border-bottom: 1px solid #1a2230;"> <div style="font-size: 1.15em; font-weight: 700; color: #1a2230;">Théorie de la valeur</div> </div> <table style="width: 100%; border-collapse: separate; border-spacing: 14px 14px;"> <tr> <td style="width: 33.33%; vertical-align: top; background: #f8fafc; border-radius: 10px; padding: 14px 16px; box-shadow: 0 1px 3px rgba(0,0,0,0.06);"> <div style="font-size: 0.96em; font-weight: 600; color: #1a2230; margin-bottom: 6px;">Éthique normative <span style="color: #8a94a3; font-weight: 400;">(9)</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Philosophie/Morale|Morale]]<br/> [[Manuel de terminale de philosophie/Devoir|Devoir]]<br/> [[Dictionnaire de philosophie/Bonheur|Bonheur]]<br/> [[Manuel de terminale de philosophie/Liberté|Liberté (terminale)]]<br/> [[Philosophie/Ataraxie|Ataraxie]]<br/> [[Dictionnaire de philosophie/Amour|Amour]]<br/> [[Dictionnaire de philosophie/Amitié|Amitié]] </div> </td> <td style="width: 33.33%; vertical-align: top; background: #f8fafc; border-radius: 10px; padding: 14px 16px; box-shadow: 0 1px 3px rgba(0,0,0,0.06);"> <div style="font-size: 0.96em; font-weight: 600; color: #1a2230; margin-bottom: 6px;">Éthique appliquée <span style="color: #8a94a3; font-weight: 400;">(3)</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Dictionnaire de philosophie/Avortement|Avortement (éthique)]]<br/> [[Dictionnaire de philosophie/Animal|Animal (droits)]]<br/> [[Dictionnaire de philosophie/Anthropocentrisme|Anthropocentrisme]] </div> </td> <td style="width: 33.33%; vertical-align: top; background: #f8fafc; border-radius: 10px; padding: 14px 16px; box-shadow: 0 1px 3px rgba(0,0,0,0.06);"> <div style="font-size: 0.96em; font-weight: 600; color: #1a2230; margin-bottom: 6px;">Esthétique <span style="color: #8a94a3; font-weight: 400;">(4)</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Manuel de terminale de philosophie/Art|Art]]<br/> [[Dictionnaire de philosophie/Art|Art (dictionnaire)]]<br/> [[Dictionnaire de philosophie/Art (introduction)|Art — introduction]]<br/> [[Dictionnaire de philosophie/Art et Vérité|Art et vérité]] </div> </td> </tr> <tr> <td style="width: 33.33%; vertical-align: top; background: #f8fafc; border-radius: 10px; padding: 14px 16px; box-shadow: 0 1px 3px rgba(0,0,0,0.06);"> <div style="font-size: 0.96em; font-weight: 600; color: #1a2230; margin-bottom: 6px;">Philosophie sociale et politique <span style="color: #8a94a3; font-weight: 400;">(6)</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Manuel de terminale de philosophie/État|État]]<br/> [[Manuel de terminale de philosophie/Justice|Justice]]<br/> [[Manuel de terminale de philosophie/Travail|Travail]]<br/> [[Dictionnaire de philosophie/Absolutisme|Absolutisme]]<br/> [[Dictionnaire de philosophie/Anarchisme|Anarchisme]]<br/> [[Philosophie/Aliénation|Aliénation]] </div> </td> <td style="width: 33.33%; vertical-align: top; background: #f8fafc; border-radius: 10px; padding: 14px 16px; box-shadow: 0 1px 3px rgba(0,0,0,0.06);"> <div style="font-size: 0.96em; font-weight: 600; color: #1a2230; margin-bottom: 6px;">Autrui et reconnaissance <span style="color: #8a94a3; font-weight: 400;">(4)</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Philosophie/Autrui|Autrui]]<br/> [[Dictionnaire de philosophie/Altérité|Altérité]]<br/> [[Dictionnaire de philosophie/Autorité|Autorité]]<br/> [[Dictionnaire de philosophie/Autonomie|Autonomie]] </div> </td> <td style="width: 33.33%; vertical-align: top; background: #f8fafc; border-radius: 10px; padding: 14px 16px; box-shadow: 0 1px 3px rgba(0,0,0,0.06);"> <div style="font-size: 0.96em; font-weight: 600; color: #1a2230; margin-bottom: 6px;">Métaéthique <span style="color: #8a94a3; font-weight: 400;">(3)</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Dictionnaire de philosophie/Absurde|Absurde]]<br/> [[Dictionnaire de philosophie/Affection|Affection]]<br/> [[Dictionnaire de philosophie/Angoisse|Angoisse]] </div> </td> </tr> </table> <!-- ══════════════════════════════════════════════ CLUSTER 3 — SCIENCE, LOGIQUE ET MATHÉMATIQUES ══════════════════════════════════════════════ --> <div style="margin: 22px 0 10px; padding: 6px 0 4px; border-bottom: 1px solid #1a2230;"> <div style="font-size: 1.15em; font-weight: 700; color: #1a2230;">Science, logique et mathématiques</div> </div> <table style="width: 100%; border-collapse: separate; border-spacing: 14px 14px;"> <tr> <td style="width: 33.33%; vertical-align: top; background: #f8fafc; border-radius: 10px; padding: 14px 16px; box-shadow: 0 1px 3px rgba(0,0,0,0.06);"> <div style="font-size: 0.96em; font-weight: 600; color: #1a2230; margin-bottom: 6px;">Logique <span style="color: #8a94a3; font-weight: 400;">(6)</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Dictionnaire de philosophie/A (logique)|A (logique)]]<br/> [[Dictionnaire de philosophie/Abstraction|Abstraction]]<br/> [[Dictionnaire de philosophie/Antinomie|Antinomie]]<br/> [[Philosophie/Aporie|Aporie]]<br/> [[Dictionnaire de philosophie/Axiome|Axiome]]<br/> [[Dictionnaire de philosophie/Analyse|Analyse]] </div> </td> <td style="width: 33.33%; vertical-align: top; background: #f8fafc; border-radius: 10px; padding: 14px 16px; box-shadow: 0 1px 3px rgba(0,0,0,0.06);"> <div style="font-size: 0.96em; font-weight: 600; color: #1a2230; margin-bottom: 6px;">Philosophie des sciences <span style="color: #8a94a3; font-weight: 400;">(3)</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Philosophie/Théorie et expérience|Théorie et expérience]]<br/> [[Manuel de terminale de philosophie/Science|Science]]<br/> [[Manuel de terminale de philosophie/Technique|Technique]] </div> </td> <td style="width: 33.33%; vertical-align: top; background: #f8fafc; border-radius: 10px; padding: 14px 16px; box-shadow: 0 1px 3px rgba(0,0,0,0.06);"> <div style="font-size: 0.96em; font-weight: 600; color: #1a2230; margin-bottom: 6px;">Philosophie du vivant <span style="color: #8a94a3; font-weight: 400;">(3)</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Manuel de terminale de philosophie/Nature|Nature]]<br/> [[Manuel de terminale de philosophie/Vivant|Vivant]]<br/> [[Dictionnaire de philosophie/Animal|Animal]] </div> </td> </tr> </table> <!-- ══════════════════════════════════════════════ CLUSTER 4 — HISTOIRE DE LA PHILOSOPHIE ══════════════════════════════════════════════ --> <div style="margin: 22px 0 10px; padding: 6px 0 4px; border-bottom: 1px solid #1a2230;"> <div style="font-size: 1.15em; font-weight: 700; color: #1a2230;">[[Philosophie/Histoire de la philosophie|Histoire de la philosophie]] <span style="color: #8a94a3; font-weight: 400; font-size: 0.92em;">(30)</span></div> </div> <table style="width: 100%; border-collapse: separate; border-spacing: 14px 14px;"> <tr> <td style="width: 33.33%; vertical-align: top; background: #f8fafc; border-radius: 10px; padding: 14px 16px; box-shadow: 0 1px 3px rgba(0,0,0,0.06);"> <div style="font-size: 0.96em; font-weight: 600; color: #1a2230; margin-bottom: 2px;">Antiquité <span style="color: #8a94a3; font-weight: 400;">(14)</span></div> <div style="font-size: 0.82em; color: #8a94a3; margin-bottom: 6px;">VI<sup>e</sup>&nbsp;av.&nbsp;— V<sup>e</sup>&nbsp;siècle</div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> <span style="font-weight: 500;">Présocratiques</span><br/> <span style="padding-left: 10px;">[[Philosophie/Thalès de Milet|Thalès de Milet]]</span><br/> <span style="padding-left: 20px; color: #8a94a3; font-size: 0.94em;">[[Philosophie/Thalès de Milet/Textes et traductions Ier millénaire AEC|textes]] · [[Philosophie/Thalès de Milet/Tannery|Tannery]] · [[Philosophie/Thalès de Milet/Theodor Gomperz|Gomperz]]</span><br/> <span style="padding-left: 10px;">[[Philosophie/Anaximandre de Milet|Anaximandre]]</span><br/> <span style="padding-left: 10px;">[[Dictionnaire de philosophie/Anaxagore|Anaxagore]]</span><br/> <span style="padding-left: 10px;">[[Philosophie/Présocratiques/Liste des Présocratiques|Liste complète]]</span><br/> [[Pour lire Platon|Platon]]<br/> [[Dictionnaire de philosophie/Aristote|Aristote]]<br/> <span style="font-weight: 500;">Philosophie hellénistique</span><br/> <span style="padding-left: 10px;">[[Commentaire philosophique/Lettre à Ménécée|Épicure]]</span><br/> <span style="padding-left: 10px;">[[Philosophie/Ataraxie|Stoïcisme]]</span> </div> </td> <td style="width: 33.33%; vertical-align: top; background: #f8fafc; border-radius: 10px; padding: 14px 16px; box-shadow: 0 1px 3px rgba(0,0,0,0.06);"> <div style="font-size: 0.96em; font-weight: 600; color: #1a2230; margin-bottom: 2px;">Philosophie médiévale <span style="color: #8a94a3; font-weight: 400;">(0)</span></div> <div style="font-size: 0.82em; color: #8a94a3; margin-bottom: 6px;">V<sup>e</sup>&nbsp;— XV<sup>e</sup>&nbsp;siècle</div> <div style="font-size: 0.88em; color: #8a94a3; line-height: 1.75; font-style: italic;"> Augustin, Anselme, Thomas d'Aquin, Averroès — pages à rédiger. </div> </td> <td style="width: 33.33%; vertical-align: top; background: #f8fafc; border-radius: 10px; padding: 14px 16px; box-shadow: 0 1px 3px rgba(0,0,0,0.06);"> <div style="font-size: 0.96em; font-weight: 600; color: #1a2230; margin-bottom: 2px;">Philosophie classique <span style="color: #8a94a3; font-weight: 400;">(6)</span></div> <div style="font-size: 0.82em; color: #8a94a3; margin-bottom: 6px;">XVI<sup>e</sup>&nbsp;— XVIII<sup>e</sup>&nbsp;siècle</div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Méditations métaphysiques|Descartes — Méditations]]<br/> [[Philosophie/Commentaire du passage à propos de l'Homme esclave du divertissement|Pascal — Divertissement]]<br/> [[Commentaire de l'Éthique|Spinoza — Éthique]]<br/> [[Philosophie/Vocabulaire/David Hume|Hume]]<br/> [[Philosophie/Vocabulaire/Kant|Kant]]<br/> [[Commentaire philosophique/Discours sur l'origine et les fondements de l'inégalité parmi les hommes|Rousseau — Inégalité]] </div> </td> </tr> <tr> <td style="width: 33.33%; vertical-align: top; background: #f8fafc; border-radius: 10px; padding: 14px 16px; box-shadow: 0 1px 3px rgba(0,0,0,0.06);"> <div style="font-size: 0.96em; font-weight: 600; color: #1a2230; margin-bottom: 2px;">Philosophie contemporaine <span style="color: #8a94a3; font-weight: 400;">(5)</span></div> <div style="font-size: 0.82em; color: #8a94a3; margin-bottom: 6px;">XIX<sup>e</sup>&nbsp;— XXI<sup>e</sup>&nbsp;siècle</div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Philosophie/Nietzsche|Nietzsche]]<br/> <span style="padding-left: 10px;">[[Philosophie/Nietzsche/La culture grecque|Culture grecque]]</span><br/> <span style="padding-left: 10px;">[[Philosophie/Nietzsche/La culture moderne|Culture moderne]]</span><br/> <span style="padding-left: 10px;">[[Philosophie/Nietzsche/La métaphysique|Métaphysique]]</span><br/> <span style="padding-left: 10px;">[[Philosophie/Nietzsche/La moralité des mœurs|Moralité des mœurs]]</span> </div> </td> <td style="width: 33.33%; vertical-align: top; background: #f8fafc; border-radius: 10px; padding: 14px 16px; box-shadow: 0 1px 3px rgba(0,0,0,0.06);"> <div style="font-size: 0.96em; font-weight: 600; color: #1a2230; margin-bottom: 2px;">Courants du XX<sup>e</sup> siècle <span style="color: #8a94a3; font-weight: 400;">(3)</span></div> <div style="font-size: 0.82em; color: #8a94a3; margin-bottom: 6px;">Écoles et traditions</div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Philosophie/Philosophie analytique|Philosophie analytique]]<br/> [[Philosophie/Existence et temps|Existentialisme]]<br/> [[Dictionnaire de philosophie/Dasein|Phénoménologie / Dasein]] </div> </td> <td style="width: 33.33%; vertical-align: top; background: #f8fafc; border-radius: 10px; padding: 14px 16px; box-shadow: 0 1px 3px rgba(0,0,0,0.06);"> <div style="font-size: 0.96em; font-weight: 600; color: #1a2230; margin-bottom: 2px;">Traditions non occidentales <span style="color: #8a94a3; font-weight: 400;">(2)</span></div> <div style="font-size: 0.82em; color: #8a94a3; margin-bottom: 6px;">Afrique, Amériques</div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Dictionnaire de philosophie/Philosophie africaine|Philosophie africaine]]<br/> [[Dictionnaire de philosophie/Argentine (Philosophie)|Argentine — XX<sup>e</sup> siècle]] </div> </td> </tr> </table> <!-- ══════════════════════════════════════════════ CLUSTER 5 — OUVRAGES DE RÉFÉRENCE ══════════════════════════════════════════════ --> <div style="margin: 22px 0 10px; padding: 6px 0 4px; border-bottom: 1px solid #1a2230;"> <div style="font-size: 1.15em; font-weight: 700; color: #1a2230;">Ouvrages de référence et pédagogiques</div> </div> <!-- Dictionnaire — pleine largeur --> <div style="background: #f8fafc; border-radius: 10px; padding: 16px 20px; margin-bottom: 14px; box-shadow: 0 1px 3px rgba(0,0,0,0.06);"> <div style="font-size: 0.96em; font-weight: 600; color: #1a2230; margin-bottom: 8px;">[[Dictionnaire de philosophie|Dictionnaire de philosophie]] <span style="color: #8a94a3; font-weight: 400;">(35)</span></div> <div style="font-size: 0.92em; color: #1a2230; line-height: 1.7; letter-spacing: 0.04em;"> [[Dictionnaire de philosophie/A|A]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/B|B]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/C|C]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/D|D]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/E|E]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/F|F]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/G|G]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/H|H]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/I|I]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/J|J]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/K|K]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/L|L]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/M|M]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/N|N]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/O|O]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/P|P]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/Q|Q]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/R|R]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/S|S]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/T|T]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/U|U]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/V|V]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/W|W]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/X|X]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/Y|Y]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/Z|Z]] </div> </div> <!-- Autres ouvrages --> <table style="width: 100%; border-collapse: separate; border-spacing: 14px 14px;"> <tr> <td style="width: 33.33%; vertical-align: top; background: #f8fafc; border-radius: 10px; padding: 14px 16px; box-shadow: 0 1px 3px rgba(0,0,0,0.06);"> <div style="font-size: 0.96em; font-weight: 600; color: #1a2230; margin-bottom: 6px;">[[Manuel de terminale de philosophie|Manuel de terminale]] <span style="color: #8a94a3; font-weight: 400;">(26)</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> Éthique &amp; action <span style="color: #8a94a3;">(4)</span><br/> Esprit &amp; conscience <span style="color: #8a94a3;">(4)</span><br/> Connaissance &amp; vérité <span style="color: #8a94a3;">(5)</span><br/> Monde &amp; société <span style="color: #8a94a3;">(4)</span><br/> Culture &amp; sens <span style="color: #8a94a3;">(6)</span><br/> Méthode &amp; ressources <span style="color: #8a94a3;">(3)</span> </div> </td> <td style="width: 33.33%; vertical-align: top; background: #f8fafc; border-radius: 10px; padding: 14px 16px; box-shadow: 0 1px 3px rgba(0,0,0,0.06);"> <div style="font-size: 0.96em; font-weight: 600; color: #1a2230; margin-bottom: 6px;">[[:Catégorie:Commentaire philosophique|Commentaires d'œuvres]] <span style="color: #8a94a3; font-weight: 400;">(20)</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Commentaire philosophique/Lettre à Ménécée|Lettre à Ménécée]] <span style="color: #8a94a3;">·</span> Épicure<br/> [[Méditations métaphysiques|Méditations]] <span style="color: #8a94a3;">·</span> Descartes<br/> [[Commentaire de l'Éthique|Éthique]] <span style="color: #8a94a3;">·</span> Spinoza<br/> [[Commentaire philosophique/Discours sur l'origine et les fondements de l'inégalité parmi les hommes|Inégalité]] <span style="color: #8a94a3;">·</span> Rousseau<br/> [[Philosophie/Commentaire du passage à propos de l'Homme esclave du divertissement|Divertissement]] <span style="color: #8a94a3;">·</span> Pascal </div> </td> <td style="width: 33.33%; vertical-align: top; background: #f8fafc; border-radius: 10px; padding: 14px 16px; box-shadow: 0 1px 3px rgba(0,0,0,0.06);"> <div style="font-size: 0.96em; font-weight: 600; color: #1a2230; margin-bottom: 6px;">[[:Catégorie:Vocabulaire philosophique|Vocabulaires]] <span style="color: #8a94a3; font-weight: 400;">(4)</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Pour lire Platon/Vocabulaire|Platon]]<br/> [[Philosophie/Vocabulaire/David Hume|David Hume]]<br/> [[Philosophie/Vocabulaire/Kant|Kant]] </div> <div style="height: 1px; background: #e2e8ef; margin: 10px 0;"></div> <div style="font-size: 0.96em; font-weight: 600; color: #1a2230; margin-bottom: 6px;">Introductions <span style="color: #8a94a3; font-weight: 400;">(1)</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Philosophie/Une brève introduction|Une brève introduction à la philosophie]] </div> </td> </tr> </table> <!-- ══════════════════════════════════════════════ PIED DE PAGE ══════════════════════════════════════════════ --> <div style="margin-top: 24px; padding: 14px 0 4px; border-top: 1px solid #1a2230; font-size: 0.86em; color: #555e6b;"> <div style="margin-bottom: 6px;"><span style="font-weight: 600; color: #1a2230;">Consulter ailleurs</span> &nbsp;·&nbsp; [[w:Philosophie|Wikipédia]] &nbsp;·&nbsp; [[wikt:philosophie|Wiktionnaire]] &nbsp;·&nbsp; [[v:Philosophie|Wikiversité]] &nbsp;·&nbsp; [[s:Portail:Philosophie|Wikisource]]</div> <div><span style="font-weight: 600; color: #1a2230;">Catégories sources</span> &nbsp;·&nbsp; [[:Catégorie:Discipline philosophique|Disciplines]] &nbsp;·&nbsp; [[:Catégorie:Histoire de la philosophie|Histoire]] &nbsp;·&nbsp; [[:Catégorie:Philosophe|Philosophes]] &nbsp;·&nbsp; [[:Catégorie:Dictionnaire de philosophie (livre)|Dictionnaire]] &nbsp;·&nbsp; [[:Catégorie:Manuel de terminale de philosophie (livre)|Manuel]] &nbsp;·&nbsp; [[:Catégorie:Commentaire philosophique|Commentaires]]</div> </div> </div> [[Catégorie:Philosophie|*]] 1oon2j683jpspez42y2bzlpjgxc1kfe 764799 764756 2026-04-24T10:24:42Z ~2026-25114-99 123599 764799 wikitext text/x-wiki <!-- ══════════════════════════════════════════════ EN-TÊTE ══════════════════════════════════════════════ --> <div style="padding: 6px 0 18px; border-bottom: 2px solid #1a2230; margin-bottom: 22px;"> <div style="font-size: 1.6em; font-weight: 600; color: #1a2230; line-height: 1.2; margin-bottom: 4px;">Classification de la philosophie</div> <div style="font-size: 0.9em; color: #555e6b; line-height: 1.5;">Portail raisonné des livres, manuels et dictionnaires de philosophie sur Wikilivres. Les entrées sont rangées en cinq grandes aires, subdivisées selon la taxonomie standard des champs philosophiques.</div> </div> {{PhiloRecherche}} <!-- ══════════════════════════════════════════════ CLUSTER 1 — MÉTAPHYSIQUE ET ÉPISTÉMOLOGIE ══════════════════════════════════════════════ --> <div style="margin: 26px 0 10px; padding: 6px 0 4px; border-bottom: 1px solid #1a2230;"> <div style="font-size: 1.15em; font-weight: 700; color: #1a2230;">Métaphysique et épistémologie</div> </div> <table style="width: 100%; border-collapse: separate; border-spacing: 14px 14px;"> <tr> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-top: 3px solid #3a5a80; border-radius: 8px; padding: 0; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="background: #e4eaf1; padding: 10px 16px; font-size: 0.94em; font-weight: 600; color: #1a2230; letter-spacing: 0.01em;">[[Philosophie/Théorie de la connaissance|Théorie de la connaissance]] <span style="color: #8a94a3; font-weight: 400;">(8)</span></div> <div style="padding: 12px 16px 14px;"> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Philosophie/Théorie de la connaissance/Une définition traditionnelle|Définition traditionnelle]]<br/> [[Philosophie/Théorie de la connaissance/Le Problème de Gettier|Problème de Gettier]]<br/> [[Dictionnaire de philosophie/Vérité|Vérité]]<br/> [[Dictionnaire de philosophie/Certitude|Certitude]]<br/> [[Dictionnaire de philosophie/A priori|A priori]]<br/> [[Dictionnaire de philosophie/Abduction|Abduction]]<br/> [[Dictionnaire de philosophie/Agnosticisme|Agnosticisme]] </div> </div> </td> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-top: 3px solid #3a5a80; border-radius: 8px; padding: 0; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="background: #e4eaf1; padding: 10px 16px; font-size: 0.94em; font-weight: 600; color: #1a2230; letter-spacing: 0.01em;">Métaphysique <span style="color: #8a94a3; font-weight: 400;">(12)</span></div> <div style="padding: 12px 16px 14px;"> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Dictionnaire de philosophie/Absolu|Absolu]]<br/> [[Philosophie/Acte/Puissance|Acte et Puissance]]<br/> [[Dictionnaire de philosophie/Accident|Accident]]<br/> [[Dictionnaire de philosophie/Attribut|Attribut]]<br/> [[Dictionnaire de philosophie/Atomisme|Atomisme]]<br/> [[Dictionnaire de philosophie/Déterminisme|Déterminisme]]<br/> </div> </div> </td> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-top: 3px solid #3a5a80; border-radius: 8px; padding: 0; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="background: #e4eaf1; padding: 10px 16px; font-size: 0.94em; font-weight: 600; color: #1a2230; letter-spacing: 0.01em;">[[Philosophie/Philosophie de l'esprit|Philosophie de l'esprit]] <span style="color: #8a94a3; font-weight: 400;">(14)</span></div> <div style="padding: 12px 16px 14px;"> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Philosophie/Philosophie de l'esprit/Introduction|Introduction]]<br/> [[Philosophie/Philosophie de l'esprit/Ce que Marie ne savait pas|Ce que Marie ne savait pas]]<br/> [[Dictionnaire de philosophie/Conscience|Conscience]]<br/> [[Dictionnaire de philosophie/Âme|Âme]] </div> </div> </td> </tr> <tr> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-top: 3px solid #3a5a80; border-radius: 8px; padding: 0; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="background: #e4eaf1; padding: 10px 16px; font-size: 0.94em; font-weight: 600; color: #1a2230; letter-spacing: 0.01em;">Philosophie du langage <span style="color: #8a94a3; font-weight: 400;">(3)</span></div> <div style="padding: 12px 16px 14px;"> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Manuel de terminale de philosophie/Langage|Langage]]<br/> [[Dictionnaire de philosophie/Analogie|Analogie]]<br/> [[Dictionnaire de philosophie/Argument|Argument]] </div> </div> </td> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-top: 3px solid #3a5a80; border-radius: 8px; padding: 0; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="background: #e4eaf1; padding: 10px 16px; font-size: 0.94em; font-weight: 600; color: #1a2230; letter-spacing: 0.01em;">Philosophie de l'action <span style="color: #8a94a3; font-weight: 400;">(5)</span></div> <div style="padding: 12px 16px 14px;"> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Dictionnaire de philosophie/Action|Action]]<br/> [[Dictionnaire de philosophie/Aboulie|Aboulie]]<br/> [[Dictionnaire de philosophie/Altruisme|Altruisme]]<br/> [[Dictionnaire de philosophie/Authenticité|Authenticité]]<br/> [[Philosophie/Liberté|Liberté]] </div> </div> </td> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-top: 3px solid #3a5a80; border-radius: 8px; padding: 0; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="background: #e4eaf1; padding: 10px 16px; font-size: 0.94em; font-weight: 600; color: #1a2230; letter-spacing: 0.01em;">Philosophie de la religion <span style="color: #8a94a3; font-weight: 400;">(5)</span></div> <div style="padding: 12px 16px 14px;"> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Manuel de terminale de philosophie/Religion|Religion]]<br/> [[Philosophie/Athéisme|Athéisme]]<br/> [[Philosophie/Déisme|Déisme]]<br/> [[Philosophie/Monothéisme|Monothéisme]]<br/> [[Dictionnaire de philosophie/Panthéisme|Panthéisme]] </div> </div> </td> </tr> </table> <!-- ══════════════════════════════════════════════ CLUSTER 2 — THÉORIE DE LA VALEUR ══════════════════════════════════════════════ --> <div style="margin: 22px 0 10px; padding: 6px 0 4px; border-bottom: 1px solid #1a2230;"> <div style="font-size: 1.15em; font-weight: 700; color: #1a2230;">Théorie de la valeur</div> </div> <table style="width: 100%; border-collapse: separate; border-spacing: 14px 14px;"> <tr> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-top: 3px solid #3a5a80; border-radius: 8px; padding: 0; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="background: #e4eaf1; padding: 10px 16px; font-size: 0.94em; font-weight: 600; color: #1a2230; letter-spacing: 0.01em;">Éthique normative <span style="color: #8a94a3; font-weight: 400;">(9)</span></div> <div style="padding: 12px 16px 14px;"> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Philosophie/Morale|Morale]]<br/> [[Manuel de terminale de philosophie/Devoir|Devoir]]<br/> [[Dictionnaire de philosophie/Bonheur|Bonheur]]<br/> [[Manuel de terminale de philosophie/Liberté|Liberté (terminale)]]<br/> [[Philosophie/Ataraxie|Ataraxie]]<br/> [[Dictionnaire de philosophie/Amour|Amour]]<br/> [[Dictionnaire de philosophie/Amitié|Amitié]] </div> </div> </td> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-top: 3px solid #3a5a80; border-radius: 8px; padding: 0; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="background: #e4eaf1; padding: 10px 16px; font-size: 0.94em; font-weight: 600; color: #1a2230; letter-spacing: 0.01em;">Éthique appliquée <span style="color: #8a94a3; font-weight: 400;">(3)</span></div> <div style="padding: 12px 16px 14px;"> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Dictionnaire de philosophie/Avortement|Avortement (éthique)]]<br/> [[Dictionnaire de philosophie/Animal|Animal (droits)]]<br/> [[Dictionnaire de philosophie/Anthropocentrisme|Anthropocentrisme]] </div> </div> </td> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-top: 3px solid #3a5a80; border-radius: 8px; padding: 0; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="background: #e4eaf1; padding: 10px 16px; font-size: 0.94em; font-weight: 600; color: #1a2230; letter-spacing: 0.01em;">Esthétique <span style="color: #8a94a3; font-weight: 400;">(4)</span></div> <div style="padding: 12px 16px 14px;"> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Manuel de terminale de philosophie/Art|Art]]<br/> [[Dictionnaire de philosophie/Art|Art (dictionnaire)]]<br/> [[Dictionnaire de philosophie/Art (introduction)|Art — introduction]]<br/> [[Dictionnaire de philosophie/Art et Vérité|Art et vérité]] </div> </div> </td> </tr> <tr> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-top: 3px solid #3a5a80; border-radius: 8px; padding: 0; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="background: #e4eaf1; padding: 10px 16px; font-size: 0.94em; font-weight: 600; color: #1a2230; letter-spacing: 0.01em;">Philosophie sociale et politique <span style="color: #8a94a3; font-weight: 400;">(6)</span></div> <div style="padding: 12px 16px 14px;"> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Manuel de terminale de philosophie/État|État]]<br/> [[Manuel de terminale de philosophie/Justice|Justice]]<br/> [[Manuel de terminale de philosophie/Travail|Travail]]<br/> [[Dictionnaire de philosophie/Absolutisme|Absolutisme]]<br/> [[Dictionnaire de philosophie/Anarchisme|Anarchisme]]<br/> [[Philosophie/Aliénation|Aliénation]] </div> </div> </td> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-top: 3px solid #3a5a80; border-radius: 8px; padding: 0; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="background: #e4eaf1; padding: 10px 16px; font-size: 0.94em; font-weight: 600; color: #1a2230; letter-spacing: 0.01em;">Autrui et reconnaissance <span style="color: #8a94a3; font-weight: 400;">(4)</span></div> <div style="padding: 12px 16px 14px;"> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Philosophie/Autrui|Autrui]]<br/> [[Dictionnaire de philosophie/Altérité|Altérité]]<br/> [[Dictionnaire de philosophie/Autorité|Autorité]]<br/> [[Dictionnaire de philosophie/Autonomie|Autonomie]] </div> </div> </td> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-top: 3px solid #3a5a80; border-radius: 8px; padding: 0; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="background: #e4eaf1; padding: 10px 16px; font-size: 0.94em; font-weight: 600; color: #1a2230; letter-spacing: 0.01em;">Métaéthique <span style="color: #8a94a3; font-weight: 400;">(3)</span></div> <div style="padding: 12px 16px 14px;"> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Dictionnaire de philosophie/Absurde|Absurde]]<br/> [[Dictionnaire de philosophie/Affection|Affection]]<br/> [[Dictionnaire de philosophie/Angoisse|Angoisse]] </div> </div> </td> </tr> </table> <!-- ══════════════════════════════════════════════ CLUSTER 3 — SCIENCE, LOGIQUE ET MATHÉMATIQUES ══════════════════════════════════════════════ --> <div style="margin: 22px 0 10px; padding: 6px 0 4px; border-bottom: 1px solid #1a2230;"> <div style="font-size: 1.15em; font-weight: 700; color: #1a2230;">Science, logique et mathématiques</div> </div> <table style="width: 100%; border-collapse: separate; border-spacing: 14px 14px;"> <tr> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-top: 3px solid #3a5a80; border-radius: 8px; padding: 0; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="background: #e4eaf1; padding: 10px 16px; font-size: 0.94em; font-weight: 600; color: #1a2230; letter-spacing: 0.01em;">Logique <span style="color: #8a94a3; font-weight: 400;">(6)</span></div> <div style="padding: 12px 16px 14px;"> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Dictionnaire de philosophie/A (logique)|A (logique)]]<br/> [[Dictionnaire de philosophie/Abstraction|Abstraction]]<br/> [[Dictionnaire de philosophie/Antinomie|Antinomie]]<br/> [[Philosophie/Aporie|Aporie]]<br/> [[Dictionnaire de philosophie/Axiome|Axiome]]<br/> </div> </div> </td> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-top: 3px solid #3a5a80; border-radius: 8px; padding: 0; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="background: #e4eaf1; padding: 10px 16px; font-size: 0.94em; font-weight: 600; color: #1a2230; letter-spacing: 0.01em;">Philosophie des sciences <span style="color: #8a94a3; font-weight: 400;">(3)</span></div> <div style="padding: 12px 16px 14px;"> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Philosophie/Théorie et expérience|Théorie et expérience]]<br/> [[Manuel de terminale de philosophie/Science|Science]]<br/> [[Manuel de terminale de philosophie/Technique|Technique]] </div> </div> </td> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-top: 3px solid #3a5a80; border-radius: 8px; padding: 0; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="background: #e4eaf1; padding: 10px 16px; font-size: 0.94em; font-weight: 600; color: #1a2230; letter-spacing: 0.01em;">Philosophie du vivant <span style="color: #8a94a3; font-weight: 400;">(3)</span></div> <div style="padding: 12px 16px 14px;"> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Manuel de terminale de philosophie/Nature|Nature]]<br/> [[Manuel de terminale de philosophie/Vivant|Vivant]]<br/> [[Dictionnaire de philosophie/Animal|Animal]] </div> </div> </td> </tr> </table> <!-- ══════════════════════════════════════════════ CLUSTER 4 — HISTOIRE DE LA PHILOSOPHIE ══════════════════════════════════════════════ --> <div style="margin: 22px 0 10px; padding: 6px 0 4px; border-bottom: 1px solid #1a2230;"> <div style="font-size: 1.15em; font-weight: 700; color: #1a2230;">[[Philosophie/Histoire de la philosophie|Histoire de la philosophie]] <span style="color: #8a94a3; font-weight: 400; font-size: 0.92em;">(30)</span></div> </div> <table style="width: 100%; border-collapse: separate; border-spacing: 14px 14px;"> <tr> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-top: 3px solid #3a5a80; border-radius: 8px; padding: 0; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="background: #e4eaf1; padding: 10px 16px; font-size: 0.94em; font-weight: 600; color: #1a2230; letter-spacing: 0.01em;">Antiquité <span style="color: #8a94a3; font-weight: 400;">(14)</span></div> <div style="padding: 10px 16px 14px;"> <div style="font-size: 0.82em; color: #8a94a3; margin-bottom: 6px;">VI<sup>e</sup>&nbsp;av.&nbsp;— V<sup>e</sup>&nbsp;siècle</div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> <span style="font-weight: 500;">Présocratiques</span><br/> <span style="padding-left: 10px;">[[Philosophie/Thalès de Milet|Thalès de Milet]]</span><br/> <span style="padding-left: 10px;">[[Philosophie/Anaximandre de Milet|Anaximandre]]</span><br/> <span style="padding-left: 10px;">[[Dictionnaire de philosophie/Anaxagore|Anaxagore]]</span><br/> <span style="padding-left: 10px;">[[Philosophie/Présocratiques/Liste des Présocratiques|Liste complète]]</span><br/> [[Pour lire Platon|Platon]]<br/> [[Dictionnaire de philosophie/Aristote|Aristote]]<br/> <span style="font-weight: 500;">Philosophie hellénistique</span><br/> <span style="padding-left: 10px;">[[Commentaire philosophique/Lettre à Ménécée|Épicure]]</span><br/> <span style="padding-left: 10px;">[[Philosophie/Ataraxie|Stoïcisme]]</span> </div> </div> </td> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-top: 3px solid #3a5a80; border-radius: 8px; padding: 0; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="background: #e4eaf1; padding: 10px 16px; font-size: 0.94em; font-weight: 600; color: #1a2230; letter-spacing: 0.01em;">Philosophie médiévale <span style="color: #8a94a3; font-weight: 400;">(0)</span></div> <div style="padding: 10px 16px 14px;"> <div style="font-size: 0.82em; color: #8a94a3; margin-bottom: 6px;">V<sup>e</sup>&nbsp;— XV<sup>e</sup>&nbsp;siècle</div> <div style="font-size: 0.88em; color: #8a94a3; line-height: 1.75; font-style: italic;"> Augustin, Anselme, Thomas d'Aquin, Averroès — pages à rédiger. </div> </div> </td> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-top: 3px solid #3a5a80; border-radius: 8px; padding: 0; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="background: #e4eaf1; padding: 10px 16px; font-size: 0.94em; font-weight: 600; color: #1a2230; letter-spacing: 0.01em;">Philosophie classique <span style="color: #8a94a3; font-weight: 400;">(6)</span></div> <div style="padding: 10px 16px 14px;"> <div style="font-size: 0.82em; color: #8a94a3; margin-bottom: 6px;">XVI<sup>e</sup>&nbsp;— XVIII<sup>e</sup>&nbsp;siècle</div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Méditations métaphysiques|Descartes — Méditations]]<br/> [[Philosophie/Commentaire du passage à propos de l'Homme esclave du divertissement|Pascal — Divertissement]]<br/> [[Commentaire de l'Éthique|Spinoza — Éthique]]<br/> [[Philosophie/Vocabulaire/David Hume|Hume]]<br/> [[Philosophie/Vocabulaire/Kant|Kant]]<br/> [[Commentaire philosophique/Discours sur l'origine et les fondements de l'inégalité parmi les hommes|Rousseau — Inégalité]] </div> </div> </td> </tr> <tr> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-top: 3px solid #3a5a80; border-radius: 8px; padding: 0; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="background: #e4eaf1; padding: 10px 16px; font-size: 0.94em; font-weight: 600; color: #1a2230; letter-spacing: 0.01em;">Philosophie contemporaine <span style="color: #8a94a3; font-weight: 400;">(5)</span></div> <div style="padding: 10px 16px 14px;"> <div style="font-size: 0.82em; color: #8a94a3; margin-bottom: 6px;">XIX<sup>e</sup>&nbsp;— XXI<sup>e</sup>&nbsp;siècle</div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Philosophie/Nietzsche|Nietzsche]]<br/> <span style="padding-left: 10px;">[[Philosophie/Nietzsche/La culture grecque|Culture grecque]]</span><br/> <span style="padding-left: 10px;">[[Philosophie/Nietzsche/La culture moderne|Culture moderne]]</span><br/> <span style="padding-left: 10px;">[[Philosophie/Nietzsche/La métaphysique|Métaphysique]]</span><br/> <span style="padding-left: 10px;">[[Philosophie/Nietzsche/La moralité des mœurs|Moralité des mœurs]]</span> </div> </div> </td> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-top: 3px solid #3a5a80; border-radius: 8px; padding: 0; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="background: #e4eaf1; padding: 10px 16px; font-size: 0.94em; font-weight: 600; color: #1a2230; letter-spacing: 0.01em;">Courants du XX<sup>e</sup> siècle <span style="color: #8a94a3; font-weight: 400;">(3)</span></div> <div style="padding: 10px 16px 14px;"> <div style="font-size: 0.82em; color: #8a94a3; margin-bottom: 6px;">Écoles et traditions</div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Philosophie/Philosophie analytique|Philosophie analytique]]<br/> [[Philosophie/Existence et temps|Existentialisme]]<br/> [[Dictionnaire de philosophie/Dasein|Phénoménologie / Dasein]] </div> </div> </td> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-top: 3px solid #3a5a80; border-radius: 8px; padding: 0; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="background: #e4eaf1; padding: 10px 16px; font-size: 0.94em; font-weight: 600; color: #1a2230; letter-spacing: 0.01em;">Traditions non occidentales <span style="color: #8a94a3; font-weight: 400;">(2)</span></div> <div style="padding: 10px 16px 14px;"> <div style="font-size: 0.82em; color: #8a94a3; margin-bottom: 6px;">Afrique, Amériques</div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Dictionnaire de philosophie/Philosophie africaine|Philosophie africaine]]<br/> [[Dictionnaire de philosophie/Argentine (Philosophie)|Argentine — XX<sup>e</sup> siècle]] </div> </div> </td> </tr> </table> <!-- ══════════════════════════════════════════════ CLUSTER 5 — OUVRAGES DE RÉFÉRENCE ══════════════════════════════════════════════ --> <div style="margin: 22px 0 10px; padding: 6px 0 4px; border-bottom: 1px solid #1a2230;"> <div style="font-size: 1.15em; font-weight: 700; color: #1a2230;">Ouvrages de référence et pédagogiques</div> </div> <!-- Dictionnaire — pleine largeur --> <div style="background: #fbfcfd; border: 1px solid #d4dae2; border-top: 3px solid #3a5a80; border-radius: 8px; padding: 0; margin-bottom: 14px; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="background: #e4eaf1; padding: 10px 16px; font-size: 0.94em; font-weight: 600; color: #1a2230; letter-spacing: 0.01em;">[[Dictionnaire de philosophie|Dictionnaire de philosophie]] <span style="color: #8a94a3; font-weight: 400;">(35)</span></div> <div style="padding: 12px 16px 14px;"> <div style="font-size: 0.92em; color: #1a2230; line-height: 1.7; letter-spacing: 0.04em;"> [[Dictionnaire de philosophie/A|A]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/B|B]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/C|C]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/D|D]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/E|E]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/F|F]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/G|G]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/H|H]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/I|I]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/J|J]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/K|K]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/L|L]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/M|M]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/N|N]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/O|O]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/P|P]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/Q|Q]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/R|R]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/S|S]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/T|T]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/U|U]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/V|V]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/W|W]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/X|X]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/Y|Y]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/Z|Z]] </div> </div> </div> <!-- Autres ouvrages --> <table style="width: 100%; border-collapse: separate; border-spacing: 14px 14px;"> <tr> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-top: 3px solid #3a5a80; border-radius: 8px; padding: 0; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="background: #e4eaf1; padding: 10px 16px; font-size: 0.94em; font-weight: 600; color: #1a2230; letter-spacing: 0.01em;">[[Manuel de terminale de philosophie|Manuel de terminale]] <span style="color: #8a94a3; font-weight: 400;">(26)</span></div> <div style="padding: 12px 16px 14px;"> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> Éthique &amp; action <span style="color: #8a94a3;">(4)</span><br/> Esprit &amp; conscience <span style="color: #8a94a3;">(4)</span><br/> Connaissance &amp; vérité <span style="color: #8a94a3;">(5)</span><br/> Monde &amp; société <span style="color: #8a94a3;">(4)</span><br/> Culture &amp; sens <span style="color: #8a94a3;">(6)</span><br/> Méthode &amp; ressources <span style="color: #8a94a3;">(3)</span> </div> </div> </td> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-top: 3px solid #3a5a80; border-radius: 8px; padding: 0; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="background: #e4eaf1; padding: 10px 16px; font-size: 0.94em; font-weight: 600; color: #1a2230; letter-spacing: 0.01em;">[[:Catégorie:Commentaire philosophique|Commentaires d'œuvres]] <span style="color: #8a94a3; font-weight: 400;">(20)</span></div> <div style="padding: 12px 16px 14px;"> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Commentaire philosophique/Lettre à Ménécée|Lettre à Ménécée]] <span style="color: #8a94a3;">·</span> Épicure<br/> [[Méditations métaphysiques|Méditations]] <span style="color: #8a94a3;">·</span> Descartes<br/> [[Commentaire de l'Éthique|Éthique]] <span style="color: #8a94a3;">·</span> Spinoza<br/> [[Commentaire philosophique/Discours sur l'origine et les fondements de l'inégalité parmi les hommes|Inégalité]] <span style="color: #8a94a3;">·</span> Rousseau<br/> [[Philosophie/Commentaire du passage à propos de l'Homme esclave du divertissement|Divertissement]] <span style="color: #8a94a3;">·</span> Pascal </div> </div> </td> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-top: 3px solid #3a5a80; border-radius: 8px; padding: 0; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="background: #e4eaf1; padding: 10px 16px; font-size: 0.94em; font-weight: 600; color: #1a2230; letter-spacing: 0.01em;">[[Philosophie/Une brève introduction|Une brève introduction]] <span style="color: #8a94a3; font-weight: 400;">(8)</span></div> <div style="padding: 12px 16px 14px;"> <div style="height: 1px; background: #e2e8ef; margin: 10px 0;"></div> <div style="font-size: 0.96em; font-weight: 600; color: #1a2230; margin-bottom: 6px;">[[:Catégorie:Vocabulaire philosophique|Vocabulaires]] <span style="color: #8a94a3; font-weight: 400;">(4)</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Pour lire Platon/Vocabulaire|Platon]]<br/> [[Philosophie/Vocabulaire/David Hume|David Hume]]<br/> [[Philosophie/Vocabulaire/Kant|Kant]] </div> </div> </td> </tr> </table> <!-- ══════════════════════════════════════════════ PIED DE PAGE ══════════════════════════════════════════════ --> <div style="margin-top: 24px; padding: 14px 0 4px; border-top: 1px solid #1a2230; font-size: 0.86em; color: #555e6b;"> <div style="margin-bottom: 6px;"><span style="font-weight: 600; color: #1a2230;">Consulter ailleurs</span> &nbsp;·&nbsp; [[w:Philosophie|Wikipédia]] &nbsp;·&nbsp; [[wikt:philosophie|Wiktionnaire]] &nbsp;·&nbsp; [[v:Philosophie|Wikiversité]] &nbsp;·&nbsp; [[s:Portail:Philosophie|Wikisource]]</div> <div><span style="font-weight: 600; color: #1a2230;">Catégories sources</span> &nbsp;·&nbsp; [[:Catégorie:Discipline philosophique|Disciplines]] &nbsp;·&nbsp; [[:Catégorie:Histoire de la philosophie|Histoire]] &nbsp;·&nbsp; [[:Catégorie:Philosophe|Philosophes]] &nbsp;·&nbsp; [[:Catégorie:Dictionnaire de philosophie (livre)|Dictionnaire]] &nbsp;·&nbsp; [[:Catégorie:Manuel de terminale de philosophie (livre)|Manuel]] &nbsp;·&nbsp; [[:Catégorie:Commentaire philosophique|Commentaires]]</div> </div> </div> [[Catégorie:Philosophie|*]] a7y1632jtle2syd2hs2hwft8lnfry20 764808 764799 2026-04-24T10:31:33Z ~2026-25114-99 123599 764808 wikitext text/x-wiki <!-- ══════════════════════════════════════════════ EN-TÊTE ══════════════════════════════════════════════ --> <div style="padding: 6px 0 18px; border-bottom: 2px solid #1a2230; margin-bottom: 22px;"> <div style="font-size: 1.6em; font-weight: 600; color: #1a2230; line-height: 1.2; margin-bottom: 4px;">Classification de la philosophie</div> <div style="font-size: 0.9em; color: #555e6b; line-height: 1.5;">Portail raisonné des livres, manuels et dictionnaires de philosophie sur Wikilivres. Les entrées sont rangées en cinq grandes aires, subdivisées selon la taxonomie standard des champs philosophiques.</div> </div> {{PhiloRecherche}} <!-- ══════════════════════════════════════════════ CLUSTER 1 — MÉTAPHYSIQUE ET ÉPISTÉMOLOGIE ══════════════════════════════════════════════ --> <div style="margin: 26px 0 10px; padding: 6px 0 4px; border-bottom: 1px solid #1a2230;"> <div style="font-size: 1.15em; font-weight: 700; color: #1a2230;">Métaphysique et épistémologie</div> </div> <table style="width: 100%; border-collapse: separate; border-spacing: 14px 14px;"> <tr> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-top: 3px solid #3a5a80; border-radius: 8px; padding: 0; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="background: #e4eaf1; padding: 10px 16px; font-size: 0.94em; font-weight: 600; color: #1a2230; letter-spacing: 0.01em;">[[Philosophie/Théorie de la connaissance|Théorie de la connaissance]] <span style="color: #8a94a3; font-weight: 400;">(8)</span></div> <div style="padding: 12px 16px 14px;"> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Philosophie/Théorie de la connaissance/Une définition traditionnelle|Définition traditionnelle]]<br/> [[Philosophie/Théorie de la connaissance/Le Problème de Gettier|Problème de Gettier]]<br/> [[Dictionnaire de philosophie/Vérité|Vérité]]<br/> [[Dictionnaire de philosophie/Certitude|Certitude]]<br/> [[Dictionnaire de philosophie/A priori|A priori]]<br/> [[Dictionnaire de philosophie/Abduction|Abduction]]<br/> [[Dictionnaire de philosophie/Agnosticisme|Agnosticisme]] </div> </div> </td> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-top: 3px solid #3a5a80; border-radius: 8px; padding: 0; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="background: #e4eaf1; padding: 10px 16px; font-size: 0.94em; font-weight: 600; color: #1a2230; letter-spacing: 0.01em;">Métaphysique <span style="color: #8a94a3; font-weight: 400;">(12)</span></div> <div style="padding: 12px 16px 14px;"> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Dictionnaire de philosophie/Absolu|Absolu]]<br/> [[Philosophie/Acte/Puissance|Acte et Puissance]]<br/> [[Dictionnaire de philosophie/Accident|Accident]]<br/> [[Dictionnaire de philosophie/Attribut|Attribut]]<br/> [[Dictionnaire de philosophie/Atomisme|Atomisme]]<br/> [[Dictionnaire de philosophie/Déterminisme|Déterminisme]]<br/> </div> </div> </td> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-top: 3px solid #3a5a80; border-radius: 8px; padding: 0; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="background: #e4eaf1; padding: 10px 16px; font-size: 0.94em; font-weight: 600; color: #1a2230; letter-spacing: 0.01em;">[[Philosophie/Philosophie de l'esprit|Philosophie de l'esprit]] <span style="color: #8a94a3; font-weight: 400;">(14)</span></div> <div style="padding: 12px 16px 14px;"> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Philosophie/Philosophie de l'esprit/Introduction|Introduction]]<br/> [[Philosophie/Philosophie de l'esprit/Ce que Marie ne savait pas|Ce que Marie ne savait pas]]<br/> [[Dictionnaire de philosophie/Conscience|Conscience]]<br/> [[Dictionnaire de philosophie/Âme|Âme]] </div> </div> </td> </tr> <tr> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-top: 3px solid #3a5a80; border-radius: 8px; padding: 0; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="background: #e4eaf1; padding: 10px 16px; font-size: 0.94em; font-weight: 600; color: #1a2230; letter-spacing: 0.01em;">Philosophie du langage <span style="color: #8a94a3; font-weight: 400;">(3)</span></div> <div style="padding: 12px 16px 14px;"> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Manuel de terminale de philosophie/Langage|Langage]]<br/> [[Dictionnaire de philosophie/Analogie|Analogie]]<br/> [[Dictionnaire de philosophie/Argument|Argument]] </div> </div> </td> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-top: 3px solid #3a5a80; border-radius: 8px; padding: 0; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="background: #e4eaf1; padding: 10px 16px; font-size: 0.94em; font-weight: 600; color: #1a2230; letter-spacing: 0.01em;">Philosophie de l'action <span style="color: #8a94a3; font-weight: 400;">(5)</span></div> <div style="padding: 12px 16px 14px;"> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Dictionnaire de philosophie/Action|Action]]<br/> [[Dictionnaire de philosophie/Aboulie|Aboulie]]<br/> [[Dictionnaire de philosophie/Altruisme|Altruisme]]<br/> [[Dictionnaire de philosophie/Authenticité|Authenticité]]<br/> [[Philosophie/Liberté|Liberté]] </div> </div> </td> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-top: 3px solid #3a5a80; border-radius: 8px; padding: 0; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="background: #e4eaf1; padding: 10px 16px; font-size: 0.94em; font-weight: 600; color: #1a2230; letter-spacing: 0.01em;">Philosophie de la religion <span style="color: #8a94a3; font-weight: 400;">(5)</span></div> <div style="padding: 12px 16px 14px;"> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Manuel de terminale de philosophie/Religion|Religion]]<br/> [[Philosophie/Athéisme|Athéisme]]<br/> [[Philosophie/Déisme|Déisme]]<br/> [[Philosophie/Monothéisme|Monothéisme]]<br/> [[Dictionnaire de philosophie/Panthéisme|Panthéisme]] </div> </div> </td> </tr> </table> <!-- ══════════════════════════════════════════════ CLUSTER 2 — THÉORIE DE LA VALEUR ══════════════════════════════════════════════ --> <div style="margin: 22px 0 10px; padding: 6px 0 4px; border-bottom: 1px solid #1a2230;"> <div style="font-size: 1.15em; font-weight: 700; color: #1a2230;">Théorie de la valeur</div> </div> <table style="width: 100%; border-collapse: separate; border-spacing: 14px 14px;"> <tr> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-top: 3px solid #3a5a80; border-radius: 8px; padding: 0; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="background: #e4eaf1; padding: 10px 16px; font-size: 0.94em; font-weight: 600; color: #1a2230; letter-spacing: 0.01em;">Éthique normative <span style="color: #8a94a3; font-weight: 400;">(9)</span></div> <div style="padding: 12px 16px 14px;"> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Philosophie/Morale|Morale]]<br/> [[Manuel de terminale de philosophie/Devoir|Devoir]]<br/> [[Dictionnaire de philosophie/Bonheur|Bonheur]]<br/> [[Manuel de terminale de philosophie/Liberté|Liberté (terminale)]]<br/> [[Philosophie/Ataraxie|Ataraxie]]<br/> [[Dictionnaire de philosophie/Amour|Amour]]<br/> [[Dictionnaire de philosophie/Amitié|Amitié]] </div> </div> </td> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-top: 3px solid #3a5a80; border-radius: 8px; padding: 0; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="background: #e4eaf1; padding: 10px 16px; font-size: 0.94em; font-weight: 600; color: #1a2230; letter-spacing: 0.01em;">Éthique appliquée <span style="color: #8a94a3; font-weight: 400;">(3)</span></div> <div style="padding: 12px 16px 14px;"> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Dictionnaire de philosophie/Avortement|Avortement (éthique)]]<br/> [[Dictionnaire de philosophie/Animal|Animal (droits)]]<br/> [[Dictionnaire de philosophie/Anthropocentrisme|Anthropocentrisme]] </div> </div> </td> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-top: 3px solid #3a5a80; border-radius: 8px; padding: 0; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="background: #e4eaf1; padding: 10px 16px; font-size: 0.94em; font-weight: 600; color: #1a2230; letter-spacing: 0.01em;">Esthétique <span style="color: #8a94a3; font-weight: 400;">(4)</span></div> <div style="padding: 12px 16px 14px;"> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Manuel de terminale de philosophie/Art|Art]]<br/> [[Dictionnaire de philosophie/Art|Art (dictionnaire)]]<br/> [[Dictionnaire de philosophie/Art (introduction)|Art — introduction]]<br/> [[Dictionnaire de philosophie/Art et Vérité|Art et vérité]] </div> </div> </td> </tr> <tr> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-top: 3px solid #3a5a80; border-radius: 8px; padding: 0; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="background: #e4eaf1; padding: 10px 16px; font-size: 0.94em; font-weight: 600; color: #1a2230; letter-spacing: 0.01em;">Philosophie sociale et politique <span style="color: #8a94a3; font-weight: 400;">(6)</span></div> <div style="padding: 12px 16px 14px;"> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Manuel de terminale de philosophie/État|État]]<br/> [[Manuel de terminale de philosophie/Justice|Justice]]<br/> [[Manuel de terminale de philosophie/Travail|Travail]]<br/> [[Dictionnaire de philosophie/Absolutisme|Absolutisme]]<br/> [[Dictionnaire de philosophie/Anarchisme|Anarchisme]]<br/> [[Philosophie/Aliénation|Aliénation]] </div> </div> </td> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-top: 3px solid #3a5a80; border-radius: 8px; padding: 0; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="background: #e4eaf1; padding: 10px 16px; font-size: 0.94em; font-weight: 600; color: #1a2230; letter-spacing: 0.01em;">Autrui et reconnaissance <span style="color: #8a94a3; font-weight: 400;">(4)</span></div> <div style="padding: 12px 16px 14px;"> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Philosophie/Autrui|Autrui]]<br/> [[Dictionnaire de philosophie/Altérité|Altérité]]<br/> [[Dictionnaire de philosophie/Autorité|Autorité]]<br/> [[Dictionnaire de philosophie/Autonomie|Autonomie]] </div> </div> </td> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-top: 3px solid #3a5a80; border-radius: 8px; padding: 0; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="background: #e4eaf1; padding: 10px 16px; font-size: 0.94em; font-weight: 600; color: #1a2230; letter-spacing: 0.01em;">Métaéthique <span style="color: #8a94a3; font-weight: 400;">(3)</span></div> <div style="padding: 12px 16px 14px;"> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Dictionnaire de philosophie/Absurde|Absurde]]<br/> [[Dictionnaire de philosophie/Affection|Affection]]<br/> [[Dictionnaire de philosophie/Angoisse|Angoisse]] </div> </div> </td> </tr> </table> <!-- ══════════════════════════════════════════════ CLUSTER 3 — SCIENCE, LOGIQUE ET MATHÉMATIQUES ══════════════════════════════════════════════ --> <div style="margin: 22px 0 10px; padding: 6px 0 4px; border-bottom: 1px solid #1a2230;"> <div style="font-size: 1.15em; font-weight: 700; color: #1a2230;">Science, logique et mathématiques</div> </div> <table style="width: 100%; border-collapse: separate; border-spacing: 14px 14px;"> <tr> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-top: 3px solid #3a5a80; border-radius: 8px; padding: 0; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="background: #e4eaf1; padding: 10px 16px; font-size: 0.94em; font-weight: 600; color: #1a2230; letter-spacing: 0.01em;">Logique <span style="color: #8a94a3; font-weight: 400;">(6)</span></div> <div style="padding: 12px 16px 14px;"> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Dictionnaire de philosophie/A (logique)|A (logique)]]<br/> [[Dictionnaire de philosophie/Abstraction|Abstraction]]<br/> [[Dictionnaire de philosophie/Antinomie|Antinomie]]<br/> [[Philosophie/Aporie|Aporie]]<br/> [[Dictionnaire de philosophie/Axiome|Axiome]]<br/> </div> </div> </td> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-top: 3px solid #3a5a80; border-radius: 8px; padding: 0; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="background: #e4eaf1; padding: 10px 16px; font-size: 0.94em; font-weight: 600; color: #1a2230; letter-spacing: 0.01em;">Philosophie des sciences <span style="color: #8a94a3; font-weight: 400;">(3)</span></div> <div style="padding: 12px 16px 14px;"> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Philosophie/Théorie et expérience|Théorie et expérience]]<br/> [[Manuel de terminale de philosophie/Science|Science]]<br/> [[Manuel de terminale de philosophie/Technique|Technique]] </div> </div> </td> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-top: 3px solid #3a5a80; border-radius: 8px; padding: 0; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="background: #e4eaf1; padding: 10px 16px; font-size: 0.94em; font-weight: 600; color: #1a2230; letter-spacing: 0.01em;">Philosophie du vivant <span style="color: #8a94a3; font-weight: 400;">(3)</span></div> <div style="padding: 12px 16px 14px;"> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Manuel de terminale de philosophie/Nature|Nature]]<br/> [[Manuel de terminale de philosophie/Vivant|Vivant]]<br/> [[Dictionnaire de philosophie/Animal|Animal]] </div> </div> </td> </tr> </table> <!-- ══════════════════════════════════════════════ CLUSTER 4 — HISTOIRE DE LA PHILOSOPHIE ══════════════════════════════════════════════ --> <div style="margin: 22px 0 10px; padding: 6px 0 4px; border-bottom: 1px solid #1a2230;"> <div style="font-size: 1.15em; font-weight: 700; color: #1a2230;">[[Philosophie/Histoire de la philosophie|Histoire de la philosophie]] <span style="color: #8a94a3; font-weight: 400; font-size: 0.92em;">(30)</span></div> </div> <table style="width: 100%; border-collapse: separate; border-spacing: 14px 14px;"> <tr> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-top: 3px solid #3a5a80; border-radius: 8px; padding: 0; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="background: #e4eaf1; padding: 10px 16px; font-size: 0.94em; font-weight: 600; color: #1a2230; letter-spacing: 0.01em;">Antiquité <span style="color: #8a94a3; font-weight: 400;">(14)</span></div> <div style="padding: 10px 16px 14px;"> <div style="font-size: 0.82em; color: #8a94a3; margin-bottom: 6px;">VI<sup>e</sup>&nbsp;av.&nbsp;— V<sup>e</sup>&nbsp;siècle</div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> <span style="font-weight: 500;">Présocratiques</span><br/> <span style="padding-left: 10px;">[[Philosophie/Thalès de Milet|Thalès de Milet]]</span><br/> <span style="padding-left: 10px;">[[Philosophie/Anaximandre de Milet|Anaximandre]]</span><br/> <span style="padding-left: 10px;">[[Dictionnaire de philosophie/Anaxagore|Anaxagore]]</span><br/> <span style="padding-left: 10px;">[[Dictionnaire de philosophie/Empédocle|Empédocle]]</span><br/> <span style="padding-left: 10px;">[[Philosophie/Présocratiques/Liste des Présocratiques|Liste complète]]</span><br/> [[Pour lire Platon|Platon]]<br/> [[Dictionnaire de philosophie/Aristote|Aristote]]<br/> <span style="font-weight: 500;">Philosophie hellénistique</span><br/> <span style="padding-left: 10px;">[[Commentaire philosophique/Lettre à Ménécée|Épicure]]</span><br/> <span style="padding-left: 10px;">[[Philosophie/Ataraxie|Stoïcisme]]</span> </div> </div> </td> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-top: 3px solid #3a5a80; border-radius: 8px; padding: 0; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="background: #e4eaf1; padding: 10px 16px; font-size: 0.94em; font-weight: 600; color: #1a2230; letter-spacing: 0.01em;">Philosophie médiévale <span style="color: #8a94a3; font-weight: 400;">(0)</span></div> <div style="padding: 10px 16px 14px;"> <div style="font-size: 0.82em; color: #8a94a3; margin-bottom: 6px;">V<sup>e</sup>&nbsp;— XV<sup>e</sup>&nbsp;siècle</div> <div style="font-size: 0.88em; color: #8a94a3; line-height: 1.75; font-style: italic;"> Augustin, Anselme, Thomas d'Aquin, Averroès — pages à rédiger. </div> </div> </td> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-top: 3px solid #3a5a80; border-radius: 8px; padding: 0; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="background: #e4eaf1; padding: 10px 16px; font-size: 0.94em; font-weight: 600; color: #1a2230; letter-spacing: 0.01em;">Philosophie classique <span style="color: #8a94a3; font-weight: 400;">(6)</span></div> <div style="padding: 10px 16px 14px;"> <div style="font-size: 0.82em; color: #8a94a3; margin-bottom: 6px;">XVI<sup>e</sup>&nbsp;— XVIII<sup>e</sup>&nbsp;siècle</div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Méditations métaphysiques|Descartes — Méditations]]<br/> [[Philosophie/Commentaire du passage à propos de l'Homme esclave du divertissement|Pascal — Divertissement]]<br/> [[Commentaire de l'Éthique|Spinoza — Éthique]]<br/> [[Philosophie/Vocabulaire/David Hume|Hume]]<br/> [[Philosophie/Vocabulaire/Kant|Kant]]<br/> [[Commentaire philosophique/Discours sur l'origine et les fondements de l'inégalité parmi les hommes|Rousseau — Inégalité]] </div> </div> </td> </tr> <tr> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-top: 3px solid #3a5a80; border-radius: 8px; padding: 0; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="background: #e4eaf1; padding: 10px 16px; font-size: 0.94em; font-weight: 600; color: #1a2230; letter-spacing: 0.01em;">Philosophie contemporaine <span style="color: #8a94a3; font-weight: 400;">(5)</span></div> <div style="padding: 10px 16px 14px;"> <div style="font-size: 0.82em; color: #8a94a3; margin-bottom: 6px;">XIX<sup>e</sup>&nbsp;— XXI<sup>e</sup>&nbsp;siècle</div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Philosophie/Nietzsche|Nietzsche]]<br/> <span style="padding-left: 10px;">[[Philosophie/Nietzsche/La culture grecque|Culture grecque]]</span><br/> <span style="padding-left: 10px;">[[Philosophie/Nietzsche/La culture moderne|Culture moderne]]</span><br/> <span style="padding-left: 10px;">[[Philosophie/Nietzsche/La métaphysique|Métaphysique]]</span><br/> <span style="padding-left: 10px;">[[Philosophie/Nietzsche/La moralité des mœurs|Moralité des mœurs]]</span> </div> </div> </td> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-top: 3px solid #3a5a80; border-radius: 8px; padding: 0; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="background: #e4eaf1; padding: 10px 16px; font-size: 0.94em; font-weight: 600; color: #1a2230; letter-spacing: 0.01em;">Courants du XX<sup>e</sup> siècle <span style="color: #8a94a3; font-weight: 400;">(3)</span></div> <div style="padding: 10px 16px 14px;"> <div style="font-size: 0.82em; color: #8a94a3; margin-bottom: 6px;">Écoles et traditions</div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Philosophie/Philosophie analytique|Philosophie analytique]]<br/> [[Philosophie/Existence et temps|Existentialisme]]<br/> [[Dictionnaire de philosophie/Dasein|Phénoménologie / Dasein]] </div> </div> </td> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-top: 3px solid #3a5a80; border-radius: 8px; padding: 0; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="background: #e4eaf1; padding: 10px 16px; font-size: 0.94em; font-weight: 600; color: #1a2230; letter-spacing: 0.01em;">Traditions non occidentales <span style="color: #8a94a3; font-weight: 400;">(2)</span></div> <div style="padding: 10px 16px 14px;"> <div style="font-size: 0.82em; color: #8a94a3; margin-bottom: 6px;">Afrique, Amériques</div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Dictionnaire de philosophie/Philosophie africaine|Philosophie africaine]]<br/> [[Dictionnaire de philosophie/Argentine (Philosophie)|Argentine — XX<sup>e</sup> siècle]] </div> </div> </td> </tr> </table> <!-- ══════════════════════════════════════════════ CLUSTER 5 — OUVRAGES DE RÉFÉRENCE ══════════════════════════════════════════════ --> <div style="margin: 22px 0 10px; padding: 6px 0 4px; border-bottom: 1px solid #1a2230;"> <div style="font-size: 1.15em; font-weight: 700; color: #1a2230;">Ouvrages de référence et pédagogiques</div> </div> <!-- Dictionnaire — pleine largeur --> <div style="background: #fbfcfd; border: 1px solid #d4dae2; border-top: 3px solid #3a5a80; border-radius: 8px; padding: 0; margin-bottom: 14px; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="background: #e4eaf1; padding: 10px 16px; font-size: 0.94em; font-weight: 600; color: #1a2230; letter-spacing: 0.01em;">[[Dictionnaire de philosophie|Dictionnaire de philosophie]] <span style="color: #8a94a3; font-weight: 400;">(35)</span></div> <div style="padding: 12px 16px 14px;"> <div style="font-size: 0.92em; color: #1a2230; line-height: 1.7; letter-spacing: 0.04em;"> [[Dictionnaire de philosophie/A|A]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/B|B]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/C|C]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/D|D]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/E|E]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/F|F]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/G|G]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/H|H]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/I|I]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/J|J]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/K|K]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/L|L]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/M|M]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/N|N]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/O|O]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/P|P]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/Q|Q]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/R|R]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/S|S]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/T|T]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/U|U]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/V|V]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/W|W]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/X|X]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/Y|Y]] <span style="color: #8a94a3;">·</span> [[Dictionnaire de philosophie/Z|Z]] </div> </div> </div> <!-- Autres ouvrages --> <table style="width: 100%; border-collapse: separate; border-spacing: 14px 14px;"> <tr> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-top: 3px solid #3a5a80; border-radius: 8px; padding: 0; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="background: #e4eaf1; padding: 10px 16px; font-size: 0.94em; font-weight: 600; color: #1a2230; letter-spacing: 0.01em;">[[Manuel de terminale de philosophie|Manuel de terminale]] <span style="color: #8a94a3; font-weight: 400;">(26)</span></div> <div style="padding: 12px 16px 14px;"> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> Éthique &amp; action <span style="color: #8a94a3;">(4)</span><br/> Esprit &amp; conscience <span style="color: #8a94a3;">(4)</span><br/> Connaissance &amp; vérité <span style="color: #8a94a3;">(5)</span><br/> Monde &amp; société <span style="color: #8a94a3;">(4)</span><br/> Culture &amp; sens <span style="color: #8a94a3;">(6)</span><br/> Méthode &amp; ressources <span style="color: #8a94a3;">(3)</span> </div> </div> </td> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-top: 3px solid #3a5a80; border-radius: 8px; padding: 0; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="background: #e4eaf1; padding: 10px 16px; font-size: 0.94em; font-weight: 600; color: #1a2230; letter-spacing: 0.01em;">[[:Catégorie:Commentaire philosophique|Commentaires d'œuvres]] <span style="color: #8a94a3; font-weight: 400;">(20)</span></div> <div style="padding: 12px 16px 14px;"> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Commentaire philosophique/Lettre à Ménécée|Lettre à Ménécée]] <span style="color: #8a94a3;">·</span> Épicure<br/> [[Méditations métaphysiques|Méditations]] <span style="color: #8a94a3;">·</span> Descartes<br/> [[Commentaire de l'Éthique|Éthique]] <span style="color: #8a94a3;">·</span> Spinoza<br/> [[Commentaire philosophique/Discours sur l'origine et les fondements de l'inégalité parmi les hommes|Inégalité]] <span style="color: #8a94a3;">·</span> Rousseau<br/> [[Philosophie/Commentaire du passage à propos de l'Homme esclave du divertissement|Divertissement]] <span style="color: #8a94a3;">·</span> Pascal </div> </div> </td> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-top: 3px solid #3a5a80; border-radius: 8px; padding: 0; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="background: #e4eaf1; padding: 10px 16px; font-size: 0.94em; font-weight: 600; color: #1a2230; letter-spacing: 0.01em;">[[Philosophie/Une brève introduction|Une brève introduction]] <span style="color: #8a94a3; font-weight: 400;">(8)</span></div> <div style="padding: 12px 16px 14px;"> <div style="height: 1px; background: #e2e8ef; margin: 10px 0;"></div> <div style="font-size: 0.96em; font-weight: 600; color: #1a2230; margin-bottom: 6px;">[[:Catégorie:Vocabulaire philosophique|Vocabulaires]] <span style="color: #8a94a3; font-weight: 400;">(4)</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Pour lire Platon/Vocabulaire|Platon]]<br/> [[Philosophie/Vocabulaire/David Hume|David Hume]]<br/> [[Philosophie/Vocabulaire/Kant|Kant]] </div> </div> </td> </tr> </table> <!-- ══════════════════════════════════════════════ PIED DE PAGE ══════════════════════════════════════════════ --> <div style="margin-top: 24px; padding: 14px 0 4px; border-top: 1px solid #1a2230; font-size: 0.86em; color: #555e6b;"> <div style="margin-bottom: 6px;"><span style="font-weight: 600; color: #1a2230;">Consulter ailleurs</span> &nbsp;·&nbsp; [[w:Philosophie|Wikipédia]] &nbsp;·&nbsp; [[wikt:philosophie|Wiktionnaire]] &nbsp;·&nbsp; [[v:Philosophie|Wikiversité]] &nbsp;·&nbsp; [[s:Portail:Philosophie|Wikisource]]</div> <div><span style="font-weight: 600; color: #1a2230;">Catégories sources</span> &nbsp;·&nbsp; [[:Catégorie:Discipline philosophique|Disciplines]] &nbsp;·&nbsp; [[:Catégorie:Histoire de la philosophie|Histoire]] &nbsp;·&nbsp; [[:Catégorie:Philosophe|Philosophes]] &nbsp;·&nbsp; [[:Catégorie:Dictionnaire de philosophie (livre)|Dictionnaire]] &nbsp;·&nbsp; [[:Catégorie:Manuel de terminale de philosophie (livre)|Manuel]] &nbsp;·&nbsp; [[:Catégorie:Commentaire philosophique|Commentaires]]</div> </div> </div> [[Catégorie:Philosophie|*]] gpm82xr8tb6gn667g79hue1q95n7ccd 764827 764808 2026-04-24T11:13:52Z ~2026-25114-99 123599 764827 wikitext text/x-wiki <!-- ══════════════════════════════════════════════ EN-TÊTE ══════════════════════════════════════════════ --> <div style="padding: 6px 0 18px; border-bottom: 2px solid #1a2230; margin-bottom: 22px;"> <div style="font-size: 1.6em; font-weight: 600; color: #1a2230; line-height: 1.2; margin-bottom: 4px;">Classification de la philosophie</div> <div style="font-size: 0.9em; color: #555e6b; line-height: 1.5;">Portail raisonné des livres, manuels et dictionnaires de philosophie sur Wikilivres. Les entrées sont rangées en cinq grandes aires, subdivisées selon la taxonomie standard des champs philosophiques.</div> </div> {{PhiloRecherche}} <!-- ══════════════════════════════════════════════ CLUSTER 1 — MÉTAPHYSIQUE ET ÉPISTÉMOLOGIE ══════════════════════════════════════════════ --> <div style="margin: 26px 0 10px; padding: 6px 0 4px; border-bottom: 1px solid #1a2230;"> <div style="font-size: 1.15em; font-weight: 700; color: #1a2230;">Métaphysique et épistémologie</div> </div> <table style="width: 100%; border-collapse: separate; border-spacing: 14px 14px;"> <tr> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-top: 3px solid #3a5a80; border-radius: 8px; padding: 0; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="background: #e4eaf1; padding: 10px 16px; font-size: 0.94em; font-weight: 600; color: #1a2230; letter-spacing: 0.01em;">[[Philosophie/Théorie de la connaissance|Théorie de la connaissance]] <span style="color: #8a94a3; font-weight: 400;">(8)</span></div> <div style="padding: 12px 16px 14px;"> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Philosophie/Théorie de la connaissance/Une définition traditionnelle|Définition traditionnelle]]<br/> [[Philosophie/Théorie de la connaissance/Le Problème de Gettier|Problème de Gettier]]<br/> [[Dictionnaire de philosophie/Vérité|Vérité]]<br/> [[Dictionnaire de philosophie/Certitude|Certitude]]<br/> [[Dictionnaire de philosophie/A priori|A priori]]<br/> [[Dictionnaire de philosophie/Abduction|Abduction]]<br/> [[Dictionnaire de philosophie/Agnosticisme|Agnosticisme]] </div> </div> </td> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-top: 3px solid #3a5a80; border-radius: 8px; padding: 0; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="background: #e4eaf1; padding: 10px 16px; font-size: 0.94em; font-weight: 600; color: #1a2230; letter-spacing: 0.01em;">Métaphysique <span style="color: #8a94a3; font-weight: 400;">(12)</span></div> <div style="padding: 12px 16px 14px;"> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Dictionnaire de philosophie/Absolu|Absolu]]<br/> [[Philosophie/Acte/Puissance|Acte et Puissance]]<br/> [[Dictionnaire de philosophie/Accident|Accident]]<br/> [[Dictionnaire de philosophie/Attribut|Attribut]]<br/> [[Dictionnaire de philosophie/Atomisme|Atomisme]]<br/> [[Dictionnaire de philosophie/Déterminisme|Déterminisme]]<br/> </div> </div> </td> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-top: 3px solid #3a5a80; border-radius: 8px; padding: 0; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="background: #e4eaf1; padding: 10px 16px; font-size: 0.94em; font-weight: 600; color: #1a2230; letter-spacing: 0.01em;">[[Philosophie/Philosophie de l'esprit|Philosophie de l'esprit]] <span style="color: #8a94a3; font-weight: 400;">(14)</span></div> <div style="padding: 12px 16px 14px;"> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Philosophie/Philosophie de l'esprit/Introduction|Introduction]]<br/> [[Philosophie/Philosophie de l'esprit/Ce que Marie ne savait pas|Ce que Marie ne savait pas]]<br/> [[Dictionnaire de philosophie/Conscience|Conscience]]<br/> [[Dictionnaire de philosophie/Âme|Âme]] </div> </div> </td> </tr> <tr> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-top: 3px solid #3a5a80; border-radius: 8px; padding: 0; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="background: #e4eaf1; padding: 10px 16px; font-size: 0.94em; font-weight: 600; color: #1a2230; letter-spacing: 0.01em;">Philosophie du langage <span style="color: #8a94a3; font-weight: 400;">(3)</span></div> <div style="padding: 12px 16px 14px;"> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Manuel de terminale de philosophie/Langage|Langage]]<br/> [[Dictionnaire de philosophie/Analogie|Analogie]]<br/> [[Dictionnaire de philosophie/Argument|Argument]] </div> </div> </td> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-top: 3px solid #3a5a80; border-radius: 8px; padding: 0; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="background: #e4eaf1; padding: 10px 16px; font-size: 0.94em; font-weight: 600; color: #1a2230; letter-spacing: 0.01em;">Philosophie de l'action <span style="color: #8a94a3; font-weight: 400;">(5)</span></div> <div style="padding: 12px 16px 14px;"> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Dictionnaire de philosophie/Action|Action]]<br/> [[Dictionnaire de philosophie/Aboulie|Aboulie]]<br/> [[Dictionnaire de philosophie/Altruisme|Altruisme]]<br/> [[Dictionnaire de philosophie/Authenticité|Authenticité]]<br/> [[Philosophie/Liberté|Liberté]] </div> </div> </td> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-top: 3px solid #3a5a80; border-radius: 8px; padding: 0; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="background: #e4eaf1; padding: 10px 16px; font-size: 0.94em; font-weight: 600; color: #1a2230; letter-spacing: 0.01em;">Philosophie de la religion <span style="color: #8a94a3; font-weight: 400;">(5)</span></div> <div style="padding: 12px 16px 14px;"> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Manuel de terminale de philosophie/Religion|Religion]]<br/> [[Philosophie/Athéisme|Athéisme]]<br/> [[Philosophie/Déisme|Déisme]]<br/> [[Philosophie/Monothéisme|Monothéisme]]<br/> [[Dictionnaire de philosophie/Panthéisme|Panthéisme]] </div> </div> </td> </tr> </table> <!-- ══════════════════════════════════════════════ CLUSTER 2 — THÉORIE DE LA VALEUR ══════════════════════════════════════════════ --> <div style="margin: 22px 0 10px; padding: 6px 0 4px; border-bottom: 1px solid #1a2230;"> <div style="font-size: 1.15em; font-weight: 700; color: #1a2230;">Théorie de la valeur</div> </div> <table style="width: 100%; border-collapse: separate; border-spacing: 14px 14px;"> <tr> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-top: 3px solid #3a5a80; border-radius: 8px; padding: 0; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="background: #e4eaf1; padding: 10px 16px; font-size: 0.94em; font-weight: 600; color: #1a2230; letter-spacing: 0.01em;">Éthique normative <span style="color: #8a94a3; font-weight: 400;">(9)</span></div> <div style="padding: 12px 16px 14px;"> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Philosophie/Morale|Morale]]<br/> [[Manuel de terminale de philosophie/Devoir|Devoir]]<br/> [[Dictionnaire de philosophie/Bonheur|Bonheur]]<br/> [[Manuel de terminale de philosophie/Liberté|Liberté (terminale)]]<br/> [[Philosophie/Ataraxie|Ataraxie]]<br/> [[Dictionnaire de philosophie/Amour|Amour]]<br/> [[Dictionnaire de philosophie/Amitié|Amitié]] </div> </div> </td> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-top: 3px solid #3a5a80; border-radius: 8px; padding: 0; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="background: #e4eaf1; padding: 10px 16px; font-size: 0.94em; font-weight: 600; color: #1a2230; letter-spacing: 0.01em;">Éthique appliquée <span style="color: #8a94a3; font-weight: 400;">(3)</span></div> <div style="padding: 12px 16px 14px;"> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Dictionnaire de philosophie/Avortement|Avortement (éthique)]]<br/> [[Dictionnaire de philosophie/Animal|Animal (droits)]]<br/> [[Dictionnaire de philosophie/Anthropocentrisme|Anthropocentrisme]] </div> </div> </td> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-top: 3px solid #3a5a80; border-radius: 8px; padding: 0; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="background: #e4eaf1; padding: 10px 16px; font-size: 0.94em; font-weight: 600; color: #1a2230; letter-spacing: 0.01em;">Esthétique <span style="color: #8a94a3; font-weight: 400;">(4)</span></div> <div style="padding: 12px 16px 14px;"> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Manuel de terminale de philosophie/Art|Art]]<br/> [[Dictionnaire de philosophie/Art|Art (dictionnaire)]]<br/> [[Dictionnaire de philosophie/Art (introduction)|Art — introduction]]<br/> [[Dictionnaire de philosophie/Art et Vérité|Art et vérité]] </div> </div> </td> </tr> <tr> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-top: 3px solid #3a5a80; border-radius: 8px; padding: 0; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="background: #e4eaf1; padding: 10px 16px; font-size: 0.94em; font-weight: 600; color: #1a2230; letter-spacing: 0.01em;">Philosophie sociale et politique <span style="color: #8a94a3; font-weight: 400;">(6)</span></div> <div style="padding: 12px 16px 14px;"> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Manuel de terminale de philosophie/État|État]]<br/> [[Manuel de terminale de philosophie/Justice|Justice]]<br/> [[Manuel de terminale de philosophie/Travail|Travail]]<br/> [[Dictionnaire de philosophie/Absolutisme|Absolutisme]]<br/> [[Dictionnaire de philosophie/Anarchisme|Anarchisme]]<br/> [[Philosophie/Aliénation|Aliénation]] </div> </div> </td> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-top: 3px solid #3a5a80; border-radius: 8px; padding: 0; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="background: #e4eaf1; padding: 10px 16px; font-size: 0.94em; font-weight: 600; color: #1a2230; letter-spacing: 0.01em;">Autrui et reconnaissance <span style="color: #8a94a3; font-weight: 400;">(4)</span></div> <div style="padding: 12px 16px 14px;"> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Philosophie/Autrui|Autrui]]<br/> [[Dictionnaire de philosophie/Altérité|Altérité]]<br/> [[Dictionnaire de philosophie/Autorité|Autorité]]<br/> [[Dictionnaire de philosophie/Autonomie|Autonomie]] </div> </div> </td> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-top: 3px solid #3a5a80; border-radius: 8px; padding: 0; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="background: #e4eaf1; padding: 10px 16px; font-size: 0.94em; font-weight: 600; color: #1a2230; letter-spacing: 0.01em;">Métaéthique <span style="color: #8a94a3; font-weight: 400;">(3)</span></div> <div style="padding: 12px 16px 14px;"> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Dictionnaire de philosophie/Absurde|Absurde]]<br/> [[Dictionnaire de philosophie/Affection|Affection]]<br/> [[Dictionnaire de philosophie/Angoisse|Angoisse]] </div> </div> </td> </tr> </table> <!-- ══════════════════════════════════════════════ CLUSTER 3 — SCIENCE, LOGIQUE ET MATHÉMATIQUES ══════════════════════════════════════════════ --> <div style="margin: 22px 0 10px; padding: 6px 0 4px; border-bottom: 1px solid #1a2230;"> <div style="font-size: 1.15em; font-weight: 700; color: #1a2230;">Science, logique et mathématiques</div> </div> <table style="width: 100%; border-collapse: separate; border-spacing: 14px 14px;"> <tr> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-top: 3px solid #3a5a80; border-radius: 8px; padding: 0; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="background: #e4eaf1; padding: 10px 16px; font-size: 0.94em; font-weight: 600; color: #1a2230; letter-spacing: 0.01em;">Logique <span style="color: #8a94a3; font-weight: 400;">(6)</span></div> <div style="padding: 12px 16px 14px;"> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Dictionnaire de philosophie/A (logique)|A (logique)]]<br/> [[Dictionnaire de philosophie/Abstraction|Abstraction]]<br/> [[Dictionnaire de philosophie/Antinomie|Antinomie]]<br/> [[Philosophie/Aporie|Aporie]]<br/> [[Dictionnaire de philosophie/Axiome|Axiome]]<br/> </div> </div> </td> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-top: 3px solid #3a5a80; border-radius: 8px; padding: 0; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="background: #e4eaf1; padding: 10px 16px; font-size: 0.94em; font-weight: 600; color: #1a2230; letter-spacing: 0.01em;">Philosophie des sciences <span style="color: #8a94a3; font-weight: 400;">(3)</span></div> <div style="padding: 12px 16px 14px;"> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Philosophie/Théorie et expérience|Théorie et expérience]]<br/> [[Manuel de terminale de philosophie/Science|Science]]<br/> [[Manuel de terminale de philosophie/Technique|Technique]] </div> </div> </td> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-top: 3px solid #3a5a80; border-radius: 8px; padding: 0; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="background: #e4eaf1; padding: 10px 16px; font-size: 0.94em; font-weight: 600; color: #1a2230; letter-spacing: 0.01em;">Philosophie du vivant <span style="color: #8a94a3; font-weight: 400;">(3)</span></div> <div style="padding: 12px 16px 14px;"> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Manuel de terminale de philosophie/Nature|Nature]]<br/> [[Manuel de terminale de philosophie/Vivant|Vivant]]<br/> [[Dictionnaire de philosophie/Animal|Animal]] </div> </div> </td> </tr> </table> <!-- ══════════════════════════════════════════════ CLUSTER 4 — HISTOIRE DE LA PHILOSOPHIE ══════════════════════════════════════════════ --> <div style="margin: 22px 0 10px; padding: 6px 0 4px; border-bottom: 1px solid #1a2230;"> <div style="font-size: 1.15em; font-weight: 700; color: #1a2230;">[[Philosophie/Histoire de la philosophie|Histoire de la philosophie]] <span style="color: #8a94a3; font-weight: 400; font-size: 0.92em;">(30)</span></div> </div> <table style="width: 100%; border-collapse: separate; border-spacing: 14px 14px;"> <tr> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-top: 3px solid #3a5a80; border-radius: 8px; padding: 0; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="background: #e4eaf1; padding: 10px 16px; font-size: 0.94em; font-weight: 600; color: #1a2230; letter-spacing: 0.01em;">Antiquité <span style="color: #8a94a3; font-weight: 400;">(14)</span></div> <div style="padding: 10px 16px 14px;"> <div style="font-size: 0.82em; color: #8a94a3; margin-bottom: 6px;">VI<sup>e</sup>&nbsp;av.&nbsp;— V<sup>e</sup>&nbsp;siècle</div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> <span style="font-weight: 500;">Présocratiques</span><br/> <span style="padding-left: 10px;">[[Philosophie/Thalès de Milet|Thalès de Milet]]</span><br/> <span style="padding-left: 10px;">[[Philosophie/Anaximandre de Milet|Anaximandre]]</span><br/> <span style="padding-left: 10px;">[[Dictionnaire de philosophie/Anaxagore|Anaxagore]]</span><br/> <span style="padding-left: 10px;">[[Dictionnaire de philosophie/Empédocle|Empédocle]]</span><br/> <span style="padding-left: 10px;">[[Philosophie/Présocratiques/Liste des Présocratiques|Liste complète]]</span><br/> [[Pour lire Platon|Platon]]<br/> [[Dictionnaire de philosophie/Aristote|Aristote]]<br/> <span style="font-weight: 500;">Philosophie hellénistique</span><br/> <span style="padding-left: 10px;">[[Commentaire philosophique/Lettre à Ménécée|Épicure]]</span><br/> <span style="padding-left: 10px;">[[Philosophie/Ataraxie|Stoïcisme]]</span> </div> </div> </td> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-top: 3px solid #3a5a80; border-radius: 8px; padding: 0; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="background: #e4eaf1; padding: 10px 16px; font-size: 0.94em; font-weight: 600; color: #1a2230; letter-spacing: 0.01em;">Philosophie médiévale <span style="color: #8a94a3; font-weight: 400;">(0)</span></div> <div style="padding: 10px 16px 14px;"> <div style="font-size: 0.82em; color: #8a94a3; margin-bottom: 6px;">V<sup>e</sup>&nbsp;— XV<sup>e</sup>&nbsp;siècle</div> <div style="font-size: 0.88em; color: #8a94a3; line-height: 1.75; font-style: italic;"> Augustin, Anselme, Thomas d'Aquin, Averroès — pages à rédiger. </div> </div> </td> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-top: 3px solid #3a5a80; border-radius: 8px; padding: 0; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="background: #e4eaf1; padding: 10px 16px; font-size: 0.94em; font-weight: 600; color: #1a2230; letter-spacing: 0.01em;">Philosophie classique <span style="color: #8a94a3; font-weight: 400;">(6)</span></div> <div style="padding: 10px 16px 14px;"> <div style="font-size: 0.82em; color: #8a94a3; margin-bottom: 6px;">XVI<sup>e</sup>&nbsp;— XVIII<sup>e</sup>&nbsp;siècle</div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Méditations métaphysiques|Descartes — Méditations]]<br/> [[Philosophie/Commentaire du passage à propos de l'Homme esclave du divertissement|Pascal — Divertissement]]<br/> [[Commentaire de l'Éthique|Spinoza — Éthique]]<br/> [[Philosophie/Vocabulaire/David Hume|Hume]]<br/> [[Philosophie/Vocabulaire/Kant|Kant]]<br/> [[Commentaire philosophique/Discours sur l'origine et les fondements de l'inégalité parmi les hommes|Rousseau — Inégalité]] </div> </div> </td> </tr> <tr> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-top: 3px solid #3a5a80; border-radius: 8px; padding: 0; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="background: #e4eaf1; padding: 10px 16px; font-size: 0.94em; font-weight: 600; color: #1a2230; letter-spacing: 0.01em;">Philosophie contemporaine <span style="color: #8a94a3; font-weight: 400;">(5)</span></div> <div style="padding: 10px 16px 14px;"> <div style="font-size: 0.82em; color: #8a94a3; margin-bottom: 6px;">XIX<sup>e</sup>&nbsp;— XXI<sup>e</sup>&nbsp;siècle</div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Philosophie/Nietzsche|Nietzsche]]<br/> <span style="padding-left: 10px;">[[Philosophie/Nietzsche/La culture grecque|Culture grecque]]</span><br/> <span style="padding-left: 10px;">[[Philosophie/Nietzsche/La culture moderne|Culture moderne]]</span><br/> <span style="padding-left: 10px;">[[Philosophie/Nietzsche/La métaphysique|Métaphysique]]</span><br/> <span style="padding-left: 10px;">[[Philosophie/Nietzsche/La moralité des mœurs|Moralité des mœurs]]</span> </div> </div> </td> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-top: 3px solid #3a5a80; border-radius: 8px; padding: 0; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="background: #e4eaf1; padding: 10px 16px; font-size: 0.94em; font-weight: 600; color: #1a2230; letter-spacing: 0.01em;">Courants du XX<sup>e</sup> siècle <span style="color: #8a94a3; font-weight: 400;">(3)</span></div> <div style="padding: 10px 16px 14px;"> <div style="font-size: 0.82em; color: #8a94a3; margin-bottom: 6px;">Écoles et traditions</div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Philosophie/Philosophie analytique|Philosophie analytique]]<br/> [[Philosophie/Existence et temps|Existentialisme]]<br/> [[Dictionnaire de philosophie/Dasein|Phénoménologie / Dasein]] </div> </div> </td> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-top: 3px solid #3a5a80; border-radius: 8px; padding: 0; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="background: #e4eaf1; padding: 10px 16px; font-size: 0.94em; font-weight: 600; color: #1a2230; letter-spacing: 0.01em;">Traditions non occidentales <span style="color: #8a94a3; font-weight: 400;">(2)</span></div> <div style="padding: 10px 16px 14px;"> <div style="font-size: 0.82em; color: #8a94a3; margin-bottom: 6px;">Afrique, Amériques</div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Dictionnaire de philosophie/Philosophie africaine|Philosophie africaine]]<br/> [[Dictionnaire de philosophie/Argentine (Philosophie)|Argentine — XX<sup>e</sup> siècle]] </div> </div> </td> </tr> </table> <!-- ══════════════════════════════════════════════ CLUSTER 5 — OUVRAGES DE RÉFÉRENCE ══════════════════════════════════════════════ --> <div style="margin: 22px 0 10px; padding: 6px 0 4px; border-bottom: 1px solid #1a2230;"> <div style="font-size: 1.15em; font-weight: 700; color: #1a2230;">Ouvrages de référence et pédagogiques</div> </div> <!-- Dictionnaire — pleine largeur --> <div style="background: #fbfcfd; border: 1px solid #d4dae2; border-top: 3px solid #3a5a80; border-radius: 8px; padding: 0; margin-bottom: 14px; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="background: #e4eaf1; padding: 10px 16px; font-size: 0.94em; font-weight: 600; color: #1a2230; letter-spacing: 0.01em;">[[Dictionnaire de philosophie|Dictionnaire de philosophie]] <span style="color: #8a94a3; font-weight: 400;">(35)</span></div> <div style="padding: 14px 14px 16px;"> <table style="width: 100%; border-collapse: separate; border-spacing: 5px 5px;"> <tr> <td style="width: 7.69%; text-align: center; background: #eef2f7; border: 1px solid #dbe2eb; border-radius: 4px; padding: 9px 0; font-size: 1.08em; font-weight: 500;">[[Dictionnaire de philosophie/A|A]]</td> <td style="width: 7.69%; text-align: center; background: #eef2f7; border: 1px solid #dbe2eb; border-radius: 4px; padding: 9px 0; font-size: 1.08em; font-weight: 500;">[[Dictionnaire de philosophie/B|B]]</td> <td style="width: 7.69%; text-align: center; background: #eef2f7; border: 1px solid #dbe2eb; border-radius: 4px; padding: 9px 0; font-size: 1.08em; font-weight: 500;">[[Dictionnaire de philosophie/C|C]]</td> <td style="width: 7.69%; text-align: center; background: #eef2f7; border: 1px solid #dbe2eb; border-radius: 4px; padding: 9px 0; font-size: 1.08em; font-weight: 500;">[[Dictionnaire de philosophie/D|D]]</td> <td style="width: 7.69%; text-align: center; background: #eef2f7; border: 1px solid #dbe2eb; border-radius: 4px; padding: 9px 0; font-size: 1.08em; font-weight: 500;">[[Dictionnaire de philosophie/E|E]]</td> <td style="width: 7.69%; text-align: center; background: #eef2f7; border: 1px solid #dbe2eb; border-radius: 4px; padding: 9px 0; font-size: 1.08em; font-weight: 500;">[[Dictionnaire de philosophie/F|F]]</td> <td style="width: 7.69%; text-align: center; background: #eef2f7; border: 1px solid #dbe2eb; border-radius: 4px; padding: 9px 0; font-size: 1.08em; font-weight: 500;">[[Dictionnaire de philosophie/G|G]]</td> <td style="width: 7.69%; text-align: center; background: #eef2f7; border: 1px solid #dbe2eb; border-radius: 4px; padding: 9px 0; font-size: 1.08em; font-weight: 500;">[[Dictionnaire de philosophie/H|H]]</td> <td style="width: 7.69%; text-align: center; background: #eef2f7; border: 1px solid #dbe2eb; border-radius: 4px; padding: 9px 0; font-size: 1.08em; font-weight: 500;">[[Dictionnaire de philosophie/I|I]]</td> <td style="width: 7.69%; text-align: center; background: #eef2f7; border: 1px solid #dbe2eb; border-radius: 4px; padding: 9px 0; font-size: 1.08em; font-weight: 500;">[[Dictionnaire de philosophie/J|J]]</td> <td style="width: 7.69%; text-align: center; background: #eef2f7; border: 1px solid #dbe2eb; border-radius: 4px; padding: 9px 0; font-size: 1.08em; font-weight: 500;">[[Dictionnaire de philosophie/K|K]]</td> <td style="width: 7.69%; text-align: center; background: #eef2f7; border: 1px solid #dbe2eb; border-radius: 4px; padding: 9px 0; font-size: 1.08em; font-weight: 500;">[[Dictionnaire de philosophie/L|L]]</td> <td style="width: 7.69%; text-align: center; background: #eef2f7; border: 1px solid #dbe2eb; border-radius: 4px; padding: 9px 0; font-size: 1.08em; font-weight: 500;">[[Dictionnaire de philosophie/M|M]]</td> </tr> <tr> <td style="width: 7.69%; text-align: center; background: #eef2f7; border: 1px solid #dbe2eb; border-radius: 4px; padding: 9px 0; font-size: 1.08em; font-weight: 500;">[[Dictionnaire de philosophie/N|N]]</td> <td style="width: 7.69%; text-align: center; background: #eef2f7; border: 1px solid #dbe2eb; border-radius: 4px; padding: 9px 0; font-size: 1.08em; font-weight: 500;">[[Dictionnaire de philosophie/O|O]]</td> <td style="width: 7.69%; text-align: center; background: #eef2f7; border: 1px solid #dbe2eb; border-radius: 4px; padding: 9px 0; font-size: 1.08em; font-weight: 500;">[[Dictionnaire de philosophie/P|P]]</td> <td style="width: 7.69%; text-align: center; background: #eef2f7; border: 1px solid #dbe2eb; border-radius: 4px; padding: 9px 0; font-size: 1.08em; font-weight: 500;">[[Dictionnaire de philosophie/Q|Q]]</td> <td style="width: 7.69%; text-align: center; background: #eef2f7; border: 1px solid #dbe2eb; border-radius: 4px; padding: 9px 0; font-size: 1.08em; font-weight: 500;">[[Dictionnaire de philosophie/R|R]]</td> <td style="width: 7.69%; text-align: center; background: #eef2f7; border: 1px solid #dbe2eb; border-radius: 4px; padding: 9px 0; font-size: 1.08em; font-weight: 500;">[[Dictionnaire de philosophie/S|S]]</td> <td style="width: 7.69%; text-align: center; background: #eef2f7; border: 1px solid #dbe2eb; border-radius: 4px; padding: 9px 0; font-size: 1.08em; font-weight: 500;">[[Dictionnaire de philosophie/T|T]]</td> <td style="width: 7.69%; text-align: center; background: #eef2f7; border: 1px solid #dbe2eb; border-radius: 4px; padding: 9px 0; font-size: 1.08em; font-weight: 500;">[[Dictionnaire de philosophie/U|U]]</td> <td style="width: 7.69%; text-align: center; background: #eef2f7; border: 1px solid #dbe2eb; border-radius: 4px; padding: 9px 0; font-size: 1.08em; font-weight: 500;">[[Dictionnaire de philosophie/V|V]]</td> <td style="width: 7.69%; text-align: center; background: #eef2f7; border: 1px solid #dbe2eb; border-radius: 4px; padding: 9px 0; font-size: 1.08em; font-weight: 500;">[[Dictionnaire de philosophie/W|W]]</td> <td style="width: 7.69%; text-align: center; background: #eef2f7; border: 1px solid #dbe2eb; border-radius: 4px; padding: 9px 0; font-size: 1.08em; font-weight: 500;">[[Dictionnaire de philosophie/X|X]]</td> <td style="width: 7.69%; text-align: center; background: #eef2f7; border: 1px solid #dbe2eb; border-radius: 4px; padding: 9px 0; font-size: 1.08em; font-weight: 500;">[[Dictionnaire de philosophie/Y|Y]]</td> <td style="width: 7.69%; text-align: center; background: #eef2f7; border: 1px solid #dbe2eb; border-radius: 4px; padding: 9px 0; font-size: 1.08em; font-weight: 500;">[[Dictionnaire de philosophie/Z|Z]]</td> </tr> </table> </div> </div> <!-- Autres ouvrages --> <table style="width: 100%; border-collapse: separate; border-spacing: 14px 14px;"> <tr> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-top: 3px solid #3a5a80; border-radius: 8px; padding: 0; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="background: #e4eaf1; padding: 10px 16px; font-size: 0.94em; font-weight: 600; color: #1a2230; letter-spacing: 0.01em;">[[Manuel de terminale de philosophie|Manuel de terminale]] <span style="color: #8a94a3; font-weight: 400;">(26)</span></div> <div style="padding: 12px 16px 14px;"> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> Éthique &amp; action <span style="color: #8a94a3;">(4)</span><br/> Esprit &amp; conscience <span style="color: #8a94a3;">(4)</span><br/> Connaissance &amp; vérité <span style="color: #8a94a3;">(5)</span><br/> Monde &amp; société <span style="color: #8a94a3;">(4)</span><br/> Culture &amp; sens <span style="color: #8a94a3;">(6)</span><br/> Méthode &amp; ressources <span style="color: #8a94a3;">(3)</span> </div> </div> </td> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-top: 3px solid #3a5a80; border-radius: 8px; padding: 0; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="background: #e4eaf1; padding: 10px 16px; font-size: 0.94em; font-weight: 600; color: #1a2230; letter-spacing: 0.01em;">[[:Catégorie:Commentaire philosophique|Commentaires d'œuvres]] <span style="color: #8a94a3; font-weight: 400;">(20)</span></div> <div style="padding: 12px 16px 14px;"> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Commentaire philosophique/Lettre à Ménécée|Lettre à Ménécée]] <span style="color: #8a94a3;">·</span> Épicure<br/> [[Méditations métaphysiques|Méditations]] <span style="color: #8a94a3;">·</span> Descartes<br/> [[Commentaire de l'Éthique|Éthique]] <span style="color: #8a94a3;">·</span> Spinoza<br/> [[Commentaire philosophique/Discours sur l'origine et les fondements de l'inégalité parmi les hommes|Inégalité]] <span style="color: #8a94a3;">·</span> Rousseau<br/> [[Philosophie/Commentaire du passage à propos de l'Homme esclave du divertissement|Divertissement]] <span style="color: #8a94a3;">·</span> Pascal </div> </div> </td> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-top: 3px solid #3a5a80; border-radius: 8px; padding: 0; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="background: #e4eaf1; padding: 10px 16px; font-size: 0.94em; font-weight: 600; color: #1a2230; letter-spacing: 0.01em;">[[Philosophie/Une brève introduction|Une brève introduction]] <span style="color: #8a94a3; font-weight: 400;">(8)</span></div> <div style="padding: 12px 16px 14px;"> <div style="height: 1px; background: #e2e8ef; margin: 10px 0;"></div> <div style="font-size: 0.96em; font-weight: 600; color: #1a2230; margin-bottom: 6px;">[[:Catégorie:Vocabulaire philosophique|Vocabulaires]] <span style="color: #8a94a3; font-weight: 400;">(4)</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Pour lire Platon/Vocabulaire|Platon]]<br/> [[Philosophie/Vocabulaire/David Hume|David Hume]]<br/> [[Philosophie/Vocabulaire/Kant|Kant]] </div> </div> </td> </tr> </table> <!-- ══════════════════════════════════════════════ PIED DE PAGE ══════════════════════════════════════════════ --> <div style="margin-top: 24px; padding: 14px 0 4px; border-top: 1px solid #1a2230; font-size: 0.86em; color: #555e6b;"> <div style="margin-bottom: 6px;"><span style="font-weight: 600; color: #1a2230;">Consulter ailleurs</span> &nbsp;·&nbsp; [[w:Philosophie|Wikipédia]] &nbsp;·&nbsp; [[wikt:philosophie|Wiktionnaire]] &nbsp;·&nbsp; [[v:Philosophie|Wikiversité]] &nbsp;·&nbsp; [[s:Portail:Philosophie|Wikisource]]</div> <div><span style="font-weight: 600; color: #1a2230;">Catégories sources</span> &nbsp;·&nbsp; [[:Catégorie:Discipline philosophique|Disciplines]] &nbsp;·&nbsp; [[:Catégorie:Histoire de la philosophie|Histoire]] &nbsp;·&nbsp; [[:Catégorie:Philosophe|Philosophes]] &nbsp;·&nbsp; [[:Catégorie:Dictionnaire de philosophie (livre)|Dictionnaire]] &nbsp;·&nbsp; [[:Catégorie:Manuel de terminale de philosophie (livre)|Manuel]] &nbsp;·&nbsp; [[:Catégorie:Commentaire philosophique|Commentaires]]</div> </div> </div> [[Catégorie:Philosophie|*]] 9pyarequnk5bi6sb3wp311bkse36ad2 764828 764827 2026-04-24T11:32:43Z ~2026-25114-99 123599 764828 wikitext text/x-wiki <!-- ══════════════════════════════════════════════ EN-TÊTE ══════════════════════════════════════════════ --> <div style="padding: 6px 0 18px; border-bottom: 2px solid #1a2230; margin-bottom: 22px;"> <div style="font-size: 1.6em; font-weight: 600; color: #1a2230; line-height: 1.2; margin-bottom: 4px;">Classification de la philosophie</div> <div style="font-size: 0.9em; color: #555e6b; line-height: 1.5;">Portail raisonné des livres, manuels et dictionnaires de philosophie sur Wikilivres. Les entrées sont rangées en cinq grandes aires, subdivisées selon la taxonomie standard des champs philosophiques.</div> </div> {{PhiloRecherche}} <!-- ══════════════════════════════════════════════ CLUSTER 1 — MÉTAPHYSIQUE ET ÉPISTÉMOLOGIE ══════════════════════════════════════════════ --> <div style="margin: 26px 0 10px; padding: 6px 0 4px; border-bottom: 1px solid #1a2230;"> <div style="font-size: 1.15em; font-weight: 700; color: #1a2230;">Métaphysique et épistémologie</div> </div> <table style="width: 100%; border-collapse: separate; border-spacing: 14px 14px;"> <tr> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-left: 4px solid #3a5a80; border-radius: 0 8px 8px 0; padding: 12px 16px 14px; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="font-size: 0.96em; font-weight: 600; color: #1a2230; padding-bottom: 8px; border-bottom: 1px solid #dfe4eb; margin-bottom: 10px;">[[Philosophie/Théorie de la connaissance|Théorie de la connaissance]] <span style="color: #8a94a3; font-weight: 400;">(8)</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Philosophie/Théorie de la connaissance/Une définition traditionnelle|Définition traditionnelle]]<br/> [[Philosophie/Théorie de la connaissance/Le Problème de Gettier|Problème de Gettier]]<br/> [[Dictionnaire de philosophie/Vérité|Vérité]]<br/> [[Dictionnaire de philosophie/Certitude|Certitude]]<br/> [[Dictionnaire de philosophie/A priori|A priori]]<br/> [[Dictionnaire de philosophie/Abduction|Abduction]]<br/> [[Dictionnaire de philosophie/Agnosticisme|Agnosticisme]] </div> </td> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-left: 4px solid #3a5a80; border-radius: 0 8px 8px 0; padding: 12px 16px 14px; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="font-size: 0.96em; font-weight: 600; color: #1a2230; padding-bottom: 8px; border-bottom: 1px solid #dfe4eb; margin-bottom: 10px;">Métaphysique <span style="color: #8a94a3; font-weight: 400;">(12)</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Dictionnaire de philosophie/Absolu|Absolu]]<br/> [[Philosophie/Acte/Puissance|Acte et Puissance]]<br/> [[Dictionnaire de philosophie/Accident|Accident]]<br/> [[Dictionnaire de philosophie/Attribut|Attribut]]<br/> [[Dictionnaire de philosophie/Atomisme|Atomisme]]<br/> [[Dictionnaire de philosophie/Déterminisme|Déterminisme]]<br/> </div> </td> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-left: 4px solid #3a5a80; border-radius: 0 8px 8px 0; padding: 12px 16px 14px; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="font-size: 0.96em; font-weight: 600; color: #1a2230; padding-bottom: 8px; border-bottom: 1px solid #dfe4eb; margin-bottom: 10px;">[[Philosophie/Philosophie de l'esprit|Philosophie de l'esprit]] <span style="color: #8a94a3; font-weight: 400;">(14)</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Philosophie/Philosophie de l'esprit/Introduction|Introduction]]<br/> [[Philosophie/Philosophie de l'esprit/Ce que Marie ne savait pas|Ce que Marie ne savait pas]]<br/> [[Dictionnaire de philosophie/Conscience|Conscience]]<br/> [[Dictionnaire de philosophie/Âme|Âme]] </div> </td> </tr> <tr> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-left: 4px solid #3a5a80; border-radius: 0 8px 8px 0; padding: 12px 16px 14px; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="font-size: 0.96em; font-weight: 600; color: #1a2230; padding-bottom: 8px; border-bottom: 1px solid #dfe4eb; margin-bottom: 10px;">Philosophie du langage <span style="color: #8a94a3; font-weight: 400;">(3)</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Manuel de terminale de philosophie/Langage|Langage]]<br/> [[Dictionnaire de philosophie/Analogie|Analogie]]<br/> [[Dictionnaire de philosophie/Argument|Argument]] </div> </td> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-left: 4px solid #3a5a80; border-radius: 0 8px 8px 0; padding: 12px 16px 14px; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="font-size: 0.96em; font-weight: 600; color: #1a2230; padding-bottom: 8px; border-bottom: 1px solid #dfe4eb; margin-bottom: 10px;">Philosophie de l'action <span style="color: #8a94a3; font-weight: 400;">(5)</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Dictionnaire de philosophie/Action|Action]]<br/> [[Dictionnaire de philosophie/Aboulie|Aboulie]]<br/> [[Dictionnaire de philosophie/Altruisme|Altruisme]]<br/> [[Dictionnaire de philosophie/Authenticité|Authenticité]]<br/> [[Philosophie/Liberté|Liberté]] </div> </td> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-left: 4px solid #3a5a80; border-radius: 0 8px 8px 0; padding: 12px 16px 14px; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="font-size: 0.96em; font-weight: 600; color: #1a2230; padding-bottom: 8px; border-bottom: 1px solid #dfe4eb; margin-bottom: 10px;">Philosophie de la religion <span style="color: #8a94a3; font-weight: 400;">(5)</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Manuel de terminale de philosophie/Religion|Religion]]<br/> [[Philosophie/Athéisme|Athéisme]]<br/> [[Philosophie/Déisme|Déisme]]<br/> [[Philosophie/Monothéisme|Monothéisme]]<br/> [[Dictionnaire de philosophie/Panthéisme|Panthéisme]] </div> </td> </tr> </table> <!-- ══════════════════════════════════════════════ CLUSTER 2 — THÉORIE DE LA VALEUR ══════════════════════════════════════════════ --> <div style="margin: 22px 0 10px; padding: 6px 0 4px; border-bottom: 1px solid #1a2230;"> <div style="font-size: 1.15em; font-weight: 700; color: #1a2230;">Théorie de la valeur</div> </div> <table style="width: 100%; border-collapse: separate; border-spacing: 14px 14px;"> <tr> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-left: 4px solid #3a5a80; border-radius: 0 8px 8px 0; padding: 12px 16px 14px; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="font-size: 0.96em; font-weight: 600; color: #1a2230; padding-bottom: 8px; border-bottom: 1px solid #dfe4eb; margin-bottom: 10px;">Éthique normative <span style="color: #8a94a3; font-weight: 400;">(9)</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Philosophie/Morale|Morale]]<br/> [[Manuel de terminale de philosophie/Devoir|Devoir]]<br/> [[Dictionnaire de philosophie/Bonheur|Bonheur]]<br/> [[Manuel de terminale de philosophie/Liberté|Liberté (terminale)]]<br/> [[Philosophie/Ataraxie|Ataraxie]]<br/> [[Dictionnaire de philosophie/Amour|Amour]]<br/> [[Dictionnaire de philosophie/Amitié|Amitié]] </div> </td> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-left: 4px solid #3a5a80; border-radius: 0 8px 8px 0; padding: 12px 16px 14px; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="font-size: 0.96em; font-weight: 600; color: #1a2230; padding-bottom: 8px; border-bottom: 1px solid #dfe4eb; margin-bottom: 10px;">Éthique appliquée <span style="color: #8a94a3; font-weight: 400;">(3)</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Dictionnaire de philosophie/Avortement|Avortement (éthique)]]<br/> [[Dictionnaire de philosophie/Animal|Animal (droits)]]<br/> [[Dictionnaire de philosophie/Anthropocentrisme|Anthropocentrisme]] </div> </td> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-left: 4px solid #3a5a80; border-radius: 0 8px 8px 0; padding: 12px 16px 14px; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="font-size: 0.96em; font-weight: 600; color: #1a2230; padding-bottom: 8px; border-bottom: 1px solid #dfe4eb; margin-bottom: 10px;">Esthétique <span style="color: #8a94a3; font-weight: 400;">(4)</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Manuel de terminale de philosophie/Art|Art]]<br/> [[Dictionnaire de philosophie/Art|Art (dictionnaire)]]<br/> [[Dictionnaire de philosophie/Art (introduction)|Art — introduction]]<br/> [[Dictionnaire de philosophie/Art et Vérité|Art et vérité]] </div> </td> </tr> <tr> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-left: 4px solid #3a5a80; border-radius: 0 8px 8px 0; padding: 12px 16px 14px; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="font-size: 0.96em; font-weight: 600; color: #1a2230; padding-bottom: 8px; border-bottom: 1px solid #dfe4eb; margin-bottom: 10px;">Philosophie sociale et politique <span style="color: #8a94a3; font-weight: 400;">(6)</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Manuel de terminale de philosophie/État|État]]<br/> [[Manuel de terminale de philosophie/Justice|Justice]]<br/> [[Manuel de terminale de philosophie/Travail|Travail]]<br/> [[Dictionnaire de philosophie/Absolutisme|Absolutisme]]<br/> [[Dictionnaire de philosophie/Anarchisme|Anarchisme]]<br/> [[Philosophie/Aliénation|Aliénation]] </div> </td> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-left: 4px solid #3a5a80; border-radius: 0 8px 8px 0; padding: 12px 16px 14px; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="font-size: 0.96em; font-weight: 600; color: #1a2230; padding-bottom: 8px; border-bottom: 1px solid #dfe4eb; margin-bottom: 10px;">Autrui et reconnaissance <span style="color: #8a94a3; font-weight: 400;">(4)</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Philosophie/Autrui|Autrui]]<br/> [[Dictionnaire de philosophie/Altérité|Altérité]]<br/> [[Dictionnaire de philosophie/Autorité|Autorité]]<br/> [[Dictionnaire de philosophie/Autonomie|Autonomie]] </div> </td> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-left: 4px solid #3a5a80; border-radius: 0 8px 8px 0; padding: 12px 16px 14px; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="font-size: 0.96em; font-weight: 600; color: #1a2230; padding-bottom: 8px; border-bottom: 1px solid #dfe4eb; margin-bottom: 10px;">Métaéthique <span style="color: #8a94a3; font-weight: 400;">(3)</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Dictionnaire de philosophie/Absurde|Absurde]]<br/> [[Dictionnaire de philosophie/Affection|Affection]]<br/> [[Dictionnaire de philosophie/Angoisse|Angoisse]] </div> </td> </tr> </table> <!-- ══════════════════════════════════════════════ CLUSTER 3 — SCIENCE, LOGIQUE ET MATHÉMATIQUES ══════════════════════════════════════════════ --> <div style="margin: 22px 0 10px; padding: 6px 0 4px; border-bottom: 1px solid #1a2230;"> <div style="font-size: 1.15em; font-weight: 700; color: #1a2230;">Science, logique et mathématiques</div> </div> <table style="width: 100%; border-collapse: separate; border-spacing: 14px 14px;"> <tr> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-left: 4px solid #3a5a80; border-radius: 0 8px 8px 0; padding: 12px 16px 14px; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="font-size: 0.96em; font-weight: 600; color: #1a2230; padding-bottom: 8px; border-bottom: 1px solid #dfe4eb; margin-bottom: 10px;">Logique <span style="color: #8a94a3; font-weight: 400;">(6)</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Dictionnaire de philosophie/A (logique)|A (logique)]]<br/> [[Dictionnaire de philosophie/Abstraction|Abstraction]]<br/> [[Dictionnaire de philosophie/Antinomie|Antinomie]]<br/> [[Philosophie/Aporie|Aporie]]<br/> [[Dictionnaire de philosophie/Axiome|Axiome]]<br/> </div> </td> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-left: 4px solid #3a5a80; border-radius: 0 8px 8px 0; padding: 12px 16px 14px; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="font-size: 0.96em; font-weight: 600; color: #1a2230; padding-bottom: 8px; border-bottom: 1px solid #dfe4eb; margin-bottom: 10px;">Philosophie des sciences <span style="color: #8a94a3; font-weight: 400;">(3)</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Philosophie/Théorie et expérience|Théorie et expérience]]<br/> [[Manuel de terminale de philosophie/Science|Science]]<br/> [[Manuel de terminale de philosophie/Technique|Technique]] </div> </td> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-left: 4px solid #3a5a80; border-radius: 0 8px 8px 0; padding: 12px 16px 14px; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="font-size: 0.96em; font-weight: 600; color: #1a2230; padding-bottom: 8px; border-bottom: 1px solid #dfe4eb; margin-bottom: 10px;">Philosophie du vivant <span style="color: #8a94a3; font-weight: 400;">(3)</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Manuel de terminale de philosophie/Nature|Nature]]<br/> [[Manuel de terminale de philosophie/Vivant|Vivant]]<br/> [[Dictionnaire de philosophie/Animal|Animal]] </div> </td> </tr> </table> <!-- ══════════════════════════════════════════════ CLUSTER 4 — HISTOIRE DE LA PHILOSOPHIE ══════════════════════════════════════════════ --> <div style="margin: 22px 0 10px; padding: 6px 0 4px; border-bottom: 1px solid #1a2230;"> <div style="font-size: 1.15em; font-weight: 700; color: #1a2230;">[[Philosophie/Histoire de la philosophie|Histoire de la philosophie]] <span style="color: #8a94a3; font-weight: 400; font-size: 0.92em;">(30)</span></div> </div> <table style="width: 100%; border-collapse: separate; border-spacing: 14px 14px;"> <tr> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-left: 4px solid #3a5a80; border-radius: 0 8px 8px 0; padding: 12px 16px 14px; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="font-size: 0.96em; font-weight: 600; color: #1a2230; padding-bottom: 8px; border-bottom: 1px solid #dfe4eb; margin-bottom: 10px;">Antiquité <span style="color: #8a94a3; font-weight: 400;">(14)</span></div> <div style="font-size: 0.82em; color: #8a94a3; margin-bottom: 6px;">VI<sup>e</sup>&nbsp;av.&nbsp;— V<sup>e</sup>&nbsp;siècle</div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> <span style="font-weight: 500;">Présocratiques</span><br/> <span style="padding-left: 10px;">[[Philosophie/Thalès de Milet|Thalès de Milet]]</span><br/> <span style="padding-left: 10px;">[[Philosophie/Anaximandre de Milet|Anaximandre]]</span><br/> <span style="padding-left: 10px;">[[Dictionnaire de philosophie/Anaxagore|Anaxagore]]</span><br/> <span style="padding-left: 10px;">[[Dictionnaire de philosophie/Empédocle|Empédocle]]</span><br/> <span style="padding-left: 10px;">[[Philosophie/Présocratiques/Liste des Présocratiques|Liste complète]]</span><br/> [[Pour lire Platon|Platon]]<br/> [[Dictionnaire de philosophie/Aristote|Aristote]]<br/> <span style="font-weight: 500;">Philosophie hellénistique</span><br/> <span style="padding-left: 10px;">[[Commentaire philosophique/Lettre à Ménécée|Épicure]]</span><br/> <span style="padding-left: 10px;">[[Philosophie/Ataraxie|Stoïcisme]]</span> </div> </td> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-left: 4px solid #3a5a80; border-radius: 0 8px 8px 0; padding: 12px 16px 14px; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="font-size: 0.96em; font-weight: 600; color: #1a2230; padding-bottom: 8px; border-bottom: 1px solid #dfe4eb; margin-bottom: 10px;">Philosophie médiévale <span style="color: #8a94a3; font-weight: 400;">(0)</span></div> <div style="font-size: 0.82em; color: #8a94a3; margin-bottom: 6px;">V<sup>e</sup>&nbsp;— XV<sup>e</sup>&nbsp;siècle</div> <div style="font-size: 0.88em; color: #8a94a3; line-height: 1.75; font-style: italic;"> Augustin, Anselme, Thomas d'Aquin, Averroès — pages à rédiger. </div> </td> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-left: 4px solid #3a5a80; border-radius: 0 8px 8px 0; padding: 12px 16px 14px; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="font-size: 0.96em; font-weight: 600; color: #1a2230; padding-bottom: 8px; border-bottom: 1px solid #dfe4eb; margin-bottom: 10px;">Philosophie classique <span style="color: #8a94a3; font-weight: 400;">(6)</span></div> <div style="font-size: 0.82em; color: #8a94a3; margin-bottom: 6px;">XVI<sup>e</sup>&nbsp;— XVIII<sup>e</sup>&nbsp;siècle</div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Méditations métaphysiques|Descartes — Méditations]]<br/> [[Philosophie/Commentaire du passage à propos de l'Homme esclave du divertissement|Pascal — Divertissement]]<br/> [[Commentaire de l'Éthique|Spinoza — Éthique]]<br/> [[Philosophie/Vocabulaire/David Hume|Hume]]<br/> [[Philosophie/Vocabulaire/Kant|Kant]]<br/> [[Commentaire philosophique/Discours sur l'origine et les fondements de l'inégalité parmi les hommes|Rousseau — Inégalité]] </div> </td> </tr> <tr> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-left: 4px solid #3a5a80; border-radius: 0 8px 8px 0; padding: 12px 16px 14px; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="font-size: 0.96em; font-weight: 600; color: #1a2230; padding-bottom: 8px; border-bottom: 1px solid #dfe4eb; margin-bottom: 10px;">Philosophie contemporaine <span style="color: #8a94a3; font-weight: 400;">(5)</span></div> <div style="font-size: 0.82em; color: #8a94a3; margin-bottom: 6px;">XIX<sup>e</sup>&nbsp;— XXI<sup>e</sup>&nbsp;siècle</div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Philosophie/Nietzsche|Nietzsche]]<br/> <span style="padding-left: 10px;">[[Philosophie/Nietzsche/La culture grecque|Culture grecque]]</span><br/> <span style="padding-left: 10px;">[[Philosophie/Nietzsche/La culture moderne|Culture moderne]]</span><br/> <span style="padding-left: 10px;">[[Philosophie/Nietzsche/La métaphysique|Métaphysique]]</span><br/> <span style="padding-left: 10px;">[[Philosophie/Nietzsche/La moralité des mœurs|Moralité des mœurs]]</span> </div> </td> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-left: 4px solid #3a5a80; border-radius: 0 8px 8px 0; padding: 12px 16px 14px; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="font-size: 0.96em; font-weight: 600; color: #1a2230; padding-bottom: 8px; border-bottom: 1px solid #dfe4eb; margin-bottom: 10px;">Courants du XX<sup>e</sup> siècle <span style="color: #8a94a3; font-weight: 400;">(3)</span></div> <div style="font-size: 0.82em; color: #8a94a3; margin-bottom: 6px;">Écoles et traditions</div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Philosophie/Philosophie analytique|Philosophie analytique]]<br/> [[Philosophie/Existence et temps|Existentialisme]]<br/> [[Dictionnaire de philosophie/Dasein|Phénoménologie / Dasein]] </div> </td> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-left: 4px solid #3a5a80; border-radius: 0 8px 8px 0; padding: 12px 16px 14px; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="font-size: 0.96em; font-weight: 600; color: #1a2230; padding-bottom: 8px; border-bottom: 1px solid #dfe4eb; margin-bottom: 10px;">Traditions non occidentales <span style="color: #8a94a3; font-weight: 400;">(2)</span></div> <div style="font-size: 0.82em; color: #8a94a3; margin-bottom: 6px;">Afrique, Amériques</div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Dictionnaire de philosophie/Philosophie africaine|Philosophie africaine]]<br/> [[Dictionnaire de philosophie/Argentine (Philosophie)|Argentine — XX<sup>e</sup> siècle]] </div> </td> </tr> </table> <!-- ══════════════════════════════════════════════ CLUSTER 5 — OUVRAGES DE RÉFÉRENCE ══════════════════════════════════════════════ --> <div style="margin: 22px 0 10px; padding: 6px 0 4px; border-bottom: 1px solid #1a2230;"> <div style="font-size: 1.15em; font-weight: 700; color: #1a2230;">Ouvrages de référence et pédagogiques</div> </div> <!-- Dictionnaire — pleine largeur --> <div style="background: #fbfcfd; border: 1px solid #d4dae2; border-left: 4px solid #3a5a80; border-radius: 0 8px 8px 0; padding: 14px 16px 16px; margin-bottom: 14px; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="font-size: 0.96em; font-weight: 600; color: #1a2230; padding-bottom: 8px; border-bottom: 1px solid #dfe4eb; margin-bottom: 10px;">[[Dictionnaire de philosophie|Dictionnaire de philosophie]] <span style="color: #8a94a3; font-weight: 400;">(35)</span></div> <table style="width: 100%; border-collapse: separate; border-spacing: 5px 5px;"> <tr> <td style="width: 7.69%; text-align: center; background: #eef2f7; border: 1px solid #dbe2eb; border-radius: 4px; padding: 9px 0; font-size: 1.08em; font-weight: 500;">[[Dictionnaire de philosophie/A|A]]</td> <td style="width: 7.69%; text-align: center; background: #eef2f7; border: 1px solid #dbe2eb; border-radius: 4px; padding: 9px 0; font-size: 1.08em; font-weight: 500;">[[Dictionnaire de philosophie/B|B]]</td> <td style="width: 7.69%; text-align: center; background: #eef2f7; border: 1px solid #dbe2eb; border-radius: 4px; padding: 9px 0; font-size: 1.08em; font-weight: 500;">[[Dictionnaire de philosophie/C|C]]</td> <td style="width: 7.69%; text-align: center; background: #eef2f7; border: 1px solid #dbe2eb; border-radius: 4px; padding: 9px 0; font-size: 1.08em; font-weight: 500;">[[Dictionnaire de philosophie/D|D]]</td> <td style="width: 7.69%; text-align: center; background: #eef2f7; border: 1px solid #dbe2eb; border-radius: 4px; padding: 9px 0; font-size: 1.08em; font-weight: 500;">[[Dictionnaire de philosophie/E|E]]</td> <td style="width: 7.69%; text-align: center; background: #eef2f7; border: 1px solid #dbe2eb; border-radius: 4px; padding: 9px 0; font-size: 1.08em; font-weight: 500;">[[Dictionnaire de philosophie/F|F]]</td> <td style="width: 7.69%; text-align: center; background: #eef2f7; border: 1px solid #dbe2eb; border-radius: 4px; padding: 9px 0; font-size: 1.08em; font-weight: 500;">[[Dictionnaire de philosophie/G|G]]</td> <td style="width: 7.69%; text-align: center; background: #eef2f7; border: 1px solid #dbe2eb; border-radius: 4px; padding: 9px 0; font-size: 1.08em; font-weight: 500;">[[Dictionnaire de philosophie/H|H]]</td> <td style="width: 7.69%; text-align: center; background: #eef2f7; border: 1px solid #dbe2eb; border-radius: 4px; padding: 9px 0; font-size: 1.08em; font-weight: 500;">[[Dictionnaire de philosophie/I|I]]</td> <td style="width: 7.69%; text-align: center; background: #eef2f7; border: 1px solid #dbe2eb; border-radius: 4px; padding: 9px 0; font-size: 1.08em; font-weight: 500;">[[Dictionnaire de philosophie/J|J]]</td> <td style="width: 7.69%; text-align: center; background: #eef2f7; border: 1px solid #dbe2eb; border-radius: 4px; padding: 9px 0; font-size: 1.08em; font-weight: 500;">[[Dictionnaire de philosophie/K|K]]</td> <td style="width: 7.69%; text-align: center; background: #eef2f7; border: 1px solid #dbe2eb; border-radius: 4px; padding: 9px 0; font-size: 1.08em; font-weight: 500;">[[Dictionnaire de philosophie/L|L]]</td> <td style="width: 7.69%; text-align: center; background: #eef2f7; border: 1px solid #dbe2eb; border-radius: 4px; padding: 9px 0; font-size: 1.08em; font-weight: 500;">[[Dictionnaire de philosophie/M|M]]</td> </tr> <tr> <td style="width: 7.69%; text-align: center; background: #eef2f7; border: 1px solid #dbe2eb; border-radius: 4px; padding: 9px 0; font-size: 1.08em; font-weight: 500;">[[Dictionnaire de philosophie/N|N]]</td> <td style="width: 7.69%; text-align: center; background: #eef2f7; border: 1px solid #dbe2eb; border-radius: 4px; padding: 9px 0; font-size: 1.08em; font-weight: 500;">[[Dictionnaire de philosophie/O|O]]</td> <td style="width: 7.69%; text-align: center; background: #eef2f7; border: 1px solid #dbe2eb; border-radius: 4px; padding: 9px 0; font-size: 1.08em; font-weight: 500;">[[Dictionnaire de philosophie/P|P]]</td> <td style="width: 7.69%; text-align: center; background: #eef2f7; border: 1px solid #dbe2eb; border-radius: 4px; padding: 9px 0; font-size: 1.08em; font-weight: 500;">[[Dictionnaire de philosophie/Q|Q]]</td> <td style="width: 7.69%; text-align: center; background: #eef2f7; border: 1px solid #dbe2eb; border-radius: 4px; padding: 9px 0; font-size: 1.08em; font-weight: 500;">[[Dictionnaire de philosophie/R|R]]</td> <td style="width: 7.69%; text-align: center; background: #eef2f7; border: 1px solid #dbe2eb; border-radius: 4px; padding: 9px 0; font-size: 1.08em; font-weight: 500;">[[Dictionnaire de philosophie/S|S]]</td> <td style="width: 7.69%; text-align: center; background: #eef2f7; border: 1px solid #dbe2eb; border-radius: 4px; padding: 9px 0; font-size: 1.08em; font-weight: 500;">[[Dictionnaire de philosophie/T|T]]</td> <td style="width: 7.69%; text-align: center; background: #eef2f7; border: 1px solid #dbe2eb; border-radius: 4px; padding: 9px 0; font-size: 1.08em; font-weight: 500;">[[Dictionnaire de philosophie/U|U]]</td> <td style="width: 7.69%; text-align: center; background: #eef2f7; border: 1px solid #dbe2eb; border-radius: 4px; padding: 9px 0; font-size: 1.08em; font-weight: 500;">[[Dictionnaire de philosophie/V|V]]</td> <td style="width: 7.69%; text-align: center; background: #eef2f7; border: 1px solid #dbe2eb; border-radius: 4px; padding: 9px 0; font-size: 1.08em; font-weight: 500;">[[Dictionnaire de philosophie/W|W]]</td> <td style="width: 7.69%; text-align: center; background: #eef2f7; border: 1px solid #dbe2eb; border-radius: 4px; padding: 9px 0; font-size: 1.08em; font-weight: 500;">[[Dictionnaire de philosophie/X|X]]</td> <td style="width: 7.69%; text-align: center; background: #eef2f7; border: 1px solid #dbe2eb; border-radius: 4px; padding: 9px 0; font-size: 1.08em; font-weight: 500;">[[Dictionnaire de philosophie/Y|Y]]</td> <td style="width: 7.69%; text-align: center; background: #eef2f7; border: 1px solid #dbe2eb; border-radius: 4px; padding: 9px 0; font-size: 1.08em; font-weight: 500;">[[Dictionnaire de philosophie/Z|Z]]</td> </tr> </table> </div> <!-- Autres ouvrages --> <table style="width: 100%; border-collapse: separate; border-spacing: 14px 14px;"> <tr> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-left: 4px solid #3a5a80; border-radius: 0 8px 8px 0; padding: 12px 16px 14px; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="font-size: 0.96em; font-weight: 600; color: #1a2230; padding-bottom: 8px; border-bottom: 1px solid #dfe4eb; margin-bottom: 10px;">[[Manuel de terminale de philosophie|Manuel de terminale]] <span style="color: #8a94a3; font-weight: 400;">(26)</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> Éthique &amp; action <span style="color: #8a94a3;">(4)</span><br/> Esprit &amp; conscience <span style="color: #8a94a3;">(4)</span><br/> Connaissance &amp; vérité <span style="color: #8a94a3;">(5)</span><br/> Monde &amp; société <span style="color: #8a94a3;">(4)</span><br/> Culture &amp; sens <span style="color: #8a94a3;">(6)</span><br/> Méthode &amp; ressources <span style="color: #8a94a3;">(3)</span> </div> </td> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-left: 4px solid #3a5a80; border-radius: 0 8px 8px 0; padding: 12px 16px 14px; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="font-size: 0.96em; font-weight: 600; color: #1a2230; padding-bottom: 8px; border-bottom: 1px solid #dfe4eb; margin-bottom: 10px;">[[:Catégorie:Commentaire philosophique|Commentaires d'œuvres]] <span style="color: #8a94a3; font-weight: 400;">(20)</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Commentaire philosophique/Lettre à Ménécée|Lettre à Ménécée]] <span style="color: #8a94a3;">·</span> Épicure<br/> [[Méditations métaphysiques|Méditations]] <span style="color: #8a94a3;">·</span> Descartes<br/> [[Commentaire de l'Éthique|Éthique]] <span style="color: #8a94a3;">·</span> Spinoza<br/> [[Commentaire philosophique/Discours sur l'origine et les fondements de l'inégalité parmi les hommes|Inégalité]] <span style="color: #8a94a3;">·</span> Rousseau<br/> [[Philosophie/Commentaire du passage à propos de l'Homme esclave du divertissement|Divertissement]] <span style="color: #8a94a3;">·</span> Pascal </div> </td> <td style="width: 33.33%; vertical-align: top; background: #fbfcfd; border: 1px solid #d4dae2; border-left: 4px solid #3a5a80; border-radius: 0 8px 8px 0; padding: 12px 16px 14px; box-shadow: 0 1px 4px rgba(26,34,48,0.05);"> <div style="font-size: 0.96em; font-weight: 600; color: #1a2230; margin-bottom: 6px;">[[:Catégorie:Vocabulaire philosophique|Vocabulaires]] <span style="color: #8a94a3; font-weight: 400;">(4)</span></div> <div style="font-size: 0.88em; color: #1a2230; line-height: 1.75;"> [[Pour lire Platon/Vocabulaire|Platon]]<br/> [[Philosophie/Vocabulaire/David Hume|David Hume]]<br/> [[Philosophie/Vocabulaire/Kant|Kant]] </div> <div style="height: 1px; background: #e2e8ef; margin: 10px 0;"></div> <div style="font-size: 0.96em; font-weight: 600; color: #1a2230; padding-bottom: 8px; border-bottom: 1px solid #dfe4eb; margin-bottom: 10px;">[[Philosophie/Une brève introduction|Une brève introduction]] </div> </td> </tr> </table> <!-- ══════════════════════════════════════════════ PIED DE PAGE ══════════════════════════════════════════════ --> <div style="margin-top: 24px; padding: 14px 0 4px; border-top: 1px solid #1a2230; font-size: 0.86em; color: #555e6b;"> <div style="margin-bottom: 6px;"><span style="font-weight: 600; color: #1a2230;">Consulter ailleurs</span> &nbsp;·&nbsp; [[w:Philosophie|Wikipédia]] &nbsp;·&nbsp; [[wikt:philosophie|Wiktionnaire]] &nbsp;·&nbsp; [[v:Philosophie|Wikiversité]] &nbsp;·&nbsp; [[s:Portail:Philosophie|Wikisource]]</div> <div><span style="font-weight: 600; color: #1a2230;">Catégories sources</span> &nbsp;·&nbsp; [[:Catégorie:Discipline philosophique|Disciplines]] &nbsp;·&nbsp; [[:Catégorie:Histoire de la philosophie|Histoire]] &nbsp;·&nbsp; [[:Catégorie:Philosophe|Philosophes]] &nbsp;·&nbsp; [[:Catégorie:Dictionnaire de philosophie (livre)|Dictionnaire]] &nbsp;·&nbsp; [[:Catégorie:Manuel de terminale de philosophie (livre)|Manuel]] &nbsp;·&nbsp; [[:Catégorie:Commentaire philosophique|Commentaires]]</div> </div> </div> [[Catégorie:Philosophie|*]] jmwxjz8vb99fzrqhq7k8se580pow6s1 Philosophie/Présocratiques/Liste des Présocratiques 0 5331 764806 155486 2026-04-24T10:30:14Z ~2026-25114-99 123599 764806 wikitext text/x-wiki {| border="0" align="center" width="100%" | style="vertical-align:top" width="50%"| *Les Ioniens **École milésienne ***[[Philosophie/Thalès de Milet|Thalès de Milet]] ***[[Philosophie/Anaximandre de Milet|Anaximandre de Milet]] ***[[Anaximène de Milet]] **[[Héraclite d'Ephèse]] *École pythagoricienne **Pythagore **Cercops **Pétron **Brontin **Hippase **Calliphon **Démocédès **Parméniscos **Ménestor **Xouthos **Boïdas **Thrasyalcès **Ion de Chio **Alcméon de Crotone **Iccos **Paron **Aminias **Philolaos de Crotone **Archytas **Timée de Locres **Damon le musicien **Hippon **Phaléas **Hippodamos **Polyclète **Hippocrate de Chio **Théodore de Cyrène **Eurytos **Archippos **Lysis **Opsimos **Occelos **Hicétas **Ecphantos **Xenophile **Dioclès **Échécrate **Polymnastos **Phanton **Arion **Proros **Amyclas **Clinias **Phintias **Simos **Myonide **Euphranor **Lycon |style="vertical-align:top" width="33%"| *Xénophane de Colophon *Les Eléates **Parménide **Zénon d'Elée **[[Dictionnaire de philosophie/Empédocle|Empédocle d'Agrigente]] *[[Dictionnaire de philosophie/Anaxagore|Anaxagore de Clazomènes]] *Diogène d'Apollonie *Les atomistes **Leucippe **Démocrite d'Abdère **Nessas **Métrodore de Chio **Diogène de Smyrne **Anaxarque **Hécatée d'Abdère **Apollodore de Cyzique **Nausiphane **Diotime **Bion d'Abdère **Bolos *Les Sophistes **Protagoras **Prodicos de Céos **Gorgias de Léontion **Antiphon **Lycophron **Thrasymaque **Xéniade *Mélissos *Archélaos *Métrodore de Lampsaque *Clidèmos *Idaios *Cratyle *Antisthène l'Héraclitéen |} [[Catégorie:Philosophe]] e8j4wm5fxfe6a6i5sj25rdwp8x3pk5u Pour lire Platon/Guide des dialogues 0 30540 764688 764617 2026-04-23T19:18:16Z PandaMystique 119061 764688 wikitext text/x-wiki <noinclude>{{sous-pages}}</noinclude> <div style="text-align: center; font-size: 1.4em; font-weight: bold; margin-bottom: 1em; padding-bottom: 0.5em; border-bottom: 2px solid #8b7355; color: #3a3530;">※ Le Guide des Dialogues ※</div> <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 1.5em; width: 100%;"> <div style="padding: 1.5em; background: linear-gradient(135deg, #ffffff40 0%, #e0e4e840 100%); border-radius: 10px; box-shadow: 0 3px 10px rgba(0,0,0,0.1);"> : [[/Premier Alcibiade/]] : [[/Second Alcibiade/]] : [[/Apologie de Socrate/]] : [[/Le Banquet/]] : [[/Charmide/]] : [[/Cratyle/]] : [[/Critias/]] : [[/Criton/]] : [[/Euthydème/]] : [[/Euthyphron/]] </div> <div style="padding: 1.5em; background: linear-gradient(135deg, #ffffff40 0%, #e0e4e840 100%); border-radius: 10px; box-shadow: 0 3px 10px rgba(0,0,0,0.1);"> : [[/Gorgias/]] : [[/Hippias majeur/]] : [[/Hippias mineur/]] : [[/Ion/]] : [[/Lachès/]] : [[/Les Lois/]] : [[/Lysis/]] : [[/Ménexène/]] : [[/Ménon/]] : [[/Parménide/]] </div> <div style="padding: 1.5em; background: linear-gradient(135deg, #ffffff40 0%, #e0e4e840 100%); border-radius: 10px; box-shadow: 0 3px 10px rgba(0,0,0,0.1);"> : [[/Phédon/]] : [[/Phèdre/]] : [[/Philèbe/]] : [[/Le Politique/]] : [[/Protagoras/]] : [[/La République/]] : [[/Le Sophiste/]] : [[/Théétète/]] : [[/Timée/]] </div> </div> <br> [[Catégorie:Pour lire Platon (livre)]] 59nszptwmcdkog65zewtvvg0fqluxsf 764691 764688 2026-04-23T19:25:11Z PandaMystique 119061 764691 wikitext text/x-wiki <noinclude>{{sous-pages}}</noinclude> <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 1.5em; width: 100%;"> <div style="padding: 1.5em; background: linear-gradient(135deg, #ffffff40 0%, #e0e4e840 100%); border-radius: 10px; box-shadow: 0 3px 10px rgba(0,0,0,0.1);"> : [[/Premier Alcibiade/]] : [[/Second Alcibiade/]] : [[/Apologie de Socrate/]] : [[/Le Banquet/]] : [[/Charmide/]] : [[/Cratyle/]] : [[/Critias/]] : [[/Criton/]] : [[/Euthydème/]] : [[/Euthyphron/]] </div> <div style="padding: 1.5em; background: linear-gradient(135deg, #ffffff40 0%, #e0e4e840 100%); border-radius: 10px; box-shadow: 0 3px 10px rgba(0,0,0,0.1);"> : [[/Gorgias/]] : [[/Hippias majeur/]] : [[/Hippias mineur/]] : [[/Ion/]] : [[/Lachès/]] : [[/Les Lois/]] : [[/Lysis/]] : [[/Ménexène/]] : [[/Ménon/]] : [[/Parménide/]] </div> <div style="padding: 1.5em; background: linear-gradient(135deg, #ffffff40 0%, #e0e4e840 100%); border-radius: 10px; box-shadow: 0 3px 10px rgba(0,0,0,0.1);"> : [[/Phédon/]] : [[/Phèdre/]] : [[/Philèbe/]] : [[/Le Politique/]] : [[/Protagoras/]] : [[/La République/]] : [[/Le Sophiste/]] : [[/Théétète/]] : [[/Timée/]] </div> <br> [[Catégorie:Pour lire Platon (livre)]] nq3mm3j2q43fntigsmhl1779pl1b60c 764692 764691 2026-04-23T19:25:41Z PandaMystique 119061 764692 wikitext text/x-wiki <noinclude>{{sous-pages}}</noinclude> <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 1.5em; width: 100%;"> <div style="padding: 1.5em; background: linear-gradient(135deg, #ffffff40 0%, #e0e4e840 100%); border-radius: 10px; box-shadow: 0 3px 10px rgba(0,0,0,0.1);"> : [[/Premier Alcibiade/]] : [[/Second Alcibiade/]] : [[/Apologie de Socrate/]] : [[/Le Banquet/]] : [[/Charmide/]] : [[/Cratyle/]] : [[/Critias/]] : [[/Criton/]] : [[/Euthydème/]] : [[/Euthyphron/]] </div> <div style="padding: 1.5em; background: linear-gradient(135deg, #ffffff40 0%, #e0e4e840 100%); border-radius: 10px; box-shadow: 0 3px 10px rgba(0,0,0,0.1);"> : [[/Gorgias/]] : [[/Hippias majeur/]] : [[/Hippias mineur/]] : [[/Ion/]] : [[/Lachès/]] : [[/Les Lois/]] : [[/Lysis/]] : [[/Ménexène/]] : [[/Ménon/]] : [[/Parménide/]] </div> <div style="padding: 1.5em; background: linear-gradient(135deg, #ffffff40 0%, #e0e4e840 100%); border-radius: 10px; box-shadow: 0 3px 10px rgba(0,0,0,0.1);"> : [[/Phédon/]] : [[/Phèdre/]] : [[/Philèbe/]] : [[/Le Politique/]] : [[/Protagoras/]] : [[/La République/]] : [[/Le Sophiste/]] : [[/Théétète/]] : [[/Timée/]] </div> <br> [[Catégorie:Pour lire Platon (livre)]] 4evtloaq7i5v8n3t7tjn66jrqfqg3ig Pour lire Platon/Guide des dialogues/Introduction 0 30547 764670 764254 2026-04-23T18:00:16Z PandaMystique 119061 764670 wikitext text/x-wiki <noinclude>{{sous-pages}}</noinclude> Ce guide des dialogues platoniciens est écrit dans le même esprit que l'ensemble du livre consacré à Platon : fournir au lecteur débutant en philosophie les éléments indispensables pour aborder la pensée de cet auteur. Dans ce but, la présentation de chaque texte se composera de deux parties : d'une part, une introduction qui donnera un exposé de certains des thèmes les plus importants de l'œuvre ; d'autre part, un commentaire qui visera avant tout à éclairer quelques passages difficiles et à fournir des informations sur des personnes, des lieux, des événements historiques. == Authenticité des dialogues == Avant de lire un dialogue attribué à Platon, il convient de se demander s'il est bien de lui. La question peut surprendre : on ne se la pose pas, d'ordinaire, lorsque l'on ouvre Descartes ou Kant. Elle s'impose pourtant pour Platon, comme pour la plupart des auteurs antiques, parce que le corpus qui nous est parvenu sous son nom s'est constitué par sédimentation, et que la tradition scolaire y a intégré, au fil des siècles, des textes dont l'attribution est douteuse ou manifestement fausse. Distinguer le vrai du faux engage ici des enjeux à la fois philologiques et philosophiques : la connaissance que nous avons de Platon dépend, pour une part, de la liste des textes que nous tenons pour authentiques. === Le corpus de Thrasylle === La forme canonique du corpus platonicien remonte au grammairien Thrasylle de Mendès, astrologue et conseiller de l'empereur Tibère, qui vivait à Rome au début du I{{er}} siècle de notre ère. Soucieux d'éditer Platon sur le modèle des tragiques, Thrasylle a organisé les œuvres en neuf tétralogies, par analogie avec la trilogie tragique augmentée d'un drame satyrique<ref>Diogène Laërce rapporte cette classification dans ''Vies et doctrines des philosophes illustres'', III, 56-61 ; voir l'édition sous la direction de Marie-Odile Goulet-Cazé, Paris, Le Livre de Poche, « La Pochothèque », 1999, livre III. Thrasylle a vraisemblablement adapté une classification plus ancienne due à Aristophane de Byzance (III{{e}} siècle av. J.-C.), qui regroupait les dialogues par trilogies.</ref>. Le total atteint ainsi trente-six œuvres, auxquelles la tradition a joint treize ''Lettres'' et un recueil de ''Définitions''. La première tétralogie réunit l{{'}}''Euthyphron'', l{{'}}''Apologie de Socrate'', le ''Criton'' et le ''Phédon'' : groupement qui dessine, en filigrane, le procès et la mort de Socrate. La deuxième rassemble le ''Cratyle'', le ''Théétète'', le ''Sophiste'' et le ''Politique''. La troisième se compose du ''Parménide'', du ''Philèbe'', du ''Banquet'' et du ''Phèdre''. La quatrième contient quatre textes à l'authenticité très discutée : l{{'}}''Alcibiade'' (ou ''Alcibiade majeur''), le ''Second Alcibiade'', l{{'}}''Hipparque'' et les ''Rivaux''. La cinquième réunit le ''Théagès'', le ''Charmide'', le ''Lachès'' et le ''Lysis''. La sixième regroupe l{{'}}''Euthydème'', le ''Protagoras'', le ''Gorgias'' et le ''Ménon''. La septième comprend l{{'}}''Hippias majeur'', l{{'}}''Hippias mineur'', l{{'}}''Ion'' et le ''Ménexène''. La huitième contient le ''Clitophon'', la ''République'', le ''Timée'' et le ''Critias''. La neuvième, enfin, rassemble le ''Minos'', les ''Lois'', l{{'}}''Épinomis'' et les ''Lettres''. === Dialogues apocryphes === Dès l'Antiquité, certains textes de ce corpus ont été mis en doute. Diogène Laërce mentionne une liste de dialogues que, selon lui, nul ne tenait pour authentiques : le ''Midon'', l{{'}}''Éryxias'', l{{'}}''Alcyon'', les ''Sisyphe'', ''Axiochus'', ''Démodocos'', ainsi que quelques autres<ref>Diogène Laërce, ''Vies et doctrines des philosophes illustres'', III, 62.</ref>. La critique moderne a confirmé ce verdict ancien. Mais elle va plus loin : elle rejette aussi un certain nombre de textes intégrés aux neuf tétralogies elles-mêmes. C'est le cas, pour la quasi-totalité des spécialistes, du ''Second Alcibiade'', de l{{'}}''Hipparque'', des ''Rivaux'', du ''Théagès'' et du ''Minos'', tous regroupés dans les quatrième, cinquième et neuvième tétralogies. Leur style, la pauvreté doctrinale qui s'y lit, l'absence de toute trace dans les témoignages antérieurs au Portique conduisent à y voir des écrits de l'ancienne Académie, composés à l'imitation de Platon peu après sa mort<ref>Voir Luc Brisson, « Introduction générale », dans Platon, ''Œuvres complètes'', sous la direction de Luc Brisson, Paris, Flammarion, 2008, p. IX-LXIX ; ainsi que Julia Annas et Christopher Rowe (dir.), ''New Perspectives on Plato, Modern and Ancient'', Washington, Center for Hellenic Studies, 2002.</ref>. L{{'}}''Épinomis'', qui se présente comme une suite des ''Lois'', est le plus souvent attribué à Philippe d'Oponte, disciple de Platon et éditeur des ''Lois'' après la mort du maître<ref>Leonardo Tarán, ''Academica : Plato, Philip of Opus, and the Pseudo-Platonic'' Epinomis, Philadelphie, American Philosophical Society (''Memoirs of the American Philosophical Society'', vol. 107), 1975.</ref>. === Dialogues douteux === Entre les œuvres incontestées et les apocryphes se situe une zone grise qui a nourri les débats les plus animés. Trois cas méritent d'être mentionnés. L{{'}}''Alcibiade majeur'', d'abord, a joui d'un prestige considérable dans la tradition néoplatonicienne : Proclus et Olympiodore le plaçaient en tête du cursus philosophique, parce qu'on y voyait une introduction à la connaissance de soi. Friedrich Schleiermacher, dans les introductions à sa traduction allemande des dialogues (1804-1828), a contesté son authenticité ; la majorité des chercheurs du XX{{e}} siècle ont suivi son verdict, même si certains, comme Nicholas Denyer ou Julia Annas, ont plaidé récemment pour une authenticité partielle ou entière<ref>Nicholas Denyer (éd.), Plato, ''Alcibiades'', Cambridge, Cambridge University Press, « Cambridge Greek and Latin Classics », 2001, p. 14-26 de l'introduction.</ref>. L{{'}}''Hippias majeur'', ensuite, a fait l'objet d'un débat comparable : sa facture pourrait être celle d'un disciple imitant le style socratique, mais la plupart des éditeurs modernes penchent aujourd'hui pour l'authenticité<ref>Pour un état de la question, voir Paul Woodruff, ''Plato : Hippias Major'', Indianapolis, Hackett, 1982, introduction.</ref>. Le ''Ménexène'', enfin, pose un problème particulier : ce dialogue contient un long discours funèbre, attribué à Aspasie de Milet, qui fait difficulté tant par son ton que par certains apparents anachronismes. Aristote, pourtant, le cite à deux reprises en l'attribuant expressément à Platon, ce qui incline la plupart des critiques à le tenir pour authentique, malgré les réserves que certains expriment<ref>Aristote, ''Rhétorique'', I, 9, 1367 b 8 ; III, 14, 1415 b 30. Pour une discussion, voir Susan Sara Monoson, ''Plato's Democratic Entanglements'', Princeton, Princeton University Press, 2000, chapitre 7.</ref>. === Le cas des ''Lettres'' === Les treize ''Lettres'' transmises sous le nom de Platon forment un ensemble à part. La tradition les donnait toutes pour authentiques ; la critique moderne a procédé à un tri sévère. Seules trois sont aujourd'hui considérées comme peut-être authentiques : la troisième, la septième et la huitième, toutes relatives aux affaires de Sicile et à la tentative platonicienne de convertir Denys II de Syracuse à la philosophie. La ''Lettre VII'' occupe une place singulière : long récit autobiographique, elle contient la célèbre « digression philosophique » (341 c-345 c) où Platon expose pourquoi il n'a rien écrit, et n'écrira rien, sur ce qui constitue le cœur de sa pensée. Son authenticité a été longtemps tenue pour acquise, notamment par W. K. C. Guthrie et Luc Brisson<ref>Luc Brisson, ''Lettres'', introduction, traduction et notes, Paris, Flammarion, « GF », 1987, rééd. 2004.</ref>. Un ouvrage influent de Myles Burnyeat et Michael Frede a cependant ébranlé ce consensus, en concluant à une probable fabrication posthume<ref>Myles Burnyeat et Michael Frede, ''The Pseudo-Platonic Seventh Letter'', édité par Dominic Scott, Oxford, Oxford University Press, 2015.</ref>. Le débat reste ouvert. Les autres lettres sont, pour la plupart, considérées comme des exercices rhétoriques composés par des membres tardifs de l'Académie. === Critères d'authentification === Sur quels fondements la critique moderne procède-t-elle à ce tri ? Trois types de critères sont principalement mobilisés. Les premiers sont externes : il s'agit des témoignages des auteurs anciens, et au premier chef ceux d'Aristote, qui cite abondamment les dialogues dans la ''Métaphysique'', la ''Politique'', l{{'}}''Éthique à Nicomaque'' et la ''Poétique''. Tout texte cité par Aristote sous le nom de Platon jouit d'une forte présomption d'authenticité. Les seconds sont stylistiques : les études de stylométrie, dont il sera question plus bas, permettent de comparer la langue d'un dialogue avec celle des œuvres assurément authentiques. Les troisièmes, enfin, sont doctrinaux : l'accord ou le désaccord d'un texte avec les positions prêtées à Platon par la tradition. Ce dernier critère est le plus fragile, car il suppose que l'on sache déjà ce que Platon pense, or c'est précisément l'objet de la lecture. On ne peut donc lui accorder qu'un rôle auxiliaire. == Chronologie == Une fois établie la liste des dialogues authentiques, une autre question se pose : dans quel ordre ont-ils été composés ? Platon, à la différence d'Aristote, ne date aucun de ses textes ; il n'y fait jamais référence explicite à d'autres de ses œuvres. Il faut donc reconstituer la chronologie à partir d'indices. Le problème peut paraître technique, mais ses enjeux philosophiques sont considérables : selon que l'on place le ''Parménide'' avant ou après la ''République'', par exemple, l'interprétation de la théorie des Formes n'est pas la même. === Un problème sans solution définitive === Il importe d'abord de reconnaître la difficulté intrinsèque du problème. Les indices internes, renvois d'un dialogue à l'autre, allusions à des événements datables, sont rares et parfois ambigus. Le ''Théétète'' fait référence, dans sa scène d'introduction, à une bataille de Corinthe : s'agit-il de celle de 394 ou de celle de 369 av. J.-C. ? Les philologues penchent pour la seconde, ce qui daterait la rédaction du dialogue au plus tôt à la fin des années 360<ref>Pour un examen de la question, voir Auguste Diès, notice au ''Théétète'', dans Platon, ''Œuvres complètes'', tome VIII, 2{{e}} partie, Paris, Les Belles Lettres, « Collection des universités de France », 1924, p. 116-120.</ref>. Le ''Ménexène'' mentionne la paix d'Antalcidas (386), ce qui impose une date postérieure à cet événement. La ''République'', en revanche, ne contient guère d'allusions exploitables, et pour la majorité des dialogues on ne dispose d'aucun point de repère historique véritablement précis. === La méthode stylométrique === Face à cette pénurie d'indices, la philologie du XIX{{e}} siècle a élaboré une méthode originale : la stylométrie. Son postulat est simple : la langue d'un auteur évolue au cours de sa carrière, et l'on peut mesurer cette évolution en relevant, de façon statistique, la fréquence de certains traits stylistiques, particules, hiatus, rythmes de clausule, tournures de phrase, choix lexicaux. Le point de départ en est l'ouvrage de l'Écossais Lewis Campbell, ''The Sophistes and Politicus of Plato'', paru en 1867<ref>Lewis Campbell, ''The Sophistes and Politicus of Plato'', Oxford, Clarendon Press, 1867, en particulier l'introduction.</ref>. Campbell a le premier observé que les ''Lois'', dialogue que la tradition plaçait déjà en fin de carrière, partageaient avec le ''Sophiste'', le ''Politique'', le ''Philèbe'', le ''Timée'' et le ''Critias'' un certain nombre de particularités lexicales et rythmiques qui les distinguaient des autres dialogues. On tenait ainsi un « groupe tardif » constitué sur une base linguistique objective. Cette voie a été développée par Wilhelm Dittenberger, Constantin Ritter et, surtout, par le philosophe polonais Wincenty Lutosławski, qui a forgé en 1897 le terme même de « stylométrie » dans son livre ''The Origin and Growth of Plato's Logic''<ref>Wincenty Lutosławski, ''The Origin and Growth of Plato's Logic, with an Account of Plato's Style and of the Chronology of his Writings'', Londres, Longmans, Green & Co., 1897.</ref>. Au XX{{e}} siècle, les travaux de Gerard Ledger, puis la synthèse de Leonard Brandwood, ont consolidé la méthode en tirant parti du traitement informatique des données<ref>Gerard R. Ledger, ''Re-counting Plato : A Computer Analysis of Plato's Style'', Oxford, Clarendon Press, 1989 ; Leonard Brandwood, ''The Chronology of Plato's Dialogues'', Cambridge, Cambridge University Press, 1990.</ref>. La stylométrie n'offre pas un classement complet des dialogues, mais elle permet d'isoler avec une forte probabilité le groupe des œuvres tardives. Pour les autres, il faut combiner les indices linguistiques avec des considérations doctrinales et historiques. === Les trois grands groupes === La majorité des chercheurs s'accorde aujourd'hui pour répartir les dialogues authentiques en trois ensembles, sans prétendre fixer, à l'intérieur de chaque ensemble, un ordre strict. Le premier groupe, dit des « dialogues de jeunesse » ou « dialogues socratiques », rassemble des textes généralement brefs, souvent aporétiques, centrés sur la définition d'une vertu particulière : ''Apologie de Socrate'', ''Criton'', ''Euthyphron'', ''Ion'', ''Lachès'', ''Lysis'', ''Charmide'', ''Hippias mineur'', ''Protagoras''. On y joint parfois l{{'}}''Euthydème'', le ''Ménexène'', l{{'}}''Hippias majeur'', voire le premier livre de la ''République''. Ces œuvres seraient contemporaines ou peu postérieures au premier voyage de Platon en Sicile (388 av. J.-C.). Leur Socrate est encore proche du personnage historique : il interroge, réfute, mais n'expose aucune doctrine positive, et la plupart des entretiens s'achèvent sur un aveu d'ignorance. Le deuxième groupe, dit des « dialogues de maturité », comprend le ''Ménon'', le ''Phédon'', le ''Banquet'', la ''République'' (à partir du livre II), le ''Phèdre'' et, selon les auteurs, le ''Cratyle''. On y trouve l'exposition la plus ample de la théorie des Formes et de la doctrine de l'immortalité de l'âme ; c'est là aussi que Platon déploie ses grandes mises en scène littéraires. Leur composition se situerait entre le premier voyage en Sicile et le deuxième (c'est-à-dire, en gros, entre 388 et 367 av. J.-C.). C'est l'époque où la production de l'Académie, fondée vers 387, bat son plein. Le troisième groupe, dit des « dialogues de vieillesse » ou « dialogues tardifs », contient le ''Parménide'', le ''Théétète'', le ''Sophiste'', le ''Politique'', le ''Philèbe'', le ''Timée'', le ''Critias'' et les ''Lois''. Ces textes, postérieurs au deuxième voyage en Sicile (367), sont marqués par un effort poussé de formalisation logique et méthodologique, notamment par la « méthode de division » mise en œuvre dans le ''Sophiste'' et le ''Politique'', ainsi que par une réflexion critique sur la théorie des Formes elle-même, particulièrement dans la première partie du ''Parménide''. === Les points de débat === Cette tripartition, reçue par la plupart des manuels, n'est pas exempte de difficultés. Trois questions, en particulier, demeurent ouvertes. La première concerne le ''Timée''. La tradition le tenait pour tardif, mais Gwilym Ellis Lane Owen a proposé en 1953 de le redater et de le placer avant la ''République'', dans le groupe de maturité<ref>G. E. L. Owen, « The Place of the ''Timaeus'' in Plato's Dialogues », ''The Classical Quarterly'', vol. 3, n° 1-2, 1953, p. 79-95.</ref>. Harold Cherniss lui a répondu point par point dès 1957 et le consensus, après un débat très nourri, est revenu à la position classique<ref>Harold Cherniss, « The Relation of the ''Timaeus'' to Plato's Later Dialogues », ''American Journal of Philology'', vol. 78, 1957, p. 225-266.</ref>. La question, toutefois, n'est pas close. La deuxième porte sur le ''Parménide''. Ce dialogue présente un jeune Socrate soumis à une critique serrée de la part du vieux Parménide : la théorie des Formes y est attaquée par une série d'arguments, dont le fameux « argument du troisième homme », auxquels Platon ne fournit aucune réfutation explicite. Faut-il y voir une crise interne de la pensée platonicienne, un moment où Platon douterait de sa propre doctrine, ou un exercice dialectique destiné à préparer le lecteur à une refonte de la théorie ? La réponse engage toute l'interprétation des derniers dialogues<ref>Pour une présentation de la question, voir Constance C. Meinwald, ''Plato's Parmenides'', Oxford, Oxford University Press, 1991 ; et, en français, Denis O'Brien, ''Le Non-être : deux études sur le'' Sophiste ''de Platon'', Sankt Augustin, Academia Verlag, 1995.</ref>. La troisième, enfin, oppose deux lectures générales de l'œuvre. Les lectures dites « développementales », majoritaires aujourd'hui, voient dans le passage d'un groupe à l'autre une véritable évolution de la pensée, avec ses remises en cause, ses progrès et ses renoncements. Les lectures dites « unitariennes », défendues notamment par Paul Shorey au début du XX{{e}} siècle<ref>Paul Shorey, ''The Unity of Plato's Thought'', Chicago, University of Chicago Press, 1903.</ref>, puis par Harold Cherniss, tiennent au contraire que la pensée de Platon est substantiellement stable d'un bout à l'autre des dialogues, et que les différences apparentes tiennent à la diversité des sujets traités ou des interlocuteurs mis en scène. Une position médiane, articulée par Holger Thesleff, refuse la linéarité chronologique elle-même : Platon aurait composé plusieurs dialogues en parallèle, les retouchant au fil des années, de sorte qu'il n'existerait pas un ordre strict mais plusieurs « couches » de rédaction imbriquées<ref>Holger Thesleff, ''Studies in Platonic Chronology'', Helsinki, Societas Scientiarum Fennica (''Commentationes Humanarum Litterarum'', vol. 70), 1982 ; repris et actualisé dans ''Platonic Patterns : A Collection of Studies'', Las Vegas, Parmenides Publishing, 2009.</ref>. Le lecteur débutant retiendra cette leçon : la chronologie des dialogues est un cadre commode, utile pour orienter la lecture, mais il ne faut pas le prendre pour un fait établi. On peut très bien lire Platon en suivant un autre ordre, thématique, par exemple, sans rien perdre de l'essentiel. == Pagination == Qui ouvre une édition moderne de Platon, qu'il s'agisse de la collection Budé, de la collection GF-Flammarion, de l{{'}}''Oxford Classical Texts'' ou de la ''Loeb Classical Library'', y trouve, en marge du texte, des chiffres et des lettres : « 509 b », « 247 c », « 71 e ». Ces références, qui paraissent énigmatiques au premier abord, constituent en réalité le système de citation universel de Platon, connu sous le nom de « pagination de Stephanus ». Leur emploi mérite d'être expliqué, car elles sont la clef de toute référence précise au texte. === Henri Estienne et l'édition de 1578 === Henri II Estienne, humaniste et imprimeur parisien (vers 1528-1598), est l'un des plus grands éditeurs de textes grecs de la Renaissance. On lui doit, entre autres monuments, le ''Thesaurus Graecae Linguae'' (1572), dictionnaire de grec ancien qui est resté l'ouvrage de référence pendant près de trois siècles. En 1578, installé à Genève où il s'était réfugié pour raisons religieuses, il publie une édition complète des œuvres de Platon en trois volumes in-folio, sous le titre latin ''Platonis opera quae extant omnia''<ref>''Platonis opera quae extant omnia. Ex nova Ioannis Serrani interpretatione, perpetuis eiusdem notis illustrata'', 3 vol., [Genève], Henri Estienne, 1578.</ref>. Le nom latin d'Estienne étant ''Stephanus'', l'usage international désigne depuis lors cette édition comme celle de « Stephanus ». Son dispositif typographique est remarquable. Chaque double page présente, à gauche, le texte grec ; à droite, une traduction latine nouvelle, due à Jean de Serres (''Serranus''). Et, afin de faciliter le va-et-vient entre les deux langues, Estienne a divisé chacune de ses pages en cinq sections à peu près égales, désignées, en marge, par les lettres A, B, C, D et E. Pour localiser un passage, il suffit ainsi d'indiquer le numéro de page et la lettre de la section. Par exemple, « ''République'', 509 b » signifie : page 509 du volume où se trouve la ''République'', section B de cette page. === Un standard international === Cette pagination a connu une fortune exceptionnelle. Dès le XIX{{e}} siècle, elle s'est imposée dans toutes les éditions savantes, et toutes les éditions modernes, y compris les traductions en langues vernaculaires, la reproduisent en marge. Elle joue pour Platon le rôle que joue, pour Aristote, la pagination de l'édition d'Immanuel Bekker (Berlin, Académie royale de Prusse, 1831-1870). Un étudiant qui lit le ''Phédon'' dans la traduction de Monique Dixsaut<ref>Platon, ''Phédon'', traduction, introduction et notes par Monique Dixsaut, Paris, Flammarion, « GF », 1991.</ref> et un commentateur anglo-saxon qui l'étudie dans la version de David Gallop<ref>Plato, ''Phaedo'', translated with notes by David Gallop, Oxford, Clarendon Press, « Clarendon Plato Series », 1975.</ref> peuvent ainsi dialoguer sans ambiguïté, en renvoyant l'un et l'autre à « 72 e » ou à « 100 b ». === Usage pratique === Dans la pratique, la référence complète comporte trois éléments : le titre du dialogue, le numéro de page Stephanus et la lettre de section. Ainsi ''République'' 514 a renvoie-t-il au début de l'allégorie de la caverne ; ''Phédon'' 72 e-73 a, au passage qui articule la théorie de la réminiscence ; ''Banquet'' 209 e-210 a, au début du fameux discours de Diotime. Lorsque la citation s'étend sur plusieurs pages, on indique la page et la section de début, puis, séparé par un tiret, la page et la section de fin : ''Ménon'' 81 a-86 c, par exemple, pour l'épisode de l'esclave géomètre. À l'intérieur d'une section, certains éditeurs ajoutent encore un numéro de ligne (« Stephanus 509 b 6 » désignera alors la sixième ligne de la section B de la page 509), mais cette précision, courante dans les travaux philologiques, n'est pas indispensable au lecteur ordinaire. Quelques particularités méritent d'être signalées. L{{'}}''Apologie de Socrate'' occupe, dans la numérotation Stephanus, les pages 17 à 42 du premier volume ; la ''République'', parce qu'elle est de loin l'œuvre la plus étendue, court de la page 327 à la page 621 : toute référence située dans cette fourchette y renvoie, sauf mention contraire. Les ''Lettres'' forment, dans le volume III de l'édition d'Estienne, un ensemble numéroté à part, à partir de la page 309. Pour un petit nombre de textes, principalement les ''Définitions'' et quelques dialogues apocryphes, la pagination Stephanus est moins uniformément utilisée, et l'on trouve parfois des systèmes alternatifs. Il faut savoir, enfin, que cette pagination ne correspond à aucune division interne du texte voulue par Platon lui-même : il s'agit d'un découpage typographique, commode mais arbitraire. Les livres de la ''République'' ou des ''Lois'', en revanche, reflètent une division éditoriale ancienne, probablement antérieure à Thrasylle, et vraisemblablement héritée de la bibliothèque d'Alexandrie. Les subdivisions par paragraphes ou par chapitres, présentes dans certaines éditions, sont, elles, l'œuvre des traducteurs modernes et peuvent varier de l'une à l'autre. Pour se repérer sans faille dans la littérature secondaire, mieux vaut donc se fier à la pagination Stephanus : elle seule est universellement partagée. == Notes == {{Références|colonnes = 2}} 24bpsvisb4qpz8bt1rvm0nnl4sngmeb Fonctionnement d'un ordinateur/Le bus mémoire 0 65769 764757 755746 2026-04-24T08:39:29Z Mewtow 31375 /* Les bus mémoire larges : multiples canaux et arrangement horizontal */ 764757 wikitext text/x-wiki Le bus mémoire est en soi très simple : c'est juste un ensemble de fils, avec quelques circuits annexes qui servent à interfacer ce bus avec la processeur et la mémoire. En somme, des fils et de la ''glue logic''. Néanmoins, il y a quand même des choses à dire dessus, surtout qu'il ne prend pas la même forme sur les ordinateurs PC et sur les autres architectures. [[File:Ram-module.svg|droite|vignette|upright=0.5|Barrette de mémoire RAM.]] La plupart des PC commerciaux utilisent des '''barrettes de RAM''', qu'on peut retirer de la carte mère si besoin. Le bus mémoire est donc relié à un connecteur standardisé, appelé '''slot mémoire''', dans lequel on insère une barrette de RAM. La barrette de RAM est en soi un morceau de plastique sur lequel on place les puces mémoires, avec des broches dorées qui font contact avec le connecteur, et des interconnexions pour relier les puces aux broches dorées. [[File:Dual channel slots.jpg|centre|vignette|Slots mémoires.]] Par contre, les autres systèmes n'utilisent pas de barrettes de RAM. Ils sont fournit avec une quantité de RAM bien précise, qu'on ne peut pas upgrader. La RAM est alors soudée sur la carte mère, et le bus mémoire est une connexion directe entre processeur et mémoire RAM. Il n'y a pas de connecteur dédié, juste des puces mémoire. De nombreux ordinateurs portables font ça, mais aussi les smartphones, les microcontrôleurs ou d'autres systèmes du même genre. Les systèmes anciens avaient des bus mémoires assez réduits, peu larges, en raison de contraintes techniques. Il était intéressant de limiter la taille du bus mémoire pour économiser des broches, que ce soit sur le processeur ou la mémoire RAM. Les systèmes modernes n'ont pas ce problème, l'évolution de la technologie permet au contraire d'avoir des bus mémoire assez large. Il s'agit de deux contraintes différentes : soit on économise des broches au détriment de la performance, soit on sacrifie beaucoup de fils/broches pour avoir des performances excellentes. Les deux cas donnent des contraintes très différentes, voyons comment les deux contraintes façonnent le bus mémoire. ==Les bus mémoire réduits : l'économie de broches== Faire des économies sur le bus mémoire peut viser plusieurs objectifs. Il est possible de réduire le nombre de fils du bus mémoire, de réduire le nombre de broches du processeur ou d'économiser les broches de la mémoire RAM/ROM. Les trois objectifs sont assez différents, et certains sont plus utiles que d'autres. Par exemple, un processeur a besoin de beaucoup plus de broches qu'une mémoire pour faire son travail, vu que l'interface d'un processeur est assez complexe. Les processeurs doivent donc utiliser pas mal de ruses pour économiser des broches, comme un usage de bus multiplexés, de bus d'adresse multiplexé, etc. À l'inverse, les mémoires RAM/ROM peuvent parfaitement s'en passer, vu que leur interface est assez simple. Les contraintes entre processeur et mémoires RAM/ROM sont donc opposées. Une méthode intéressante pour économiser des broches sur le processeur est d'utiliser un bus multiplexé. En théorie, cela demande d'avoir une mémoire compatible, qui tendent à être rares et plus chères. Les mémoires de faible capacité sont souvent sans bus multiplexés, alors que les processeurs à bas cout avec bus multiplexés sont plus fréquents. Heureusement, il est possible d'implémenter un bus multiplexé avec une mémoire qui ne l'est pas, ce qui permet d'avoir le meilleur des deux mondes : cela permet d'utiliser un processeur et une mémoire à bas prix, tout en ayant des processeurs avec peu de broches. Mais cela demande de faire quelques modifications sur le bus mémoire pour que cela fonctionne. Un exemple est donné dans le schéma ci-dessous. Le processeur possède un bus multiplexé, alors que la mémoire EPROM a un bus d'adresse séparé du bus de données. Dans cet exemple, le processeur ne peut faire que des lectures, vu que la mémoire est une mémoire EEPROM, mais la solution marche aussi dans le cas où la mémoire est une RAM. L'implémentation demande juste l'ajout d'un registre sur le bus d'adresse et une commande adéquate de l'entrée OE (''Output Enable''). Pour faire une lecture, le processeur procède en deux étapes, comme sur un bus multiplexé normale : l'envoi de l'adresse, puis la lecture de la donnée. * Lors de l'envoi de l'adresse, l'adresse est mémorisée dans le registre, la broche ALE étant reliée à l'entrée ''Enable'' du registre. De plus, on doit déconnecter la mémoire du bus de donnée pour éviter un conflit entre l'envoi de la donnée par la mémoire et l'envoi de l'adresse par le processeur. Pour cela, on utilise l'entrée OE (''Output Enable''). * La lecture de la donnée consiste à mettre ALE à 0, et à récupérer la donnée sur le bus. Pendant cette étape, le registre maintient l'adresse sur le bus d'adresse. Le bit OE est configuré de manière à activer la sortie de données. [[File:8051ALE.svg|centre|vignette|upright=2|8051 ALE]] ==Le ''dual-channel'', ''triple-channel'' ou ''quad-channel''== Les techniques de ''dual-channel'' permettent de combiner ensemble 2 barrettes de mémoire RAM de manière à soit gagner en performances, soit gagner en stabilité mémoire. Le cas le plus courant est celui où les deux barrettes sont combinées de manière à doubler le débit binaire de la mémoire RAM. Cependant, il existe aussi des techniques de ''memory mirroring'', qui sont une forme particulière de ''dual channel'' visant à dupliquer les données, de manière à améliorer la résistance face aux pannes. Il existe aussi des techniques de ''triple-channel'' ou ''quad-channel'', qui font la même chose mais avec respectivement 3 et 4 barrettes de RAM. Voyons ces deux techniques dans le détail. Le ''dual-channel'' a pour principe que les données peuvent être lues ou écrites dans deux barrettes de mémoire en même temps. Pour cela, le bus mémoire est doublé. Intuitivement, vous vous dites que chaque barrette a sa propre connexion au contrôleur mémoire, elle a un bus mémoire dédiée rien qu'à elle. Mais dans les faits, ce n'est pas le cas. A la place, le bus de données est élargit. Sans ''dual channel'', il y a un unique bus mémoire de 64 bits, qui est relié aux deux barrettes, chacune ayant une interface de 64 bits. Avec le ''dual-channel'', le bus mémoire passe à 128 bits. Reste à voir comment est exploité ce bus de données doublé. ===Le ''dual-channel'' classique : de meilleures performances=== Le bus mémoire des PC modernes est très important pour les performances. Les processeurs sont de plus en plus exigeants et la vitesse de la mémoire commence à être de plus en plus limitante pour leurs performances. La solution la plus évidente est d'augmenter la fréquence des mémoires et/ou de diminuer leur temps d'accès. Mais c'est que c'est plus facile à dire qu'à faire ! Les mémoires actuelles ne peut pas vraiment être rendu plus rapides, compte tenu des contraintes techniques actuelles. La solution actuellement retenue est d'augmenter le débit de la mémoire. Et pour cela, la performance du bus mémoire est primordiale. Le débit binaire des mémoires actuelles dépend beaucoup de la performance du bus mémoire. La performance d'un bus dépend de son débit binaire, qui lui-même est le produit de sa fréquence et de sa largeur. Diverses technologies tentent d'augmenter le débit binaire du bus mémoire, que ce soit en augmentant sa largeur ou sa fréquence. La largeur du bus mémoire est quelque peu limitée par le fait qu'il faut câbler des fils sur la carte mère et ajouter des broches sur les barrettes de mémoire. Les deux possibilités sont déjà utilisées à fond, les bus actuels ayant plusieurs centaines de fils/broches. Le ''dual-channel'' permet de lire/écrire 128 bits en une fois, dans deux barrettes qui font 64 bits chacune. Ainsi, on lit/écrit 64 bits de poids faible depuis la première barrette, puis les 64 bits de poids fort depuis la seconde barrette. Le ''triple-channel'' fait de même avec trois barrettes de mémoire, le ''quad-channel'' avec quatre barrettes de mémoire. Ces techniques augmentent la largeur du bus, donc influencent le débit binaire, mais n'ont pas d'effet sur le temps de latence de la mémoire. Et ce ne sont pas les seules techniques dans ce genre. Pour en profiter, il faut placer les barrettes mémoire d'une certaine manière sur la carte mère. Typiquement, une carte mère ''dual channel'' a deux slots mémoires, voire quatre. Quand il y en a deux, tout va bien, il suffit de placer une barrette dans chaque slot. Mais dans le cas où la carte mère en a quatre, les slots sont d'une couleur différent pour indiquer comment les placer. Il faut placer les barrettes dans les slots de la même couleur pour profiter du ''dual channel''. ===Le ''memory mirroring''=== Le ''memory mirroring'' est utilisé sur des serveurs, ou des systèmes devant fonctionner en permanence avec un haut niveau de fiabilité. Sur de tels ordinateurs, il est très important d'éviter tout problème matériel, toute panne, mais aussi : tout corruption de données. Et c'est là que la technique du ''memory mirroring'' entre en scène. Le ''memory mirroring'' duplique les données à l'identique sur deux barrettes de mémoire. Les deux barrettes mémoires contiennent donc les mêmes données, aux mêmes endroits. Le fait de dupliquer les données a plusieurs avantages. Premièrement, si une barrette de RAM tombe en panne, l'autre peut prendre la relève, le temps qu'on remplace la barrette fautive. Il existe des serveurs qui sont conçus pour qu'on puisse rajouter ou retirer de la RAM à chaud, sans avoir à éteindre l'ordinateur. D'autres demandent que l'ordinateur soit arrêté, ce qui demande une intervention pouvant arriver dans un moment, l'ordinateur peut continuer à tourner avec une seule barrette pendant ce temps. Deuxièmement, cela permet de détecter d'une corruption de données quelconque. Les mémoires RAM ne sont pas fiables à 100%, et il est possible qu'un bit s'inverse soudainement, pour des raisons très variées, corrompant un octet dans la RAM. Pour éviter cela, les mémoires sur ce genre de serveurs utilise des codes de détection et de correction d'erreur et toute autre technique visant à éviter que des données soient corrompues. Mais le ''memory mirroring'' permet de détecter tout problème. Si une corruption de données survient, elle sera localisée dans une barrette. On peut alors détecter ces corruptions en comparant les données lues dans les deux barrettes. S'il y a une différence, c'est qu'il y a un problème. ==Les bus mémoire à base de liaisons point à point : les barrettes FB-DIMM== Dans le cas le plus fréquent, toutes les barrettes d'un PC sont reliées au même bus mémoire, comme indiqué dans le schéma ci-dessous. Le bus mémoire est un bus parallèle, avec tous les défauts que ca implique quand on travaille à haute fréquence. Diverses contraintes électriques assez compliquées à expliquer font que les bus parallèles ont du mal à fonctionner à haute fréquence, la stabilité de transmission du signal est altérée. [[File:Bus mémoire.PNG|centre|vignette|upright=2|Bus mémoire]] [[File:FB-DIMM - principe.PNG|vignette|FB-DIMM - principe]] Les barrettes mémoire '''FB-DIMM''' contournent le problème en utilisant plusieurs liaisons point à point. Il y a deux choses à comprendre. La première est que chaque barrette est connectée à la suivante par une liaison point à point, comme indiqué ci-dessous. Il n'y a pas de bus sur lequel on connecte toutes les barrettes, mais une série de plusieurs liaisons point à point. Les commandes/données passent d'une barrette à l'autre jusqu'à destination. Par exemple, une commande SDRAM part du contrôleur mémoire, passe d'une barrette à l'autre, avant d'arriver à la barrette de destination. Même chose pour les données lues depuis les DRAM, qui partent de la barrette, passent d'une barrette à la suivante, jusqu’à arriver au contrôleur mémoire. Ensuite, les liaisons point à point sont au nombre de deux par barrette : une pour la lecture (''northbound channel''), l'autre pour l'écriture (''southbound channel''). Chaque barrette est reliée aux liaisons point à point par un circuit de contrôle qui fait l'interface. Le circuit de contrôle s'appelle l''''''Advanced Memory Buffer''''', il vérifie si chaque transmission est destinée à la barrette, et envoie la commande/donnée à la barrette suivante si ce n'est pas le cas. [[File:FB-DIMM system organization.svg|centre|vignette|upright=2|Bus mémoire pour les barrettes FB-DIMM, schéma détaillé.]] L'avantage de cette organisation est que l'on peut facilement brancher beaucoup de barrettes mémoire sur la carte mère. Avec un bus parallèle, il est difficile de mettre plus de 4 barrettes mémoire. Plus on insère de barrettes de mémoire, plus la stabilité du signal transmis avec un bus parallèle se dégrade. Cela ne pose pas de problème quand on rajoute des barrettes sur la carte mère, car elles sont conçues pour que le signal reste exploitable même si tous les slots mémoire sont remplis. Mais cela fait qu'on a rarement plus de 4 slots mémoire par carte mère. Avec des barrettes FB-DIMM, on peut monter facilement à 8 ou 16 barrettes. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=L'interface d'une mémoire électronique | prevText=L'interface d'une mémoire électronique | next=Les cellules mémoires | nextText=Les cellules mémoires }} </noinclude> eadlyxpf4g9psgipn33d04mcahbsovp 764759 764757 2026-04-24T08:46:28Z Mewtow 31375 /* Le memory mirroring */ 764759 wikitext text/x-wiki Le bus mémoire est en soi très simple : c'est juste un ensemble de fils, avec quelques circuits annexes qui servent à interfacer ce bus avec la processeur et la mémoire. En somme, des fils et de la ''glue logic''. Néanmoins, il y a quand même des choses à dire dessus, surtout qu'il ne prend pas la même forme sur les ordinateurs PC et sur les autres architectures. [[File:Ram-module.svg|droite|vignette|upright=0.5|Barrette de mémoire RAM.]] La plupart des PC commerciaux utilisent des '''barrettes de RAM''', qu'on peut retirer de la carte mère si besoin. Le bus mémoire est donc relié à un connecteur standardisé, appelé '''slot mémoire''', dans lequel on insère une barrette de RAM. La barrette de RAM est en soi un morceau de plastique sur lequel on place les puces mémoires, avec des broches dorées qui font contact avec le connecteur, et des interconnexions pour relier les puces aux broches dorées. [[File:Dual channel slots.jpg|centre|vignette|Slots mémoires.]] Par contre, les autres systèmes n'utilisent pas de barrettes de RAM. Ils sont fournit avec une quantité de RAM bien précise, qu'on ne peut pas upgrader. La RAM est alors soudée sur la carte mère, et le bus mémoire est une connexion directe entre processeur et mémoire RAM. Il n'y a pas de connecteur dédié, juste des puces mémoire. De nombreux ordinateurs portables font ça, mais aussi les smartphones, les microcontrôleurs ou d'autres systèmes du même genre. Les systèmes anciens avaient des bus mémoires assez réduits, peu larges, en raison de contraintes techniques. Il était intéressant de limiter la taille du bus mémoire pour économiser des broches, que ce soit sur le processeur ou la mémoire RAM. Les systèmes modernes n'ont pas ce problème, l'évolution de la technologie permet au contraire d'avoir des bus mémoire assez large. Il s'agit de deux contraintes différentes : soit on économise des broches au détriment de la performance, soit on sacrifie beaucoup de fils/broches pour avoir des performances excellentes. Les deux cas donnent des contraintes très différentes, voyons comment les deux contraintes façonnent le bus mémoire. ==Les bus mémoire réduits : l'économie de broches== Faire des économies sur le bus mémoire peut viser plusieurs objectifs. Il est possible de réduire le nombre de fils du bus mémoire, de réduire le nombre de broches du processeur ou d'économiser les broches de la mémoire RAM/ROM. Les trois objectifs sont assez différents, et certains sont plus utiles que d'autres. Par exemple, un processeur a besoin de beaucoup plus de broches qu'une mémoire pour faire son travail, vu que l'interface d'un processeur est assez complexe. Les processeurs doivent donc utiliser pas mal de ruses pour économiser des broches, comme un usage de bus multiplexés, de bus d'adresse multiplexé, etc. À l'inverse, les mémoires RAM/ROM peuvent parfaitement s'en passer, vu que leur interface est assez simple. Les contraintes entre processeur et mémoires RAM/ROM sont donc opposées. Une méthode intéressante pour économiser des broches sur le processeur est d'utiliser un bus multiplexé. En théorie, cela demande d'avoir une mémoire compatible, qui tendent à être rares et plus chères. Les mémoires de faible capacité sont souvent sans bus multiplexés, alors que les processeurs à bas cout avec bus multiplexés sont plus fréquents. Heureusement, il est possible d'implémenter un bus multiplexé avec une mémoire qui ne l'est pas, ce qui permet d'avoir le meilleur des deux mondes : cela permet d'utiliser un processeur et une mémoire à bas prix, tout en ayant des processeurs avec peu de broches. Mais cela demande de faire quelques modifications sur le bus mémoire pour que cela fonctionne. Un exemple est donné dans le schéma ci-dessous. Le processeur possède un bus multiplexé, alors que la mémoire EPROM a un bus d'adresse séparé du bus de données. Dans cet exemple, le processeur ne peut faire que des lectures, vu que la mémoire est une mémoire EEPROM, mais la solution marche aussi dans le cas où la mémoire est une RAM. L'implémentation demande juste l'ajout d'un registre sur le bus d'adresse et une commande adéquate de l'entrée OE (''Output Enable''). Pour faire une lecture, le processeur procède en deux étapes, comme sur un bus multiplexé normale : l'envoi de l'adresse, puis la lecture de la donnée. * Lors de l'envoi de l'adresse, l'adresse est mémorisée dans le registre, la broche ALE étant reliée à l'entrée ''Enable'' du registre. De plus, on doit déconnecter la mémoire du bus de donnée pour éviter un conflit entre l'envoi de la donnée par la mémoire et l'envoi de l'adresse par le processeur. Pour cela, on utilise l'entrée OE (''Output Enable''). * La lecture de la donnée consiste à mettre ALE à 0, et à récupérer la donnée sur le bus. Pendant cette étape, le registre maintient l'adresse sur le bus d'adresse. Le bit OE est configuré de manière à activer la sortie de données. [[File:8051ALE.svg|centre|vignette|upright=2|8051 ALE]] ==Le ''dual-channel'', ''triple-channel'' ou ''quad-channel''== Les techniques de ''dual-channel'' permettent de combiner ensemble 2 barrettes de mémoire RAM de manière à soit gagner en performances, soit gagner en stabilité mémoire. Le cas le plus courant est celui où les deux barrettes sont combinées de manière à doubler le débit binaire de la mémoire RAM. Cependant, il existe aussi des techniques de ''memory mirroring'', qui sont une forme particulière de ''dual channel'' visant à dupliquer les données, de manière à améliorer la résistance face aux pannes. Il existe aussi des techniques de ''triple-channel'' ou ''quad-channel'', qui font la même chose mais avec respectivement 3 et 4 barrettes de RAM. Voyons ces deux techniques dans le détail. Le ''dual-channel'' a pour principe que les données peuvent être lues ou écrites dans deux barrettes de mémoire en même temps. Pour cela, le bus mémoire est doublé. Intuitivement, vous vous dites que chaque barrette a sa propre connexion au contrôleur mémoire, elle a un bus mémoire dédiée rien qu'à elle. Mais dans les faits, ce n'est pas le cas. A la place, le bus de données est élargit. Sans ''dual channel'', il y a un unique bus mémoire de 64 bits, qui est relié aux deux barrettes, chacune ayant une interface de 64 bits. Avec le ''dual-channel'', le bus mémoire passe à 128 bits. Reste à voir comment est exploité ce bus de données doublé. ===Le ''dual-channel'' classique : de meilleures performances=== Le bus mémoire des PC modernes est très important pour les performances. Les processeurs sont de plus en plus exigeants et la vitesse de la mémoire commence à être de plus en plus limitante pour leurs performances. La solution la plus évidente est d'augmenter la fréquence des mémoires et/ou de diminuer leur temps d'accès. Mais c'est que c'est plus facile à dire qu'à faire ! Les mémoires actuelles ne peut pas vraiment être rendu plus rapides, compte tenu des contraintes techniques actuelles. La solution actuellement retenue est d'augmenter le débit de la mémoire. Et pour cela, la performance du bus mémoire est primordiale. Le débit binaire des mémoires actuelles dépend beaucoup de la performance du bus mémoire. La performance d'un bus dépend de son débit binaire, qui lui-même est le produit de sa fréquence et de sa largeur. Diverses technologies tentent d'augmenter le débit binaire du bus mémoire, que ce soit en augmentant sa largeur ou sa fréquence. La largeur du bus mémoire est quelque peu limitée par le fait qu'il faut câbler des fils sur la carte mère et ajouter des broches sur les barrettes de mémoire. Les deux possibilités sont déjà utilisées à fond, les bus actuels ayant plusieurs centaines de fils/broches. Le ''dual-channel'' permet de lire/écrire 128 bits en une fois, dans deux barrettes qui font 64 bits chacune. Ainsi, on lit/écrit 64 bits de poids faible depuis la première barrette, puis les 64 bits de poids fort depuis la seconde barrette. Le ''triple-channel'' fait de même avec trois barrettes de mémoire, le ''quad-channel'' avec quatre barrettes de mémoire. Ces techniques augmentent la largeur du bus, donc influencent le débit binaire, mais n'ont pas d'effet sur le temps de latence de la mémoire. Et ce ne sont pas les seules techniques dans ce genre. Pour en profiter, il faut placer les barrettes mémoire d'une certaine manière sur la carte mère. Typiquement, une carte mère ''dual channel'' a deux slots mémoires, voire quatre. Quand il y en a deux, tout va bien, il suffit de placer une barrette dans chaque slot. Mais dans le cas où la carte mère en a quatre, les slots sont d'une couleur différent pour indiquer comment les placer. Il faut placer les barrettes dans les slots de la même couleur pour profiter du ''dual channel''. ===Le ''memory mirroring''=== Le ''memory mirroring'' est utilisé sur des serveurs, ou des systèmes devant fonctionner en permanence avec un haut niveau de fiabilité. Sur de tels ordinateurs, il est très important d'éviter tout problème matériel, toute panne, mais aussi : tout corruption de données. Et c'est là que la technique du ''memory mirroring'' entre en scène. Le ''memory mirroring'' duplique les données à l'identique sur deux barrettes de mémoire. Les deux barrettes mémoires contiennent donc les mêmes données, aux mêmes endroits. Le fait de dupliquer les données a plusieurs avantages. Premièrement, si une barrette de RAM tombe en panne, l'autre peut prendre la relève, le temps qu'on remplace la barrette fautive. Il existe des serveurs qui sont conçus pour qu'on puisse rajouter ou retirer de la RAM à chaud, sans avoir à éteindre l'ordinateur. D'autres demandent que l'ordinateur soit arrêté, ce qui demande une intervention pouvant arriver dans un moment, l'ordinateur peut continuer à tourner avec une seule barrette pendant ce temps. Deuxièmement, cela permet de détecter d'une corruption de données quelconque. Les mémoires RAM ne sont pas fiables à 100%, et il est possible qu'un bit s'inverse soudainement, pour des raisons très variées, corrompant un octet dans la RAM. Pour éviter cela, les mémoires sur ce genre de serveurs utilise des codes de détection et de correction d'erreur, mais le ''memory mirroring'' est parfois utilisé en complément. Si une corruption de données survient, elle sera localisée dans une barrette. On peut alors détecter ces corruptions en comparant les données lues dans les deux barrettes. S'il y a une différence, c'est qu'il y a un problème. Reste au contrôleur mémoire à gérer le problème. Les serveurs ont souvent des cartes mères spéciales, avec de quoi mettre facilement 8 à 16 barrettes de mémoire dessus. Aussi, le ''memory mirroring'' peut être exploité à son plein potentiel. Il est possible d'avoir 16 barrettes de RAM, groupées en paires, chaque paire utilisant le ''memory mirroring''. ==Les bus mémoire à base de liaisons point à point : les barrettes FB-DIMM== Dans le cas le plus fréquent, toutes les barrettes d'un PC sont reliées au même bus mémoire, comme indiqué dans le schéma ci-dessous. Le bus mémoire est un bus parallèle, avec tous les défauts que ca implique quand on travaille à haute fréquence. Diverses contraintes électriques assez compliquées à expliquer font que les bus parallèles ont du mal à fonctionner à haute fréquence, la stabilité de transmission du signal est altérée. [[File:Bus mémoire.PNG|centre|vignette|upright=2|Bus mémoire]] [[File:FB-DIMM - principe.PNG|vignette|FB-DIMM - principe]] Les barrettes mémoire '''FB-DIMM''' contournent le problème en utilisant plusieurs liaisons point à point. Il y a deux choses à comprendre. La première est que chaque barrette est connectée à la suivante par une liaison point à point, comme indiqué ci-dessous. Il n'y a pas de bus sur lequel on connecte toutes les barrettes, mais une série de plusieurs liaisons point à point. Les commandes/données passent d'une barrette à l'autre jusqu'à destination. Par exemple, une commande SDRAM part du contrôleur mémoire, passe d'une barrette à l'autre, avant d'arriver à la barrette de destination. Même chose pour les données lues depuis les DRAM, qui partent de la barrette, passent d'une barrette à la suivante, jusqu’à arriver au contrôleur mémoire. Ensuite, les liaisons point à point sont au nombre de deux par barrette : une pour la lecture (''northbound channel''), l'autre pour l'écriture (''southbound channel''). Chaque barrette est reliée aux liaisons point à point par un circuit de contrôle qui fait l'interface. Le circuit de contrôle s'appelle l''''''Advanced Memory Buffer''''', il vérifie si chaque transmission est destinée à la barrette, et envoie la commande/donnée à la barrette suivante si ce n'est pas le cas. [[File:FB-DIMM system organization.svg|centre|vignette|upright=2|Bus mémoire pour les barrettes FB-DIMM, schéma détaillé.]] L'avantage de cette organisation est que l'on peut facilement brancher beaucoup de barrettes mémoire sur la carte mère. Avec un bus parallèle, il est difficile de mettre plus de 4 barrettes mémoire. Plus on insère de barrettes de mémoire, plus la stabilité du signal transmis avec un bus parallèle se dégrade. Cela ne pose pas de problème quand on rajoute des barrettes sur la carte mère, car elles sont conçues pour que le signal reste exploitable même si tous les slots mémoire sont remplis. Mais cela fait qu'on a rarement plus de 4 slots mémoire par carte mère. Avec des barrettes FB-DIMM, on peut monter facilement à 8 ou 16 barrettes. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=L'interface d'une mémoire électronique | prevText=L'interface d'une mémoire électronique | next=Les cellules mémoires | nextText=Les cellules mémoires }} </noinclude> p5nzajg1kwmp7r4bjyycqj0kr86eels 764761 764759 2026-04-24T08:58:51Z Mewtow 31375 /* Le dual-channel classique : de meilleures performances */ 764761 wikitext text/x-wiki Le bus mémoire est en soi très simple : c'est juste un ensemble de fils, avec quelques circuits annexes qui servent à interfacer ce bus avec la processeur et la mémoire. En somme, des fils et de la ''glue logic''. Néanmoins, il y a quand même des choses à dire dessus, surtout qu'il ne prend pas la même forme sur les ordinateurs PC et sur les autres architectures. [[File:Ram-module.svg|droite|vignette|upright=0.5|Barrette de mémoire RAM.]] La plupart des PC commerciaux utilisent des '''barrettes de RAM''', qu'on peut retirer de la carte mère si besoin. Le bus mémoire est donc relié à un connecteur standardisé, appelé '''slot mémoire''', dans lequel on insère une barrette de RAM. La barrette de RAM est en soi un morceau de plastique sur lequel on place les puces mémoires, avec des broches dorées qui font contact avec le connecteur, et des interconnexions pour relier les puces aux broches dorées. [[File:Dual channel slots.jpg|centre|vignette|Slots mémoires.]] Par contre, les autres systèmes n'utilisent pas de barrettes de RAM. Ils sont fournit avec une quantité de RAM bien précise, qu'on ne peut pas upgrader. La RAM est alors soudée sur la carte mère, et le bus mémoire est une connexion directe entre processeur et mémoire RAM. Il n'y a pas de connecteur dédié, juste des puces mémoire. De nombreux ordinateurs portables font ça, mais aussi les smartphones, les microcontrôleurs ou d'autres systèmes du même genre. Les systèmes anciens avaient des bus mémoires assez réduits, peu larges, en raison de contraintes techniques. Il était intéressant de limiter la taille du bus mémoire pour économiser des broches, que ce soit sur le processeur ou la mémoire RAM. Les systèmes modernes n'ont pas ce problème, l'évolution de la technologie permet au contraire d'avoir des bus mémoire assez large. Il s'agit de deux contraintes différentes : soit on économise des broches au détriment de la performance, soit on sacrifie beaucoup de fils/broches pour avoir des performances excellentes. Les deux cas donnent des contraintes très différentes, voyons comment les deux contraintes façonnent le bus mémoire. ==Les bus mémoire réduits : l'économie de broches== Faire des économies sur le bus mémoire peut viser plusieurs objectifs. Il est possible de réduire le nombre de fils du bus mémoire, de réduire le nombre de broches du processeur ou d'économiser les broches de la mémoire RAM/ROM. Les trois objectifs sont assez différents, et certains sont plus utiles que d'autres. Par exemple, un processeur a besoin de beaucoup plus de broches qu'une mémoire pour faire son travail, vu que l'interface d'un processeur est assez complexe. Les processeurs doivent donc utiliser pas mal de ruses pour économiser des broches, comme un usage de bus multiplexés, de bus d'adresse multiplexé, etc. À l'inverse, les mémoires RAM/ROM peuvent parfaitement s'en passer, vu que leur interface est assez simple. Les contraintes entre processeur et mémoires RAM/ROM sont donc opposées. Une méthode intéressante pour économiser des broches sur le processeur est d'utiliser un bus multiplexé. En théorie, cela demande d'avoir une mémoire compatible, qui tendent à être rares et plus chères. Les mémoires de faible capacité sont souvent sans bus multiplexés, alors que les processeurs à bas cout avec bus multiplexés sont plus fréquents. Heureusement, il est possible d'implémenter un bus multiplexé avec une mémoire qui ne l'est pas, ce qui permet d'avoir le meilleur des deux mondes : cela permet d'utiliser un processeur et une mémoire à bas prix, tout en ayant des processeurs avec peu de broches. Mais cela demande de faire quelques modifications sur le bus mémoire pour que cela fonctionne. Un exemple est donné dans le schéma ci-dessous. Le processeur possède un bus multiplexé, alors que la mémoire EPROM a un bus d'adresse séparé du bus de données. Dans cet exemple, le processeur ne peut faire que des lectures, vu que la mémoire est une mémoire EEPROM, mais la solution marche aussi dans le cas où la mémoire est une RAM. L'implémentation demande juste l'ajout d'un registre sur le bus d'adresse et une commande adéquate de l'entrée OE (''Output Enable''). Pour faire une lecture, le processeur procède en deux étapes, comme sur un bus multiplexé normale : l'envoi de l'adresse, puis la lecture de la donnée. * Lors de l'envoi de l'adresse, l'adresse est mémorisée dans le registre, la broche ALE étant reliée à l'entrée ''Enable'' du registre. De plus, on doit déconnecter la mémoire du bus de donnée pour éviter un conflit entre l'envoi de la donnée par la mémoire et l'envoi de l'adresse par le processeur. Pour cela, on utilise l'entrée OE (''Output Enable''). * La lecture de la donnée consiste à mettre ALE à 0, et à récupérer la donnée sur le bus. Pendant cette étape, le registre maintient l'adresse sur le bus d'adresse. Le bit OE est configuré de manière à activer la sortie de données. [[File:8051ALE.svg|centre|vignette|upright=2|8051 ALE]] ==Le ''dual-channel'', ''triple-channel'' ou ''quad-channel''== Les techniques de ''dual-channel'' permettent de combiner ensemble 2 barrettes de mémoire RAM de manière à soit gagner en performances, soit gagner en stabilité mémoire. Le cas le plus courant est celui où les deux barrettes sont combinées de manière à doubler le débit binaire de la mémoire RAM. Cependant, il existe aussi des techniques de ''memory mirroring'', qui sont une forme particulière de ''dual channel'' visant à dupliquer les données, de manière à améliorer la résistance face aux pannes. Il existe aussi des techniques de ''triple-channel'' ou ''quad-channel'', qui font la même chose mais avec respectivement 3 et 4 barrettes de RAM. Voyons ces deux techniques dans le détail. Le ''dual-channel'' a pour principe que les données peuvent être lues ou écrites dans deux barrettes de mémoire en même temps. Pour cela, le bus mémoire est doublé. Intuitivement, vous vous dites que chaque barrette a sa propre connexion au contrôleur mémoire, elle a un bus mémoire dédiée rien qu'à elle. Mais dans les faits, ce n'est pas le cas. A la place, le bus de données est élargit. Sans ''dual channel'', il y a un unique bus mémoire de 64 bits, qui est relié aux deux barrettes, chacune ayant une interface de 64 bits. Avec le ''dual-channel'', le bus mémoire passe à 128 bits. Reste à voir comment est exploité ce bus de données doublé. Pour en profiter, il faut placer les barrettes mémoire d'une certaine manière sur la carte mère. Typiquement, une carte mère ''dual channel'' a deux slots mémoires, voire quatre. Quand il y en a deux, tout va bien, il suffit de placer une barrette dans chaque slot. Mais dans le cas où la carte mère en a quatre, les slots sont d'une couleur différent pour indiquer comment les placer. Il faut placer les barrettes dans les slots de la même couleur pour profiter du ''dual channel''. ===Le ''dual-channel'' classique : de meilleures performances=== Le ''dual-channel'' classique permet de lire/écrire 128 bits en une fois, dans deux barrettes qui font 64 bits chacune. Le débit binaire de la mémoire RAM est donc doublée, car le débit binaire des deux RAM s'additionne. Du moins, dans un cas idéal, il y a des conditions pour arriver à ce maximum théorique. Par contre, si ces techniques augmentent la largeur du bus, elles n'ont pas d'effet sur le temps de latence de la mémoire. L'idée est de lire/écrire des blocs de 128 bits, dont les 64 bits de poids faible sont lus/écrits dans la première barrette, les 64 bits de poids fort depuis la seconde barrette. Le ''triple-channel'' fait de même avec trois barrettes de mémoire, le ''quad-channel'' avec quatre barrettes de mémoire. Sans dual channel, la première barrette correspond à la moitié haute de la RAM, la seconde barrette correspond à la moitié basse. Avec le ''dual channel'', la répartition des adresses mémoire est différente. L'idée est que deux adresses mémoires consécutives seront placées dans deux barrettes différentes. Et faire cela demande de bidouiller l'adresse mémoire envoyée aux deux barrettes. Pour rappel, le processeur utilise des adresses d'octets, à savoir que chaque octet de la RAM a sa propre adresse. Par contre, les SDRAM modernes sont découpées en mots mémoire de 64 bits, chacun ayant sa propre adresse mémoire. Il y a donc une différence entre les adresses manipulées par le processeur et celles que comprend la mémoire RAM. L'adresse d'octet est découpée en deux portions : l'adresse mémoire envoyée à la SDRAM,, et la position de l'octet dans le mot 64 bits adressé par l'adresse mémoire. {|class="wikitable" |+ Adresse d'octet (processeur) |- ! Adresse mémoire !! Position de l'octet dans un bloc de 64 bits |- | Adresse mémoire || 5 bits |} Avec le ''dual channel'', les choses sont un peu plus compliquées. Intuitivement, on se dit que les mots mémoire passent à 128 bits. Donc l'adresse ne devrait pas être coupée au même endroit, le point de coupe est décalé d'un cran. {|class="wikitable" |+ Adresse d'octet (processeur) |- ! Adresse mémoire !! Position de l'octet dans un bloc de 64 bits |- | Adresse mémoire || '''6 bits''' |} Précisément, un bit de l'adresse permet de sélectionner la barrette de mémoire, le canal voulu. [[File:Décodage d'adresse avec dual channel.png|centre|vignette|upright=2.5|Décodage d'adresse avec dual channel.]] L'adresse mémoire est donc amputée d'un bit, puis est envoyée aux barrettes de RAM. Les deux barrettes de RAM répondent à la même adresse mémoire, la même adresse de mot. Elles lisent/écrivent chacune 64 bits de leur côté. [[File:Arrangement horizontal.jpg|centre|vignette|upright=2|Arrangement horizontal]] ===Le ''memory mirroring''=== Le ''memory mirroring'' est utilisé sur des serveurs, ou des systèmes devant fonctionner en permanence avec un haut niveau de fiabilité. Sur de tels ordinateurs, il est très important d'éviter tout problème matériel, toute panne, mais aussi : tout corruption de données. Et c'est là que la technique du ''memory mirroring'' entre en scène. Le ''memory mirroring'' duplique les données à l'identique sur deux barrettes de mémoire. Les deux barrettes mémoires contiennent donc les mêmes données, aux mêmes endroits. Le fait de dupliquer les données a plusieurs avantages. Premièrement, si une barrette de RAM tombe en panne, l'autre peut prendre la relève, le temps qu'on remplace la barrette fautive. Il existe des serveurs qui sont conçus pour qu'on puisse rajouter ou retirer de la RAM à chaud, sans avoir à éteindre l'ordinateur. D'autres demandent que l'ordinateur soit arrêté, ce qui demande une intervention pouvant arriver dans un moment, l'ordinateur peut continuer à tourner avec une seule barrette pendant ce temps. Deuxièmement, cela permet de détecter d'une corruption de données quelconque. Les mémoires RAM ne sont pas fiables à 100%, et il est possible qu'un bit s'inverse soudainement, pour des raisons très variées, corrompant un octet dans la RAM. Pour éviter cela, les mémoires sur ce genre de serveurs utilise des codes de détection et de correction d'erreur, mais le ''memory mirroring'' est parfois utilisé en complément. Si une corruption de données survient, elle sera localisée dans une barrette. On peut alors détecter ces corruptions en comparant les données lues dans les deux barrettes. S'il y a une différence, c'est qu'il y a un problème. Reste au contrôleur mémoire à gérer le problème. Les serveurs ont souvent des cartes mères spéciales, avec de quoi mettre facilement 8 à 16 barrettes de mémoire dessus. Aussi, le ''memory mirroring'' peut être exploité à son plein potentiel. Il est possible d'avoir 16 barrettes de RAM, groupées en paires, chaque paire utilisant le ''memory mirroring''. ==Les bus mémoire à base de liaisons point à point : les barrettes FB-DIMM== Dans le cas le plus fréquent, toutes les barrettes d'un PC sont reliées au même bus mémoire, comme indiqué dans le schéma ci-dessous. Le bus mémoire est un bus parallèle, avec tous les défauts que ca implique quand on travaille à haute fréquence. Diverses contraintes électriques assez compliquées à expliquer font que les bus parallèles ont du mal à fonctionner à haute fréquence, la stabilité de transmission du signal est altérée. [[File:Bus mémoire.PNG|centre|vignette|upright=2|Bus mémoire]] [[File:FB-DIMM - principe.PNG|vignette|FB-DIMM - principe]] Les barrettes mémoire '''FB-DIMM''' contournent le problème en utilisant plusieurs liaisons point à point. Il y a deux choses à comprendre. La première est que chaque barrette est connectée à la suivante par une liaison point à point, comme indiqué ci-dessous. Il n'y a pas de bus sur lequel on connecte toutes les barrettes, mais une série de plusieurs liaisons point à point. Les commandes/données passent d'une barrette à l'autre jusqu'à destination. Par exemple, une commande SDRAM part du contrôleur mémoire, passe d'une barrette à l'autre, avant d'arriver à la barrette de destination. Même chose pour les données lues depuis les DRAM, qui partent de la barrette, passent d'une barrette à la suivante, jusqu’à arriver au contrôleur mémoire. Ensuite, les liaisons point à point sont au nombre de deux par barrette : une pour la lecture (''northbound channel''), l'autre pour l'écriture (''southbound channel''). Chaque barrette est reliée aux liaisons point à point par un circuit de contrôle qui fait l'interface. Le circuit de contrôle s'appelle l''''''Advanced Memory Buffer''''', il vérifie si chaque transmission est destinée à la barrette, et envoie la commande/donnée à la barrette suivante si ce n'est pas le cas. [[File:FB-DIMM system organization.svg|centre|vignette|upright=2|Bus mémoire pour les barrettes FB-DIMM, schéma détaillé.]] L'avantage de cette organisation est que l'on peut facilement brancher beaucoup de barrettes mémoire sur la carte mère. Avec un bus parallèle, il est difficile de mettre plus de 4 barrettes mémoire. Plus on insère de barrettes de mémoire, plus la stabilité du signal transmis avec un bus parallèle se dégrade. Cela ne pose pas de problème quand on rajoute des barrettes sur la carte mère, car elles sont conçues pour que le signal reste exploitable même si tous les slots mémoire sont remplis. Mais cela fait qu'on a rarement plus de 4 slots mémoire par carte mère. Avec des barrettes FB-DIMM, on peut monter facilement à 8 ou 16 barrettes. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=L'interface d'une mémoire électronique | prevText=L'interface d'une mémoire électronique | next=Les cellules mémoires | nextText=Les cellules mémoires }} </noinclude> jg8g5k46k6xriwgklduznm81ct7413u 764763 764761 2026-04-24T09:01:34Z Mewtow 31375 /* Le dual-channel classique : de meilleures performances */ 764763 wikitext text/x-wiki Le bus mémoire est en soi très simple : c'est juste un ensemble de fils, avec quelques circuits annexes qui servent à interfacer ce bus avec la processeur et la mémoire. En somme, des fils et de la ''glue logic''. Néanmoins, il y a quand même des choses à dire dessus, surtout qu'il ne prend pas la même forme sur les ordinateurs PC et sur les autres architectures. [[File:Ram-module.svg|droite|vignette|upright=0.5|Barrette de mémoire RAM.]] La plupart des PC commerciaux utilisent des '''barrettes de RAM''', qu'on peut retirer de la carte mère si besoin. Le bus mémoire est donc relié à un connecteur standardisé, appelé '''slot mémoire''', dans lequel on insère une barrette de RAM. La barrette de RAM est en soi un morceau de plastique sur lequel on place les puces mémoires, avec des broches dorées qui font contact avec le connecteur, et des interconnexions pour relier les puces aux broches dorées. [[File:Dual channel slots.jpg|centre|vignette|Slots mémoires.]] Par contre, les autres systèmes n'utilisent pas de barrettes de RAM. Ils sont fournit avec une quantité de RAM bien précise, qu'on ne peut pas upgrader. La RAM est alors soudée sur la carte mère, et le bus mémoire est une connexion directe entre processeur et mémoire RAM. Il n'y a pas de connecteur dédié, juste des puces mémoire. De nombreux ordinateurs portables font ça, mais aussi les smartphones, les microcontrôleurs ou d'autres systèmes du même genre. Les systèmes anciens avaient des bus mémoires assez réduits, peu larges, en raison de contraintes techniques. Il était intéressant de limiter la taille du bus mémoire pour économiser des broches, que ce soit sur le processeur ou la mémoire RAM. Les systèmes modernes n'ont pas ce problème, l'évolution de la technologie permet au contraire d'avoir des bus mémoire assez large. Il s'agit de deux contraintes différentes : soit on économise des broches au détriment de la performance, soit on sacrifie beaucoup de fils/broches pour avoir des performances excellentes. Les deux cas donnent des contraintes très différentes, voyons comment les deux contraintes façonnent le bus mémoire. ==Les bus mémoire réduits : l'économie de broches== Faire des économies sur le bus mémoire peut viser plusieurs objectifs. Il est possible de réduire le nombre de fils du bus mémoire, de réduire le nombre de broches du processeur ou d'économiser les broches de la mémoire RAM/ROM. Les trois objectifs sont assez différents, et certains sont plus utiles que d'autres. Par exemple, un processeur a besoin de beaucoup plus de broches qu'une mémoire pour faire son travail, vu que l'interface d'un processeur est assez complexe. Les processeurs doivent donc utiliser pas mal de ruses pour économiser des broches, comme un usage de bus multiplexés, de bus d'adresse multiplexé, etc. À l'inverse, les mémoires RAM/ROM peuvent parfaitement s'en passer, vu que leur interface est assez simple. Les contraintes entre processeur et mémoires RAM/ROM sont donc opposées. Une méthode intéressante pour économiser des broches sur le processeur est d'utiliser un bus multiplexé. En théorie, cela demande d'avoir une mémoire compatible, qui tendent à être rares et plus chères. Les mémoires de faible capacité sont souvent sans bus multiplexés, alors que les processeurs à bas cout avec bus multiplexés sont plus fréquents. Heureusement, il est possible d'implémenter un bus multiplexé avec une mémoire qui ne l'est pas, ce qui permet d'avoir le meilleur des deux mondes : cela permet d'utiliser un processeur et une mémoire à bas prix, tout en ayant des processeurs avec peu de broches. Mais cela demande de faire quelques modifications sur le bus mémoire pour que cela fonctionne. Un exemple est donné dans le schéma ci-dessous. Le processeur possède un bus multiplexé, alors que la mémoire EPROM a un bus d'adresse séparé du bus de données. Dans cet exemple, le processeur ne peut faire que des lectures, vu que la mémoire est une mémoire EEPROM, mais la solution marche aussi dans le cas où la mémoire est une RAM. L'implémentation demande juste l'ajout d'un registre sur le bus d'adresse et une commande adéquate de l'entrée OE (''Output Enable''). Pour faire une lecture, le processeur procède en deux étapes, comme sur un bus multiplexé normale : l'envoi de l'adresse, puis la lecture de la donnée. * Lors de l'envoi de l'adresse, l'adresse est mémorisée dans le registre, la broche ALE étant reliée à l'entrée ''Enable'' du registre. De plus, on doit déconnecter la mémoire du bus de donnée pour éviter un conflit entre l'envoi de la donnée par la mémoire et l'envoi de l'adresse par le processeur. Pour cela, on utilise l'entrée OE (''Output Enable''). * La lecture de la donnée consiste à mettre ALE à 0, et à récupérer la donnée sur le bus. Pendant cette étape, le registre maintient l'adresse sur le bus d'adresse. Le bit OE est configuré de manière à activer la sortie de données. [[File:8051ALE.svg|centre|vignette|upright=2|8051 ALE]] ==Le ''dual-channel'', ''triple-channel'' ou ''quad-channel''== Les techniques de ''dual-channel'' permettent de combiner ensemble 2 barrettes de mémoire RAM de manière à soit gagner en performances, soit gagner en stabilité mémoire. Le cas le plus courant est celui où les deux barrettes sont combinées de manière à doubler le débit binaire de la mémoire RAM. Cependant, il existe aussi des techniques de ''memory mirroring'', qui sont une forme particulière de ''dual channel'' visant à dupliquer les données, de manière à améliorer la résistance face aux pannes. Il existe aussi des techniques de ''triple-channel'' ou ''quad-channel'', qui font la même chose mais avec respectivement 3 et 4 barrettes de RAM. Voyons ces deux techniques dans le détail. Le ''dual-channel'' a pour principe que les données peuvent être lues ou écrites dans deux barrettes de mémoire en même temps. Pour cela, le bus mémoire est doublé. Intuitivement, vous vous dites que chaque barrette a sa propre connexion au contrôleur mémoire, elle a un bus mémoire dédiée rien qu'à elle. Mais dans les faits, ce n'est pas le cas. A la place, le bus de données est élargit. Sans ''dual channel'', il y a un unique bus mémoire de 64 bits, qui est relié aux deux barrettes, chacune ayant une interface de 64 bits. Avec le ''dual-channel'', le bus mémoire passe à 128 bits. Reste à voir comment est exploité ce bus de données doublé. Pour en profiter, il faut placer les barrettes mémoire d'une certaine manière sur la carte mère. Typiquement, une carte mère ''dual channel'' a deux slots mémoires, voire quatre. Quand il y en a deux, tout va bien, il suffit de placer une barrette dans chaque slot. Mais dans le cas où la carte mère en a quatre, les slots sont d'une couleur différent pour indiquer comment les placer. Il faut placer les barrettes dans les slots de la même couleur pour profiter du ''dual channel''. ===Le ''dual-channel'' classique : de meilleures performances=== Le ''dual-channel'' classique permet de lire/écrire 128 bits en une fois, dans deux barrettes qui font 64 bits chacune. Le débit binaire de la mémoire RAM est donc doublée, car le débit binaire des deux RAM s'additionne. Du moins, dans un cas idéal, il y a des conditions pour arriver à ce maximum théorique. Par contre, si ces techniques augmentent la largeur du bus, elles n'ont pas d'effet sur le temps de latence de la mémoire. L'idée est de lire/écrire des blocs de 128 bits, dont les 64 bits de poids faible sont lus/écrits dans la première barrette, les 64 bits de poids fort depuis la seconde barrette. Le ''dual channel'' se contente d'agrandir chaque mot mémoire, en doublant sa taille. Pour cela, l'adresse mémoire est envoyée aux deux barrettes en même temps. Les deux barrettes de RAM répondent à la même adresse mémoire, la même adresse de mot. Elles lisent/écrivent chacune 64 bits de leur côté. Le ''triple-channel'' fait de même avec trois barrettes de mémoire, le ''quad-channel'' avec quatre barrettes de mémoire. [[File:Arrangement horizontal.jpg|centre|vignette|upright=2|Arrangement horizontal]] Il y a cependant une petite subtilité, liée à l'alignement mémoire. Sans dual channel, la première barrette correspond à la moitié haute de la RAM, la seconde barrette correspond à la moitié basse. Avec le ''dual channel'', la répartition des adresses mémoire est différente. L'idée est que deux adresses mémoires consécutives seront placées dans deux barrettes différentes. Et faire cela demande de bidouiller l'adresse mémoire envoyée aux deux barrettes. Pour rappel, le processeur utilise des adresses d'octets, à savoir que chaque octet de la RAM a sa propre adresse. Par contre, les SDRAM modernes sont découpées en ''mots mémoire'' de 64 bits, chacun ayant sa propre adresse mémoire. Il y a donc une différence entre les adresses manipulées par le processeur et celles que comprend la mémoire RAM. L'adresse d'octet est découpée en deux portions : l'adresse mémoire envoyée à la SDRAM, et la position de l'octet dans le mot 64 bits adressé par l'adresse mémoire. {|class="wikitable" |+ Adresse d'octet (processeur) |- ! Adresse mémoire !! Position de l'octet dans un bloc de 64 bits |- | Adresse mémoire || 5 bits |} Avec le ''dual channel'', les choses sont un peu plus compliquées. Intuitivement, on se dit que les mots mémoire passent à 128 bits. Donc l'adresse ne devrait pas être coupée au même endroit, le point de coupe est décalé d'un cran. {|class="wikitable" |+ Adresse d'octet (processeur) |- ! Adresse mémoire !! Position de l'octet dans un bloc de 64 bits |- | Adresse mémoire || '''6 bits''' |} Précisément, un bit de l'adresse permet de sélectionner la barrette de mémoire, le canal voulu. L'adresse mémoire est donc amputée d'un bit, puis est envoyée aux barrettes de RAM. [[File:Décodage d'adresse avec dual channel.png|centre|vignette|upright=2.5|Décodage d'adresse avec dual channel.]] ===Le ''memory mirroring''=== Le ''memory mirroring'' est utilisé sur des serveurs, ou des systèmes devant fonctionner en permanence avec un haut niveau de fiabilité. Sur de tels ordinateurs, il est très important d'éviter tout problème matériel, toute panne, mais aussi : tout corruption de données. Et c'est là que la technique du ''memory mirroring'' entre en scène. Le ''memory mirroring'' duplique les données à l'identique sur deux barrettes de mémoire. Les deux barrettes mémoires contiennent donc les mêmes données, aux mêmes endroits. Le fait de dupliquer les données a plusieurs avantages. Premièrement, si une barrette de RAM tombe en panne, l'autre peut prendre la relève, le temps qu'on remplace la barrette fautive. Il existe des serveurs qui sont conçus pour qu'on puisse rajouter ou retirer de la RAM à chaud, sans avoir à éteindre l'ordinateur. D'autres demandent que l'ordinateur soit arrêté, ce qui demande une intervention pouvant arriver dans un moment, l'ordinateur peut continuer à tourner avec une seule barrette pendant ce temps. Deuxièmement, cela permet de détecter d'une corruption de données quelconque. Les mémoires RAM ne sont pas fiables à 100%, et il est possible qu'un bit s'inverse soudainement, pour des raisons très variées, corrompant un octet dans la RAM. Pour éviter cela, les mémoires sur ce genre de serveurs utilise des codes de détection et de correction d'erreur, mais le ''memory mirroring'' est parfois utilisé en complément. Si une corruption de données survient, elle sera localisée dans une barrette. On peut alors détecter ces corruptions en comparant les données lues dans les deux barrettes. S'il y a une différence, c'est qu'il y a un problème. Reste au contrôleur mémoire à gérer le problème. Les serveurs ont souvent des cartes mères spéciales, avec de quoi mettre facilement 8 à 16 barrettes de mémoire dessus. Aussi, le ''memory mirroring'' peut être exploité à son plein potentiel. Il est possible d'avoir 16 barrettes de RAM, groupées en paires, chaque paire utilisant le ''memory mirroring''. ==Les bus mémoire à base de liaisons point à point : les barrettes FB-DIMM== Dans le cas le plus fréquent, toutes les barrettes d'un PC sont reliées au même bus mémoire, comme indiqué dans le schéma ci-dessous. Le bus mémoire est un bus parallèle, avec tous les défauts que ca implique quand on travaille à haute fréquence. Diverses contraintes électriques assez compliquées à expliquer font que les bus parallèles ont du mal à fonctionner à haute fréquence, la stabilité de transmission du signal est altérée. [[File:Bus mémoire.PNG|centre|vignette|upright=2|Bus mémoire]] [[File:FB-DIMM - principe.PNG|vignette|FB-DIMM - principe]] Les barrettes mémoire '''FB-DIMM''' contournent le problème en utilisant plusieurs liaisons point à point. Il y a deux choses à comprendre. La première est que chaque barrette est connectée à la suivante par une liaison point à point, comme indiqué ci-dessous. Il n'y a pas de bus sur lequel on connecte toutes les barrettes, mais une série de plusieurs liaisons point à point. Les commandes/données passent d'une barrette à l'autre jusqu'à destination. Par exemple, une commande SDRAM part du contrôleur mémoire, passe d'une barrette à l'autre, avant d'arriver à la barrette de destination. Même chose pour les données lues depuis les DRAM, qui partent de la barrette, passent d'une barrette à la suivante, jusqu’à arriver au contrôleur mémoire. Ensuite, les liaisons point à point sont au nombre de deux par barrette : une pour la lecture (''northbound channel''), l'autre pour l'écriture (''southbound channel''). Chaque barrette est reliée aux liaisons point à point par un circuit de contrôle qui fait l'interface. Le circuit de contrôle s'appelle l''''''Advanced Memory Buffer''''', il vérifie si chaque transmission est destinée à la barrette, et envoie la commande/donnée à la barrette suivante si ce n'est pas le cas. [[File:FB-DIMM system organization.svg|centre|vignette|upright=2|Bus mémoire pour les barrettes FB-DIMM, schéma détaillé.]] L'avantage de cette organisation est que l'on peut facilement brancher beaucoup de barrettes mémoire sur la carte mère. Avec un bus parallèle, il est difficile de mettre plus de 4 barrettes mémoire. Plus on insère de barrettes de mémoire, plus la stabilité du signal transmis avec un bus parallèle se dégrade. Cela ne pose pas de problème quand on rajoute des barrettes sur la carte mère, car elles sont conçues pour que le signal reste exploitable même si tous les slots mémoire sont remplis. Mais cela fait qu'on a rarement plus de 4 slots mémoire par carte mère. Avec des barrettes FB-DIMM, on peut monter facilement à 8 ou 16 barrettes. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=L'interface d'une mémoire électronique | prevText=L'interface d'une mémoire électronique | next=Les cellules mémoires | nextText=Les cellules mémoires }} </noinclude> e34e872ckmerq5x9j058hgxwd5yhgvm 764764 764763 2026-04-24T09:01:49Z Mewtow 31375 /* Le dual-channel classique : de meilleures performances */ 764764 wikitext text/x-wiki Le bus mémoire est en soi très simple : c'est juste un ensemble de fils, avec quelques circuits annexes qui servent à interfacer ce bus avec la processeur et la mémoire. En somme, des fils et de la ''glue logic''. Néanmoins, il y a quand même des choses à dire dessus, surtout qu'il ne prend pas la même forme sur les ordinateurs PC et sur les autres architectures. [[File:Ram-module.svg|droite|vignette|upright=0.5|Barrette de mémoire RAM.]] La plupart des PC commerciaux utilisent des '''barrettes de RAM''', qu'on peut retirer de la carte mère si besoin. Le bus mémoire est donc relié à un connecteur standardisé, appelé '''slot mémoire''', dans lequel on insère une barrette de RAM. La barrette de RAM est en soi un morceau de plastique sur lequel on place les puces mémoires, avec des broches dorées qui font contact avec le connecteur, et des interconnexions pour relier les puces aux broches dorées. [[File:Dual channel slots.jpg|centre|vignette|Slots mémoires.]] Par contre, les autres systèmes n'utilisent pas de barrettes de RAM. Ils sont fournit avec une quantité de RAM bien précise, qu'on ne peut pas upgrader. La RAM est alors soudée sur la carte mère, et le bus mémoire est une connexion directe entre processeur et mémoire RAM. Il n'y a pas de connecteur dédié, juste des puces mémoire. De nombreux ordinateurs portables font ça, mais aussi les smartphones, les microcontrôleurs ou d'autres systèmes du même genre. Les systèmes anciens avaient des bus mémoires assez réduits, peu larges, en raison de contraintes techniques. Il était intéressant de limiter la taille du bus mémoire pour économiser des broches, que ce soit sur le processeur ou la mémoire RAM. Les systèmes modernes n'ont pas ce problème, l'évolution de la technologie permet au contraire d'avoir des bus mémoire assez large. Il s'agit de deux contraintes différentes : soit on économise des broches au détriment de la performance, soit on sacrifie beaucoup de fils/broches pour avoir des performances excellentes. Les deux cas donnent des contraintes très différentes, voyons comment les deux contraintes façonnent le bus mémoire. ==Les bus mémoire réduits : l'économie de broches== Faire des économies sur le bus mémoire peut viser plusieurs objectifs. Il est possible de réduire le nombre de fils du bus mémoire, de réduire le nombre de broches du processeur ou d'économiser les broches de la mémoire RAM/ROM. Les trois objectifs sont assez différents, et certains sont plus utiles que d'autres. Par exemple, un processeur a besoin de beaucoup plus de broches qu'une mémoire pour faire son travail, vu que l'interface d'un processeur est assez complexe. Les processeurs doivent donc utiliser pas mal de ruses pour économiser des broches, comme un usage de bus multiplexés, de bus d'adresse multiplexé, etc. À l'inverse, les mémoires RAM/ROM peuvent parfaitement s'en passer, vu que leur interface est assez simple. Les contraintes entre processeur et mémoires RAM/ROM sont donc opposées. Une méthode intéressante pour économiser des broches sur le processeur est d'utiliser un bus multiplexé. En théorie, cela demande d'avoir une mémoire compatible, qui tendent à être rares et plus chères. Les mémoires de faible capacité sont souvent sans bus multiplexés, alors que les processeurs à bas cout avec bus multiplexés sont plus fréquents. Heureusement, il est possible d'implémenter un bus multiplexé avec une mémoire qui ne l'est pas, ce qui permet d'avoir le meilleur des deux mondes : cela permet d'utiliser un processeur et une mémoire à bas prix, tout en ayant des processeurs avec peu de broches. Mais cela demande de faire quelques modifications sur le bus mémoire pour que cela fonctionne. Un exemple est donné dans le schéma ci-dessous. Le processeur possède un bus multiplexé, alors que la mémoire EPROM a un bus d'adresse séparé du bus de données. Dans cet exemple, le processeur ne peut faire que des lectures, vu que la mémoire est une mémoire EEPROM, mais la solution marche aussi dans le cas où la mémoire est une RAM. L'implémentation demande juste l'ajout d'un registre sur le bus d'adresse et une commande adéquate de l'entrée OE (''Output Enable''). Pour faire une lecture, le processeur procède en deux étapes, comme sur un bus multiplexé normale : l'envoi de l'adresse, puis la lecture de la donnée. * Lors de l'envoi de l'adresse, l'adresse est mémorisée dans le registre, la broche ALE étant reliée à l'entrée ''Enable'' du registre. De plus, on doit déconnecter la mémoire du bus de donnée pour éviter un conflit entre l'envoi de la donnée par la mémoire et l'envoi de l'adresse par le processeur. Pour cela, on utilise l'entrée OE (''Output Enable''). * La lecture de la donnée consiste à mettre ALE à 0, et à récupérer la donnée sur le bus. Pendant cette étape, le registre maintient l'adresse sur le bus d'adresse. Le bit OE est configuré de manière à activer la sortie de données. [[File:8051ALE.svg|centre|vignette|upright=2|8051 ALE]] ==Le ''dual-channel'', ''triple-channel'' ou ''quad-channel''== Les techniques de ''dual-channel'' permettent de combiner ensemble 2 barrettes de mémoire RAM de manière à soit gagner en performances, soit gagner en stabilité mémoire. Le cas le plus courant est celui où les deux barrettes sont combinées de manière à doubler le débit binaire de la mémoire RAM. Cependant, il existe aussi des techniques de ''memory mirroring'', qui sont une forme particulière de ''dual channel'' visant à dupliquer les données, de manière à améliorer la résistance face aux pannes. Il existe aussi des techniques de ''triple-channel'' ou ''quad-channel'', qui font la même chose mais avec respectivement 3 et 4 barrettes de RAM. Voyons ces deux techniques dans le détail. Le ''dual-channel'' a pour principe que les données peuvent être lues ou écrites dans deux barrettes de mémoire en même temps. Pour cela, le bus mémoire est doublé. Intuitivement, vous vous dites que chaque barrette a sa propre connexion au contrôleur mémoire, elle a un bus mémoire dédiée rien qu'à elle. Mais dans les faits, ce n'est pas le cas. A la place, le bus de données est élargit. Sans ''dual channel'', il y a un unique bus mémoire de 64 bits, qui est relié aux deux barrettes, chacune ayant une interface de 64 bits. Avec le ''dual-channel'', le bus mémoire passe à 128 bits. Reste à voir comment est exploité ce bus de données doublé. Pour en profiter, il faut placer les barrettes mémoire d'une certaine manière sur la carte mère. Typiquement, une carte mère ''dual channel'' a deux slots mémoires, voire quatre. Quand il y en a deux, tout va bien, il suffit de placer une barrette dans chaque slot. Mais dans le cas où la carte mère en a quatre, les slots sont d'une couleur différent pour indiquer comment les placer. Il faut placer les barrettes dans les slots de la même couleur pour profiter du ''dual channel''. ===Le ''dual-channel'' classique : de meilleures performances=== Le ''dual-channel'' classique permet de lire/écrire 128 bits en une fois, dans deux barrettes qui font 64 bits chacune. Le débit binaire de la mémoire RAM est donc doublée, car le débit binaire des deux RAM s'additionne. Du moins, dans un cas idéal, il y a des conditions pour arriver à ce maximum théorique. Par contre, si ces techniques augmentent la largeur du bus, elles n'ont pas d'effet sur le temps de latence de la mémoire. L'idée est de lire/écrire des blocs de 128 bits, dont les 64 bits de poids faible sont lus/écrits dans la première barrette, les 64 bits de poids fort depuis la seconde barrette. Le ''dual channel'' se contente d'agrandir chaque mot mémoire, en doublant sa taille. Pour cela, l'adresse mémoire est envoyée aux deux barrettes en même temps. Les deux barrettes de RAM répondent à la même adresse mémoire, la même adresse de mot. Elles lisent/écrivent chacune 64 bits de leur côté. Le ''triple-channel'' fait de même avec trois barrettes de mémoire, le ''quad-channel'' avec quatre barrettes de mémoire. [[File:Arrangement horizontal.jpg|centre|vignette|upright=2.5|Arrangement horizontal]] Il y a cependant une petite subtilité, liée à l'alignement mémoire. Sans dual channel, la première barrette correspond à la moitié haute de la RAM, la seconde barrette correspond à la moitié basse. Avec le ''dual channel'', la répartition des adresses mémoire est différente. L'idée est que deux adresses mémoires consécutives seront placées dans deux barrettes différentes. Et faire cela demande de bidouiller l'adresse mémoire envoyée aux deux barrettes. Pour rappel, le processeur utilise des adresses d'octets, à savoir que chaque octet de la RAM a sa propre adresse. Par contre, les SDRAM modernes sont découpées en ''mots mémoire'' de 64 bits, chacun ayant sa propre adresse mémoire. Il y a donc une différence entre les adresses manipulées par le processeur et celles que comprend la mémoire RAM. L'adresse d'octet est découpée en deux portions : l'adresse mémoire envoyée à la SDRAM, et la position de l'octet dans le mot 64 bits adressé par l'adresse mémoire. {|class="wikitable" |+ Adresse d'octet (processeur) |- ! Adresse mémoire !! Position de l'octet dans un bloc de 64 bits |- | Adresse mémoire || 5 bits |} Avec le ''dual channel'', les choses sont un peu plus compliquées. Intuitivement, on se dit que les mots mémoire passent à 128 bits. Donc l'adresse ne devrait pas être coupée au même endroit, le point de coupe est décalé d'un cran. {|class="wikitable" |+ Adresse d'octet (processeur) |- ! Adresse mémoire !! Position de l'octet dans un bloc de 64 bits |- | Adresse mémoire || '''6 bits''' |} Précisément, un bit de l'adresse permet de sélectionner la barrette de mémoire, le canal voulu. L'adresse mémoire est donc amputée d'un bit, puis est envoyée aux barrettes de RAM. [[File:Décodage d'adresse avec dual channel.png|centre|vignette|upright=2.5|Décodage d'adresse avec dual channel.]] ===Le ''memory mirroring''=== Le ''memory mirroring'' est utilisé sur des serveurs, ou des systèmes devant fonctionner en permanence avec un haut niveau de fiabilité. Sur de tels ordinateurs, il est très important d'éviter tout problème matériel, toute panne, mais aussi : tout corruption de données. Et c'est là que la technique du ''memory mirroring'' entre en scène. Le ''memory mirroring'' duplique les données à l'identique sur deux barrettes de mémoire. Les deux barrettes mémoires contiennent donc les mêmes données, aux mêmes endroits. Le fait de dupliquer les données a plusieurs avantages. Premièrement, si une barrette de RAM tombe en panne, l'autre peut prendre la relève, le temps qu'on remplace la barrette fautive. Il existe des serveurs qui sont conçus pour qu'on puisse rajouter ou retirer de la RAM à chaud, sans avoir à éteindre l'ordinateur. D'autres demandent que l'ordinateur soit arrêté, ce qui demande une intervention pouvant arriver dans un moment, l'ordinateur peut continuer à tourner avec une seule barrette pendant ce temps. Deuxièmement, cela permet de détecter d'une corruption de données quelconque. Les mémoires RAM ne sont pas fiables à 100%, et il est possible qu'un bit s'inverse soudainement, pour des raisons très variées, corrompant un octet dans la RAM. Pour éviter cela, les mémoires sur ce genre de serveurs utilise des codes de détection et de correction d'erreur, mais le ''memory mirroring'' est parfois utilisé en complément. Si une corruption de données survient, elle sera localisée dans une barrette. On peut alors détecter ces corruptions en comparant les données lues dans les deux barrettes. S'il y a une différence, c'est qu'il y a un problème. Reste au contrôleur mémoire à gérer le problème. Les serveurs ont souvent des cartes mères spéciales, avec de quoi mettre facilement 8 à 16 barrettes de mémoire dessus. Aussi, le ''memory mirroring'' peut être exploité à son plein potentiel. Il est possible d'avoir 16 barrettes de RAM, groupées en paires, chaque paire utilisant le ''memory mirroring''. ==Les bus mémoire à base de liaisons point à point : les barrettes FB-DIMM== Dans le cas le plus fréquent, toutes les barrettes d'un PC sont reliées au même bus mémoire, comme indiqué dans le schéma ci-dessous. Le bus mémoire est un bus parallèle, avec tous les défauts que ca implique quand on travaille à haute fréquence. Diverses contraintes électriques assez compliquées à expliquer font que les bus parallèles ont du mal à fonctionner à haute fréquence, la stabilité de transmission du signal est altérée. [[File:Bus mémoire.PNG|centre|vignette|upright=2|Bus mémoire]] [[File:FB-DIMM - principe.PNG|vignette|FB-DIMM - principe]] Les barrettes mémoire '''FB-DIMM''' contournent le problème en utilisant plusieurs liaisons point à point. Il y a deux choses à comprendre. La première est que chaque barrette est connectée à la suivante par une liaison point à point, comme indiqué ci-dessous. Il n'y a pas de bus sur lequel on connecte toutes les barrettes, mais une série de plusieurs liaisons point à point. Les commandes/données passent d'une barrette à l'autre jusqu'à destination. Par exemple, une commande SDRAM part du contrôleur mémoire, passe d'une barrette à l'autre, avant d'arriver à la barrette de destination. Même chose pour les données lues depuis les DRAM, qui partent de la barrette, passent d'une barrette à la suivante, jusqu’à arriver au contrôleur mémoire. Ensuite, les liaisons point à point sont au nombre de deux par barrette : une pour la lecture (''northbound channel''), l'autre pour l'écriture (''southbound channel''). Chaque barrette est reliée aux liaisons point à point par un circuit de contrôle qui fait l'interface. Le circuit de contrôle s'appelle l''''''Advanced Memory Buffer''''', il vérifie si chaque transmission est destinée à la barrette, et envoie la commande/donnée à la barrette suivante si ce n'est pas le cas. [[File:FB-DIMM system organization.svg|centre|vignette|upright=2|Bus mémoire pour les barrettes FB-DIMM, schéma détaillé.]] L'avantage de cette organisation est que l'on peut facilement brancher beaucoup de barrettes mémoire sur la carte mère. Avec un bus parallèle, il est difficile de mettre plus de 4 barrettes mémoire. Plus on insère de barrettes de mémoire, plus la stabilité du signal transmis avec un bus parallèle se dégrade. Cela ne pose pas de problème quand on rajoute des barrettes sur la carte mère, car elles sont conçues pour que le signal reste exploitable même si tous les slots mémoire sont remplis. Mais cela fait qu'on a rarement plus de 4 slots mémoire par carte mère. Avec des barrettes FB-DIMM, on peut monter facilement à 8 ou 16 barrettes. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=L'interface d'une mémoire électronique | prevText=L'interface d'une mémoire électronique | next=Les cellules mémoires | nextText=Les cellules mémoires }} </noinclude> kyegtt91i3wf9vcwpety40qkns8xkq5 764767 764764 2026-04-24T09:08:00Z Mewtow 31375 /* Le dual-channel classique : de meilleures performances */ 764767 wikitext text/x-wiki Le bus mémoire est en soi très simple : c'est juste un ensemble de fils, avec quelques circuits annexes qui servent à interfacer ce bus avec la processeur et la mémoire. En somme, des fils et de la ''glue logic''. Néanmoins, il y a quand même des choses à dire dessus, surtout qu'il ne prend pas la même forme sur les ordinateurs PC et sur les autres architectures. [[File:Ram-module.svg|droite|vignette|upright=0.5|Barrette de mémoire RAM.]] La plupart des PC commerciaux utilisent des '''barrettes de RAM''', qu'on peut retirer de la carte mère si besoin. Le bus mémoire est donc relié à un connecteur standardisé, appelé '''slot mémoire''', dans lequel on insère une barrette de RAM. La barrette de RAM est en soi un morceau de plastique sur lequel on place les puces mémoires, avec des broches dorées qui font contact avec le connecteur, et des interconnexions pour relier les puces aux broches dorées. [[File:Dual channel slots.jpg|centre|vignette|Slots mémoires.]] Par contre, les autres systèmes n'utilisent pas de barrettes de RAM. Ils sont fournit avec une quantité de RAM bien précise, qu'on ne peut pas upgrader. La RAM est alors soudée sur la carte mère, et le bus mémoire est une connexion directe entre processeur et mémoire RAM. Il n'y a pas de connecteur dédié, juste des puces mémoire. De nombreux ordinateurs portables font ça, mais aussi les smartphones, les microcontrôleurs ou d'autres systèmes du même genre. Les systèmes anciens avaient des bus mémoires assez réduits, peu larges, en raison de contraintes techniques. Il était intéressant de limiter la taille du bus mémoire pour économiser des broches, que ce soit sur le processeur ou la mémoire RAM. Les systèmes modernes n'ont pas ce problème, l'évolution de la technologie permet au contraire d'avoir des bus mémoire assez large. Il s'agit de deux contraintes différentes : soit on économise des broches au détriment de la performance, soit on sacrifie beaucoup de fils/broches pour avoir des performances excellentes. Les deux cas donnent des contraintes très différentes, voyons comment les deux contraintes façonnent le bus mémoire. ==Les bus mémoire réduits : l'économie de broches== Faire des économies sur le bus mémoire peut viser plusieurs objectifs. Il est possible de réduire le nombre de fils du bus mémoire, de réduire le nombre de broches du processeur ou d'économiser les broches de la mémoire RAM/ROM. Les trois objectifs sont assez différents, et certains sont plus utiles que d'autres. Par exemple, un processeur a besoin de beaucoup plus de broches qu'une mémoire pour faire son travail, vu que l'interface d'un processeur est assez complexe. Les processeurs doivent donc utiliser pas mal de ruses pour économiser des broches, comme un usage de bus multiplexés, de bus d'adresse multiplexé, etc. À l'inverse, les mémoires RAM/ROM peuvent parfaitement s'en passer, vu que leur interface est assez simple. Les contraintes entre processeur et mémoires RAM/ROM sont donc opposées. Une méthode intéressante pour économiser des broches sur le processeur est d'utiliser un bus multiplexé. En théorie, cela demande d'avoir une mémoire compatible, qui tendent à être rares et plus chères. Les mémoires de faible capacité sont souvent sans bus multiplexés, alors que les processeurs à bas cout avec bus multiplexés sont plus fréquents. Heureusement, il est possible d'implémenter un bus multiplexé avec une mémoire qui ne l'est pas, ce qui permet d'avoir le meilleur des deux mondes : cela permet d'utiliser un processeur et une mémoire à bas prix, tout en ayant des processeurs avec peu de broches. Mais cela demande de faire quelques modifications sur le bus mémoire pour que cela fonctionne. Un exemple est donné dans le schéma ci-dessous. Le processeur possède un bus multiplexé, alors que la mémoire EPROM a un bus d'adresse séparé du bus de données. Dans cet exemple, le processeur ne peut faire que des lectures, vu que la mémoire est une mémoire EEPROM, mais la solution marche aussi dans le cas où la mémoire est une RAM. L'implémentation demande juste l'ajout d'un registre sur le bus d'adresse et une commande adéquate de l'entrée OE (''Output Enable''). Pour faire une lecture, le processeur procède en deux étapes, comme sur un bus multiplexé normale : l'envoi de l'adresse, puis la lecture de la donnée. * Lors de l'envoi de l'adresse, l'adresse est mémorisée dans le registre, la broche ALE étant reliée à l'entrée ''Enable'' du registre. De plus, on doit déconnecter la mémoire du bus de donnée pour éviter un conflit entre l'envoi de la donnée par la mémoire et l'envoi de l'adresse par le processeur. Pour cela, on utilise l'entrée OE (''Output Enable''). * La lecture de la donnée consiste à mettre ALE à 0, et à récupérer la donnée sur le bus. Pendant cette étape, le registre maintient l'adresse sur le bus d'adresse. Le bit OE est configuré de manière à activer la sortie de données. [[File:8051ALE.svg|centre|vignette|upright=2|8051 ALE]] ==Le ''dual-channel'', ''triple-channel'' ou ''quad-channel''== Les techniques de ''dual-channel'' permettent de combiner ensemble 2 barrettes de mémoire RAM de manière à soit gagner en performances, soit gagner en stabilité mémoire. Le cas le plus courant est celui où les deux barrettes sont combinées de manière à doubler le débit binaire de la mémoire RAM. Cependant, il existe aussi des techniques de ''memory mirroring'', qui sont une forme particulière de ''dual channel'' visant à dupliquer les données, de manière à améliorer la résistance face aux pannes. Il existe aussi des techniques de ''triple-channel'' ou ''quad-channel'', qui font la même chose mais avec respectivement 3 et 4 barrettes de RAM. Voyons ces deux techniques dans le détail. Le ''dual-channel'' a pour principe que les données peuvent être lues ou écrites dans deux barrettes de mémoire en même temps. Pour cela, le bus mémoire est doublé. Intuitivement, vous vous dites que chaque barrette a sa propre connexion au contrôleur mémoire, elle a un bus mémoire dédiée rien qu'à elle. Mais dans les faits, ce n'est pas le cas. A la place, le bus de données est élargit. Sans ''dual channel'', il y a un unique bus mémoire de 64 bits, qui est relié aux deux barrettes, chacune ayant une interface de 64 bits. Avec le ''dual-channel'', le bus mémoire passe à 128 bits. Reste à voir comment est exploité ce bus de données doublé. Pour en profiter, il faut placer les barrettes mémoire d'une certaine manière sur la carte mère. Typiquement, une carte mère ''dual channel'' a deux slots mémoires, voire quatre. Quand il y en a deux, tout va bien, il suffit de placer une barrette dans chaque slot. Mais dans le cas où la carte mère en a quatre, les slots sont d'une couleur différent pour indiquer comment les placer. Il faut placer les barrettes dans les slots de la même couleur pour profiter du ''dual channel''. ===Le ''dual-channel'' classique : de meilleures performances=== Le ''dual-channel'' classique permet de lire/écrire 128 bits en une fois, dans deux barrettes qui font 64 bits chacune. Le débit binaire de la mémoire RAM est donc doublée, car le débit binaire des deux RAM s'additionne. Du moins, dans un cas idéal, il y a des conditions pour arriver à ce maximum théorique. Par contre, si ces techniques augmentent la largeur du bus, elles n'ont pas d'effet sur le temps de latence de la mémoire. Le ''dual channel'' se contente d'agrandir chaque mot mémoire, en doublant sa taille. L'idée est de lire/écrire des mots mémoire de 128 bits, dont les 64 bits de poids faible sont lus/écrits dans la première barrette, les 64 bits de poids fort depuis la seconde barrette. Pour cela, l'adresse mémoire est envoyée aux deux barrettes en même temps. Les deux barrettes de RAM répondent à la même adresse mémoire, la même adresse de mot. Elles lisent/écrivent chacune 64 bits de leur côté. Le ''triple-channel'' fait de même avec des mots mémoire de 192 bits répartis sur trois barrettes de RAM, le ''quad-channel'' utilise des mots mémoire de 256 bits répartis sur quatre barrettes de mémoire. [[File:Arrangement horizontal.jpg|centre|vignette|upright=2.5|Arrangement horizontal]] Il y a cependant une petite subtilité, liée à l'alignement mémoire. Voyons ce qui se passe du point de vue du processeur. Sans dual channel, la première barrette correspond à la moitié haute de la RAM, la seconde barrette correspond à la moitié basse. Avec le ''dual channel'', la répartition des adresses mémoire est différente. Intuitivement, on se dit que deux adresses mémoires consécutives seront placées dans deux barrettes différentes, mais c'est plus complexe que ça. Pour rappel, le processeur utilise des adresses d'octets, à savoir que chaque octet de la RAM a sa propre adresse. Par contre, les SDRAM modernes sont découpées en ''mots mémoire'' de 64 bits, chacun ayant sa propre adresse mémoire. Il y a donc une différence entre les adresses manipulées par le processeur et celles que comprend la mémoire RAM. L'adresse d'octet est découpée en deux portions : l'adresse mémoire envoyée à la SDRAM, et la position de l'octet dans le mot 64 bits adressé par l'adresse mémoire. {|class="wikitable" |+ Adresse d'octet (processeur) |- ! Adresse mémoire !! Position de l'octet dans un bloc de 64 bits |- | Adresse mémoire || 5 bits |} Avec le ''dual channel'', les mots mémoire passent à 128 bits. Donc l'adresse ne devrait pas être coupée au même endroit, le point de coupe est décalé d'un cran. Précisément, un bit de l'adresse permet de sélectionner la barrette de mémoire, le canal voulu. L'adresse mémoire est donc amputée d'un bit, puis est envoyée aux barrettes de RAM. {|class="wikitable" |+ Adresse d'octet (processeur) |- ! Adresse mémoire !! Numéro de barrette !! Position de l'octet dans un bloc de 64 bits |- | Adresse mémoire || 1 bit || 5 bits |} Avec le ''triple-'' ou ''quad channel'', il faut utiliser deux bits pour dire dans quelle barrette se trouve la donnée. {|class="wikitable" |+ Adresse d'octet (processeur) |- ! Adresse mémoire !! Numéro de barrette !! Position de l'octet dans un bloc de 64 bits |- | Adresse mémoire || 2 bits || 5 bits |} De manière générale, voici comment est découpée l'adresse d'octet, quand elle passe dans le contrôleur mémoire. {|class="wikitable" |+ Adresse d'octet (processeur) |- ! colspan="6" | Sans entrelacement |- | Numéro de canal/barrette || Adresse mémoire || Position de l'octet dans un bloc de 64 bits |- | colspan="6" | |- ! colspan="6" | Avec entrelacement |- | Adresse mémoire || Numéro de canal/barrette || Position de l'octet dans un bloc de 64 bits |} Pour adresser la mémoire, seule l'adresse mémoire est utilisée. C'est elle qui est envoyée à toutes les barrettes de RAM. [[File:Décodage d'adresse avec dual channel.png|centre|vignette|upright=2.5|Décodage d'adresse avec dual channel.]] ===Le ''memory mirroring''=== Le ''memory mirroring'' est utilisé sur des serveurs, ou des systèmes devant fonctionner en permanence avec un haut niveau de fiabilité. Sur de tels ordinateurs, il est très important d'éviter tout problème matériel, toute panne, mais aussi : tout corruption de données. Et c'est là que la technique du ''memory mirroring'' entre en scène. Le ''memory mirroring'' duplique les données à l'identique sur deux barrettes de mémoire. Les deux barrettes mémoires contiennent donc les mêmes données, aux mêmes endroits. Le fait de dupliquer les données a plusieurs avantages. Premièrement, si une barrette de RAM tombe en panne, l'autre peut prendre la relève, le temps qu'on remplace la barrette fautive. Il existe des serveurs qui sont conçus pour qu'on puisse rajouter ou retirer de la RAM à chaud, sans avoir à éteindre l'ordinateur. D'autres demandent que l'ordinateur soit arrêté, ce qui demande une intervention pouvant arriver dans un moment, l'ordinateur peut continuer à tourner avec une seule barrette pendant ce temps. Deuxièmement, cela permet de détecter d'une corruption de données quelconque. Les mémoires RAM ne sont pas fiables à 100%, et il est possible qu'un bit s'inverse soudainement, pour des raisons très variées, corrompant un octet dans la RAM. Pour éviter cela, les mémoires sur ce genre de serveurs utilise des codes de détection et de correction d'erreur, mais le ''memory mirroring'' est parfois utilisé en complément. Si une corruption de données survient, elle sera localisée dans une barrette. On peut alors détecter ces corruptions en comparant les données lues dans les deux barrettes. S'il y a une différence, c'est qu'il y a un problème. Reste au contrôleur mémoire à gérer le problème. Les serveurs ont souvent des cartes mères spéciales, avec de quoi mettre facilement 8 à 16 barrettes de mémoire dessus. Aussi, le ''memory mirroring'' peut être exploité à son plein potentiel. Il est possible d'avoir 16 barrettes de RAM, groupées en paires, chaque paire utilisant le ''memory mirroring''. ==Les bus mémoire à base de liaisons point à point : les barrettes FB-DIMM== Dans le cas le plus fréquent, toutes les barrettes d'un PC sont reliées au même bus mémoire, comme indiqué dans le schéma ci-dessous. Le bus mémoire est un bus parallèle, avec tous les défauts que ca implique quand on travaille à haute fréquence. Diverses contraintes électriques assez compliquées à expliquer font que les bus parallèles ont du mal à fonctionner à haute fréquence, la stabilité de transmission du signal est altérée. [[File:Bus mémoire.PNG|centre|vignette|upright=2|Bus mémoire]] [[File:FB-DIMM - principe.PNG|vignette|FB-DIMM - principe]] Les barrettes mémoire '''FB-DIMM''' contournent le problème en utilisant plusieurs liaisons point à point. Il y a deux choses à comprendre. La première est que chaque barrette est connectée à la suivante par une liaison point à point, comme indiqué ci-dessous. Il n'y a pas de bus sur lequel on connecte toutes les barrettes, mais une série de plusieurs liaisons point à point. Les commandes/données passent d'une barrette à l'autre jusqu'à destination. Par exemple, une commande SDRAM part du contrôleur mémoire, passe d'une barrette à l'autre, avant d'arriver à la barrette de destination. Même chose pour les données lues depuis les DRAM, qui partent de la barrette, passent d'une barrette à la suivante, jusqu’à arriver au contrôleur mémoire. Ensuite, les liaisons point à point sont au nombre de deux par barrette : une pour la lecture (''northbound channel''), l'autre pour l'écriture (''southbound channel''). Chaque barrette est reliée aux liaisons point à point par un circuit de contrôle qui fait l'interface. Le circuit de contrôle s'appelle l''''''Advanced Memory Buffer''''', il vérifie si chaque transmission est destinée à la barrette, et envoie la commande/donnée à la barrette suivante si ce n'est pas le cas. [[File:FB-DIMM system organization.svg|centre|vignette|upright=2|Bus mémoire pour les barrettes FB-DIMM, schéma détaillé.]] L'avantage de cette organisation est que l'on peut facilement brancher beaucoup de barrettes mémoire sur la carte mère. Avec un bus parallèle, il est difficile de mettre plus de 4 barrettes mémoire. Plus on insère de barrettes de mémoire, plus la stabilité du signal transmis avec un bus parallèle se dégrade. Cela ne pose pas de problème quand on rajoute des barrettes sur la carte mère, car elles sont conçues pour que le signal reste exploitable même si tous les slots mémoire sont remplis. Mais cela fait qu'on a rarement plus de 4 slots mémoire par carte mère. Avec des barrettes FB-DIMM, on peut monter facilement à 8 ou 16 barrettes. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=L'interface d'une mémoire électronique | prevText=L'interface d'une mémoire électronique | next=Les cellules mémoires | nextText=Les cellules mémoires }} </noinclude> 5g9q6vppu9yplyvo9hbld86s7v3f77i 764771 764767 2026-04-24T09:18:58Z Mewtow 31375 /* Le dual-channel classique : de meilleures performances */ 764771 wikitext text/x-wiki Le bus mémoire est en soi très simple : c'est juste un ensemble de fils, avec quelques circuits annexes qui servent à interfacer ce bus avec la processeur et la mémoire. En somme, des fils et de la ''glue logic''. Néanmoins, il y a quand même des choses à dire dessus, surtout qu'il ne prend pas la même forme sur les ordinateurs PC et sur les autres architectures. [[File:Ram-module.svg|droite|vignette|upright=0.5|Barrette de mémoire RAM.]] La plupart des PC commerciaux utilisent des '''barrettes de RAM''', qu'on peut retirer de la carte mère si besoin. Le bus mémoire est donc relié à un connecteur standardisé, appelé '''slot mémoire''', dans lequel on insère une barrette de RAM. La barrette de RAM est en soi un morceau de plastique sur lequel on place les puces mémoires, avec des broches dorées qui font contact avec le connecteur, et des interconnexions pour relier les puces aux broches dorées. [[File:Dual channel slots.jpg|centre|vignette|Slots mémoires.]] Par contre, les autres systèmes n'utilisent pas de barrettes de RAM. Ils sont fournit avec une quantité de RAM bien précise, qu'on ne peut pas upgrader. La RAM est alors soudée sur la carte mère, et le bus mémoire est une connexion directe entre processeur et mémoire RAM. Il n'y a pas de connecteur dédié, juste des puces mémoire. De nombreux ordinateurs portables font ça, mais aussi les smartphones, les microcontrôleurs ou d'autres systèmes du même genre. Les systèmes anciens avaient des bus mémoires assez réduits, peu larges, en raison de contraintes techniques. Il était intéressant de limiter la taille du bus mémoire pour économiser des broches, que ce soit sur le processeur ou la mémoire RAM. Les systèmes modernes n'ont pas ce problème, l'évolution de la technologie permet au contraire d'avoir des bus mémoire assez large. Il s'agit de deux contraintes différentes : soit on économise des broches au détriment de la performance, soit on sacrifie beaucoup de fils/broches pour avoir des performances excellentes. Les deux cas donnent des contraintes très différentes, voyons comment les deux contraintes façonnent le bus mémoire. ==Les bus mémoire réduits : l'économie de broches== Faire des économies sur le bus mémoire peut viser plusieurs objectifs. Il est possible de réduire le nombre de fils du bus mémoire, de réduire le nombre de broches du processeur ou d'économiser les broches de la mémoire RAM/ROM. Les trois objectifs sont assez différents, et certains sont plus utiles que d'autres. Par exemple, un processeur a besoin de beaucoup plus de broches qu'une mémoire pour faire son travail, vu que l'interface d'un processeur est assez complexe. Les processeurs doivent donc utiliser pas mal de ruses pour économiser des broches, comme un usage de bus multiplexés, de bus d'adresse multiplexé, etc. À l'inverse, les mémoires RAM/ROM peuvent parfaitement s'en passer, vu que leur interface est assez simple. Les contraintes entre processeur et mémoires RAM/ROM sont donc opposées. Une méthode intéressante pour économiser des broches sur le processeur est d'utiliser un bus multiplexé. En théorie, cela demande d'avoir une mémoire compatible, qui tendent à être rares et plus chères. Les mémoires de faible capacité sont souvent sans bus multiplexés, alors que les processeurs à bas cout avec bus multiplexés sont plus fréquents. Heureusement, il est possible d'implémenter un bus multiplexé avec une mémoire qui ne l'est pas, ce qui permet d'avoir le meilleur des deux mondes : cela permet d'utiliser un processeur et une mémoire à bas prix, tout en ayant des processeurs avec peu de broches. Mais cela demande de faire quelques modifications sur le bus mémoire pour que cela fonctionne. Un exemple est donné dans le schéma ci-dessous. Le processeur possède un bus multiplexé, alors que la mémoire EPROM a un bus d'adresse séparé du bus de données. Dans cet exemple, le processeur ne peut faire que des lectures, vu que la mémoire est une mémoire EEPROM, mais la solution marche aussi dans le cas où la mémoire est une RAM. L'implémentation demande juste l'ajout d'un registre sur le bus d'adresse et une commande adéquate de l'entrée OE (''Output Enable''). Pour faire une lecture, le processeur procède en deux étapes, comme sur un bus multiplexé normale : l'envoi de l'adresse, puis la lecture de la donnée. * Lors de l'envoi de l'adresse, l'adresse est mémorisée dans le registre, la broche ALE étant reliée à l'entrée ''Enable'' du registre. De plus, on doit déconnecter la mémoire du bus de donnée pour éviter un conflit entre l'envoi de la donnée par la mémoire et l'envoi de l'adresse par le processeur. Pour cela, on utilise l'entrée OE (''Output Enable''). * La lecture de la donnée consiste à mettre ALE à 0, et à récupérer la donnée sur le bus. Pendant cette étape, le registre maintient l'adresse sur le bus d'adresse. Le bit OE est configuré de manière à activer la sortie de données. [[File:8051ALE.svg|centre|vignette|upright=2|8051 ALE]] ==Le ''dual-channel'', ''triple-channel'' ou ''quad-channel''== Les techniques de ''dual-channel'' permettent de combiner ensemble 2 barrettes de mémoire RAM de manière à soit gagner en performances, soit gagner en stabilité mémoire. Le cas le plus courant est celui où les deux barrettes sont combinées de manière à doubler le débit binaire de la mémoire RAM. Cependant, il existe aussi des techniques de ''memory mirroring'', qui sont une forme particulière de ''dual channel'' visant à dupliquer les données, de manière à améliorer la résistance face aux pannes. Il existe aussi des techniques de ''triple-channel'' ou ''quad-channel'', qui font la même chose mais avec respectivement 3 et 4 barrettes de RAM. Voyons ces deux techniques dans le détail. Le ''dual-channel'' a pour principe que les données peuvent être lues ou écrites dans deux barrettes de mémoire en même temps. Pour cela, le bus mémoire est doublé. Intuitivement, vous vous dites que chaque barrette a sa propre connexion au contrôleur mémoire, elle a un bus mémoire dédiée rien qu'à elle. Mais dans les faits, ce n'est pas le cas. A la place, le bus de données est élargit. Sans ''dual channel'', il y a un unique bus mémoire de 64 bits, qui est relié aux deux barrettes, chacune ayant une interface de 64 bits. Avec le ''dual-channel'', le bus mémoire passe à 128 bits. Reste à voir comment est exploité ce bus de données doublé. Pour en profiter, il faut placer les barrettes mémoire d'une certaine manière sur la carte mère. Typiquement, une carte mère ''dual channel'' a deux slots mémoires, voire quatre. Quand il y en a deux, tout va bien, il suffit de placer une barrette dans chaque slot. Mais dans le cas où la carte mère en a quatre, les slots sont d'une couleur différent pour indiquer comment les placer. Il faut placer les barrettes dans les slots de la même couleur pour profiter du ''dual channel''. ===Le ''dual-channel'' classique : de meilleures performances=== Le ''dual-channel'' classique permet de lire/écrire 128 bits en une fois, dans deux barrettes qui font 64 bits chacune. Le débit binaire de la mémoire RAM est donc doublée, car le débit binaire des deux RAM s'additionne. Du moins, dans un cas idéal, il y a des conditions pour arriver à ce maximum théorique. Par contre, si ces techniques augmentent la largeur du bus, elles n'ont pas d'effet sur le temps de latence de la mémoire. Le ''dual channel'' se contente d'agrandir chaque mot mémoire, en doublant sa taille. L'idée est de lire/écrire des mots mémoire de 128 bits, dont les 64 bits de poids faible sont lus/écrits dans la première barrette, les 64 bits de poids fort depuis la seconde barrette. Pour cela, l'adresse mémoire est envoyée aux deux barrettes en même temps. Les deux barrettes de RAM répondent à la même adresse mémoire, la même adresse de mot. Elles lisent/écrivent chacune 64 bits de leur côté. Le ''triple-channel'' fait de même avec des mots mémoire de 192 bits répartis sur trois barrettes de RAM, le ''quad-channel'' utilise des mots mémoire de 256 bits répartis sur quatre barrettes de mémoire. [[File:Arrangement horizontal.jpg|centre|vignette|upright=2.5|Arrangement horizontal]] Il y a cependant une petite subtilité, liée à l'alignement mémoire. Voyons ce qui se passe du point de vue du processeur. Pour rappel, le processeur utilise des adresses d'octets, à savoir que chaque octet de la RAM a sa propre adresse. Par contre, les SDRAM modernes sont découpées en ''mots mémoire'' de 64 bits, chacun ayant sa propre adresse mémoire. Il y a donc une différence entre les adresses manipulées par le processeur et celles que comprend la mémoire RAM. L'adresse d'octet est découpée en deux portions : l'adresse mémoire envoyée à la SDRAM, et la position de l'octet dans le mot 64 bits adressé par l'adresse mémoire. {|class="wikitable" |+ Adresse d'octet (processeur) |- ! colspan="2" | Adresse mémoire !! Position de l'octet dans un bloc de 64 bits |- | Adresse mémoire || 5 bits |} Sans ''dual channel'', la première barrette correspond à la moitié haute de la RAM, la seconde barrette correspond à la moitié basse. Pour cela, le contrôleur mémoire doit connaitre la taille de la RAM installée. De son point de vue, certains bits de l'adresse seront inutilisés, car il n'y aura pas assez de RAM installé pour avoir à les utiliser. {|class="wikitable" |+ Adresse d'octet (processeur) |- ! colspan="2" | Adresse mémoire !! Position de l'octet dans un bloc de 64 bits |- | Bits inutilisés car pas assez de RAM installé || Reste de l'adresse mémoire || 5 bits |} Vu que les deux barrettes ont la même capacité, un bit de l'adresse dit dans quelle barrette cette adresse est attribuée. Et pour du ''triple-quad channel'', c'est deux bits qui sont utilisés.Les bits en question sont les bits de poids fort de l'adresse mémoire. L'adresse est donc découpée comme suit : {|class="wikitable" |+ Adresse d'octet (processeur) |- ! colspan="2" | Adresse mémoire !! Position de l'octet dans un bloc de 64 bits |- | Bits inutilisés car pas assez de RAM installé || Numéro de barrette - 1 ou 2 bits || Adresse mémoire || 5 bits |} Avec le ''dual channel'', la répartition des adresses mémoire est différente. Intuitivement, on se dit que deux adresses mémoires consécutives seront placées dans deux barrettes différentes. Pour cela, le numéro de barrette est déplacé. Il est placé dans les bits de poids faible de l'adresse mémoire, pas dans les bits de poids fort. {|class="wikitable" |+ Adresse d'octet (processeur) |- ! colspan="6" | Sans entrelacement |- | Numéro de canal/barrette || Adresse mémoire || Position de l'octet dans un bloc de 64 bits |- | colspan="6" | |- ! colspan="6" | Avec entrelacement |- | Adresse mémoire || Numéro de canal/barrette || Position de l'octet dans un bloc de 64 bits |} Pour adresser la mémoire, seule l'adresse mémoire est utilisée. C'est elle qui est envoyée à toutes les barrettes de RAM. L'adresse mémoire est donc amputée d'un ou deux bits, puis est envoyée aux barrettes de RAM. [[File:Décodage d'adresse avec dual channel.png|centre|vignette|upright=2.5|Décodage d'adresse avec dual channel.]] ===Le ''memory mirroring''=== Le ''memory mirroring'' est utilisé sur des serveurs, ou des systèmes devant fonctionner en permanence avec un haut niveau de fiabilité. Sur de tels ordinateurs, il est très important d'éviter tout problème matériel, toute panne, mais aussi : tout corruption de données. Et c'est là que la technique du ''memory mirroring'' entre en scène. Le ''memory mirroring'' duplique les données à l'identique sur deux barrettes de mémoire. Les deux barrettes mémoires contiennent donc les mêmes données, aux mêmes endroits. Le fait de dupliquer les données a plusieurs avantages. Premièrement, si une barrette de RAM tombe en panne, l'autre peut prendre la relève, le temps qu'on remplace la barrette fautive. Il existe des serveurs qui sont conçus pour qu'on puisse rajouter ou retirer de la RAM à chaud, sans avoir à éteindre l'ordinateur. D'autres demandent que l'ordinateur soit arrêté, ce qui demande une intervention pouvant arriver dans un moment, l'ordinateur peut continuer à tourner avec une seule barrette pendant ce temps. Deuxièmement, cela permet de détecter d'une corruption de données quelconque. Les mémoires RAM ne sont pas fiables à 100%, et il est possible qu'un bit s'inverse soudainement, pour des raisons très variées, corrompant un octet dans la RAM. Pour éviter cela, les mémoires sur ce genre de serveurs utilise des codes de détection et de correction d'erreur, mais le ''memory mirroring'' est parfois utilisé en complément. Si une corruption de données survient, elle sera localisée dans une barrette. On peut alors détecter ces corruptions en comparant les données lues dans les deux barrettes. S'il y a une différence, c'est qu'il y a un problème. Reste au contrôleur mémoire à gérer le problème. Les serveurs ont souvent des cartes mères spéciales, avec de quoi mettre facilement 8 à 16 barrettes de mémoire dessus. Aussi, le ''memory mirroring'' peut être exploité à son plein potentiel. Il est possible d'avoir 16 barrettes de RAM, groupées en paires, chaque paire utilisant le ''memory mirroring''. ==Les bus mémoire à base de liaisons point à point : les barrettes FB-DIMM== Dans le cas le plus fréquent, toutes les barrettes d'un PC sont reliées au même bus mémoire, comme indiqué dans le schéma ci-dessous. Le bus mémoire est un bus parallèle, avec tous les défauts que ca implique quand on travaille à haute fréquence. Diverses contraintes électriques assez compliquées à expliquer font que les bus parallèles ont du mal à fonctionner à haute fréquence, la stabilité de transmission du signal est altérée. [[File:Bus mémoire.PNG|centre|vignette|upright=2|Bus mémoire]] [[File:FB-DIMM - principe.PNG|vignette|FB-DIMM - principe]] Les barrettes mémoire '''FB-DIMM''' contournent le problème en utilisant plusieurs liaisons point à point. Il y a deux choses à comprendre. La première est que chaque barrette est connectée à la suivante par une liaison point à point, comme indiqué ci-dessous. Il n'y a pas de bus sur lequel on connecte toutes les barrettes, mais une série de plusieurs liaisons point à point. Les commandes/données passent d'une barrette à l'autre jusqu'à destination. Par exemple, une commande SDRAM part du contrôleur mémoire, passe d'une barrette à l'autre, avant d'arriver à la barrette de destination. Même chose pour les données lues depuis les DRAM, qui partent de la barrette, passent d'une barrette à la suivante, jusqu’à arriver au contrôleur mémoire. Ensuite, les liaisons point à point sont au nombre de deux par barrette : une pour la lecture (''northbound channel''), l'autre pour l'écriture (''southbound channel''). Chaque barrette est reliée aux liaisons point à point par un circuit de contrôle qui fait l'interface. Le circuit de contrôle s'appelle l''''''Advanced Memory Buffer''''', il vérifie si chaque transmission est destinée à la barrette, et envoie la commande/donnée à la barrette suivante si ce n'est pas le cas. [[File:FB-DIMM system organization.svg|centre|vignette|upright=2|Bus mémoire pour les barrettes FB-DIMM, schéma détaillé.]] L'avantage de cette organisation est que l'on peut facilement brancher beaucoup de barrettes mémoire sur la carte mère. Avec un bus parallèle, il est difficile de mettre plus de 4 barrettes mémoire. Plus on insère de barrettes de mémoire, plus la stabilité du signal transmis avec un bus parallèle se dégrade. Cela ne pose pas de problème quand on rajoute des barrettes sur la carte mère, car elles sont conçues pour que le signal reste exploitable même si tous les slots mémoire sont remplis. Mais cela fait qu'on a rarement plus de 4 slots mémoire par carte mère. Avec des barrettes FB-DIMM, on peut monter facilement à 8 ou 16 barrettes. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=L'interface d'une mémoire électronique | prevText=L'interface d'une mémoire électronique | next=Les cellules mémoires | nextText=Les cellules mémoires }} </noinclude> 4t06g2l39xfedb26utlep0ecfclvgvb 764772 764771 2026-04-24T09:19:10Z Mewtow 31375 /* Le dual-channel classique : de meilleures performances */ 764772 wikitext text/x-wiki Le bus mémoire est en soi très simple : c'est juste un ensemble de fils, avec quelques circuits annexes qui servent à interfacer ce bus avec la processeur et la mémoire. En somme, des fils et de la ''glue logic''. Néanmoins, il y a quand même des choses à dire dessus, surtout qu'il ne prend pas la même forme sur les ordinateurs PC et sur les autres architectures. [[File:Ram-module.svg|droite|vignette|upright=0.5|Barrette de mémoire RAM.]] La plupart des PC commerciaux utilisent des '''barrettes de RAM''', qu'on peut retirer de la carte mère si besoin. Le bus mémoire est donc relié à un connecteur standardisé, appelé '''slot mémoire''', dans lequel on insère une barrette de RAM. La barrette de RAM est en soi un morceau de plastique sur lequel on place les puces mémoires, avec des broches dorées qui font contact avec le connecteur, et des interconnexions pour relier les puces aux broches dorées. [[File:Dual channel slots.jpg|centre|vignette|Slots mémoires.]] Par contre, les autres systèmes n'utilisent pas de barrettes de RAM. Ils sont fournit avec une quantité de RAM bien précise, qu'on ne peut pas upgrader. La RAM est alors soudée sur la carte mère, et le bus mémoire est une connexion directe entre processeur et mémoire RAM. Il n'y a pas de connecteur dédié, juste des puces mémoire. De nombreux ordinateurs portables font ça, mais aussi les smartphones, les microcontrôleurs ou d'autres systèmes du même genre. Les systèmes anciens avaient des bus mémoires assez réduits, peu larges, en raison de contraintes techniques. Il était intéressant de limiter la taille du bus mémoire pour économiser des broches, que ce soit sur le processeur ou la mémoire RAM. Les systèmes modernes n'ont pas ce problème, l'évolution de la technologie permet au contraire d'avoir des bus mémoire assez large. Il s'agit de deux contraintes différentes : soit on économise des broches au détriment de la performance, soit on sacrifie beaucoup de fils/broches pour avoir des performances excellentes. Les deux cas donnent des contraintes très différentes, voyons comment les deux contraintes façonnent le bus mémoire. ==Les bus mémoire réduits : l'économie de broches== Faire des économies sur le bus mémoire peut viser plusieurs objectifs. Il est possible de réduire le nombre de fils du bus mémoire, de réduire le nombre de broches du processeur ou d'économiser les broches de la mémoire RAM/ROM. Les trois objectifs sont assez différents, et certains sont plus utiles que d'autres. Par exemple, un processeur a besoin de beaucoup plus de broches qu'une mémoire pour faire son travail, vu que l'interface d'un processeur est assez complexe. Les processeurs doivent donc utiliser pas mal de ruses pour économiser des broches, comme un usage de bus multiplexés, de bus d'adresse multiplexé, etc. À l'inverse, les mémoires RAM/ROM peuvent parfaitement s'en passer, vu que leur interface est assez simple. Les contraintes entre processeur et mémoires RAM/ROM sont donc opposées. Une méthode intéressante pour économiser des broches sur le processeur est d'utiliser un bus multiplexé. En théorie, cela demande d'avoir une mémoire compatible, qui tendent à être rares et plus chères. Les mémoires de faible capacité sont souvent sans bus multiplexés, alors que les processeurs à bas cout avec bus multiplexés sont plus fréquents. Heureusement, il est possible d'implémenter un bus multiplexé avec une mémoire qui ne l'est pas, ce qui permet d'avoir le meilleur des deux mondes : cela permet d'utiliser un processeur et une mémoire à bas prix, tout en ayant des processeurs avec peu de broches. Mais cela demande de faire quelques modifications sur le bus mémoire pour que cela fonctionne. Un exemple est donné dans le schéma ci-dessous. Le processeur possède un bus multiplexé, alors que la mémoire EPROM a un bus d'adresse séparé du bus de données. Dans cet exemple, le processeur ne peut faire que des lectures, vu que la mémoire est une mémoire EEPROM, mais la solution marche aussi dans le cas où la mémoire est une RAM. L'implémentation demande juste l'ajout d'un registre sur le bus d'adresse et une commande adéquate de l'entrée OE (''Output Enable''). Pour faire une lecture, le processeur procède en deux étapes, comme sur un bus multiplexé normale : l'envoi de l'adresse, puis la lecture de la donnée. * Lors de l'envoi de l'adresse, l'adresse est mémorisée dans le registre, la broche ALE étant reliée à l'entrée ''Enable'' du registre. De plus, on doit déconnecter la mémoire du bus de donnée pour éviter un conflit entre l'envoi de la donnée par la mémoire et l'envoi de l'adresse par le processeur. Pour cela, on utilise l'entrée OE (''Output Enable''). * La lecture de la donnée consiste à mettre ALE à 0, et à récupérer la donnée sur le bus. Pendant cette étape, le registre maintient l'adresse sur le bus d'adresse. Le bit OE est configuré de manière à activer la sortie de données. [[File:8051ALE.svg|centre|vignette|upright=2|8051 ALE]] ==Le ''dual-channel'', ''triple-channel'' ou ''quad-channel''== Les techniques de ''dual-channel'' permettent de combiner ensemble 2 barrettes de mémoire RAM de manière à soit gagner en performances, soit gagner en stabilité mémoire. Le cas le plus courant est celui où les deux barrettes sont combinées de manière à doubler le débit binaire de la mémoire RAM. Cependant, il existe aussi des techniques de ''memory mirroring'', qui sont une forme particulière de ''dual channel'' visant à dupliquer les données, de manière à améliorer la résistance face aux pannes. Il existe aussi des techniques de ''triple-channel'' ou ''quad-channel'', qui font la même chose mais avec respectivement 3 et 4 barrettes de RAM. Voyons ces deux techniques dans le détail. Le ''dual-channel'' a pour principe que les données peuvent être lues ou écrites dans deux barrettes de mémoire en même temps. Pour cela, le bus mémoire est doublé. Intuitivement, vous vous dites que chaque barrette a sa propre connexion au contrôleur mémoire, elle a un bus mémoire dédiée rien qu'à elle. Mais dans les faits, ce n'est pas le cas. A la place, le bus de données est élargit. Sans ''dual channel'', il y a un unique bus mémoire de 64 bits, qui est relié aux deux barrettes, chacune ayant une interface de 64 bits. Avec le ''dual-channel'', le bus mémoire passe à 128 bits. Reste à voir comment est exploité ce bus de données doublé. Pour en profiter, il faut placer les barrettes mémoire d'une certaine manière sur la carte mère. Typiquement, une carte mère ''dual channel'' a deux slots mémoires, voire quatre. Quand il y en a deux, tout va bien, il suffit de placer une barrette dans chaque slot. Mais dans le cas où la carte mère en a quatre, les slots sont d'une couleur différent pour indiquer comment les placer. Il faut placer les barrettes dans les slots de la même couleur pour profiter du ''dual channel''. ===Le ''dual-channel'' classique : de meilleures performances=== Le ''dual-channel'' classique permet de lire/écrire 128 bits en une fois, dans deux barrettes qui font 64 bits chacune. Le débit binaire de la mémoire RAM est donc doublée, car le débit binaire des deux RAM s'additionne. Du moins, dans un cas idéal, il y a des conditions pour arriver à ce maximum théorique. Par contre, si ces techniques augmentent la largeur du bus, elles n'ont pas d'effet sur le temps de latence de la mémoire. Le ''dual channel'' se contente d'agrandir chaque mot mémoire, en doublant sa taille. L'idée est de lire/écrire des mots mémoire de 128 bits, dont les 64 bits de poids faible sont lus/écrits dans la première barrette, les 64 bits de poids fort depuis la seconde barrette. Pour cela, l'adresse mémoire est envoyée aux deux barrettes en même temps. Les deux barrettes de RAM répondent à la même adresse mémoire, la même adresse de mot. Elles lisent/écrivent chacune 64 bits de leur côté. Le ''triple-channel'' fait de même avec des mots mémoire de 192 bits répartis sur trois barrettes de RAM, le ''quad-channel'' utilise des mots mémoire de 256 bits répartis sur quatre barrettes de mémoire. [[File:Arrangement horizontal.jpg|centre|vignette|upright=2.5|Arrangement horizontal]] Il y a cependant une petite subtilité, liée à l'alignement mémoire. Voyons ce qui se passe du point de vue du processeur. Pour rappel, le processeur utilise des adresses d'octets, à savoir que chaque octet de la RAM a sa propre adresse. Par contre, les SDRAM modernes sont découpées en ''mots mémoire'' de 64 bits, chacun ayant sa propre adresse mémoire. Il y a donc une différence entre les adresses manipulées par le processeur et celles que comprend la mémoire RAM. L'adresse d'octet est découpée en deux portions : l'adresse mémoire envoyée à la SDRAM, et la position de l'octet dans le mot 64 bits adressé par l'adresse mémoire. {|class="wikitable" |+ Adresse d'octet (processeur) |- ! Adresse mémoire !! Position de l'octet dans un bloc de 64 bits |- | Adresse mémoire || 5 bits |} Sans ''dual channel'', la première barrette correspond à la moitié haute de la RAM, la seconde barrette correspond à la moitié basse. Pour cela, le contrôleur mémoire doit connaitre la taille de la RAM installée. De son point de vue, certains bits de l'adresse seront inutilisés, car il n'y aura pas assez de RAM installé pour avoir à les utiliser. {|class="wikitable" |+ Adresse d'octet (processeur) |- ! colspan="2" | Adresse mémoire !! Position de l'octet dans un bloc de 64 bits |- | Bits inutilisés car pas assez de RAM installé || Reste de l'adresse mémoire || 5 bits |} Vu que les deux barrettes ont la même capacité, un bit de l'adresse dit dans quelle barrette cette adresse est attribuée. Et pour du ''triple-quad channel'', c'est deux bits qui sont utilisés.Les bits en question sont les bits de poids fort de l'adresse mémoire. L'adresse est donc découpée comme suit : {|class="wikitable" |+ Adresse d'octet (processeur) |- ! colspan="2" | Adresse mémoire !! Position de l'octet dans un bloc de 64 bits |- | Bits inutilisés car pas assez de RAM installé || Numéro de barrette - 1 ou 2 bits || Adresse mémoire || 5 bits |} Avec le ''dual channel'', la répartition des adresses mémoire est différente. Intuitivement, on se dit que deux adresses mémoires consécutives seront placées dans deux barrettes différentes. Pour cela, le numéro de barrette est déplacé. Il est placé dans les bits de poids faible de l'adresse mémoire, pas dans les bits de poids fort. {|class="wikitable" |+ Adresse d'octet (processeur) |- ! colspan="6" | Sans entrelacement |- | Numéro de canal/barrette || Adresse mémoire || Position de l'octet dans un bloc de 64 bits |- | colspan="6" | |- ! colspan="6" | Avec entrelacement |- | Adresse mémoire || Numéro de canal/barrette || Position de l'octet dans un bloc de 64 bits |} Pour adresser la mémoire, seule l'adresse mémoire est utilisée. C'est elle qui est envoyée à toutes les barrettes de RAM. L'adresse mémoire est donc amputée d'un ou deux bits, puis est envoyée aux barrettes de RAM. [[File:Décodage d'adresse avec dual channel.png|centre|vignette|upright=2.5|Décodage d'adresse avec dual channel.]] ===Le ''memory mirroring''=== Le ''memory mirroring'' est utilisé sur des serveurs, ou des systèmes devant fonctionner en permanence avec un haut niveau de fiabilité. Sur de tels ordinateurs, il est très important d'éviter tout problème matériel, toute panne, mais aussi : tout corruption de données. Et c'est là que la technique du ''memory mirroring'' entre en scène. Le ''memory mirroring'' duplique les données à l'identique sur deux barrettes de mémoire. Les deux barrettes mémoires contiennent donc les mêmes données, aux mêmes endroits. Le fait de dupliquer les données a plusieurs avantages. Premièrement, si une barrette de RAM tombe en panne, l'autre peut prendre la relève, le temps qu'on remplace la barrette fautive. Il existe des serveurs qui sont conçus pour qu'on puisse rajouter ou retirer de la RAM à chaud, sans avoir à éteindre l'ordinateur. D'autres demandent que l'ordinateur soit arrêté, ce qui demande une intervention pouvant arriver dans un moment, l'ordinateur peut continuer à tourner avec une seule barrette pendant ce temps. Deuxièmement, cela permet de détecter d'une corruption de données quelconque. Les mémoires RAM ne sont pas fiables à 100%, et il est possible qu'un bit s'inverse soudainement, pour des raisons très variées, corrompant un octet dans la RAM. Pour éviter cela, les mémoires sur ce genre de serveurs utilise des codes de détection et de correction d'erreur, mais le ''memory mirroring'' est parfois utilisé en complément. Si une corruption de données survient, elle sera localisée dans une barrette. On peut alors détecter ces corruptions en comparant les données lues dans les deux barrettes. S'il y a une différence, c'est qu'il y a un problème. Reste au contrôleur mémoire à gérer le problème. Les serveurs ont souvent des cartes mères spéciales, avec de quoi mettre facilement 8 à 16 barrettes de mémoire dessus. Aussi, le ''memory mirroring'' peut être exploité à son plein potentiel. Il est possible d'avoir 16 barrettes de RAM, groupées en paires, chaque paire utilisant le ''memory mirroring''. ==Les bus mémoire à base de liaisons point à point : les barrettes FB-DIMM== Dans le cas le plus fréquent, toutes les barrettes d'un PC sont reliées au même bus mémoire, comme indiqué dans le schéma ci-dessous. Le bus mémoire est un bus parallèle, avec tous les défauts que ca implique quand on travaille à haute fréquence. Diverses contraintes électriques assez compliquées à expliquer font que les bus parallèles ont du mal à fonctionner à haute fréquence, la stabilité de transmission du signal est altérée. [[File:Bus mémoire.PNG|centre|vignette|upright=2|Bus mémoire]] [[File:FB-DIMM - principe.PNG|vignette|FB-DIMM - principe]] Les barrettes mémoire '''FB-DIMM''' contournent le problème en utilisant plusieurs liaisons point à point. Il y a deux choses à comprendre. La première est que chaque barrette est connectée à la suivante par une liaison point à point, comme indiqué ci-dessous. Il n'y a pas de bus sur lequel on connecte toutes les barrettes, mais une série de plusieurs liaisons point à point. Les commandes/données passent d'une barrette à l'autre jusqu'à destination. Par exemple, une commande SDRAM part du contrôleur mémoire, passe d'une barrette à l'autre, avant d'arriver à la barrette de destination. Même chose pour les données lues depuis les DRAM, qui partent de la barrette, passent d'une barrette à la suivante, jusqu’à arriver au contrôleur mémoire. Ensuite, les liaisons point à point sont au nombre de deux par barrette : une pour la lecture (''northbound channel''), l'autre pour l'écriture (''southbound channel''). Chaque barrette est reliée aux liaisons point à point par un circuit de contrôle qui fait l'interface. Le circuit de contrôle s'appelle l''''''Advanced Memory Buffer''''', il vérifie si chaque transmission est destinée à la barrette, et envoie la commande/donnée à la barrette suivante si ce n'est pas le cas. [[File:FB-DIMM system organization.svg|centre|vignette|upright=2|Bus mémoire pour les barrettes FB-DIMM, schéma détaillé.]] L'avantage de cette organisation est que l'on peut facilement brancher beaucoup de barrettes mémoire sur la carte mère. Avec un bus parallèle, il est difficile de mettre plus de 4 barrettes mémoire. Plus on insère de barrettes de mémoire, plus la stabilité du signal transmis avec un bus parallèle se dégrade. Cela ne pose pas de problème quand on rajoute des barrettes sur la carte mère, car elles sont conçues pour que le signal reste exploitable même si tous les slots mémoire sont remplis. Mais cela fait qu'on a rarement plus de 4 slots mémoire par carte mère. Avec des barrettes FB-DIMM, on peut monter facilement à 8 ou 16 barrettes. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=L'interface d'une mémoire électronique | prevText=L'interface d'une mémoire électronique | next=Les cellules mémoires | nextText=Les cellules mémoires }} </noinclude> 5n8gfdnryu9462illqu1lv9hyf4qv7p 764773 764772 2026-04-24T09:19:58Z Mewtow 31375 /* Le dual-channel classique : de meilleures performances */ 764773 wikitext text/x-wiki Le bus mémoire est en soi très simple : c'est juste un ensemble de fils, avec quelques circuits annexes qui servent à interfacer ce bus avec la processeur et la mémoire. En somme, des fils et de la ''glue logic''. Néanmoins, il y a quand même des choses à dire dessus, surtout qu'il ne prend pas la même forme sur les ordinateurs PC et sur les autres architectures. [[File:Ram-module.svg|droite|vignette|upright=0.5|Barrette de mémoire RAM.]] La plupart des PC commerciaux utilisent des '''barrettes de RAM''', qu'on peut retirer de la carte mère si besoin. Le bus mémoire est donc relié à un connecteur standardisé, appelé '''slot mémoire''', dans lequel on insère une barrette de RAM. La barrette de RAM est en soi un morceau de plastique sur lequel on place les puces mémoires, avec des broches dorées qui font contact avec le connecteur, et des interconnexions pour relier les puces aux broches dorées. [[File:Dual channel slots.jpg|centre|vignette|Slots mémoires.]] Par contre, les autres systèmes n'utilisent pas de barrettes de RAM. Ils sont fournit avec une quantité de RAM bien précise, qu'on ne peut pas upgrader. La RAM est alors soudée sur la carte mère, et le bus mémoire est une connexion directe entre processeur et mémoire RAM. Il n'y a pas de connecteur dédié, juste des puces mémoire. De nombreux ordinateurs portables font ça, mais aussi les smartphones, les microcontrôleurs ou d'autres systèmes du même genre. Les systèmes anciens avaient des bus mémoires assez réduits, peu larges, en raison de contraintes techniques. Il était intéressant de limiter la taille du bus mémoire pour économiser des broches, que ce soit sur le processeur ou la mémoire RAM. Les systèmes modernes n'ont pas ce problème, l'évolution de la technologie permet au contraire d'avoir des bus mémoire assez large. Il s'agit de deux contraintes différentes : soit on économise des broches au détriment de la performance, soit on sacrifie beaucoup de fils/broches pour avoir des performances excellentes. Les deux cas donnent des contraintes très différentes, voyons comment les deux contraintes façonnent le bus mémoire. ==Les bus mémoire réduits : l'économie de broches== Faire des économies sur le bus mémoire peut viser plusieurs objectifs. Il est possible de réduire le nombre de fils du bus mémoire, de réduire le nombre de broches du processeur ou d'économiser les broches de la mémoire RAM/ROM. Les trois objectifs sont assez différents, et certains sont plus utiles que d'autres. Par exemple, un processeur a besoin de beaucoup plus de broches qu'une mémoire pour faire son travail, vu que l'interface d'un processeur est assez complexe. Les processeurs doivent donc utiliser pas mal de ruses pour économiser des broches, comme un usage de bus multiplexés, de bus d'adresse multiplexé, etc. À l'inverse, les mémoires RAM/ROM peuvent parfaitement s'en passer, vu que leur interface est assez simple. Les contraintes entre processeur et mémoires RAM/ROM sont donc opposées. Une méthode intéressante pour économiser des broches sur le processeur est d'utiliser un bus multiplexé. En théorie, cela demande d'avoir une mémoire compatible, qui tendent à être rares et plus chères. Les mémoires de faible capacité sont souvent sans bus multiplexés, alors que les processeurs à bas cout avec bus multiplexés sont plus fréquents. Heureusement, il est possible d'implémenter un bus multiplexé avec une mémoire qui ne l'est pas, ce qui permet d'avoir le meilleur des deux mondes : cela permet d'utiliser un processeur et une mémoire à bas prix, tout en ayant des processeurs avec peu de broches. Mais cela demande de faire quelques modifications sur le bus mémoire pour que cela fonctionne. Un exemple est donné dans le schéma ci-dessous. Le processeur possède un bus multiplexé, alors que la mémoire EPROM a un bus d'adresse séparé du bus de données. Dans cet exemple, le processeur ne peut faire que des lectures, vu que la mémoire est une mémoire EEPROM, mais la solution marche aussi dans le cas où la mémoire est une RAM. L'implémentation demande juste l'ajout d'un registre sur le bus d'adresse et une commande adéquate de l'entrée OE (''Output Enable''). Pour faire une lecture, le processeur procède en deux étapes, comme sur un bus multiplexé normale : l'envoi de l'adresse, puis la lecture de la donnée. * Lors de l'envoi de l'adresse, l'adresse est mémorisée dans le registre, la broche ALE étant reliée à l'entrée ''Enable'' du registre. De plus, on doit déconnecter la mémoire du bus de donnée pour éviter un conflit entre l'envoi de la donnée par la mémoire et l'envoi de l'adresse par le processeur. Pour cela, on utilise l'entrée OE (''Output Enable''). * La lecture de la donnée consiste à mettre ALE à 0, et à récupérer la donnée sur le bus. Pendant cette étape, le registre maintient l'adresse sur le bus d'adresse. Le bit OE est configuré de manière à activer la sortie de données. [[File:8051ALE.svg|centre|vignette|upright=2|8051 ALE]] ==Le ''dual-channel'', ''triple-channel'' ou ''quad-channel''== Les techniques de ''dual-channel'' permettent de combiner ensemble 2 barrettes de mémoire RAM de manière à soit gagner en performances, soit gagner en stabilité mémoire. Le cas le plus courant est celui où les deux barrettes sont combinées de manière à doubler le débit binaire de la mémoire RAM. Cependant, il existe aussi des techniques de ''memory mirroring'', qui sont une forme particulière de ''dual channel'' visant à dupliquer les données, de manière à améliorer la résistance face aux pannes. Il existe aussi des techniques de ''triple-channel'' ou ''quad-channel'', qui font la même chose mais avec respectivement 3 et 4 barrettes de RAM. Voyons ces deux techniques dans le détail. Le ''dual-channel'' a pour principe que les données peuvent être lues ou écrites dans deux barrettes de mémoire en même temps. Pour cela, le bus mémoire est doublé. Intuitivement, vous vous dites que chaque barrette a sa propre connexion au contrôleur mémoire, elle a un bus mémoire dédiée rien qu'à elle. Mais dans les faits, ce n'est pas le cas. A la place, le bus de données est élargit. Sans ''dual channel'', il y a un unique bus mémoire de 64 bits, qui est relié aux deux barrettes, chacune ayant une interface de 64 bits. Avec le ''dual-channel'', le bus mémoire passe à 128 bits. Reste à voir comment est exploité ce bus de données doublé. Pour en profiter, il faut placer les barrettes mémoire d'une certaine manière sur la carte mère. Typiquement, une carte mère ''dual channel'' a deux slots mémoires, voire quatre. Quand il y en a deux, tout va bien, il suffit de placer une barrette dans chaque slot. Mais dans le cas où la carte mère en a quatre, les slots sont d'une couleur différent pour indiquer comment les placer. Il faut placer les barrettes dans les slots de la même couleur pour profiter du ''dual channel''. ===Le ''dual-channel'' classique : de meilleures performances=== Le ''dual-channel'' classique permet de lire/écrire 128 bits en une fois, dans deux barrettes qui font 64 bits chacune. Le débit binaire de la mémoire RAM est donc doublée, car le débit binaire des deux RAM s'additionne. Du moins, dans un cas idéal, il y a des conditions pour arriver à ce maximum théorique. Par contre, si ces techniques augmentent la largeur du bus, elles n'ont pas d'effet sur le temps de latence de la mémoire. Le ''dual channel'' se contente d'agrandir chaque mot mémoire, en doublant sa taille. L'idée est de lire/écrire des mots mémoire de 128 bits, dont les 64 bits de poids faible sont lus/écrits dans la première barrette, les 64 bits de poids fort depuis la seconde barrette. Pour cela, l'adresse mémoire est envoyée aux deux barrettes en même temps. Les deux barrettes de RAM répondent à la même adresse mémoire, la même adresse de mot. Elles lisent/écrivent chacune 64 bits de leur côté. Le ''triple-channel'' fait de même avec des mots mémoire de 192 bits répartis sur trois barrettes de RAM, le ''quad-channel'' utilise des mots mémoire de 256 bits répartis sur quatre barrettes de mémoire. [[File:Arrangement horizontal.jpg|centre|vignette|upright=2.5|Arrangement horizontal]] Il y a cependant une petite subtilité, liée à l'alignement mémoire. Voyons ce qui se passe du point de vue du processeur. Pour rappel, le processeur utilise des adresses d'octets, à savoir que chaque octet de la RAM a sa propre adresse. Par contre, les SDRAM modernes sont découpées en ''mots mémoire'' de 64 bits, chacun ayant sa propre adresse mémoire. Il y a donc une différence entre les adresses manipulées par le processeur et celles que comprend la mémoire RAM. L'adresse d'octet est découpée en deux portions : l'adresse mémoire envoyée à la SDRAM, et la position de l'octet dans le mot 64 bits adressé par l'adresse mémoire. {|class="wikitable" |+ Adresse d'octet (processeur) |- ! Adresse mémoire !! Position de l'octet dans un bloc de 64 bits |- | Adresse mémoire || 5 bits |} Sans ''dual channel'', la première barrette correspond à la moitié haute de la RAM, la seconde barrette correspond à la moitié basse. Pour cela, le contrôleur mémoire doit connaitre la taille de la RAM installée. De son point de vue, certains bits de l'adresse seront inutilisés, car il n'y aura pas assez de RAM installé pour avoir à les utiliser. {|class="wikitable" |+ Adresse d'octet (processeur) |- ! colspan="2" | Adresse mémoire !! Position de l'octet dans un bloc de 64 bits |- | Bits inutilisés car pas assez de RAM installé || Reste de l'adresse mémoire || 5 bits |} Vu que les deux barrettes ont la même capacité, un bit de l'adresse dit dans quelle barrette cette adresse est attribuée. Et pour du ''triple-quad channel'', c'est deux bits qui sont utilisés.Les bits en question sont les bits de poids fort de l'adresse mémoire. L'adresse est donc découpée comme suit : {|class="wikitable" |+ Adresse d'octet (processeur) |- ! colspan="3" | Adresse mémoire !! Position de l'octet dans un bloc de 64 bits |- | Bits inutilisés car pas assez de RAM installé || Numéro de barrette - 1 ou 2 bits || Adresse mémoire || 5 bits |} Avec le ''dual channel'', la répartition des adresses mémoire est différente. Intuitivement, on se dit que deux adresses mémoires consécutives seront placées dans deux barrettes différentes. Pour cela, le numéro de barrette est déplacé. Il est placé dans les bits de poids faible de l'adresse mémoire, pas dans les bits de poids fort. {|class="wikitable" |+ Adresse d'octet (processeur) |- ! colspan="3" | Adresse mémoire !! Position de l'octet dans un bloc de 64 bits |- | Bits inutilisés car pas assez de RAM installé || Adresse mémoire || Numéro de barrette - 1 ou 2 bits || 5 bits |} Pour adresser la mémoire, seule l'adresse mémoire est utilisée. C'est elle qui est envoyée à toutes les barrettes de RAM. L'adresse mémoire est donc amputée d'un ou deux bits, puis est envoyée aux barrettes de RAM. [[File:Décodage d'adresse avec dual channel.png|centre|vignette|upright=2.5|Décodage d'adresse avec dual channel.]] ===Le ''memory mirroring''=== Le ''memory mirroring'' est utilisé sur des serveurs, ou des systèmes devant fonctionner en permanence avec un haut niveau de fiabilité. Sur de tels ordinateurs, il est très important d'éviter tout problème matériel, toute panne, mais aussi : tout corruption de données. Et c'est là que la technique du ''memory mirroring'' entre en scène. Le ''memory mirroring'' duplique les données à l'identique sur deux barrettes de mémoire. Les deux barrettes mémoires contiennent donc les mêmes données, aux mêmes endroits. Le fait de dupliquer les données a plusieurs avantages. Premièrement, si une barrette de RAM tombe en panne, l'autre peut prendre la relève, le temps qu'on remplace la barrette fautive. Il existe des serveurs qui sont conçus pour qu'on puisse rajouter ou retirer de la RAM à chaud, sans avoir à éteindre l'ordinateur. D'autres demandent que l'ordinateur soit arrêté, ce qui demande une intervention pouvant arriver dans un moment, l'ordinateur peut continuer à tourner avec une seule barrette pendant ce temps. Deuxièmement, cela permet de détecter d'une corruption de données quelconque. Les mémoires RAM ne sont pas fiables à 100%, et il est possible qu'un bit s'inverse soudainement, pour des raisons très variées, corrompant un octet dans la RAM. Pour éviter cela, les mémoires sur ce genre de serveurs utilise des codes de détection et de correction d'erreur, mais le ''memory mirroring'' est parfois utilisé en complément. Si une corruption de données survient, elle sera localisée dans une barrette. On peut alors détecter ces corruptions en comparant les données lues dans les deux barrettes. S'il y a une différence, c'est qu'il y a un problème. Reste au contrôleur mémoire à gérer le problème. Les serveurs ont souvent des cartes mères spéciales, avec de quoi mettre facilement 8 à 16 barrettes de mémoire dessus. Aussi, le ''memory mirroring'' peut être exploité à son plein potentiel. Il est possible d'avoir 16 barrettes de RAM, groupées en paires, chaque paire utilisant le ''memory mirroring''. ==Les bus mémoire à base de liaisons point à point : les barrettes FB-DIMM== Dans le cas le plus fréquent, toutes les barrettes d'un PC sont reliées au même bus mémoire, comme indiqué dans le schéma ci-dessous. Le bus mémoire est un bus parallèle, avec tous les défauts que ca implique quand on travaille à haute fréquence. Diverses contraintes électriques assez compliquées à expliquer font que les bus parallèles ont du mal à fonctionner à haute fréquence, la stabilité de transmission du signal est altérée. [[File:Bus mémoire.PNG|centre|vignette|upright=2|Bus mémoire]] [[File:FB-DIMM - principe.PNG|vignette|FB-DIMM - principe]] Les barrettes mémoire '''FB-DIMM''' contournent le problème en utilisant plusieurs liaisons point à point. Il y a deux choses à comprendre. La première est que chaque barrette est connectée à la suivante par une liaison point à point, comme indiqué ci-dessous. Il n'y a pas de bus sur lequel on connecte toutes les barrettes, mais une série de plusieurs liaisons point à point. Les commandes/données passent d'une barrette à l'autre jusqu'à destination. Par exemple, une commande SDRAM part du contrôleur mémoire, passe d'une barrette à l'autre, avant d'arriver à la barrette de destination. Même chose pour les données lues depuis les DRAM, qui partent de la barrette, passent d'une barrette à la suivante, jusqu’à arriver au contrôleur mémoire. Ensuite, les liaisons point à point sont au nombre de deux par barrette : une pour la lecture (''northbound channel''), l'autre pour l'écriture (''southbound channel''). Chaque barrette est reliée aux liaisons point à point par un circuit de contrôle qui fait l'interface. Le circuit de contrôle s'appelle l''''''Advanced Memory Buffer''''', il vérifie si chaque transmission est destinée à la barrette, et envoie la commande/donnée à la barrette suivante si ce n'est pas le cas. [[File:FB-DIMM system organization.svg|centre|vignette|upright=2|Bus mémoire pour les barrettes FB-DIMM, schéma détaillé.]] L'avantage de cette organisation est que l'on peut facilement brancher beaucoup de barrettes mémoire sur la carte mère. Avec un bus parallèle, il est difficile de mettre plus de 4 barrettes mémoire. Plus on insère de barrettes de mémoire, plus la stabilité du signal transmis avec un bus parallèle se dégrade. Cela ne pose pas de problème quand on rajoute des barrettes sur la carte mère, car elles sont conçues pour que le signal reste exploitable même si tous les slots mémoire sont remplis. Mais cela fait qu'on a rarement plus de 4 slots mémoire par carte mère. Avec des barrettes FB-DIMM, on peut monter facilement à 8 ou 16 barrettes. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=L'interface d'une mémoire électronique | prevText=L'interface d'une mémoire électronique | next=Les cellules mémoires | nextText=Les cellules mémoires }} </noinclude> 1o0hbht3bntexzybaiubh6syt7kkqbl 764775 764773 2026-04-24T09:21:28Z Mewtow 31375 /* Le dual-channel classique : de meilleures performances */ 764775 wikitext text/x-wiki Le bus mémoire est en soi très simple : c'est juste un ensemble de fils, avec quelques circuits annexes qui servent à interfacer ce bus avec la processeur et la mémoire. En somme, des fils et de la ''glue logic''. Néanmoins, il y a quand même des choses à dire dessus, surtout qu'il ne prend pas la même forme sur les ordinateurs PC et sur les autres architectures. [[File:Ram-module.svg|droite|vignette|upright=0.5|Barrette de mémoire RAM.]] La plupart des PC commerciaux utilisent des '''barrettes de RAM''', qu'on peut retirer de la carte mère si besoin. Le bus mémoire est donc relié à un connecteur standardisé, appelé '''slot mémoire''', dans lequel on insère une barrette de RAM. La barrette de RAM est en soi un morceau de plastique sur lequel on place les puces mémoires, avec des broches dorées qui font contact avec le connecteur, et des interconnexions pour relier les puces aux broches dorées. [[File:Dual channel slots.jpg|centre|vignette|Slots mémoires.]] Par contre, les autres systèmes n'utilisent pas de barrettes de RAM. Ils sont fournit avec une quantité de RAM bien précise, qu'on ne peut pas upgrader. La RAM est alors soudée sur la carte mère, et le bus mémoire est une connexion directe entre processeur et mémoire RAM. Il n'y a pas de connecteur dédié, juste des puces mémoire. De nombreux ordinateurs portables font ça, mais aussi les smartphones, les microcontrôleurs ou d'autres systèmes du même genre. Les systèmes anciens avaient des bus mémoires assez réduits, peu larges, en raison de contraintes techniques. Il était intéressant de limiter la taille du bus mémoire pour économiser des broches, que ce soit sur le processeur ou la mémoire RAM. Les systèmes modernes n'ont pas ce problème, l'évolution de la technologie permet au contraire d'avoir des bus mémoire assez large. Il s'agit de deux contraintes différentes : soit on économise des broches au détriment de la performance, soit on sacrifie beaucoup de fils/broches pour avoir des performances excellentes. Les deux cas donnent des contraintes très différentes, voyons comment les deux contraintes façonnent le bus mémoire. ==Les bus mémoire réduits : l'économie de broches== Faire des économies sur le bus mémoire peut viser plusieurs objectifs. Il est possible de réduire le nombre de fils du bus mémoire, de réduire le nombre de broches du processeur ou d'économiser les broches de la mémoire RAM/ROM. Les trois objectifs sont assez différents, et certains sont plus utiles que d'autres. Par exemple, un processeur a besoin de beaucoup plus de broches qu'une mémoire pour faire son travail, vu que l'interface d'un processeur est assez complexe. Les processeurs doivent donc utiliser pas mal de ruses pour économiser des broches, comme un usage de bus multiplexés, de bus d'adresse multiplexé, etc. À l'inverse, les mémoires RAM/ROM peuvent parfaitement s'en passer, vu que leur interface est assez simple. Les contraintes entre processeur et mémoires RAM/ROM sont donc opposées. Une méthode intéressante pour économiser des broches sur le processeur est d'utiliser un bus multiplexé. En théorie, cela demande d'avoir une mémoire compatible, qui tendent à être rares et plus chères. Les mémoires de faible capacité sont souvent sans bus multiplexés, alors que les processeurs à bas cout avec bus multiplexés sont plus fréquents. Heureusement, il est possible d'implémenter un bus multiplexé avec une mémoire qui ne l'est pas, ce qui permet d'avoir le meilleur des deux mondes : cela permet d'utiliser un processeur et une mémoire à bas prix, tout en ayant des processeurs avec peu de broches. Mais cela demande de faire quelques modifications sur le bus mémoire pour que cela fonctionne. Un exemple est donné dans le schéma ci-dessous. Le processeur possède un bus multiplexé, alors que la mémoire EPROM a un bus d'adresse séparé du bus de données. Dans cet exemple, le processeur ne peut faire que des lectures, vu que la mémoire est une mémoire EEPROM, mais la solution marche aussi dans le cas où la mémoire est une RAM. L'implémentation demande juste l'ajout d'un registre sur le bus d'adresse et une commande adéquate de l'entrée OE (''Output Enable''). Pour faire une lecture, le processeur procède en deux étapes, comme sur un bus multiplexé normale : l'envoi de l'adresse, puis la lecture de la donnée. * Lors de l'envoi de l'adresse, l'adresse est mémorisée dans le registre, la broche ALE étant reliée à l'entrée ''Enable'' du registre. De plus, on doit déconnecter la mémoire du bus de donnée pour éviter un conflit entre l'envoi de la donnée par la mémoire et l'envoi de l'adresse par le processeur. Pour cela, on utilise l'entrée OE (''Output Enable''). * La lecture de la donnée consiste à mettre ALE à 0, et à récupérer la donnée sur le bus. Pendant cette étape, le registre maintient l'adresse sur le bus d'adresse. Le bit OE est configuré de manière à activer la sortie de données. [[File:8051ALE.svg|centre|vignette|upright=2|8051 ALE]] ==Le ''dual-channel'', ''triple-channel'' ou ''quad-channel''== Les techniques de ''dual-channel'' permettent de combiner ensemble 2 barrettes de mémoire RAM de manière à soit gagner en performances, soit gagner en stabilité mémoire. Le cas le plus courant est celui où les deux barrettes sont combinées de manière à doubler le débit binaire de la mémoire RAM. Cependant, il existe aussi des techniques de ''memory mirroring'', qui sont une forme particulière de ''dual channel'' visant à dupliquer les données, de manière à améliorer la résistance face aux pannes. Il existe aussi des techniques de ''triple-channel'' ou ''quad-channel'', qui font la même chose mais avec respectivement 3 et 4 barrettes de RAM. Voyons ces deux techniques dans le détail. Le ''dual-channel'' a pour principe que les données peuvent être lues ou écrites dans deux barrettes de mémoire en même temps. Pour cela, le bus mémoire est doublé. Intuitivement, vous vous dites que chaque barrette a sa propre connexion au contrôleur mémoire, elle a un bus mémoire dédiée rien qu'à elle. Mais dans les faits, ce n'est pas le cas. A la place, le bus de données est élargit. Sans ''dual channel'', il y a un unique bus mémoire de 64 bits, qui est relié aux deux barrettes, chacune ayant une interface de 64 bits. Avec le ''dual-channel'', le bus mémoire passe à 128 bits. Reste à voir comment est exploité ce bus de données doublé. Pour en profiter, il faut placer les barrettes mémoire d'une certaine manière sur la carte mère. Typiquement, une carte mère ''dual channel'' a deux slots mémoires, voire quatre. Quand il y en a deux, tout va bien, il suffit de placer une barrette dans chaque slot. Mais dans le cas où la carte mère en a quatre, les slots sont d'une couleur différent pour indiquer comment les placer. Il faut placer les barrettes dans les slots de la même couleur pour profiter du ''dual channel''. ===Le ''dual-channel'' classique : de meilleures performances=== Le ''dual-channel'' classique permet de lire/écrire 128 bits en une fois, dans deux barrettes qui font 64 bits chacune. Le débit binaire de la mémoire RAM est donc doublée, car le débit binaire des deux RAM s'additionne. Du moins, dans un cas idéal, il y a des conditions pour arriver à ce maximum théorique. Par contre, si ces techniques augmentent la largeur du bus, elles n'ont pas d'effet sur le temps de latence de la mémoire. Le ''dual channel'' se contente d'agrandir chaque mot mémoire, en doublant sa taille. L'idée est de lire/écrire des mots mémoire de 128 bits, dont les 64 bits de poids faible sont lus/écrits dans la première barrette, les 64 bits de poids fort depuis la seconde barrette. Pour cela, l'adresse mémoire est envoyée aux deux barrettes en même temps. Les deux barrettes de RAM répondent à la même adresse mémoire, la même adresse de mot. Elles lisent/écrivent chacune 64 bits de leur côté. Le ''triple-channel'' fait de même avec des mots mémoire de 192 bits répartis sur trois barrettes de RAM, le ''quad-channel'' utilise des mots mémoire de 256 bits répartis sur quatre barrettes de mémoire. [[File:Arrangement horizontal.jpg|centre|vignette|upright=2.5|Arrangement horizontal]] Il y a cependant une petite subtilité, liée à l'alignement mémoire. Voyons ce qui se passe du point de vue du processeur. Pour rappel, le processeur utilise des adresses d'octets, à savoir que chaque octet de la RAM a sa propre adresse. Par contre, les SDRAM modernes sont découpées en ''mots mémoire'' de 64 bits, chacun ayant sa propre adresse mémoire. Il y a donc une différence entre les adresses manipulées par le processeur et celles que comprend la mémoire RAM. L'adresse d'octet est découpée en deux portions : l'adresse mémoire envoyée à la SDRAM, et la position de l'octet dans le mot 64 bits adressé par l'adresse mémoire. {|class="wikitable" |+ Adresse d'octet (processeur) |- ! Adresse mémoire !! Position de l'octet dans un bloc de 64 bits |- | Adresse mémoire || 5 bits |} Sans ''dual channel'', la première barrette correspond à la moitié haute de la RAM, la seconde barrette correspond à la moitié basse. Pour cela, le contrôleur mémoire doit connaitre la taille de la RAM installée. De son point de vue, certains bits de l'adresse seront inutilisés, car il n'y aura pas assez de RAM installé pour avoir à les utiliser. {|class="wikitable" |+ Adresse d'octet (processeur) |- ! colspan="2" | Adresse mémoire !! Position de l'octet dans un bloc de 64 bits |- | Bits inutilisés car pas assez de RAM installé || Reste de l'adresse mémoire || 5 bits |} Vu que les deux barrettes ont la même capacité, un bit de l'adresse dit dans quelle barrette cette adresse est attribuée. Et pour du ''triple-quad channel'', c'est deux bits qui sont utilisés.Les bits en question sont les bits de poids fort de l'adresse mémoire. L'adresse est donc découpée comme suit : {|class="wikitable" |+ Adresse d'octet (processeur) |- ! colspan="3" | Adresse mémoire !! Position de l'octet dans un bloc de 64 bits |- | Bits inutilisés car pas assez de RAM installé || bgcolor="#FFFF00" | Numéro de barrette - 1 ou 2 bits || Adresse mémoire || 5 bits |} Avec le ''dual channel'', la répartition des adresses mémoire est différente. Intuitivement, on se dit que deux adresses mémoires consécutives seront placées dans deux barrettes différentes. Pour cela, le numéro de barrette est déplacé. Il est placé dans les bits de poids faible de l'adresse mémoire, pas dans les bits de poids fort. {|class="wikitable" |+ Adresse d'octet (processeur) |- ! colspan="3" | Adresse mémoire !! Position de l'octet dans un bloc de 64 bits |- | Bits inutilisés car pas assez de RAM installé || Adresse mémoire || bgcolor="#FFFF00" | Numéro de barrette - 1 ou 2 bits || 5 bits |} Pour adresser la mémoire, seule l'adresse mémoire est utilisée. C'est elle qui est envoyée à toutes les barrettes de RAM. L'adresse mémoire est donc amputée d'un ou deux bits, puis est envoyée aux barrettes de RAM. [[File:Décodage d'adresse avec dual channel.png|centre|vignette|upright=2.5|Décodage d'adresse avec dual channel.]] ===Le ''memory mirroring''=== Le ''memory mirroring'' est utilisé sur des serveurs, ou des systèmes devant fonctionner en permanence avec un haut niveau de fiabilité. Sur de tels ordinateurs, il est très important d'éviter tout problème matériel, toute panne, mais aussi : tout corruption de données. Et c'est là que la technique du ''memory mirroring'' entre en scène. Le ''memory mirroring'' duplique les données à l'identique sur deux barrettes de mémoire. Les deux barrettes mémoires contiennent donc les mêmes données, aux mêmes endroits. Le fait de dupliquer les données a plusieurs avantages. Premièrement, si une barrette de RAM tombe en panne, l'autre peut prendre la relève, le temps qu'on remplace la barrette fautive. Il existe des serveurs qui sont conçus pour qu'on puisse rajouter ou retirer de la RAM à chaud, sans avoir à éteindre l'ordinateur. D'autres demandent que l'ordinateur soit arrêté, ce qui demande une intervention pouvant arriver dans un moment, l'ordinateur peut continuer à tourner avec une seule barrette pendant ce temps. Deuxièmement, cela permet de détecter d'une corruption de données quelconque. Les mémoires RAM ne sont pas fiables à 100%, et il est possible qu'un bit s'inverse soudainement, pour des raisons très variées, corrompant un octet dans la RAM. Pour éviter cela, les mémoires sur ce genre de serveurs utilise des codes de détection et de correction d'erreur, mais le ''memory mirroring'' est parfois utilisé en complément. Si une corruption de données survient, elle sera localisée dans une barrette. On peut alors détecter ces corruptions en comparant les données lues dans les deux barrettes. S'il y a une différence, c'est qu'il y a un problème. Reste au contrôleur mémoire à gérer le problème. Les serveurs ont souvent des cartes mères spéciales, avec de quoi mettre facilement 8 à 16 barrettes de mémoire dessus. Aussi, le ''memory mirroring'' peut être exploité à son plein potentiel. Il est possible d'avoir 16 barrettes de RAM, groupées en paires, chaque paire utilisant le ''memory mirroring''. ==Les bus mémoire à base de liaisons point à point : les barrettes FB-DIMM== Dans le cas le plus fréquent, toutes les barrettes d'un PC sont reliées au même bus mémoire, comme indiqué dans le schéma ci-dessous. Le bus mémoire est un bus parallèle, avec tous les défauts que ca implique quand on travaille à haute fréquence. Diverses contraintes électriques assez compliquées à expliquer font que les bus parallèles ont du mal à fonctionner à haute fréquence, la stabilité de transmission du signal est altérée. [[File:Bus mémoire.PNG|centre|vignette|upright=2|Bus mémoire]] [[File:FB-DIMM - principe.PNG|vignette|FB-DIMM - principe]] Les barrettes mémoire '''FB-DIMM''' contournent le problème en utilisant plusieurs liaisons point à point. Il y a deux choses à comprendre. La première est que chaque barrette est connectée à la suivante par une liaison point à point, comme indiqué ci-dessous. Il n'y a pas de bus sur lequel on connecte toutes les barrettes, mais une série de plusieurs liaisons point à point. Les commandes/données passent d'une barrette à l'autre jusqu'à destination. Par exemple, une commande SDRAM part du contrôleur mémoire, passe d'une barrette à l'autre, avant d'arriver à la barrette de destination. Même chose pour les données lues depuis les DRAM, qui partent de la barrette, passent d'une barrette à la suivante, jusqu’à arriver au contrôleur mémoire. Ensuite, les liaisons point à point sont au nombre de deux par barrette : une pour la lecture (''northbound channel''), l'autre pour l'écriture (''southbound channel''). Chaque barrette est reliée aux liaisons point à point par un circuit de contrôle qui fait l'interface. Le circuit de contrôle s'appelle l''''''Advanced Memory Buffer''''', il vérifie si chaque transmission est destinée à la barrette, et envoie la commande/donnée à la barrette suivante si ce n'est pas le cas. [[File:FB-DIMM system organization.svg|centre|vignette|upright=2|Bus mémoire pour les barrettes FB-DIMM, schéma détaillé.]] L'avantage de cette organisation est que l'on peut facilement brancher beaucoup de barrettes mémoire sur la carte mère. Avec un bus parallèle, il est difficile de mettre plus de 4 barrettes mémoire. Plus on insère de barrettes de mémoire, plus la stabilité du signal transmis avec un bus parallèle se dégrade. Cela ne pose pas de problème quand on rajoute des barrettes sur la carte mère, car elles sont conçues pour que le signal reste exploitable même si tous les slots mémoire sont remplis. Mais cela fait qu'on a rarement plus de 4 slots mémoire par carte mère. Avec des barrettes FB-DIMM, on peut monter facilement à 8 ou 16 barrettes. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=L'interface d'une mémoire électronique | prevText=L'interface d'une mémoire électronique | next=Les cellules mémoires | nextText=Les cellules mémoires }} </noinclude> dq2nwgtzxg8seqwuuse6752org9hajf 764776 764775 2026-04-24T09:21:57Z Mewtow 31375 /* Le dual-channel classique : de meilleures performances */ 764776 wikitext text/x-wiki Le bus mémoire est en soi très simple : c'est juste un ensemble de fils, avec quelques circuits annexes qui servent à interfacer ce bus avec la processeur et la mémoire. En somme, des fils et de la ''glue logic''. Néanmoins, il y a quand même des choses à dire dessus, surtout qu'il ne prend pas la même forme sur les ordinateurs PC et sur les autres architectures. [[File:Ram-module.svg|droite|vignette|upright=0.5|Barrette de mémoire RAM.]] La plupart des PC commerciaux utilisent des '''barrettes de RAM''', qu'on peut retirer de la carte mère si besoin. Le bus mémoire est donc relié à un connecteur standardisé, appelé '''slot mémoire''', dans lequel on insère une barrette de RAM. La barrette de RAM est en soi un morceau de plastique sur lequel on place les puces mémoires, avec des broches dorées qui font contact avec le connecteur, et des interconnexions pour relier les puces aux broches dorées. [[File:Dual channel slots.jpg|centre|vignette|Slots mémoires.]] Par contre, les autres systèmes n'utilisent pas de barrettes de RAM. Ils sont fournit avec une quantité de RAM bien précise, qu'on ne peut pas upgrader. La RAM est alors soudée sur la carte mère, et le bus mémoire est une connexion directe entre processeur et mémoire RAM. Il n'y a pas de connecteur dédié, juste des puces mémoire. De nombreux ordinateurs portables font ça, mais aussi les smartphones, les microcontrôleurs ou d'autres systèmes du même genre. Les systèmes anciens avaient des bus mémoires assez réduits, peu larges, en raison de contraintes techniques. Il était intéressant de limiter la taille du bus mémoire pour économiser des broches, que ce soit sur le processeur ou la mémoire RAM. Les systèmes modernes n'ont pas ce problème, l'évolution de la technologie permet au contraire d'avoir des bus mémoire assez large. Il s'agit de deux contraintes différentes : soit on économise des broches au détriment de la performance, soit on sacrifie beaucoup de fils/broches pour avoir des performances excellentes. Les deux cas donnent des contraintes très différentes, voyons comment les deux contraintes façonnent le bus mémoire. ==Les bus mémoire réduits : l'économie de broches== Faire des économies sur le bus mémoire peut viser plusieurs objectifs. Il est possible de réduire le nombre de fils du bus mémoire, de réduire le nombre de broches du processeur ou d'économiser les broches de la mémoire RAM/ROM. Les trois objectifs sont assez différents, et certains sont plus utiles que d'autres. Par exemple, un processeur a besoin de beaucoup plus de broches qu'une mémoire pour faire son travail, vu que l'interface d'un processeur est assez complexe. Les processeurs doivent donc utiliser pas mal de ruses pour économiser des broches, comme un usage de bus multiplexés, de bus d'adresse multiplexé, etc. À l'inverse, les mémoires RAM/ROM peuvent parfaitement s'en passer, vu que leur interface est assez simple. Les contraintes entre processeur et mémoires RAM/ROM sont donc opposées. Une méthode intéressante pour économiser des broches sur le processeur est d'utiliser un bus multiplexé. En théorie, cela demande d'avoir une mémoire compatible, qui tendent à être rares et plus chères. Les mémoires de faible capacité sont souvent sans bus multiplexés, alors que les processeurs à bas cout avec bus multiplexés sont plus fréquents. Heureusement, il est possible d'implémenter un bus multiplexé avec une mémoire qui ne l'est pas, ce qui permet d'avoir le meilleur des deux mondes : cela permet d'utiliser un processeur et une mémoire à bas prix, tout en ayant des processeurs avec peu de broches. Mais cela demande de faire quelques modifications sur le bus mémoire pour que cela fonctionne. Un exemple est donné dans le schéma ci-dessous. Le processeur possède un bus multiplexé, alors que la mémoire EPROM a un bus d'adresse séparé du bus de données. Dans cet exemple, le processeur ne peut faire que des lectures, vu que la mémoire est une mémoire EEPROM, mais la solution marche aussi dans le cas où la mémoire est une RAM. L'implémentation demande juste l'ajout d'un registre sur le bus d'adresse et une commande adéquate de l'entrée OE (''Output Enable''). Pour faire une lecture, le processeur procède en deux étapes, comme sur un bus multiplexé normale : l'envoi de l'adresse, puis la lecture de la donnée. * Lors de l'envoi de l'adresse, l'adresse est mémorisée dans le registre, la broche ALE étant reliée à l'entrée ''Enable'' du registre. De plus, on doit déconnecter la mémoire du bus de donnée pour éviter un conflit entre l'envoi de la donnée par la mémoire et l'envoi de l'adresse par le processeur. Pour cela, on utilise l'entrée OE (''Output Enable''). * La lecture de la donnée consiste à mettre ALE à 0, et à récupérer la donnée sur le bus. Pendant cette étape, le registre maintient l'adresse sur le bus d'adresse. Le bit OE est configuré de manière à activer la sortie de données. [[File:8051ALE.svg|centre|vignette|upright=2|8051 ALE]] ==Le ''dual-channel'', ''triple-channel'' ou ''quad-channel''== Les techniques de ''dual-channel'' permettent de combiner ensemble 2 barrettes de mémoire RAM de manière à soit gagner en performances, soit gagner en stabilité mémoire. Le cas le plus courant est celui où les deux barrettes sont combinées de manière à doubler le débit binaire de la mémoire RAM. Cependant, il existe aussi des techniques de ''memory mirroring'', qui sont une forme particulière de ''dual channel'' visant à dupliquer les données, de manière à améliorer la résistance face aux pannes. Il existe aussi des techniques de ''triple-channel'' ou ''quad-channel'', qui font la même chose mais avec respectivement 3 et 4 barrettes de RAM. Voyons ces deux techniques dans le détail. Le ''dual-channel'' a pour principe que les données peuvent être lues ou écrites dans deux barrettes de mémoire en même temps. Pour cela, le bus mémoire est doublé. Intuitivement, vous vous dites que chaque barrette a sa propre connexion au contrôleur mémoire, elle a un bus mémoire dédiée rien qu'à elle. Mais dans les faits, ce n'est pas le cas. A la place, le bus de données est élargit. Sans ''dual channel'', il y a un unique bus mémoire de 64 bits, qui est relié aux deux barrettes, chacune ayant une interface de 64 bits. Avec le ''dual-channel'', le bus mémoire passe à 128 bits. Reste à voir comment est exploité ce bus de données doublé. Pour en profiter, il faut placer les barrettes mémoire d'une certaine manière sur la carte mère. Typiquement, une carte mère ''dual channel'' a deux slots mémoires, voire quatre. Quand il y en a deux, tout va bien, il suffit de placer une barrette dans chaque slot. Mais dans le cas où la carte mère en a quatre, les slots sont d'une couleur différent pour indiquer comment les placer. Il faut placer les barrettes dans les slots de la même couleur pour profiter du ''dual channel''. ===Le ''dual-channel'' classique : de meilleures performances=== Le ''dual-channel'' classique permet de lire/écrire 128 bits en une fois, dans deux barrettes qui font 64 bits chacune. Le débit binaire de la mémoire RAM est donc doublée, car le débit binaire des deux RAM s'additionne. Du moins, dans un cas idéal, il y a des conditions pour arriver à ce maximum théorique. Par contre, si ces techniques augmentent la largeur du bus, elles n'ont pas d'effet sur le temps de latence de la mémoire. Le ''dual channel'' se contente d'agrandir chaque mot mémoire, en doublant sa taille. L'idée est de lire/écrire des mots mémoire de 128 bits, dont les 64 bits de poids faible sont lus/écrits dans la première barrette, les 64 bits de poids fort depuis la seconde barrette. Pour cela, l'adresse mémoire est envoyée aux deux barrettes en même temps. Les deux barrettes de RAM répondent à la même adresse mémoire, la même adresse de mot. Elles lisent/écrivent chacune 64 bits de leur côté. Le ''triple-channel'' fait de même avec des mots mémoire de 192 bits répartis sur trois barrettes de RAM, le ''quad-channel'' utilise des mots mémoire de 256 bits répartis sur quatre barrettes de mémoire. [[File:Arrangement horizontal.jpg|centre|vignette|upright=2.5|Arrangement horizontal]] Il y a cependant une petite subtilité, liée à l'alignement mémoire. Voyons ce qui se passe du point de vue du processeur. Pour rappel, le processeur utilise des adresses d'octets, à savoir que chaque octet de la RAM a sa propre adresse. Par contre, les SDRAM modernes sont découpées en ''mots mémoire'' de 64 bits, chacun ayant sa propre adresse mémoire. Il y a donc une différence entre les adresses manipulées par le processeur et celles que comprend la mémoire RAM. L'adresse d'octet est découpée en deux portions : l'adresse mémoire envoyée à la SDRAM, et la position de l'octet dans le mot 64 bits adressé par l'adresse mémoire. {|class="wikitable" |+ Adresse d'octet (processeur) |- ! Adresse mémoire !! Position de l'octet dans un bloc de 64 bits |- | Adresse mémoire || 5 bits |} Sans ''dual channel'', la première barrette correspond à la moitié haute de la RAM, la seconde barrette correspond à la moitié basse. Pour cela, le contrôleur mémoire doit connaitre la taille de la RAM installée. De son point de vue, certains bits de l'adresse seront inutilisés, car il n'y aura pas assez de RAM installé pour avoir à les utiliser. {|class="wikitable" |+ Adresse d'octet (processeur) |- ! colspan="2" | Adresse mémoire !! Position de l'octet dans un bloc de 64 bits |- | bgcolor="#FFFFFF" | Bits inutilisés car pas assez de RAM installé || Reste de l'adresse mémoire || 5 bits |} Vu que les deux barrettes ont la même capacité, un bit de l'adresse dit dans quelle barrette cette adresse est attribuée. Et pour du ''triple-quad channel'', c'est deux bits qui sont utilisés.Les bits en question sont les bits de poids fort de l'adresse mémoire. L'adresse est donc découpée comme suit : {|class="wikitable" |+ Adresse d'octet (processeur) |- ! colspan="3" | Adresse mémoire !! Position de l'octet dans un bloc de 64 bits |- | bgcolor="#FFFFFF" | Bits inutilisés car pas assez de RAM installé || bgcolor="#FFFF00" | Numéro de barrette - 1 ou 2 bits || Adresse mémoire || 5 bits |} Avec le ''dual channel'', la répartition des adresses mémoire est différente. Intuitivement, on se dit que deux adresses mémoires consécutives seront placées dans deux barrettes différentes. Pour cela, le numéro de barrette est déplacé. Il est placé dans les bits de poids faible de l'adresse mémoire, pas dans les bits de poids fort. {|class="wikitable" |+ Adresse d'octet (processeur) |- ! colspan="3" | Adresse mémoire !! Position de l'octet dans un bloc de 64 bits |- | Bits inutilisés car pas assez de RAM installé || Adresse mémoire || bgcolor="#FFFF00" | Numéro de barrette - 1 ou 2 bits || 5 bits |} Pour adresser la mémoire, seule l'adresse mémoire est utilisée. C'est elle qui est envoyée à toutes les barrettes de RAM. L'adresse mémoire est donc amputée d'un ou deux bits, puis est envoyée aux barrettes de RAM. [[File:Décodage d'adresse avec dual channel.png|centre|vignette|upright=2.5|Décodage d'adresse avec dual channel.]] ===Le ''memory mirroring''=== Le ''memory mirroring'' est utilisé sur des serveurs, ou des systèmes devant fonctionner en permanence avec un haut niveau de fiabilité. Sur de tels ordinateurs, il est très important d'éviter tout problème matériel, toute panne, mais aussi : tout corruption de données. Et c'est là que la technique du ''memory mirroring'' entre en scène. Le ''memory mirroring'' duplique les données à l'identique sur deux barrettes de mémoire. Les deux barrettes mémoires contiennent donc les mêmes données, aux mêmes endroits. Le fait de dupliquer les données a plusieurs avantages. Premièrement, si une barrette de RAM tombe en panne, l'autre peut prendre la relève, le temps qu'on remplace la barrette fautive. Il existe des serveurs qui sont conçus pour qu'on puisse rajouter ou retirer de la RAM à chaud, sans avoir à éteindre l'ordinateur. D'autres demandent que l'ordinateur soit arrêté, ce qui demande une intervention pouvant arriver dans un moment, l'ordinateur peut continuer à tourner avec une seule barrette pendant ce temps. Deuxièmement, cela permet de détecter d'une corruption de données quelconque. Les mémoires RAM ne sont pas fiables à 100%, et il est possible qu'un bit s'inverse soudainement, pour des raisons très variées, corrompant un octet dans la RAM. Pour éviter cela, les mémoires sur ce genre de serveurs utilise des codes de détection et de correction d'erreur, mais le ''memory mirroring'' est parfois utilisé en complément. Si une corruption de données survient, elle sera localisée dans une barrette. On peut alors détecter ces corruptions en comparant les données lues dans les deux barrettes. S'il y a une différence, c'est qu'il y a un problème. Reste au contrôleur mémoire à gérer le problème. Les serveurs ont souvent des cartes mères spéciales, avec de quoi mettre facilement 8 à 16 barrettes de mémoire dessus. Aussi, le ''memory mirroring'' peut être exploité à son plein potentiel. Il est possible d'avoir 16 barrettes de RAM, groupées en paires, chaque paire utilisant le ''memory mirroring''. ==Les bus mémoire à base de liaisons point à point : les barrettes FB-DIMM== Dans le cas le plus fréquent, toutes les barrettes d'un PC sont reliées au même bus mémoire, comme indiqué dans le schéma ci-dessous. Le bus mémoire est un bus parallèle, avec tous les défauts que ca implique quand on travaille à haute fréquence. Diverses contraintes électriques assez compliquées à expliquer font que les bus parallèles ont du mal à fonctionner à haute fréquence, la stabilité de transmission du signal est altérée. [[File:Bus mémoire.PNG|centre|vignette|upright=2|Bus mémoire]] [[File:FB-DIMM - principe.PNG|vignette|FB-DIMM - principe]] Les barrettes mémoire '''FB-DIMM''' contournent le problème en utilisant plusieurs liaisons point à point. Il y a deux choses à comprendre. La première est que chaque barrette est connectée à la suivante par une liaison point à point, comme indiqué ci-dessous. Il n'y a pas de bus sur lequel on connecte toutes les barrettes, mais une série de plusieurs liaisons point à point. Les commandes/données passent d'une barrette à l'autre jusqu'à destination. Par exemple, une commande SDRAM part du contrôleur mémoire, passe d'une barrette à l'autre, avant d'arriver à la barrette de destination. Même chose pour les données lues depuis les DRAM, qui partent de la barrette, passent d'une barrette à la suivante, jusqu’à arriver au contrôleur mémoire. Ensuite, les liaisons point à point sont au nombre de deux par barrette : une pour la lecture (''northbound channel''), l'autre pour l'écriture (''southbound channel''). Chaque barrette est reliée aux liaisons point à point par un circuit de contrôle qui fait l'interface. Le circuit de contrôle s'appelle l''''''Advanced Memory Buffer''''', il vérifie si chaque transmission est destinée à la barrette, et envoie la commande/donnée à la barrette suivante si ce n'est pas le cas. [[File:FB-DIMM system organization.svg|centre|vignette|upright=2|Bus mémoire pour les barrettes FB-DIMM, schéma détaillé.]] L'avantage de cette organisation est que l'on peut facilement brancher beaucoup de barrettes mémoire sur la carte mère. Avec un bus parallèle, il est difficile de mettre plus de 4 barrettes mémoire. Plus on insère de barrettes de mémoire, plus la stabilité du signal transmis avec un bus parallèle se dégrade. Cela ne pose pas de problème quand on rajoute des barrettes sur la carte mère, car elles sont conçues pour que le signal reste exploitable même si tous les slots mémoire sont remplis. Mais cela fait qu'on a rarement plus de 4 slots mémoire par carte mère. Avec des barrettes FB-DIMM, on peut monter facilement à 8 ou 16 barrettes. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=L'interface d'une mémoire électronique | prevText=L'interface d'une mémoire électronique | next=Les cellules mémoires | nextText=Les cellules mémoires }} </noinclude> soqws0y8y7g2e47vc6udqeq387ykm7i 764777 764776 2026-04-24T09:23:26Z Mewtow 31375 /* Le dual-channel classique : de meilleures performances */ 764777 wikitext text/x-wiki Le bus mémoire est en soi très simple : c'est juste un ensemble de fils, avec quelques circuits annexes qui servent à interfacer ce bus avec la processeur et la mémoire. En somme, des fils et de la ''glue logic''. Néanmoins, il y a quand même des choses à dire dessus, surtout qu'il ne prend pas la même forme sur les ordinateurs PC et sur les autres architectures. [[File:Ram-module.svg|droite|vignette|upright=0.5|Barrette de mémoire RAM.]] La plupart des PC commerciaux utilisent des '''barrettes de RAM''', qu'on peut retirer de la carte mère si besoin. Le bus mémoire est donc relié à un connecteur standardisé, appelé '''slot mémoire''', dans lequel on insère une barrette de RAM. La barrette de RAM est en soi un morceau de plastique sur lequel on place les puces mémoires, avec des broches dorées qui font contact avec le connecteur, et des interconnexions pour relier les puces aux broches dorées. [[File:Dual channel slots.jpg|centre|vignette|Slots mémoires.]] Par contre, les autres systèmes n'utilisent pas de barrettes de RAM. Ils sont fournit avec une quantité de RAM bien précise, qu'on ne peut pas upgrader. La RAM est alors soudée sur la carte mère, et le bus mémoire est une connexion directe entre processeur et mémoire RAM. Il n'y a pas de connecteur dédié, juste des puces mémoire. De nombreux ordinateurs portables font ça, mais aussi les smartphones, les microcontrôleurs ou d'autres systèmes du même genre. Les systèmes anciens avaient des bus mémoires assez réduits, peu larges, en raison de contraintes techniques. Il était intéressant de limiter la taille du bus mémoire pour économiser des broches, que ce soit sur le processeur ou la mémoire RAM. Les systèmes modernes n'ont pas ce problème, l'évolution de la technologie permet au contraire d'avoir des bus mémoire assez large. Il s'agit de deux contraintes différentes : soit on économise des broches au détriment de la performance, soit on sacrifie beaucoup de fils/broches pour avoir des performances excellentes. Les deux cas donnent des contraintes très différentes, voyons comment les deux contraintes façonnent le bus mémoire. ==Les bus mémoire réduits : l'économie de broches== Faire des économies sur le bus mémoire peut viser plusieurs objectifs. Il est possible de réduire le nombre de fils du bus mémoire, de réduire le nombre de broches du processeur ou d'économiser les broches de la mémoire RAM/ROM. Les trois objectifs sont assez différents, et certains sont plus utiles que d'autres. Par exemple, un processeur a besoin de beaucoup plus de broches qu'une mémoire pour faire son travail, vu que l'interface d'un processeur est assez complexe. Les processeurs doivent donc utiliser pas mal de ruses pour économiser des broches, comme un usage de bus multiplexés, de bus d'adresse multiplexé, etc. À l'inverse, les mémoires RAM/ROM peuvent parfaitement s'en passer, vu que leur interface est assez simple. Les contraintes entre processeur et mémoires RAM/ROM sont donc opposées. Une méthode intéressante pour économiser des broches sur le processeur est d'utiliser un bus multiplexé. En théorie, cela demande d'avoir une mémoire compatible, qui tendent à être rares et plus chères. Les mémoires de faible capacité sont souvent sans bus multiplexés, alors que les processeurs à bas cout avec bus multiplexés sont plus fréquents. Heureusement, il est possible d'implémenter un bus multiplexé avec une mémoire qui ne l'est pas, ce qui permet d'avoir le meilleur des deux mondes : cela permet d'utiliser un processeur et une mémoire à bas prix, tout en ayant des processeurs avec peu de broches. Mais cela demande de faire quelques modifications sur le bus mémoire pour que cela fonctionne. Un exemple est donné dans le schéma ci-dessous. Le processeur possède un bus multiplexé, alors que la mémoire EPROM a un bus d'adresse séparé du bus de données. Dans cet exemple, le processeur ne peut faire que des lectures, vu que la mémoire est une mémoire EEPROM, mais la solution marche aussi dans le cas où la mémoire est une RAM. L'implémentation demande juste l'ajout d'un registre sur le bus d'adresse et une commande adéquate de l'entrée OE (''Output Enable''). Pour faire une lecture, le processeur procède en deux étapes, comme sur un bus multiplexé normale : l'envoi de l'adresse, puis la lecture de la donnée. * Lors de l'envoi de l'adresse, l'adresse est mémorisée dans le registre, la broche ALE étant reliée à l'entrée ''Enable'' du registre. De plus, on doit déconnecter la mémoire du bus de donnée pour éviter un conflit entre l'envoi de la donnée par la mémoire et l'envoi de l'adresse par le processeur. Pour cela, on utilise l'entrée OE (''Output Enable''). * La lecture de la donnée consiste à mettre ALE à 0, et à récupérer la donnée sur le bus. Pendant cette étape, le registre maintient l'adresse sur le bus d'adresse. Le bit OE est configuré de manière à activer la sortie de données. [[File:8051ALE.svg|centre|vignette|upright=2|8051 ALE]] ==Le ''dual-channel'', ''triple-channel'' ou ''quad-channel''== Les techniques de ''dual-channel'' permettent de combiner ensemble 2 barrettes de mémoire RAM de manière à soit gagner en performances, soit gagner en stabilité mémoire. Le cas le plus courant est celui où les deux barrettes sont combinées de manière à doubler le débit binaire de la mémoire RAM. Cependant, il existe aussi des techniques de ''memory mirroring'', qui sont une forme particulière de ''dual channel'' visant à dupliquer les données, de manière à améliorer la résistance face aux pannes. Il existe aussi des techniques de ''triple-channel'' ou ''quad-channel'', qui font la même chose mais avec respectivement 3 et 4 barrettes de RAM. Voyons ces deux techniques dans le détail. Le ''dual-channel'' a pour principe que les données peuvent être lues ou écrites dans deux barrettes de mémoire en même temps. Pour cela, le bus mémoire est doublé. Intuitivement, vous vous dites que chaque barrette a sa propre connexion au contrôleur mémoire, elle a un bus mémoire dédiée rien qu'à elle. Mais dans les faits, ce n'est pas le cas. A la place, le bus de données est élargit. Sans ''dual channel'', il y a un unique bus mémoire de 64 bits, qui est relié aux deux barrettes, chacune ayant une interface de 64 bits. Avec le ''dual-channel'', le bus mémoire passe à 128 bits. Reste à voir comment est exploité ce bus de données doublé. Pour en profiter, il faut placer les barrettes mémoire d'une certaine manière sur la carte mère. Typiquement, une carte mère ''dual channel'' a deux slots mémoires, voire quatre. Quand il y en a deux, tout va bien, il suffit de placer une barrette dans chaque slot. Mais dans le cas où la carte mère en a quatre, les slots sont d'une couleur différent pour indiquer comment les placer. Il faut placer les barrettes dans les slots de la même couleur pour profiter du ''dual channel''. ===Le ''dual-channel'' classique : de meilleures performances=== Le ''dual-channel'' classique permet de lire/écrire 128 bits en une fois, dans deux barrettes qui font 64 bits chacune. Le débit binaire de la mémoire RAM est donc doublée, car le débit binaire des deux RAM s'additionne. Du moins, dans un cas idéal, il y a des conditions pour arriver à ce maximum théorique. Par contre, si ces techniques augmentent la largeur du bus, elles n'ont pas d'effet sur le temps de latence de la mémoire. Le ''dual channel'' se contente d'agrandir chaque mot mémoire, en doublant sa taille. L'idée est de lire/écrire des mots mémoire de 128 bits, dont les 64 bits de poids faible sont lus/écrits dans la première barrette, les 64 bits de poids fort depuis la seconde barrette. Pour cela, l'adresse mémoire est envoyée aux deux barrettes en même temps. Les deux barrettes de RAM répondent à la même adresse mémoire, la même adresse de mot. Elles lisent/écrivent chacune 64 bits de leur côté. Le ''triple-channel'' fait de même avec des mots mémoire de 192 bits répartis sur trois barrettes de RAM, le ''quad-channel'' utilise des mots mémoire de 256 bits répartis sur quatre barrettes de mémoire. [[File:Arrangement horizontal.jpg|centre|vignette|upright=2.5|Arrangement horizontal]] Il y a cependant une petite subtilité, liée à l'alignement mémoire. Voyons ce qui se passe du point de vue du processeur. Pour rappel, le processeur utilise des adresses d'octets, à savoir que chaque octet de la RAM a sa propre adresse. Par contre, les SDRAM modernes sont découpées en ''mots mémoire'' de 64 bits, chacun ayant sa propre adresse mémoire. Il y a donc une différence entre les adresses manipulées par le processeur et celles que comprend la mémoire RAM. L'adresse d'octet est découpée en deux portions : l'adresse mémoire envoyée à la SDRAM, et la position de l'octet dans le mot 64 bits adressé par l'adresse mémoire. {|class="wikitable" |+ Adresse d'octet (processeur) |- ! Adresse mémoire !! Position de l'octet dans un bloc de 64 bits |- | Adresse mémoire || 5 bits |} Sans ''dual channel'', la première barrette correspond à la moitié haute de la RAM, la seconde barrette correspond à la moitié basse. Pour cela, le contrôleur mémoire doit connaitre la taille de la RAM installée. De son point de vue, certains bits de l'adresse seront inutilisés, car il n'y aura pas assez de RAM installée pour avoir à les utiliser. {|class="wikitable" |+ Adresse d'octet (processeur) |- ! colspan="2" | Adresse mémoire !! Position de l'octet dans un bloc de 64 bits |- | bgcolor="#FFFFFF" | Bits inutilisés car pas assez de RAM installée || Reste de l'adresse mémoire || 5 bits |} Vu que les deux barrettes ont la même capacité, un bit de l'adresse dit dans quelle barrette cette adresse est attribuée. Et pour du ''triple-quad channel'', c'est deux bits qui sont utilisés.Les bits en question sont les bits de poids fort de l'adresse mémoire. L'adresse est donc découpée comme suit : {|class="wikitable" |+ Adresse d'octet (processeur) |- ! colspan="3" | Adresse mémoire !! Position de l'octet dans un bloc de 64 bits |- | bgcolor="#FFFFFF" | Bits inutilisés car pas assez de RAM installée || bgcolor="#FFFF00" | Numéro de barrette - 1 ou 2 bits || Adresse mémoire || 5 bits |} Avec le ''dual channel'', la répartition des adresses mémoire est différente. Intuitivement, on se dit que deux adresses mémoires consécutives seront placées dans deux barrettes différentes. Pour cela, le numéro de barrette est déplacé. Il est placé dans les bits de poids faible de l'adresse mémoire, pas dans les bits de poids fort. {|class="wikitable" |+ Adresse d'octet (processeur) |- ! colspan="3" | Adresse mémoire !! Position de l'octet dans un bloc de 64 bits |- | bgcolor="#FFFFFF" | Bits inutilisés car pas assez de RAM installée || Adresse mémoire || bgcolor="#FFFF00" | Numéro de barrette - 1 ou 2 bits || 5 bits |} Pour adresser la mémoire, seule l'adresse mémoire est utilisée. C'est elle qui est envoyée à toutes les barrettes de RAM. L'adresse mémoire est donc amputée d'un ou deux bits, puis est envoyée aux barrettes de RAM. [[File:Décodage d'adresse avec dual channel.png|centre|vignette|upright=2.5|Décodage d'adresse avec dual channel.]] ===Le ''memory mirroring''=== Le ''memory mirroring'' est utilisé sur des serveurs, ou des systèmes devant fonctionner en permanence avec un haut niveau de fiabilité. Sur de tels ordinateurs, il est très important d'éviter tout problème matériel, toute panne, mais aussi : tout corruption de données. Et c'est là que la technique du ''memory mirroring'' entre en scène. Le ''memory mirroring'' duplique les données à l'identique sur deux barrettes de mémoire. Les deux barrettes mémoires contiennent donc les mêmes données, aux mêmes endroits. Le fait de dupliquer les données a plusieurs avantages. Premièrement, si une barrette de RAM tombe en panne, l'autre peut prendre la relève, le temps qu'on remplace la barrette fautive. Il existe des serveurs qui sont conçus pour qu'on puisse rajouter ou retirer de la RAM à chaud, sans avoir à éteindre l'ordinateur. D'autres demandent que l'ordinateur soit arrêté, ce qui demande une intervention pouvant arriver dans un moment, l'ordinateur peut continuer à tourner avec une seule barrette pendant ce temps. Deuxièmement, cela permet de détecter d'une corruption de données quelconque. Les mémoires RAM ne sont pas fiables à 100%, et il est possible qu'un bit s'inverse soudainement, pour des raisons très variées, corrompant un octet dans la RAM. Pour éviter cela, les mémoires sur ce genre de serveurs utilise des codes de détection et de correction d'erreur, mais le ''memory mirroring'' est parfois utilisé en complément. Si une corruption de données survient, elle sera localisée dans une barrette. On peut alors détecter ces corruptions en comparant les données lues dans les deux barrettes. S'il y a une différence, c'est qu'il y a un problème. Reste au contrôleur mémoire à gérer le problème. Les serveurs ont souvent des cartes mères spéciales, avec de quoi mettre facilement 8 à 16 barrettes de mémoire dessus. Aussi, le ''memory mirroring'' peut être exploité à son plein potentiel. Il est possible d'avoir 16 barrettes de RAM, groupées en paires, chaque paire utilisant le ''memory mirroring''. ==Les bus mémoire à base de liaisons point à point : les barrettes FB-DIMM== Dans le cas le plus fréquent, toutes les barrettes d'un PC sont reliées au même bus mémoire, comme indiqué dans le schéma ci-dessous. Le bus mémoire est un bus parallèle, avec tous les défauts que ca implique quand on travaille à haute fréquence. Diverses contraintes électriques assez compliquées à expliquer font que les bus parallèles ont du mal à fonctionner à haute fréquence, la stabilité de transmission du signal est altérée. [[File:Bus mémoire.PNG|centre|vignette|upright=2|Bus mémoire]] [[File:FB-DIMM - principe.PNG|vignette|FB-DIMM - principe]] Les barrettes mémoire '''FB-DIMM''' contournent le problème en utilisant plusieurs liaisons point à point. Il y a deux choses à comprendre. La première est que chaque barrette est connectée à la suivante par une liaison point à point, comme indiqué ci-dessous. Il n'y a pas de bus sur lequel on connecte toutes les barrettes, mais une série de plusieurs liaisons point à point. Les commandes/données passent d'une barrette à l'autre jusqu'à destination. Par exemple, une commande SDRAM part du contrôleur mémoire, passe d'une barrette à l'autre, avant d'arriver à la barrette de destination. Même chose pour les données lues depuis les DRAM, qui partent de la barrette, passent d'une barrette à la suivante, jusqu’à arriver au contrôleur mémoire. Ensuite, les liaisons point à point sont au nombre de deux par barrette : une pour la lecture (''northbound channel''), l'autre pour l'écriture (''southbound channel''). Chaque barrette est reliée aux liaisons point à point par un circuit de contrôle qui fait l'interface. Le circuit de contrôle s'appelle l''''''Advanced Memory Buffer''''', il vérifie si chaque transmission est destinée à la barrette, et envoie la commande/donnée à la barrette suivante si ce n'est pas le cas. [[File:FB-DIMM system organization.svg|centre|vignette|upright=2|Bus mémoire pour les barrettes FB-DIMM, schéma détaillé.]] L'avantage de cette organisation est que l'on peut facilement brancher beaucoup de barrettes mémoire sur la carte mère. Avec un bus parallèle, il est difficile de mettre plus de 4 barrettes mémoire. Plus on insère de barrettes de mémoire, plus la stabilité du signal transmis avec un bus parallèle se dégrade. Cela ne pose pas de problème quand on rajoute des barrettes sur la carte mère, car elles sont conçues pour que le signal reste exploitable même si tous les slots mémoire sont remplis. Mais cela fait qu'on a rarement plus de 4 slots mémoire par carte mère. Avec des barrettes FB-DIMM, on peut monter facilement à 8 ou 16 barrettes. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=L'interface d'une mémoire électronique | prevText=L'interface d'une mémoire électronique | next=Les cellules mémoires | nextText=Les cellules mémoires }} </noinclude> th4eomzso6qedypy2rak087brfq6jd6 764779 764777 2026-04-24T09:28:24Z Mewtow 31375 /* Le dual-channel classique : de meilleures performances */ 764779 wikitext text/x-wiki Le bus mémoire est en soi très simple : c'est juste un ensemble de fils, avec quelques circuits annexes qui servent à interfacer ce bus avec la processeur et la mémoire. En somme, des fils et de la ''glue logic''. Néanmoins, il y a quand même des choses à dire dessus, surtout qu'il ne prend pas la même forme sur les ordinateurs PC et sur les autres architectures. [[File:Ram-module.svg|droite|vignette|upright=0.5|Barrette de mémoire RAM.]] La plupart des PC commerciaux utilisent des '''barrettes de RAM''', qu'on peut retirer de la carte mère si besoin. Le bus mémoire est donc relié à un connecteur standardisé, appelé '''slot mémoire''', dans lequel on insère une barrette de RAM. La barrette de RAM est en soi un morceau de plastique sur lequel on place les puces mémoires, avec des broches dorées qui font contact avec le connecteur, et des interconnexions pour relier les puces aux broches dorées. [[File:Dual channel slots.jpg|centre|vignette|Slots mémoires.]] Par contre, les autres systèmes n'utilisent pas de barrettes de RAM. Ils sont fournit avec une quantité de RAM bien précise, qu'on ne peut pas upgrader. La RAM est alors soudée sur la carte mère, et le bus mémoire est une connexion directe entre processeur et mémoire RAM. Il n'y a pas de connecteur dédié, juste des puces mémoire. De nombreux ordinateurs portables font ça, mais aussi les smartphones, les microcontrôleurs ou d'autres systèmes du même genre. Les systèmes anciens avaient des bus mémoires assez réduits, peu larges, en raison de contraintes techniques. Il était intéressant de limiter la taille du bus mémoire pour économiser des broches, que ce soit sur le processeur ou la mémoire RAM. Les systèmes modernes n'ont pas ce problème, l'évolution de la technologie permet au contraire d'avoir des bus mémoire assez large. Il s'agit de deux contraintes différentes : soit on économise des broches au détriment de la performance, soit on sacrifie beaucoup de fils/broches pour avoir des performances excellentes. Les deux cas donnent des contraintes très différentes, voyons comment les deux contraintes façonnent le bus mémoire. ==Les bus mémoire réduits : l'économie de broches== Faire des économies sur le bus mémoire peut viser plusieurs objectifs. Il est possible de réduire le nombre de fils du bus mémoire, de réduire le nombre de broches du processeur ou d'économiser les broches de la mémoire RAM/ROM. Les trois objectifs sont assez différents, et certains sont plus utiles que d'autres. Par exemple, un processeur a besoin de beaucoup plus de broches qu'une mémoire pour faire son travail, vu que l'interface d'un processeur est assez complexe. Les processeurs doivent donc utiliser pas mal de ruses pour économiser des broches, comme un usage de bus multiplexés, de bus d'adresse multiplexé, etc. À l'inverse, les mémoires RAM/ROM peuvent parfaitement s'en passer, vu que leur interface est assez simple. Les contraintes entre processeur et mémoires RAM/ROM sont donc opposées. Une méthode intéressante pour économiser des broches sur le processeur est d'utiliser un bus multiplexé. En théorie, cela demande d'avoir une mémoire compatible, qui tendent à être rares et plus chères. Les mémoires de faible capacité sont souvent sans bus multiplexés, alors que les processeurs à bas cout avec bus multiplexés sont plus fréquents. Heureusement, il est possible d'implémenter un bus multiplexé avec une mémoire qui ne l'est pas, ce qui permet d'avoir le meilleur des deux mondes : cela permet d'utiliser un processeur et une mémoire à bas prix, tout en ayant des processeurs avec peu de broches. Mais cela demande de faire quelques modifications sur le bus mémoire pour que cela fonctionne. Un exemple est donné dans le schéma ci-dessous. Le processeur possède un bus multiplexé, alors que la mémoire EPROM a un bus d'adresse séparé du bus de données. Dans cet exemple, le processeur ne peut faire que des lectures, vu que la mémoire est une mémoire EEPROM, mais la solution marche aussi dans le cas où la mémoire est une RAM. L'implémentation demande juste l'ajout d'un registre sur le bus d'adresse et une commande adéquate de l'entrée OE (''Output Enable''). Pour faire une lecture, le processeur procède en deux étapes, comme sur un bus multiplexé normale : l'envoi de l'adresse, puis la lecture de la donnée. * Lors de l'envoi de l'adresse, l'adresse est mémorisée dans le registre, la broche ALE étant reliée à l'entrée ''Enable'' du registre. De plus, on doit déconnecter la mémoire du bus de donnée pour éviter un conflit entre l'envoi de la donnée par la mémoire et l'envoi de l'adresse par le processeur. Pour cela, on utilise l'entrée OE (''Output Enable''). * La lecture de la donnée consiste à mettre ALE à 0, et à récupérer la donnée sur le bus. Pendant cette étape, le registre maintient l'adresse sur le bus d'adresse. Le bit OE est configuré de manière à activer la sortie de données. [[File:8051ALE.svg|centre|vignette|upright=2|8051 ALE]] ==Le ''dual-channel'', ''triple-channel'' ou ''quad-channel''== Les techniques de ''dual-channel'' permettent de combiner ensemble 2 barrettes de mémoire RAM de manière à soit gagner en performances, soit gagner en stabilité mémoire. Le cas le plus courant est celui où les deux barrettes sont combinées de manière à doubler le débit binaire de la mémoire RAM. Cependant, il existe aussi des techniques de ''memory mirroring'', qui sont une forme particulière de ''dual channel'' visant à dupliquer les données, de manière à améliorer la résistance face aux pannes. Il existe aussi des techniques de ''triple-channel'' ou ''quad-channel'', qui font la même chose mais avec respectivement 3 et 4 barrettes de RAM. Voyons ces deux techniques dans le détail. Le ''dual-channel'' a pour principe que les données peuvent être lues ou écrites dans deux barrettes de mémoire en même temps. Pour cela, le bus mémoire est doublé. Intuitivement, vous vous dites que chaque barrette a sa propre connexion au contrôleur mémoire, elle a un bus mémoire dédiée rien qu'à elle. Mais dans les faits, ce n'est pas le cas. A la place, le bus de données est élargit. Sans ''dual channel'', il y a un unique bus mémoire de 64 bits, qui est relié aux deux barrettes, chacune ayant une interface de 64 bits. Avec le ''dual-channel'', le bus mémoire passe à 128 bits. Reste à voir comment est exploité ce bus de données doublé. Pour en profiter, il faut placer les barrettes mémoire d'une certaine manière sur la carte mère. Typiquement, une carte mère ''dual channel'' a deux slots mémoires, voire quatre. Quand il y en a deux, tout va bien, il suffit de placer une barrette dans chaque slot. Mais dans le cas où la carte mère en a quatre, les slots sont d'une couleur différent pour indiquer comment les placer. Il faut placer les barrettes dans les slots de la même couleur pour profiter du ''dual channel''. ===Le ''dual-channel'' classique : de meilleures performances=== Le ''dual-channel'' classique double le débit binaire de la mémoire RAM, car le débit binaire des deux barrettes de RAM s'additionne. Du moins, dans un cas idéal, il y a des conditions pour arriver à ce maximum théorique. Par contre, si ces techniques augmentent la largeur du bus, elles n'ont pas d'effet sur le temps de latence de la mémoire. Le ''dual channel'' se contente d'agrandir chaque mot mémoire, en doublant sa taille. L'idée est de lire/écrire des mots mémoire de 128 bits, dont les 64 bits de poids faible sont lus/écrits dans la première barrette, les 64 bits de poids fort depuis la seconde barrette. Pour cela, l'adresse mémoire est envoyée aux deux barrettes en même temps. Les deux barrettes de RAM répondent à la même adresse mémoire, la même adresse de mot. Elles lisent/écrivent chacune 64 bits de leur côté. Le ''triple-channel'' fait de même avec des mots mémoire de 192 bits répartis sur trois barrettes de RAM, le ''quad-channel'' utilise des mots mémoire de 256 bits répartis sur quatre barrettes de mémoire. [[File:Arrangement horizontal.jpg|centre|vignette|upright=2.5|Arrangement horizontal]] Il y a cependant une petite subtilité, liée à l'alignement mémoire. Voyons ce qui se passe du point de vue du processeur. Pour rappel, le processeur utilise des adresses d'octets, à savoir que chaque octet de la RAM a sa propre adresse. Par contre, les SDRAM modernes sont découpées en ''mots mémoire'' de 64 bits, chacun ayant sa propre adresse mémoire. Il y a donc une différence entre les adresses manipulées par le processeur et celles que comprend la mémoire RAM. L'adresse d'octet est découpée en deux portions : l'adresse mémoire envoyée à la SDRAM, et la position de l'octet dans le mot 64 bits adressé par l'adresse mémoire. {|class="wikitable" |+ Adresse d'octet (processeur) |- ! Adresse mémoire !! Position de l'octet dans un bloc de 64 bits |- | Adresse mémoire || 5 bits |} Sans ''dual channel'', la première barrette correspond à la moitié haute de la RAM, la seconde barrette correspond à la moitié basse. Pour cela, le contrôleur mémoire doit connaitre la taille de la RAM installée. De son point de vue, certains bits de l'adresse seront inutilisés, car il n'y aura pas assez de RAM installée pour avoir à les utiliser. {|class="wikitable" |+ Adresse d'octet (processeur) |- ! colspan="2" | Adresse mémoire !! Position de l'octet dans un bloc de 64 bits |- | bgcolor="#FFFFFF" | Bits inutilisés car pas assez de RAM installée || Reste de l'adresse mémoire || 5 bits |} Vu que les deux barrettes ont la même capacité, un bit de l'adresse dit dans quelle barrette cette adresse est attribuée. Et pour du ''triple-quad channel'', c'est deux bits qui sont utilisés.Les bits en question sont les bits de poids fort de l'adresse mémoire. L'adresse est donc découpée comme suit : {|class="wikitable" |+ Adresse d'octet (processeur) |- ! colspan="3" | Adresse mémoire !! Position de l'octet dans un bloc de 64 bits |- | bgcolor="#FFFFFF" | Bits inutilisés car pas assez de RAM installée || bgcolor="#FFFF00" | Numéro de barrette - 1 ou 2 bits || Adresse mémoire || 5 bits |} Avec le ''dual channel'', la répartition des adresses mémoire est différente. Intuitivement, on se dit que deux adresses mémoires consécutives seront placées dans deux barrettes différentes. Pour cela, le numéro de barrette est déplacé. Il est placé dans les bits de poids faible de l'adresse mémoire, pas dans les bits de poids fort. {|class="wikitable" |+ Adresse d'octet (processeur) |- ! colspan="3" | Adresse mémoire !! Position de l'octet dans un bloc de 64 bits |- | bgcolor="#FFFFFF" | Bits inutilisés car pas assez de RAM installée || Adresse mémoire || bgcolor="#FFFF00" | Numéro de barrette - 1 ou 2 bits || 5 bits |} Pour adresser la mémoire, seule l'adresse mémoire est utilisée. C'est elle qui est envoyée à toutes les barrettes de RAM. L'adresse mémoire est donc amputée d'un ou deux bits, puis est envoyée aux barrettes de RAM. [[File:Décodage d'adresse avec dual channel.png|centre|vignette|upright=2.5|Décodage d'adresse avec dual channel.]] ===Le ''memory mirroring''=== Le ''memory mirroring'' est utilisé sur des serveurs, ou des systèmes devant fonctionner en permanence avec un haut niveau de fiabilité. Sur de tels ordinateurs, il est très important d'éviter tout problème matériel, toute panne, mais aussi : tout corruption de données. Et c'est là que la technique du ''memory mirroring'' entre en scène. Le ''memory mirroring'' duplique les données à l'identique sur deux barrettes de mémoire. Les deux barrettes mémoires contiennent donc les mêmes données, aux mêmes endroits. Le fait de dupliquer les données a plusieurs avantages. Premièrement, si une barrette de RAM tombe en panne, l'autre peut prendre la relève, le temps qu'on remplace la barrette fautive. Il existe des serveurs qui sont conçus pour qu'on puisse rajouter ou retirer de la RAM à chaud, sans avoir à éteindre l'ordinateur. D'autres demandent que l'ordinateur soit arrêté, ce qui demande une intervention pouvant arriver dans un moment, l'ordinateur peut continuer à tourner avec une seule barrette pendant ce temps. Deuxièmement, cela permet de détecter d'une corruption de données quelconque. Les mémoires RAM ne sont pas fiables à 100%, et il est possible qu'un bit s'inverse soudainement, pour des raisons très variées, corrompant un octet dans la RAM. Pour éviter cela, les mémoires sur ce genre de serveurs utilise des codes de détection et de correction d'erreur, mais le ''memory mirroring'' est parfois utilisé en complément. Si une corruption de données survient, elle sera localisée dans une barrette. On peut alors détecter ces corruptions en comparant les données lues dans les deux barrettes. S'il y a une différence, c'est qu'il y a un problème. Reste au contrôleur mémoire à gérer le problème. Les serveurs ont souvent des cartes mères spéciales, avec de quoi mettre facilement 8 à 16 barrettes de mémoire dessus. Aussi, le ''memory mirroring'' peut être exploité à son plein potentiel. Il est possible d'avoir 16 barrettes de RAM, groupées en paires, chaque paire utilisant le ''memory mirroring''. ==Les bus mémoire à base de liaisons point à point : les barrettes FB-DIMM== Dans le cas le plus fréquent, toutes les barrettes d'un PC sont reliées au même bus mémoire, comme indiqué dans le schéma ci-dessous. Le bus mémoire est un bus parallèle, avec tous les défauts que ca implique quand on travaille à haute fréquence. Diverses contraintes électriques assez compliquées à expliquer font que les bus parallèles ont du mal à fonctionner à haute fréquence, la stabilité de transmission du signal est altérée. [[File:Bus mémoire.PNG|centre|vignette|upright=2|Bus mémoire]] [[File:FB-DIMM - principe.PNG|vignette|FB-DIMM - principe]] Les barrettes mémoire '''FB-DIMM''' contournent le problème en utilisant plusieurs liaisons point à point. Il y a deux choses à comprendre. La première est que chaque barrette est connectée à la suivante par une liaison point à point, comme indiqué ci-dessous. Il n'y a pas de bus sur lequel on connecte toutes les barrettes, mais une série de plusieurs liaisons point à point. Les commandes/données passent d'une barrette à l'autre jusqu'à destination. Par exemple, une commande SDRAM part du contrôleur mémoire, passe d'une barrette à l'autre, avant d'arriver à la barrette de destination. Même chose pour les données lues depuis les DRAM, qui partent de la barrette, passent d'une barrette à la suivante, jusqu’à arriver au contrôleur mémoire. Ensuite, les liaisons point à point sont au nombre de deux par barrette : une pour la lecture (''northbound channel''), l'autre pour l'écriture (''southbound channel''). Chaque barrette est reliée aux liaisons point à point par un circuit de contrôle qui fait l'interface. Le circuit de contrôle s'appelle l''''''Advanced Memory Buffer''''', il vérifie si chaque transmission est destinée à la barrette, et envoie la commande/donnée à la barrette suivante si ce n'est pas le cas. [[File:FB-DIMM system organization.svg|centre|vignette|upright=2|Bus mémoire pour les barrettes FB-DIMM, schéma détaillé.]] L'avantage de cette organisation est que l'on peut facilement brancher beaucoup de barrettes mémoire sur la carte mère. Avec un bus parallèle, il est difficile de mettre plus de 4 barrettes mémoire. Plus on insère de barrettes de mémoire, plus la stabilité du signal transmis avec un bus parallèle se dégrade. Cela ne pose pas de problème quand on rajoute des barrettes sur la carte mère, car elles sont conçues pour que le signal reste exploitable même si tous les slots mémoire sont remplis. Mais cela fait qu'on a rarement plus de 4 slots mémoire par carte mère. Avec des barrettes FB-DIMM, on peut monter facilement à 8 ou 16 barrettes. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=L'interface d'une mémoire électronique | prevText=L'interface d'une mémoire électronique | next=Les cellules mémoires | nextText=Les cellules mémoires }} </noinclude> c6ls1rerimqw00oz2qjejyngrboaib1 764781 764779 2026-04-24T09:28:57Z Mewtow 31375 /* Le dual-channel classique : de meilleures performances */ 764781 wikitext text/x-wiki Le bus mémoire est en soi très simple : c'est juste un ensemble de fils, avec quelques circuits annexes qui servent à interfacer ce bus avec la processeur et la mémoire. En somme, des fils et de la ''glue logic''. Néanmoins, il y a quand même des choses à dire dessus, surtout qu'il ne prend pas la même forme sur les ordinateurs PC et sur les autres architectures. [[File:Ram-module.svg|droite|vignette|upright=0.5|Barrette de mémoire RAM.]] La plupart des PC commerciaux utilisent des '''barrettes de RAM''', qu'on peut retirer de la carte mère si besoin. Le bus mémoire est donc relié à un connecteur standardisé, appelé '''slot mémoire''', dans lequel on insère une barrette de RAM. La barrette de RAM est en soi un morceau de plastique sur lequel on place les puces mémoires, avec des broches dorées qui font contact avec le connecteur, et des interconnexions pour relier les puces aux broches dorées. [[File:Dual channel slots.jpg|centre|vignette|Slots mémoires.]] Par contre, les autres systèmes n'utilisent pas de barrettes de RAM. Ils sont fournit avec une quantité de RAM bien précise, qu'on ne peut pas upgrader. La RAM est alors soudée sur la carte mère, et le bus mémoire est une connexion directe entre processeur et mémoire RAM. Il n'y a pas de connecteur dédié, juste des puces mémoire. De nombreux ordinateurs portables font ça, mais aussi les smartphones, les microcontrôleurs ou d'autres systèmes du même genre. Les systèmes anciens avaient des bus mémoires assez réduits, peu larges, en raison de contraintes techniques. Il était intéressant de limiter la taille du bus mémoire pour économiser des broches, que ce soit sur le processeur ou la mémoire RAM. Les systèmes modernes n'ont pas ce problème, l'évolution de la technologie permet au contraire d'avoir des bus mémoire assez large. Il s'agit de deux contraintes différentes : soit on économise des broches au détriment de la performance, soit on sacrifie beaucoup de fils/broches pour avoir des performances excellentes. Les deux cas donnent des contraintes très différentes, voyons comment les deux contraintes façonnent le bus mémoire. ==Les bus mémoire réduits : l'économie de broches== Faire des économies sur le bus mémoire peut viser plusieurs objectifs. Il est possible de réduire le nombre de fils du bus mémoire, de réduire le nombre de broches du processeur ou d'économiser les broches de la mémoire RAM/ROM. Les trois objectifs sont assez différents, et certains sont plus utiles que d'autres. Par exemple, un processeur a besoin de beaucoup plus de broches qu'une mémoire pour faire son travail, vu que l'interface d'un processeur est assez complexe. Les processeurs doivent donc utiliser pas mal de ruses pour économiser des broches, comme un usage de bus multiplexés, de bus d'adresse multiplexé, etc. À l'inverse, les mémoires RAM/ROM peuvent parfaitement s'en passer, vu que leur interface est assez simple. Les contraintes entre processeur et mémoires RAM/ROM sont donc opposées. Une méthode intéressante pour économiser des broches sur le processeur est d'utiliser un bus multiplexé. En théorie, cela demande d'avoir une mémoire compatible, qui tendent à être rares et plus chères. Les mémoires de faible capacité sont souvent sans bus multiplexés, alors que les processeurs à bas cout avec bus multiplexés sont plus fréquents. Heureusement, il est possible d'implémenter un bus multiplexé avec une mémoire qui ne l'est pas, ce qui permet d'avoir le meilleur des deux mondes : cela permet d'utiliser un processeur et une mémoire à bas prix, tout en ayant des processeurs avec peu de broches. Mais cela demande de faire quelques modifications sur le bus mémoire pour que cela fonctionne. Un exemple est donné dans le schéma ci-dessous. Le processeur possède un bus multiplexé, alors que la mémoire EPROM a un bus d'adresse séparé du bus de données. Dans cet exemple, le processeur ne peut faire que des lectures, vu que la mémoire est une mémoire EEPROM, mais la solution marche aussi dans le cas où la mémoire est une RAM. L'implémentation demande juste l'ajout d'un registre sur le bus d'adresse et une commande adéquate de l'entrée OE (''Output Enable''). Pour faire une lecture, le processeur procède en deux étapes, comme sur un bus multiplexé normale : l'envoi de l'adresse, puis la lecture de la donnée. * Lors de l'envoi de l'adresse, l'adresse est mémorisée dans le registre, la broche ALE étant reliée à l'entrée ''Enable'' du registre. De plus, on doit déconnecter la mémoire du bus de donnée pour éviter un conflit entre l'envoi de la donnée par la mémoire et l'envoi de l'adresse par le processeur. Pour cela, on utilise l'entrée OE (''Output Enable''). * La lecture de la donnée consiste à mettre ALE à 0, et à récupérer la donnée sur le bus. Pendant cette étape, le registre maintient l'adresse sur le bus d'adresse. Le bit OE est configuré de manière à activer la sortie de données. [[File:8051ALE.svg|centre|vignette|upright=2|8051 ALE]] ==Le ''dual-channel'', ''triple-channel'' ou ''quad-channel''== Les techniques de ''dual-channel'' permettent de combiner ensemble 2 barrettes de mémoire RAM de manière à soit gagner en performances, soit gagner en stabilité mémoire. Le cas le plus courant est celui où les deux barrettes sont combinées de manière à doubler le débit binaire de la mémoire RAM. Cependant, il existe aussi des techniques de ''memory mirroring'', qui sont une forme particulière de ''dual channel'' visant à dupliquer les données, de manière à améliorer la résistance face aux pannes. Il existe aussi des techniques de ''triple-channel'' ou ''quad-channel'', qui font la même chose mais avec respectivement 3 et 4 barrettes de RAM. Voyons ces deux techniques dans le détail. Le ''dual-channel'' a pour principe que les données peuvent être lues ou écrites dans deux barrettes de mémoire en même temps. Pour cela, le bus mémoire est doublé. Intuitivement, vous vous dites que chaque barrette a sa propre connexion au contrôleur mémoire, elle a un bus mémoire dédiée rien qu'à elle. Mais dans les faits, ce n'est pas le cas. A la place, le bus de données est élargit. Sans ''dual channel'', il y a un unique bus mémoire de 64 bits, qui est relié aux deux barrettes, chacune ayant une interface de 64 bits. Avec le ''dual-channel'', le bus mémoire passe à 128 bits. Reste à voir comment est exploité ce bus de données doublé. Pour en profiter, il faut placer les barrettes mémoire d'une certaine manière sur la carte mère. Typiquement, une carte mère ''dual channel'' a deux slots mémoires, voire quatre. Quand il y en a deux, tout va bien, il suffit de placer une barrette dans chaque slot. Mais dans le cas où la carte mère en a quatre, les slots sont d'une couleur différent pour indiquer comment les placer. Il faut placer les barrettes dans les slots de la même couleur pour profiter du ''dual channel''. ===Le ''dual-channel'' classique : de meilleures performances=== Le ''dual-channel'' classique double le débit binaire de la mémoire RAM, car le débit binaire des deux barrettes de RAM s'additionne. Du moins, dans un cas idéal, il y a des conditions pour arriver à ce maximum théorique. Ca ne marche que si le processeur accède à des adresses consécutives, ce qui est fréquemment le cas. Le ''dual channel'' se contente d'agrandir chaque mot mémoire, en doublant sa taille. L'idée est de lire/écrire des mots mémoire de 128 bits, dont les 64 bits de poids faible sont lus/écrits dans la première barrette, les 64 bits de poids fort depuis la seconde barrette. Pour cela, l'adresse mémoire est envoyée aux deux barrettes en même temps. Les deux barrettes de RAM répondent à la même adresse mémoire, la même adresse de mot. Elles lisent/écrivent chacune 64 bits de leur côté. Le ''triple-channel'' fait de même avec des mots mémoire de 192 bits répartis sur trois barrettes de RAM, le ''quad-channel'' utilise des mots mémoire de 256 bits répartis sur quatre barrettes de mémoire. [[File:Arrangement horizontal.jpg|centre|vignette|upright=2.5|Arrangement horizontal]] Il y a cependant une petite subtilité, liée à l'alignement mémoire. Voyons ce qui se passe du point de vue du processeur. Pour rappel, le processeur utilise des adresses d'octets, à savoir que chaque octet de la RAM a sa propre adresse. Par contre, les SDRAM modernes sont découpées en ''mots mémoire'' de 64 bits, chacun ayant sa propre adresse mémoire. Il y a donc une différence entre les adresses manipulées par le processeur et celles que comprend la mémoire RAM. L'adresse d'octet est découpée en deux portions : l'adresse mémoire envoyée à la SDRAM, et la position de l'octet dans le mot 64 bits adressé par l'adresse mémoire. {|class="wikitable" |+ Adresse d'octet (processeur) |- ! Adresse mémoire !! Position de l'octet dans un bloc de 64 bits |- | Adresse mémoire || 5 bits |} Sans ''dual channel'', la première barrette correspond à la moitié haute de la RAM, la seconde barrette correspond à la moitié basse. Pour cela, le contrôleur mémoire doit connaitre la taille de la RAM installée. De son point de vue, certains bits de l'adresse seront inutilisés, car il n'y aura pas assez de RAM installée pour avoir à les utiliser. {|class="wikitable" |+ Adresse d'octet (processeur) |- ! colspan="2" | Adresse mémoire !! Position de l'octet dans un bloc de 64 bits |- | bgcolor="#FFFFFF" | Bits inutilisés car pas assez de RAM installée || Reste de l'adresse mémoire || 5 bits |} Vu que les deux barrettes ont la même capacité, un bit de l'adresse dit dans quelle barrette cette adresse est attribuée. Et pour du ''triple-quad channel'', c'est deux bits qui sont utilisés.Les bits en question sont les bits de poids fort de l'adresse mémoire. L'adresse est donc découpée comme suit : {|class="wikitable" |+ Adresse d'octet (processeur) |- ! colspan="3" | Adresse mémoire !! Position de l'octet dans un bloc de 64 bits |- | bgcolor="#FFFFFF" | Bits inutilisés car pas assez de RAM installée || bgcolor="#FFFF00" | Numéro de barrette - 1 ou 2 bits || Adresse mémoire || 5 bits |} Avec le ''dual channel'', la répartition des adresses mémoire est différente. Intuitivement, on se dit que deux adresses mémoires consécutives seront placées dans deux barrettes différentes. Pour cela, le numéro de barrette est déplacé. Il est placé dans les bits de poids faible de l'adresse mémoire, pas dans les bits de poids fort. {|class="wikitable" |+ Adresse d'octet (processeur) |- ! colspan="3" | Adresse mémoire !! Position de l'octet dans un bloc de 64 bits |- | bgcolor="#FFFFFF" | Bits inutilisés car pas assez de RAM installée || Adresse mémoire || bgcolor="#FFFF00" | Numéro de barrette - 1 ou 2 bits || 5 bits |} Pour adresser la mémoire, seule l'adresse mémoire est utilisée. C'est elle qui est envoyée à toutes les barrettes de RAM. L'adresse mémoire est donc amputée d'un ou deux bits, puis est envoyée aux barrettes de RAM. [[File:Décodage d'adresse avec dual channel.png|centre|vignette|upright=2.5|Décodage d'adresse avec dual channel.]] ===Le ''memory mirroring''=== Le ''memory mirroring'' est utilisé sur des serveurs, ou des systèmes devant fonctionner en permanence avec un haut niveau de fiabilité. Sur de tels ordinateurs, il est très important d'éviter tout problème matériel, toute panne, mais aussi : tout corruption de données. Et c'est là que la technique du ''memory mirroring'' entre en scène. Le ''memory mirroring'' duplique les données à l'identique sur deux barrettes de mémoire. Les deux barrettes mémoires contiennent donc les mêmes données, aux mêmes endroits. Le fait de dupliquer les données a plusieurs avantages. Premièrement, si une barrette de RAM tombe en panne, l'autre peut prendre la relève, le temps qu'on remplace la barrette fautive. Il existe des serveurs qui sont conçus pour qu'on puisse rajouter ou retirer de la RAM à chaud, sans avoir à éteindre l'ordinateur. D'autres demandent que l'ordinateur soit arrêté, ce qui demande une intervention pouvant arriver dans un moment, l'ordinateur peut continuer à tourner avec une seule barrette pendant ce temps. Deuxièmement, cela permet de détecter d'une corruption de données quelconque. Les mémoires RAM ne sont pas fiables à 100%, et il est possible qu'un bit s'inverse soudainement, pour des raisons très variées, corrompant un octet dans la RAM. Pour éviter cela, les mémoires sur ce genre de serveurs utilise des codes de détection et de correction d'erreur, mais le ''memory mirroring'' est parfois utilisé en complément. Si une corruption de données survient, elle sera localisée dans une barrette. On peut alors détecter ces corruptions en comparant les données lues dans les deux barrettes. S'il y a une différence, c'est qu'il y a un problème. Reste au contrôleur mémoire à gérer le problème. Les serveurs ont souvent des cartes mères spéciales, avec de quoi mettre facilement 8 à 16 barrettes de mémoire dessus. Aussi, le ''memory mirroring'' peut être exploité à son plein potentiel. Il est possible d'avoir 16 barrettes de RAM, groupées en paires, chaque paire utilisant le ''memory mirroring''. ==Les bus mémoire à base de liaisons point à point : les barrettes FB-DIMM== Dans le cas le plus fréquent, toutes les barrettes d'un PC sont reliées au même bus mémoire, comme indiqué dans le schéma ci-dessous. Le bus mémoire est un bus parallèle, avec tous les défauts que ca implique quand on travaille à haute fréquence. Diverses contraintes électriques assez compliquées à expliquer font que les bus parallèles ont du mal à fonctionner à haute fréquence, la stabilité de transmission du signal est altérée. [[File:Bus mémoire.PNG|centre|vignette|upright=2|Bus mémoire]] [[File:FB-DIMM - principe.PNG|vignette|FB-DIMM - principe]] Les barrettes mémoire '''FB-DIMM''' contournent le problème en utilisant plusieurs liaisons point à point. Il y a deux choses à comprendre. La première est que chaque barrette est connectée à la suivante par une liaison point à point, comme indiqué ci-dessous. Il n'y a pas de bus sur lequel on connecte toutes les barrettes, mais une série de plusieurs liaisons point à point. Les commandes/données passent d'une barrette à l'autre jusqu'à destination. Par exemple, une commande SDRAM part du contrôleur mémoire, passe d'une barrette à l'autre, avant d'arriver à la barrette de destination. Même chose pour les données lues depuis les DRAM, qui partent de la barrette, passent d'une barrette à la suivante, jusqu’à arriver au contrôleur mémoire. Ensuite, les liaisons point à point sont au nombre de deux par barrette : une pour la lecture (''northbound channel''), l'autre pour l'écriture (''southbound channel''). Chaque barrette est reliée aux liaisons point à point par un circuit de contrôle qui fait l'interface. Le circuit de contrôle s'appelle l''''''Advanced Memory Buffer''''', il vérifie si chaque transmission est destinée à la barrette, et envoie la commande/donnée à la barrette suivante si ce n'est pas le cas. [[File:FB-DIMM system organization.svg|centre|vignette|upright=2|Bus mémoire pour les barrettes FB-DIMM, schéma détaillé.]] L'avantage de cette organisation est que l'on peut facilement brancher beaucoup de barrettes mémoire sur la carte mère. Avec un bus parallèle, il est difficile de mettre plus de 4 barrettes mémoire. Plus on insère de barrettes de mémoire, plus la stabilité du signal transmis avec un bus parallèle se dégrade. Cela ne pose pas de problème quand on rajoute des barrettes sur la carte mère, car elles sont conçues pour que le signal reste exploitable même si tous les slots mémoire sont remplis. Mais cela fait qu'on a rarement plus de 4 slots mémoire par carte mère. Avec des barrettes FB-DIMM, on peut monter facilement à 8 ou 16 barrettes. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=L'interface d'une mémoire électronique | prevText=L'interface d'une mémoire électronique | next=Les cellules mémoires | nextText=Les cellules mémoires }} </noinclude> 360lybf9bxujdg4oh6tryepr7lhfkwl 764782 764781 2026-04-24T09:30:53Z Mewtow 31375 /* Le dual-channel, triple-channel ou quad-channel */ 764782 wikitext text/x-wiki Le bus mémoire est en soi très simple : c'est juste un ensemble de fils, avec quelques circuits annexes qui servent à interfacer ce bus avec la processeur et la mémoire. En somme, des fils et de la ''glue logic''. Néanmoins, il y a quand même des choses à dire dessus, surtout qu'il ne prend pas la même forme sur les ordinateurs PC et sur les autres architectures. [[File:Ram-module.svg|droite|vignette|upright=0.5|Barrette de mémoire RAM.]] La plupart des PC commerciaux utilisent des '''barrettes de RAM''', qu'on peut retirer de la carte mère si besoin. Le bus mémoire est donc relié à un connecteur standardisé, appelé '''slot mémoire''', dans lequel on insère une barrette de RAM. La barrette de RAM est en soi un morceau de plastique sur lequel on place les puces mémoires, avec des broches dorées qui font contact avec le connecteur, et des interconnexions pour relier les puces aux broches dorées. [[File:Dual channel slots.jpg|centre|vignette|Slots mémoires.]] Par contre, les autres systèmes n'utilisent pas de barrettes de RAM. Ils sont fournit avec une quantité de RAM bien précise, qu'on ne peut pas upgrader. La RAM est alors soudée sur la carte mère, et le bus mémoire est une connexion directe entre processeur et mémoire RAM. Il n'y a pas de connecteur dédié, juste des puces mémoire. De nombreux ordinateurs portables font ça, mais aussi les smartphones, les microcontrôleurs ou d'autres systèmes du même genre. Les systèmes anciens avaient des bus mémoires assez réduits, peu larges, en raison de contraintes techniques. Il était intéressant de limiter la taille du bus mémoire pour économiser des broches, que ce soit sur le processeur ou la mémoire RAM. Les systèmes modernes n'ont pas ce problème, l'évolution de la technologie permet au contraire d'avoir des bus mémoire assez large. Il s'agit de deux contraintes différentes : soit on économise des broches au détriment de la performance, soit on sacrifie beaucoup de fils/broches pour avoir des performances excellentes. Les deux cas donnent des contraintes très différentes, voyons comment les deux contraintes façonnent le bus mémoire. ==Les bus mémoire réduits : l'économie de broches== Faire des économies sur le bus mémoire peut viser plusieurs objectifs. Il est possible de réduire le nombre de fils du bus mémoire, de réduire le nombre de broches du processeur ou d'économiser les broches de la mémoire RAM/ROM. Les trois objectifs sont assez différents, et certains sont plus utiles que d'autres. Par exemple, un processeur a besoin de beaucoup plus de broches qu'une mémoire pour faire son travail, vu que l'interface d'un processeur est assez complexe. Les processeurs doivent donc utiliser pas mal de ruses pour économiser des broches, comme un usage de bus multiplexés, de bus d'adresse multiplexé, etc. À l'inverse, les mémoires RAM/ROM peuvent parfaitement s'en passer, vu que leur interface est assez simple. Les contraintes entre processeur et mémoires RAM/ROM sont donc opposées. Une méthode intéressante pour économiser des broches sur le processeur est d'utiliser un bus multiplexé. En théorie, cela demande d'avoir une mémoire compatible, qui tendent à être rares et plus chères. Les mémoires de faible capacité sont souvent sans bus multiplexés, alors que les processeurs à bas cout avec bus multiplexés sont plus fréquents. Heureusement, il est possible d'implémenter un bus multiplexé avec une mémoire qui ne l'est pas, ce qui permet d'avoir le meilleur des deux mondes : cela permet d'utiliser un processeur et une mémoire à bas prix, tout en ayant des processeurs avec peu de broches. Mais cela demande de faire quelques modifications sur le bus mémoire pour que cela fonctionne. Un exemple est donné dans le schéma ci-dessous. Le processeur possède un bus multiplexé, alors que la mémoire EPROM a un bus d'adresse séparé du bus de données. Dans cet exemple, le processeur ne peut faire que des lectures, vu que la mémoire est une mémoire EEPROM, mais la solution marche aussi dans le cas où la mémoire est une RAM. L'implémentation demande juste l'ajout d'un registre sur le bus d'adresse et une commande adéquate de l'entrée OE (''Output Enable''). Pour faire une lecture, le processeur procède en deux étapes, comme sur un bus multiplexé normale : l'envoi de l'adresse, puis la lecture de la donnée. * Lors de l'envoi de l'adresse, l'adresse est mémorisée dans le registre, la broche ALE étant reliée à l'entrée ''Enable'' du registre. De plus, on doit déconnecter la mémoire du bus de donnée pour éviter un conflit entre l'envoi de la donnée par la mémoire et l'envoi de l'adresse par le processeur. Pour cela, on utilise l'entrée OE (''Output Enable''). * La lecture de la donnée consiste à mettre ALE à 0, et à récupérer la donnée sur le bus. Pendant cette étape, le registre maintient l'adresse sur le bus d'adresse. Le bit OE est configuré de manière à activer la sortie de données. [[File:8051ALE.svg|centre|vignette|upright=2|8051 ALE]] ==Le ''dual-channel'', ''triple-channel'' ou ''quad-channel''== Les techniques de ''dual-channel'' permettent de combiner ensemble 2 barrettes de mémoire RAM de manière à soit gagner en performances, soit en stabilité mémoire. Il existe aussi des techniques de ''triple-channel'' ou ''quad-channel'', qui font la même chose mais avec respectivement 3 et 4 barrettes de RAM. Le cas le plus courant est celui où les deux barrettes sont combinées de manière à doubler le débit binaire de la mémoire RAM. Cependant, il existe aussi des techniques de ''memory mirroring'', qui visent à améliorer la résistance face aux pannes. Voyons ces deux techniques dans le détail. Pour en profiter, il faut placer les barrettes mémoire d'une certaine manière sur la carte mère. Typiquement, une carte mère ''dual channel'' a deux slots mémoires, voire quatre. Quand il y en a deux, tout va bien, il suffit de placer une barrette dans chaque slot. Mais dans le cas où la carte mère en a quatre, les slots sont d'une couleur différent pour indiquer comment les placer. Il faut placer les barrettes dans les slots de la même couleur pour profiter du ''dual channel''. ===Le ''dual-channel'' classique : de meilleures performances=== Le ''dual-channel'' classique double le débit binaire de la mémoire RAM, car le débit binaire des deux barrettes de RAM s'additionne. Du moins, dans un cas idéal, il y a des conditions pour arriver à ce maximum théorique. Ca ne marche que si le processeur accède à des adresses consécutives, ce qui est fréquemment le cas. Intuitivement, vous vous dites que chaque barrette a sa propre connexion au contrôleur mémoire, elle a un bus mémoire dédiée rien qu'à elle. Mais dans les faits, ce n'est pas le cas. A la place, le bus de données est élargit. Sans ''dual channel'', il y a un unique bus mémoire de 64 bits, qui est relié aux deux barrettes, chacune ayant une interface de 64 bits. Avec le ''dual-channel'', le bus mémoire passe à 128 bits. Reste à voir comment est exploité ce bus de données doublé. Le ''dual channel'' se contente d'agrandir chaque mot mémoire, en doublant sa taille. L'idée est de lire/écrire des mots mémoire de 128 bits, dont les 64 bits de poids faible sont lus/écrits dans la première barrette, les 64 bits de poids fort depuis la seconde barrette. Pour cela, l'adresse mémoire est envoyée aux deux barrettes en même temps. Les deux barrettes de RAM répondent à la même adresse mémoire, la même adresse de mot. Elles lisent/écrivent chacune 64 bits de leur côté. Le ''triple-channel'' fait de même avec des mots mémoire de 192 bits répartis sur trois barrettes de RAM, le ''quad-channel'' utilise des mots mémoire de 256 bits répartis sur quatre barrettes de mémoire. [[File:Arrangement horizontal.jpg|centre|vignette|upright=2.5|Arrangement horizontal]] Il y a cependant une petite subtilité, liée à l'alignement mémoire. Voyons ce qui se passe du point de vue du processeur. Pour rappel, le processeur utilise des adresses d'octets, à savoir que chaque octet de la RAM a sa propre adresse. Par contre, les SDRAM modernes sont découpées en ''mots mémoire'' de 64 bits, chacun ayant sa propre adresse mémoire. Il y a donc une différence entre les adresses manipulées par le processeur et celles que comprend la mémoire RAM. L'adresse d'octet est découpée en deux portions : l'adresse mémoire envoyée à la SDRAM, et la position de l'octet dans le mot 64 bits adressé par l'adresse mémoire. {|class="wikitable" |+ Adresse d'octet (processeur) |- ! Adresse mémoire !! Position de l'octet dans un bloc de 64 bits |- | Adresse mémoire || 5 bits |} Sans ''dual channel'', la première barrette correspond à la moitié haute de la RAM, la seconde barrette correspond à la moitié basse. Pour cela, le contrôleur mémoire doit connaitre la taille de la RAM installée. De son point de vue, certains bits de l'adresse seront inutilisés, car il n'y aura pas assez de RAM installée pour avoir à les utiliser. {|class="wikitable" |+ Adresse d'octet (processeur) |- ! colspan="2" | Adresse mémoire !! Position de l'octet dans un bloc de 64 bits |- | bgcolor="#FFFFFF" | Bits inutilisés car pas assez de RAM installée || Reste de l'adresse mémoire || 5 bits |} Vu que les deux barrettes ont la même capacité, un bit de l'adresse dit dans quelle barrette cette adresse est attribuée. Et pour du ''triple-quad channel'', c'est deux bits qui sont utilisés.Les bits en question sont les bits de poids fort de l'adresse mémoire. L'adresse est donc découpée comme suit : {|class="wikitable" |+ Adresse d'octet (processeur) |- ! colspan="3" | Adresse mémoire !! Position de l'octet dans un bloc de 64 bits |- | bgcolor="#FFFFFF" | Bits inutilisés car pas assez de RAM installée || bgcolor="#FFFF00" | Numéro de barrette - 1 ou 2 bits || Adresse mémoire || 5 bits |} Avec le ''dual channel'', la répartition des adresses mémoire est différente. Intuitivement, on se dit que deux adresses mémoires consécutives seront placées dans deux barrettes différentes. Pour cela, le numéro de barrette est déplacé. Il est placé dans les bits de poids faible de l'adresse mémoire, pas dans les bits de poids fort. {|class="wikitable" |+ Adresse d'octet (processeur) |- ! colspan="3" | Adresse mémoire !! Position de l'octet dans un bloc de 64 bits |- | bgcolor="#FFFFFF" | Bits inutilisés car pas assez de RAM installée || Adresse mémoire || bgcolor="#FFFF00" | Numéro de barrette - 1 ou 2 bits || 5 bits |} Pour adresser la mémoire, seule l'adresse mémoire est utilisée. C'est elle qui est envoyée à toutes les barrettes de RAM. L'adresse mémoire est donc amputée d'un ou deux bits, puis est envoyée aux barrettes de RAM. [[File:Décodage d'adresse avec dual channel.png|centre|vignette|upright=2.5|Décodage d'adresse avec dual channel.]] ===Le ''memory mirroring''=== Le ''memory mirroring'' est utilisé sur des serveurs, ou des systèmes devant fonctionner en permanence avec un haut niveau de fiabilité. Sur de tels ordinateurs, il est très important d'éviter tout problème matériel, toute panne, mais aussi : tout corruption de données. Et c'est là que la technique du ''memory mirroring'' entre en scène. Le ''memory mirroring'' duplique les données à l'identique sur deux barrettes de mémoire. Les deux barrettes mémoires contiennent donc les mêmes données, aux mêmes endroits. Le fait de dupliquer les données a plusieurs avantages. Premièrement, si une barrette de RAM tombe en panne, l'autre peut prendre la relève, le temps qu'on remplace la barrette fautive. Il existe des serveurs qui sont conçus pour qu'on puisse rajouter ou retirer de la RAM à chaud, sans avoir à éteindre l'ordinateur. D'autres demandent que l'ordinateur soit arrêté, ce qui demande une intervention pouvant arriver dans un moment, l'ordinateur peut continuer à tourner avec une seule barrette pendant ce temps. Deuxièmement, cela permet de détecter d'une corruption de données quelconque. Les mémoires RAM ne sont pas fiables à 100%, et il est possible qu'un bit s'inverse soudainement, pour des raisons très variées, corrompant un octet dans la RAM. Pour éviter cela, les mémoires sur ce genre de serveurs utilise des codes de détection et de correction d'erreur, mais le ''memory mirroring'' est parfois utilisé en complément. Si une corruption de données survient, elle sera localisée dans une barrette. On peut alors détecter ces corruptions en comparant les données lues dans les deux barrettes. S'il y a une différence, c'est qu'il y a un problème. Reste au contrôleur mémoire à gérer le problème. Les serveurs ont souvent des cartes mères spéciales, avec de quoi mettre facilement 8 à 16 barrettes de mémoire dessus. Aussi, le ''memory mirroring'' peut être exploité à son plein potentiel. Il est possible d'avoir 16 barrettes de RAM, groupées en paires, chaque paire utilisant le ''memory mirroring''. ==Les bus mémoire à base de liaisons point à point : les barrettes FB-DIMM== Dans le cas le plus fréquent, toutes les barrettes d'un PC sont reliées au même bus mémoire, comme indiqué dans le schéma ci-dessous. Le bus mémoire est un bus parallèle, avec tous les défauts que ca implique quand on travaille à haute fréquence. Diverses contraintes électriques assez compliquées à expliquer font que les bus parallèles ont du mal à fonctionner à haute fréquence, la stabilité de transmission du signal est altérée. [[File:Bus mémoire.PNG|centre|vignette|upright=2|Bus mémoire]] [[File:FB-DIMM - principe.PNG|vignette|FB-DIMM - principe]] Les barrettes mémoire '''FB-DIMM''' contournent le problème en utilisant plusieurs liaisons point à point. Il y a deux choses à comprendre. La première est que chaque barrette est connectée à la suivante par une liaison point à point, comme indiqué ci-dessous. Il n'y a pas de bus sur lequel on connecte toutes les barrettes, mais une série de plusieurs liaisons point à point. Les commandes/données passent d'une barrette à l'autre jusqu'à destination. Par exemple, une commande SDRAM part du contrôleur mémoire, passe d'une barrette à l'autre, avant d'arriver à la barrette de destination. Même chose pour les données lues depuis les DRAM, qui partent de la barrette, passent d'une barrette à la suivante, jusqu’à arriver au contrôleur mémoire. Ensuite, les liaisons point à point sont au nombre de deux par barrette : une pour la lecture (''northbound channel''), l'autre pour l'écriture (''southbound channel''). Chaque barrette est reliée aux liaisons point à point par un circuit de contrôle qui fait l'interface. Le circuit de contrôle s'appelle l''''''Advanced Memory Buffer''''', il vérifie si chaque transmission est destinée à la barrette, et envoie la commande/donnée à la barrette suivante si ce n'est pas le cas. [[File:FB-DIMM system organization.svg|centre|vignette|upright=2|Bus mémoire pour les barrettes FB-DIMM, schéma détaillé.]] L'avantage de cette organisation est que l'on peut facilement brancher beaucoup de barrettes mémoire sur la carte mère. Avec un bus parallèle, il est difficile de mettre plus de 4 barrettes mémoire. Plus on insère de barrettes de mémoire, plus la stabilité du signal transmis avec un bus parallèle se dégrade. Cela ne pose pas de problème quand on rajoute des barrettes sur la carte mère, car elles sont conçues pour que le signal reste exploitable même si tous les slots mémoire sont remplis. Mais cela fait qu'on a rarement plus de 4 slots mémoire par carte mère. Avec des barrettes FB-DIMM, on peut monter facilement à 8 ou 16 barrettes. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=L'interface d'une mémoire électronique | prevText=L'interface d'une mémoire électronique | next=Les cellules mémoires | nextText=Les cellules mémoires }} </noinclude> 97bokuqpmif6rax14jz5u3jlp6fad53 764786 764782 2026-04-24T09:41:23Z Mewtow 31375 /* Le memory mirroring */ 764786 wikitext text/x-wiki Le bus mémoire est en soi très simple : c'est juste un ensemble de fils, avec quelques circuits annexes qui servent à interfacer ce bus avec la processeur et la mémoire. En somme, des fils et de la ''glue logic''. Néanmoins, il y a quand même des choses à dire dessus, surtout qu'il ne prend pas la même forme sur les ordinateurs PC et sur les autres architectures. [[File:Ram-module.svg|droite|vignette|upright=0.5|Barrette de mémoire RAM.]] La plupart des PC commerciaux utilisent des '''barrettes de RAM''', qu'on peut retirer de la carte mère si besoin. Le bus mémoire est donc relié à un connecteur standardisé, appelé '''slot mémoire''', dans lequel on insère une barrette de RAM. La barrette de RAM est en soi un morceau de plastique sur lequel on place les puces mémoires, avec des broches dorées qui font contact avec le connecteur, et des interconnexions pour relier les puces aux broches dorées. [[File:Dual channel slots.jpg|centre|vignette|Slots mémoires.]] Par contre, les autres systèmes n'utilisent pas de barrettes de RAM. Ils sont fournit avec une quantité de RAM bien précise, qu'on ne peut pas upgrader. La RAM est alors soudée sur la carte mère, et le bus mémoire est une connexion directe entre processeur et mémoire RAM. Il n'y a pas de connecteur dédié, juste des puces mémoire. De nombreux ordinateurs portables font ça, mais aussi les smartphones, les microcontrôleurs ou d'autres systèmes du même genre. Les systèmes anciens avaient des bus mémoires assez réduits, peu larges, en raison de contraintes techniques. Il était intéressant de limiter la taille du bus mémoire pour économiser des broches, que ce soit sur le processeur ou la mémoire RAM. Les systèmes modernes n'ont pas ce problème, l'évolution de la technologie permet au contraire d'avoir des bus mémoire assez large. Il s'agit de deux contraintes différentes : soit on économise des broches au détriment de la performance, soit on sacrifie beaucoup de fils/broches pour avoir des performances excellentes. Les deux cas donnent des contraintes très différentes, voyons comment les deux contraintes façonnent le bus mémoire. ==Les bus mémoire réduits : l'économie de broches== Faire des économies sur le bus mémoire peut viser plusieurs objectifs. Il est possible de réduire le nombre de fils du bus mémoire, de réduire le nombre de broches du processeur ou d'économiser les broches de la mémoire RAM/ROM. Les trois objectifs sont assez différents, et certains sont plus utiles que d'autres. Par exemple, un processeur a besoin de beaucoup plus de broches qu'une mémoire pour faire son travail, vu que l'interface d'un processeur est assez complexe. Les processeurs doivent donc utiliser pas mal de ruses pour économiser des broches, comme un usage de bus multiplexés, de bus d'adresse multiplexé, etc. À l'inverse, les mémoires RAM/ROM peuvent parfaitement s'en passer, vu que leur interface est assez simple. Les contraintes entre processeur et mémoires RAM/ROM sont donc opposées. Une méthode intéressante pour économiser des broches sur le processeur est d'utiliser un bus multiplexé. En théorie, cela demande d'avoir une mémoire compatible, qui tendent à être rares et plus chères. Les mémoires de faible capacité sont souvent sans bus multiplexés, alors que les processeurs à bas cout avec bus multiplexés sont plus fréquents. Heureusement, il est possible d'implémenter un bus multiplexé avec une mémoire qui ne l'est pas, ce qui permet d'avoir le meilleur des deux mondes : cela permet d'utiliser un processeur et une mémoire à bas prix, tout en ayant des processeurs avec peu de broches. Mais cela demande de faire quelques modifications sur le bus mémoire pour que cela fonctionne. Un exemple est donné dans le schéma ci-dessous. Le processeur possède un bus multiplexé, alors que la mémoire EPROM a un bus d'adresse séparé du bus de données. Dans cet exemple, le processeur ne peut faire que des lectures, vu que la mémoire est une mémoire EEPROM, mais la solution marche aussi dans le cas où la mémoire est une RAM. L'implémentation demande juste l'ajout d'un registre sur le bus d'adresse et une commande adéquate de l'entrée OE (''Output Enable''). Pour faire une lecture, le processeur procède en deux étapes, comme sur un bus multiplexé normale : l'envoi de l'adresse, puis la lecture de la donnée. * Lors de l'envoi de l'adresse, l'adresse est mémorisée dans le registre, la broche ALE étant reliée à l'entrée ''Enable'' du registre. De plus, on doit déconnecter la mémoire du bus de donnée pour éviter un conflit entre l'envoi de la donnée par la mémoire et l'envoi de l'adresse par le processeur. Pour cela, on utilise l'entrée OE (''Output Enable''). * La lecture de la donnée consiste à mettre ALE à 0, et à récupérer la donnée sur le bus. Pendant cette étape, le registre maintient l'adresse sur le bus d'adresse. Le bit OE est configuré de manière à activer la sortie de données. [[File:8051ALE.svg|centre|vignette|upright=2|8051 ALE]] ==Le ''dual-channel'', ''triple-channel'' ou ''quad-channel''== Les techniques de ''dual-channel'' permettent de combiner ensemble 2 barrettes de mémoire RAM de manière à soit gagner en performances, soit en stabilité mémoire. Il existe aussi des techniques de ''triple-channel'' ou ''quad-channel'', qui font la même chose mais avec respectivement 3 et 4 barrettes de RAM. Le cas le plus courant est celui où les deux barrettes sont combinées de manière à doubler le débit binaire de la mémoire RAM. Cependant, il existe aussi des techniques de ''memory mirroring'', qui visent à améliorer la résistance face aux pannes. Voyons ces deux techniques dans le détail. Pour en profiter, il faut placer les barrettes mémoire d'une certaine manière sur la carte mère. Typiquement, une carte mère ''dual channel'' a deux slots mémoires, voire quatre. Quand il y en a deux, tout va bien, il suffit de placer une barrette dans chaque slot. Mais dans le cas où la carte mère en a quatre, les slots sont d'une couleur différent pour indiquer comment les placer. Il faut placer les barrettes dans les slots de la même couleur pour profiter du ''dual channel''. ===Le ''dual-channel'' classique : de meilleures performances=== Le ''dual-channel'' classique double le débit binaire de la mémoire RAM, car le débit binaire des deux barrettes de RAM s'additionne. Du moins, dans un cas idéal, il y a des conditions pour arriver à ce maximum théorique. Ca ne marche que si le processeur accède à des adresses consécutives, ce qui est fréquemment le cas. Intuitivement, vous vous dites que chaque barrette a sa propre connexion au contrôleur mémoire, elle a un bus mémoire dédiée rien qu'à elle. Mais dans les faits, ce n'est pas le cas. A la place, le bus de données est élargit. Sans ''dual channel'', il y a un unique bus mémoire de 64 bits, qui est relié aux deux barrettes, chacune ayant une interface de 64 bits. Avec le ''dual-channel'', le bus mémoire passe à 128 bits. Reste à voir comment est exploité ce bus de données doublé. Le ''dual channel'' se contente d'agrandir chaque mot mémoire, en doublant sa taille. L'idée est de lire/écrire des mots mémoire de 128 bits, dont les 64 bits de poids faible sont lus/écrits dans la première barrette, les 64 bits de poids fort depuis la seconde barrette. Pour cela, l'adresse mémoire est envoyée aux deux barrettes en même temps. Les deux barrettes de RAM répondent à la même adresse mémoire, la même adresse de mot. Elles lisent/écrivent chacune 64 bits de leur côté. Le ''triple-channel'' fait de même avec des mots mémoire de 192 bits répartis sur trois barrettes de RAM, le ''quad-channel'' utilise des mots mémoire de 256 bits répartis sur quatre barrettes de mémoire. [[File:Arrangement horizontal.jpg|centre|vignette|upright=2.5|Arrangement horizontal]] Il y a cependant une petite subtilité, liée à l'alignement mémoire. Voyons ce qui se passe du point de vue du processeur. Pour rappel, le processeur utilise des adresses d'octets, à savoir que chaque octet de la RAM a sa propre adresse. Par contre, les SDRAM modernes sont découpées en ''mots mémoire'' de 64 bits, chacun ayant sa propre adresse mémoire. Il y a donc une différence entre les adresses manipulées par le processeur et celles que comprend la mémoire RAM. L'adresse d'octet est découpée en deux portions : l'adresse mémoire envoyée à la SDRAM, et la position de l'octet dans le mot 64 bits adressé par l'adresse mémoire. {|class="wikitable" |+ Adresse d'octet (processeur) |- ! Adresse mémoire !! Position de l'octet dans un bloc de 64 bits |- | Adresse mémoire || 5 bits |} Sans ''dual channel'', la première barrette correspond à la moitié haute de la RAM, la seconde barrette correspond à la moitié basse. Pour cela, le contrôleur mémoire doit connaitre la taille de la RAM installée. De son point de vue, certains bits de l'adresse seront inutilisés, car il n'y aura pas assez de RAM installée pour avoir à les utiliser. {|class="wikitable" |+ Adresse d'octet (processeur) |- ! colspan="2" | Adresse mémoire !! Position de l'octet dans un bloc de 64 bits |- | bgcolor="#FFFFFF" | Bits inutilisés car pas assez de RAM installée || Reste de l'adresse mémoire || 5 bits |} Vu que les deux barrettes ont la même capacité, un bit de l'adresse dit dans quelle barrette cette adresse est attribuée. Et pour du ''triple-quad channel'', c'est deux bits qui sont utilisés.Les bits en question sont les bits de poids fort de l'adresse mémoire. L'adresse est donc découpée comme suit : {|class="wikitable" |+ Adresse d'octet (processeur) |- ! colspan="3" | Adresse mémoire !! Position de l'octet dans un bloc de 64 bits |- | bgcolor="#FFFFFF" | Bits inutilisés car pas assez de RAM installée || bgcolor="#FFFF00" | Numéro de barrette - 1 ou 2 bits || Adresse mémoire || 5 bits |} Avec le ''dual channel'', la répartition des adresses mémoire est différente. Intuitivement, on se dit que deux adresses mémoires consécutives seront placées dans deux barrettes différentes. Pour cela, le numéro de barrette est déplacé. Il est placé dans les bits de poids faible de l'adresse mémoire, pas dans les bits de poids fort. {|class="wikitable" |+ Adresse d'octet (processeur) |- ! colspan="3" | Adresse mémoire !! Position de l'octet dans un bloc de 64 bits |- | bgcolor="#FFFFFF" | Bits inutilisés car pas assez de RAM installée || Adresse mémoire || bgcolor="#FFFF00" | Numéro de barrette - 1 ou 2 bits || 5 bits |} Pour adresser la mémoire, seule l'adresse mémoire est utilisée. C'est elle qui est envoyée à toutes les barrettes de RAM. L'adresse mémoire est donc amputée d'un ou deux bits, puis est envoyée aux barrettes de RAM. [[File:Décodage d'adresse avec dual channel.png|centre|vignette|upright=2.5|Décodage d'adresse avec dual channel.]] ===Le ''memory mirroring''=== Le ''memory mirroring'' est utilisé sur des serveurs, ou des systèmes devant fonctionner en permanence avec un haut niveau de fiabilité. Sur de tels ordinateurs, il est très important d'éviter tout problème matériel, toute panne, mais aussi : tout corruption de données. Et c'est là que la technique du ''memory mirroring'' entre en scène. Le ''memory mirroring'' duplique les données à l'identique sur deux barrettes de mémoire. Les deux barrettes mémoires contiennent donc les mêmes données, aux mêmes endroits. Le fait de dupliquer les données a plusieurs avantages. Premièrement, si une barrette de RAM tombe en panne, l'autre peut prendre la relève, le temps qu'on remplace la barrette fautive. Il existe des serveurs qui sont conçus pour qu'on puisse rajouter ou retirer de la RAM à chaud, sans avoir à éteindre l'ordinateur. D'autres demandent que l'ordinateur soit arrêté, ce qui demande une intervention pouvant arriver dans un moment, l'ordinateur peut continuer à tourner avec une seule barrette pendant ce temps. Deuxièmement, cela permet de détecter d'une corruption de données quelconque. Les mémoires RAM ne sont pas fiables à 100%, et il est possible qu'un bit s'inverse soudainement, pour des raisons très variées, corrompant un octet dans la RAM. Pour éviter cela, les mémoires sur ce genre de serveurs utilise des codes de détection et de correction d'erreur, mais le ''memory mirroring'' est parfois utilisé en complément. Si une inversion de bit survient, elle sera localisée dans une barrette. L'autre barrette peut alors prendre la relève. Reste à détecter quelle barrette est fautive. Pour cela, les barrettes de mémoire utilisent un bit de parité pour chaque octet de la RAM. Lors d'une lecture, le bit de parité est utilisé pour savoir si une inversion de bit a eu lieu. Si c'est le cas, le bit de parité et l'octet lu ne correspondent pas, ce qui fait qu'on a une erreur de parité mémoire. Sans ''memory mirroring'', une erreur de parité mémoire entraine un écran bleu ou un redémarrage de l'ordinateur. Mais avec ''memory mirroring'', l'autre barrette ne sera pas touchée, ce qui fait qu'on aura une copie correcte de la donnée lue. Les bits de parité nous disent quelle barrette/donnée est fautive et laquelle est correcte. Il est même possible de remplacer la donnée fautive par la donnée correcte, il suffit simplement de faire une copie entre les deux barrettes. Les serveurs ont souvent des cartes mères spéciales, avec de quoi mettre facilement 8 à 16 barrettes de mémoire dessus. Aussi, le ''memory mirroring'' peut être exploité à son plein potentiel. Il est possible d'avoir 16 barrettes de RAM, groupées en paires, chaque paire utilisant le ''memory mirroring''. ==Les bus mémoire à base de liaisons point à point : les barrettes FB-DIMM== Dans le cas le plus fréquent, toutes les barrettes d'un PC sont reliées au même bus mémoire, comme indiqué dans le schéma ci-dessous. Le bus mémoire est un bus parallèle, avec tous les défauts que ca implique quand on travaille à haute fréquence. Diverses contraintes électriques assez compliquées à expliquer font que les bus parallèles ont du mal à fonctionner à haute fréquence, la stabilité de transmission du signal est altérée. [[File:Bus mémoire.PNG|centre|vignette|upright=2|Bus mémoire]] [[File:FB-DIMM - principe.PNG|vignette|FB-DIMM - principe]] Les barrettes mémoire '''FB-DIMM''' contournent le problème en utilisant plusieurs liaisons point à point. Il y a deux choses à comprendre. La première est que chaque barrette est connectée à la suivante par une liaison point à point, comme indiqué ci-dessous. Il n'y a pas de bus sur lequel on connecte toutes les barrettes, mais une série de plusieurs liaisons point à point. Les commandes/données passent d'une barrette à l'autre jusqu'à destination. Par exemple, une commande SDRAM part du contrôleur mémoire, passe d'une barrette à l'autre, avant d'arriver à la barrette de destination. Même chose pour les données lues depuis les DRAM, qui partent de la barrette, passent d'une barrette à la suivante, jusqu’à arriver au contrôleur mémoire. Ensuite, les liaisons point à point sont au nombre de deux par barrette : une pour la lecture (''northbound channel''), l'autre pour l'écriture (''southbound channel''). Chaque barrette est reliée aux liaisons point à point par un circuit de contrôle qui fait l'interface. Le circuit de contrôle s'appelle l''''''Advanced Memory Buffer''''', il vérifie si chaque transmission est destinée à la barrette, et envoie la commande/donnée à la barrette suivante si ce n'est pas le cas. [[File:FB-DIMM system organization.svg|centre|vignette|upright=2|Bus mémoire pour les barrettes FB-DIMM, schéma détaillé.]] L'avantage de cette organisation est que l'on peut facilement brancher beaucoup de barrettes mémoire sur la carte mère. Avec un bus parallèle, il est difficile de mettre plus de 4 barrettes mémoire. Plus on insère de barrettes de mémoire, plus la stabilité du signal transmis avec un bus parallèle se dégrade. Cela ne pose pas de problème quand on rajoute des barrettes sur la carte mère, car elles sont conçues pour que le signal reste exploitable même si tous les slots mémoire sont remplis. Mais cela fait qu'on a rarement plus de 4 slots mémoire par carte mère. Avec des barrettes FB-DIMM, on peut monter facilement à 8 ou 16 barrettes. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=L'interface d'une mémoire électronique | prevText=L'interface d'une mémoire électronique | next=Les cellules mémoires | nextText=Les cellules mémoires }} </noinclude> 224qcns5yr43vr4r8dzmqjbopd0qgol Fonctionnement d'un ordinateur/Contrôleur mémoire externe 0 65796 764658 764588 2026-04-23T14:52:03Z Mewtow 31375 /* Le séquenceurs mémoire pour les SDRAM/DDR */ 764658 wikitext text/x-wiki Les mémoires ROM ou SRAM ont généralement une interface simple, à laquelle le processeur peut s'interfacer directement. Mais pour les DRAM, ce n'est pas le cas. Les DRAM utilisent un bus d'adresse multiplexé, où l'adresse est envoyée en deux fois. Connecter le processeur directement sur une DRAM n'est pas pratique : le bus d'adresse du processeur et celui de la mémoire ne collent pas. Les DRAM doivent aussi être rafraichies régulièrement. Le rafraichissement mémoire peut être délégué au processeur, mais c'est loin d'être idéal. Et il y a bien d'autres raisons qui font que le processeur ne peut pas s'interfacer facilement avec les mémoires DRAM. Pour gérer ces problèmes, les mémoires DRAM ne sont pas connectées directement au processeur. À la place, on ajoute un intermédiaire entre le processeur et la mémoire : le '''contrôleur mémoire externe'''. Son but est de montrer au processeur une interface simple, semblable à celle d'une SRAM classique, alors qu'il commande une mémoire DRAM à l'interface plus complexe. Il est placé sur la carte mère ou dans le processeur, et ne doit pas être confondu avec le contrôleur mémoire intégré dans la mémoire. Ce chapitre va expliquer quels sont les rôles du contrôleur mémoire, son interface et ce qu'il y a à l'intérieur. Dans ce chapitre, quand nous parlerons de ''contrôleur mémoire'', cela fera systématiquement référence au contrôleur mémoire externe. Et avant de poursuivre, sachez qu'il est difficile de faire des généralités sur les contrôleurs mémoire, car les mémoires DRAM elles-mêmes sont assez différentes les unes des autres. Entre une mémoire EDO, une mémoire SDR, une mémoire DDR et une DRAM asynchrone, les contrôleurs mémoires seront fortement différents. Aussi, il y a aura une différence entre un contrôleur pour une DRAM asynchrone et un contrôleur pour une mémoire EDO, une mémoire SDRAM, etc. J'ai choisit de vous séparer les contrôleurs mémoire pour les DRAM asynchrones de ceux pour les SDRAM/DRR. ==Le contrôleur d'une DRAM asynchrone== Les premières DRAM asynchrones avaient des contrôleurs mémoires dédiés, qui étaient séparés du processeur et du ''chipset'' de la carte mère. Par exemple, les composants Intel 8202, Intel 8203 et Intel 8207 étaient des contrôleurs mémoire pour DRAM asynchrones qui étaient vendus dans des boitiers DIP et étaient soudés sur la carte mère. Par la suite, ils ont été intégrés au ''chipset'' de la carte mère pendant les décennies 90-2000. Après les années 2000, ils ont été intégrés dans les processeurs. Leurs fonctions étaient le multiplexage de l'adresse et le rafraichissement mémoire. Ils recevaient une adresse mémoire complète, qu'ils découpaient une adresse de ligne et une adresse de colonne, envoyées l'une après l'autre sur le bus mémoire. Pour le rafraichissement mémoire, ils rafraichissaient la DRAM régulièrement, de manière automatique, entre deux accès mémoire normaux. Le processeur n'avait ainsi plus à rafraichir la mémoire lui-même, cette fonction était déléguée au contrôleur de DRAM. Ils étaient connectés au bus d'adresse et de commande, avec éventuellement des relations indirectes avec le bus de données. ===L'interface d'un contrôleur de DRAM asynchrone=== L'interface du contrôleur mémoire décrit ses broches d'entrées/sorties et leur signification. Elle est généralement très simple et contient deux ports : un connecté au processeur, un autre connecté à la DRAM. Cela trahit d'ailleurs son rôle principal, qui est de transformer les requêtes de lecture/écriture provenant du processeur en une suite de commandes acceptée par la mémoire. Le port connecté à la DRAM est connecté ua bus d'adresse et au bus de commande. Le bus de données est lui relié au processeur et/ou au bus système. Un accès mémoire provenant du processeur contient une adresse à lire/écrire, le bit R/W qui indique s'il faut faire une lecture ou une écriture, et éventuellement une donnée à écrire. Mais, nous avons vu que les accès mémoires sur une DRAM sont multiplexés : on envoie l'adresse en deux fois : la ligne d'abord, puis la colonne. De plus, il faut générer les signaux RAS, CAS et bien d'autres. Le tout est illustré ci-dessous. [[File:Contrôleur mémoire.png|centre|vignette|upright=2|Contrôleur mémoire externe.]] Un point important est que les DRAM asynchrones n'ont pas d'entrée ''Chip Select'' ou d'entrée ''Output Enable''. Les signaux RAS et CAS remplacent en quelque sorte ces deux signaux. Le bit RAS fait office de ''Chip Select'', le bit CAS fait office d'''Output Enable''. Pour certains contrôleurs de DRAM, il faut ajouter l''''interface électrique''', qui traduit les signaux du processeur en signaux compatibles avec la mémoire. Il est en effet très fréquent que la mémoire et le processeur n'utilisent pas les mêmes tensions pour coder un bit, ce qui fait qu'elles ne sont pas compatibles. Dans ce cas, le contrôleur mémoire fait la conversion. Le contrôleur DRAM peut fonctionner en mode synchrone ou asynchrone, du point de vue du processeur. Quand il fonctionne en mode synchrone, il permet d'interfacer un processeur synchrone avec une mémoire DRAM asynchrone. Un point important est que le contrôleur DRAM sert d'intermédiaire entre une mémoire DRAM et "le reste du monde". Il ne fait pas forcément office d'intermédiaire entre DRAM et processeur, mais peut aussi faire l'intermédiaire entre la DRAM et un bus système, entre une DRAM et le ''Video Display Controler'' d'une carte graphique, ou n'importe quel autre composant électronique qui utilise cette DRAM. ===Le générateur de ''timings'' et la traduction d'adresse=== Le contrôleur mémoire doit traduire les adresses du processeur en adresses compatibles avec la mémoire. Et la traduction est assez variable, suivant que le bus mémoire est un bus normal, un bus multiplexé, ou partiellement multiplexé. Nous avons vu ces trois types de bus mémoire dans le chapitre sur l'interface des mémoires, mais nous ferons quelques rappels rapides. Avec un ''bus totalement multiplexé'', le bus d'adresse et le bus de données sont fusionnés. Dans ce cas, on peut envoyer soit une adresse, soit lire/écrire une donnée sur le bus, mais on ne peut pas faire les deux en même temps. Un bit ALE indique si le bus est utilisé en tant que bus d'adresse ou bus de données. Le contrôleur mémoire gère cette situation, en fixant le bit ALE et en envoyant séparément adresse et donnée pour les écritures. [[File:Bus multiplexé avec bit ALE.png|centre|vignette|upright=2|Bus multiplexé avec bit ALE.]] Avec un ''bus d'adresse multiplexé'', l'adresse est découpée en une adresse de ligne et une adresse de colonne, envoyées l'une après l'autre. Le contrôleur mémoire prend en entrée une adresse mémoire complète, la découpe en deux, et envoie chaque morceau au bon moment. Pour cela, il suffit d'un registre pour mémoriser l'adresse et d'un multiplexeur. Le multiplexeur choisit soit les bits de poids fort de l'adresse, soit ceux de poids faible. Les premiers correspondent à l'adresse de ligne, les autres à l'adresse de colonne. La commande du multiplexeur est le fait d'un petit circuit séquentiel, qui génère aussi les signaux CAS et RAS. Au premier cycle, il met le signal RAS à 1, met le CAS à 0, et configure le MUX pour sélectionner les bits de poids fort. Au second cycle, il génère un signal CAS à 1, met le RAS à 0 et configure le MUX pour sélectionner les bits de poids faible. Le circuit en question est appelé le générateur de ''timings''. [[File:Controleur de DRAM simple, sans rafraichissement mémoire.png|centre|vignette|upright=2|Contrôleur de DRAM simple, sans rafraichissement mémoire.]] Le générateur de ''timings'' est un circuit séquentiel qui implémente une petite machine à état. Il est très simple sur une mémoire DRAM asynchrone basique, mais il est plus complexe sur les mémoires FPM, EDO, quartet, et autres. Le regroupement des multiplexeurs d'adresse et du générateur de ''timings'' est appelé le '''séquenceur mémoire'''. C'est le séquenceur mémoire qui traduit la requête processeur en commande DRAM, le reste du contrôleur est dédié au rafraichissement mémoire ou à d'autres fonctions facultatives. ===Le rafraichissement mémoire=== La gestion du rafraichissement mémoire est la fonction principale du contrôleur DRAM. Pour gérer le rafraichissement mémoire, le contrôleur mémoire intègre deux compteurs, un pour gérer l'adresse à rafraichir, l'autre pour gérer l'intervalle de temps entre deux rafraichissements. Le rafraichissement se fait à intervalle régulier, tous les x microsecondes. Pour déclencher le rafraichissement au bon moment, le contrôleur mémoire contient un ''Refresh Timer'', aussi appelé le '''compteur de rafraichissement'''. Il est initialisé avec le temps entre deux rafraichissements, une adresse est rafraichie quand ce compteur atteint 0. Le rafraichissement mémoire balaye la mémoire adresse par adresse. Pour savoir à quelle adresse il en est rendu, le contrôleur mémoire utilise un '''compteur d'adresse'''. Il contient la prochaine adresse à rafraichir, aussi appelée l'adresse de rafraichissement. Régulièrement, l'adresse dans ce compteur est envoyée à la RAM, pour une lecture. Mais la donnée lue n'est pas envoyée sur le bus de donnée, soit parce que la RAM est prévue pour, soit parce que le contrôleur désactive son bit ''output enable''. Dans le second cas, la RAM fait la lecture en interne, mais se déconnecte du bus de donnée, perdant la donnée lue dans le néant. Pour envoyer l'adresse de rafraichissement sur le bus d'adresse, il faut rajouter un multiplexeur, qui choisit entre l'adresse normale et l'adresse de rafraichissement. [[File:Controleur de DRAM avec rafraichissement mémoire.png|centre|vignette|upright=2|Controleur de DRAM avec rafraichissement mémoire.]] Le multiplexeur ne doit cependant pas être configuré si une adresse est déjà en cours de transfert. Pour cela, un circuit d'arbitrage se débrouille pour éviter qu'un accès mémoire soit interrompu par une demande de rafraichissement et inversement. Il peut être inclus dans le séquenceur mémoire ou séparé de celui-ci. [[File:Controleur mémoire, intérieur simplifié.png|centre|vignette|upright=2.5|Contrôleur mémoire, intérieur simplifié.]] Il faut noter que le rafraichissement mémoire peut être délégué non pas au contrôleur mémoire, mais au processeur où à la DRAM elle-même. Quand elle est le fait du processeur, celui-ci incorpore un ''refresh timer'' et un compteur d'adresse. Un exemple est celui du processeur Zilog Z80, qui implémentait des compteurs internes pour gérer le rafraichissement mémoire. On peut considérer que le processeur incorpore alors le contrôleur mémoire, au moins partiellement. Il est aussi possible de déléguer le rafraichissement au logiciel (certains jeux vidéos Amiga ou Commodore faisaient cela pour la mémoire vidéo). Quand la DRAM elle-même s'occupe de son propre rafraichissement, c'est elle qui intègre un ''refresh timer'' et le compteur d'adresse. ===Le décodage d'adresse=== Le contrôleur mémoire gère aussi le '''décodage d'adresse'''. pour rappel, celui-ci est utilisé quand on combine plusieurs chips mémoire ensemble, pour combiner leurs capacités mémoire. Par exemple, on peut combiner 4 chips mémoires de 1 mébioctet chacun, pour que le processeur voit comme 4 mébioctets de RAM unique. Le premier mébioctet est placé dans le premier chip mémoire, le second mébioctet dans le second chip, etc. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Pour cela, on active le chip mémoire adéquat, en fonction de l'adresse à consulter. Les autres chips mémoire sont désactivés pendant l'accès mémoire. En théorie, activer ou désactiver un chip mémoire se fait en utilisant son entrée ''Chip Select''. Il faut noter que si les SDRAM disposent bien d'un signal ''Chip Select'', ce n'est pas le cas des mémoires RAM asynchrones. A la place, ce sont les signaux RAS qui font office de ''Chip Select''. Une RAM asynchrone est activée quand son signal RAS lui demande de lire une ligne, elle est désactivée sinon. Mais c'est un détail. Toujours est-il que les signaux ''Chip Select'', ou leurs équivalents, sont générés par le contrôleur de DRAM, à partir des bits de poids fort de l'adresse. Par exemple, avec 4 chips mémoire, les deux bits de poids fort de l'adresse sont utilisés pour sélectionner le chip mémoire adéquat. Le contrôleur mémoire doit avoir plusieurs sorties ''Chip Select'', une par chip mémoire. Et le nombre de ces sorties limite le nombre de chips mémoire qu'on peut combiner. Par exemple, s'il y a seulement 4 sorties ''Chip Select'', on ne pourra brancher que 4 chips mémoire dessus. Sauf à ruser, avec un arrangement horizontal, mais cela n'est pas le ressort du contrôleur mémoire. [[File:Td6bfig3.png|centre|vignette|upright=2|Comparaison entre arrangement horizontal (à gauche) et arrangement vertical (à droite).]] ===Exemple : l'Intel 8202-8203=== L'Intel 8202 et le 8203 étaient des contrôleurs de mémoire DRAM, parmi les plus simples qui soient. Ils avaient une entrée d'adresse de 12 bits, ce qui permettait d'adresser 4 kibioctets de RAM. Ils fournissaient en sortie une adresse multiplexée sur 6 bits, envoyée en deux fois. Ils avaient donc 12 entrées d'adresse, 6 sorties d'adresse, un signal RAS, un signal CAS. Les adresses présentées en entrées n'étaient pas mémorisées dans des registres, ce qui fait qu'elles devaient être maintenues durant toute la durée de l'accès mémoire. Le processeur ne pouvait donc pas se déconnecter du bus d'adresse pendant l'accès mémoire, peu importe sa durée. Le 8202 contenait aussi un compteur de rafraichissement. Rappelons que sur les DRAM asynchrones, le rafraichissement se fait ligne par ligne. Le contrôleur mémoire a juste à présenter l'adresse de ligne, il n'a pas à envoyer l'adresse de colonne. La commande de rafraichissement se fait en mettant le signal RAS à 0, mais en laissant le CAS à 1 (je rappelle que les signaux sont actifs à 0). Le compteur d'adresse de rafraichissement a donc juste à mémoriser l'adresse de ligne. Le séquenceur mémoire était précédé par un circuit d'arbitrage, non-représenté dans le schéma ci-dessous. La microarchitecture de l'Intel 8202 est la suivante : [[File:Microarchitecture de l'Intel 8202.png|centre|vignette|upright=2|Microarchitecture de l'Intel 8202.]] Le 8202 avait une entrée pour un signal d'horloge, ainsi qu'un ''Chip Select'' un peu particulier. Si le signal CS passait à 0 lors d'un accès mémoire, le 8202/8203 ne se désactivait qu'une fois l'accès mémoire terminé. On ne pouvait pas l'interrompre pendant un accès mémoire, même en changeant le bit CS. Le signal d'horloge était utilisé pour commander le ''refresh timer''. Pour commander les lectures et écriture, il recevait en entrée un bit ''Write Request'' et un bit ''Read Request'', qui demandent respectivement une écriture et une lecture. En sortie, on trouvait un unique bit R/W qui valait 0 pour une lecture et 1 pour une écriture. Il avait aussi un bit d'entrée pour forcer le rafraichissement mémoire. S'il est à 1, la mémoire rafraichie l'adresse envoyée par le processeur. Le 8202 pouvait être connecté sur 1 à 4 chips mémoire, ce qui permettait d'adresser au maximum 16 kibioctets de RAM. Les 4 chips ne sont pas accédés en parallèle, un seul l'est à chaque fois. Pour le décodage d'adresse, le 8202 dispose de deux bits BO et B1 pour sélectionner le chip adéquat, ainsi que 4 sorties RAS pour activer la banque adéquate. On rappelle que le signal RAS remplace le signal ''Chip Select''. C'est le séquenceur mémoire qui se charge de générer ces signaux RAS, à partir des deux bits B0 et B1 (qui sont techniquement des bits d'adresse). Pour communiquer avec le processeur, l'Intel 8202 disposait de deux bits XACK et SACK. SACK indiquait au processeur que le 8202/8203 est en train de faire un accès mémoire et qu'il est indisponible pour un second accès mémoire. Cela permet de bloquer le processeur tant que le 8202 est indisponible. Le signal XACK indique que l'accès mémoire précédent est terminé et que : soit la donnée lue est présente sur le bus de données, soit que l'écriture s'est terminée. : Le signal SACK est très utile sur les configurations multiprocesseurs. Un processeur peut démarrer un accès mémoire, le signal SACK indiquera au second processeur qu'il doit attendre que l'accès soit terminé pour que ce soit son tour. L'intel 8202 était un contrôleur mémoire basique. Mais Intel a vendu des contrôleurs mémoires plus complexes. Par exemple, l'Intel 8207 était un contrôleur mémoire bien plus avancé que les deux précédents. Il avait plusieurs fonctionnalités en plus du 8202/8203. Les adresses d'entrée étaient mémorisées dans des registres pour de meilleures performances. Il pouvait gérer jusqu'à 256 kibioctets de DRAM. Mais auèdelà de ça, il y avait des fonctionnalités bien plus intéressantes, à savoir : un support de l'ECC, il était double port, il permettait de simuler une DRAM synchrone à partir d'une DRAM asynchrone. Voyons comment cela était possible. ===Les contrôleurs mémoire synchrone=== Il est parfaitement possible d'utiliser un contrôleur mémoire synchrone avec une DRAM asynchrone. A vrai dire, le contrôleur DRAM peut complétement simuler une mémoire synchrone alors que la DRAM associée est asynchrone. La traduction asynchrone vers synchrone se fait en ajoutant des registres sur le bus mémoire, notamment sur le bus de données et le bus d'adresse/commande. Nous avions détaillé cela dans le chapitre sur les SRAM, c'est la même chose avec une mémoire DRAM. Sauf que cette fois-ci, le contrôleur mémoire doit aussi être prévu pour. [[File:Controleur mémoire synchrone utilisé avec une DRAM asynchrone.png|centre|vignette|upright=2|Contrôleur mémoire synchrone utilisé avec une DRAM asynchrone]] Les deux-trois registres illustrés plus haut peuvent être intégrés directement dans le contrôleur mémoire, que ce soit totalement ou en partie. Le strict minimum pour avoir un contrôleur mémoire synchrone est que celui-ci doit mémoriser adresse et commandes dans un registre. Par exemple, le 8207 d'Intel était capable de mémoriser les requêtes processeurs dans un registre interne, mais il fallait utiliser deux registres séparés pour le bus de données. Les deux registres étaient alors commandés par le contrôleur mémoire. Il est cependant possible d'aller plus loin et d'intégrer les registres du bus de données dans le contrôleur mémoire. [[File:Controleur mémoire DRAM synchrone.png|centre|vignette|upright=2|Contrôleur mémoire DRAM synchrone.]] : Il faut noter que cette fonctionnalité est parfois disponible sur les SRAM. En clair, on peut associer une SRAM asynchrone avec un contrôleur de SRAM synchrone. Le contrôleur de SRAM se charge alors de simuler une SRAM synchrone à partir de la SRAM asynchrone. Utiliser un contrôleur mémoire synchrone a de nombreux avantages, l'un d'entre eux étant lié aux ''wait state''. Quand le processeur envoie une requête de lecture/écriture à la mémoire RAM, celle-ci met plusieurs cycles d'horloge à répondre. Et pendant ce temps, le processeur... attend. Et pendant ce temps d'attente, il doit maintenir l'adresse mémoire sur le bus d'adresse. Les cycles d'horloge perdus à attendre la mémoire RAM étaient appelés des '''''Wait states'''''. Utiliser un contrôleur mémoire synchrone d'éliminer les ''wait state'', dans une certaine mesure. Avec un contrôleur mémoire synchrone, le processeur envoie l'adresse, mais c'est le contrôleur mémoire qui la maintient sur le bus d'adresse. Le processeur peut envoyer l'adresse et la donnée à écrire, elles sont recopiées dans les registres, et le controleur mémoire y a accès sans que le processeur doive les maintenir. Le processeur peut se déconnecter du bus mémoire et faire du travail dans son coin pendant que le contrôleur mémoire accède à la DRAM. Les ''wait state'' disparaissent alors, du moins du point de vue du processeur. ===La gestion de l'ECC=== L''''ECC''' peut être géré dans le contrôleur mémoire. Pour cela, on couple les registres mentionnés dans la section précédente, avec un circuit de détection et de correction d'erreur. Le circuit d'ECC peut, comme les registres synchrones, être intégré dans le contrôleur mémoire, ou au contraire être situé dans un circuit séparé. Si le circuit d'ECC est séparé du contrôleur mémoire, il communique avec lui, histoire que le contrôleur mémoire puisse signaler toute erreur de parité ou d'ECC au processeur. [[File:Controleur mémoire synchrone avec ECC intégré.png|centre|vignette|upright=2|Controleur mémoire synchrone avec ECC intégré]] Reprenons l'exemple du 8207 d'Intel. Le contrôleur mémoire 8207 gère le bus d'adresse et de commande, mais n'a pas de connexions directes avec le bus de données. Il ne peut donc pas prendre en charge l'ECC. Il avait besoin d'être couplé avec un circuit d'ECC séparé, relié au bus de données : l'Intel 8206. Le 8026 prenait en entrée : 16 bits de données et 8 bits d'ECC. Il fournissait en sortie 16 bits de données après correction d'erreur, les 8 bits d'ECC pour indiquer qu'une erreur a été détectée mais pas corrigée, ainsi que des bits de parité. Le 8206 détectait/corrigeait les erreurs et générait les bits d'ECC, mais il communiquait avec le contrôleur mémoire pour cela. [[File:8207 avec ECC.png|centre|vignette|upright=2|8207 avec ECC]] La détection/correction d'erreur était appliquée à la fois pour les accès mémoire et pour les rafraichissements mémoire. Lors d'un rafraichissement mémoire, la donnée rafraichie est lue et réécrite. Avec l'ECC activé et configuré correctement, le rafraichissement passe par le bus de données. Au lieu d'avoir un cycle de lecture-écriture interne à la DRAM, on a un cycle de lecture-correction-écriture qui utilise le 8206. La donnée lue est envoyée sur le bus de données, puis le 8206 corrige une éventuelle erreur, et la donnée corrigée est alors réécrite en mémoire. Au passage, si une erreur non-correctible est détectée, le 8206 ne fait rien, l'erreur est ignorée. La gestion de l'erreur sera retardée jusqu'à une lecture ultérieure. Et encore : si lecture ultérieure il y a. Si la donnée est écrasée par une écriture, la donnée corrompue sera simplement écrasée et disparaitra sans avoir pu faire le moindre dégât. Mais pour cela, le 8206 doit communiquer avec le contrôleur mémoire, pour savoir s'il est dans un cycle de rafraichissement ou un accès mémoire normal. Il prévient le 8207 lors d'une erreur, et c'est ce dernier qui décide si l'erreur doit être prise en compte ou ignorée. C'est seulement lors d'un accès mémoire normal que le processeur est prévenu qu'une erreur de parité/autre a eu lieu. ===Les contrôleurs mémoires multiports=== Les '''contrôleur mémoire multiport''' disposent de plusieurs ports, chacun permettant de traiter un accès mémoire. Ils peuvent simuler une mémoire multiport à partir d'une DRAM monoport. Évidemment, la simulation n'est pas parfaite. Des accès mémoire simultanés, envoyés en même temps sur différents ports, sont en réalité exécutés un par un, pas en même temps. Il y a donc une petite pénalité en termes de performances, mais elle est mineure. Encore une fois, nous allons reprendre l'exemple du 8207. Il avait deux ports séparés, et était prévu pour fonctionner dans un système à deux processeurs. L'usage de deux ports séparés permettait de partager une unique mémoire DRAM entre deux processeurs. Le partage se faisait en interfaçant deux processeurs sur le contrôleur mémoire, chacun étant connecté à un port. Lors d'une lecture, il redirigeait la donnée lue vers le bon processeur, en configurant le bus de données correctement. Le contrôleur mémoire recevait des requêtes mémoire de deux processeurs, mais il les exécutait une à la fois. S'il recevait deux requêtes en même temps, l'une d'entre elles était mise en attente. Le contrôleur mémoire doit arbitrer les accès à la mémoire, et faire en sorte que les deux processeurs aient accès à la mémoire à tour de rôle. Et non seulement il doit arbitrer les deux ports, mais il y a aussi un troisième port interne au contrôleur mémoire : le rafraichissement mémoire ! Pour cela, le circuit d'arbitrage qui choisissait entre rafraichissement mémoire et accès mémoire, est amélioré de manière à gérer un second port. Le circuit d'arbitrage donne l'accès au séquenceur mémoire à un port sélectionné. L'arbitrage était configurable, avec deux options : soit le port A est privilégié sur le port B, soit le port le plus récemment accédé à la priorité. Les deux ports pouvaient être configurés pour fonctionner soit de manière asynchrone, soit de manière synchrone. Il était aussi possible de configurer l'ECC, des options liées à la fréquence du processeur et de la RAM, ainsi que de nombreuses options liées au rafraichissement. Pour cela, le 8207 contenait un registre de configuration interne, programmable en fournissant les entrées adéquates. Tout ce qui vient d'être dit se généralise avec plus de deux processeurs. Le 8207 ne permettait pas ça, mais les contrôleurs mémoire des PC modernes en sont capables. Ils peuvent gérer plusieurs dizaines de processeurs facilement. ==Le contrôleur mémoire d'une DRAM ''Fast Page Mode''== Les mémoires DRAM classiques sont des mémoires à tampon de ligne, mais qui sont assez mal utilisées. Notamment, tout accès mémoire se fait en deux phases : un accès pour sélectionner la ligne, un autre pour sélectionner la colonne. Les mémoires ''Fast Page Mode'' permettent d'optimiser le tout. Elles permettent de faire plusieurs accès successifs à la même ligne, à des colonnes différentes. Et le contrôleur mémoire doit être adapté pour cela. [[File:Sélection d'une ligne sur une mémoire FPM ou EDO.png|centre|vignette|upright=2|Sélection d'une ligne sur une mémoire FPM ou EDO.]] Le contrôleur mémoire doit détecter que deux accès mémoire consécutifs se font dans la même ligne. Si deux accès consécutifs accèdent à la même ligne, on doit juste changer de colonne et altérer le signal CAS. C'est un ''succès de tampon de ligne'', aussi appelé un '''succès de page'''. Si deux accès consécutifs accèdent à une ligne différente, c'est un ''défaut de tampon de ligne'', aussi appelé un '''défaut de page'''. Il faut alors changer de ligne, en altérant les signaux RAS et en envoyant une adresse de ligne. Pour détecter les succès ou défauts de page, il faut ajouter un circuit spécialisé dans le contrôleur mémoire. Il mémorise la ligne ouverte, et plus précisément son adresse de ligne (numéro de banque inclut). A chaque requête processeur, il compare l'adresse de ligne recue avec celle déjà ouverte. C'est un succès si les deux sont égales, un défaut si elles sont différentes. Le circuit qui fait cette comparaison est appelé le '''décodeur de commande'''. Il prévient le séquenceur mémoire en cas de succès de page, grâce à un signal de un bit, qui vaut 0 en cas de défaut de page et 1 en cas de succès. Le séquenceur mémoire décide alors comment gérer les signaux RAS et CAS, ainsi que l'envoi des adresses de ligne/colonne. [[File:Controleur mémoire d'une FPM-DRAM.png|centre|vignette|upright=2|Controleur mémoire d'une FPM-DRAM]] ==Le contrôleur mémoire d'une SDRAM ou d'une DDR== l'intérieur d'un contrôleur de SDRAM ne change pas significativement d'un controleur de RAM asynchrone. Il regroupe toujours un séquenceur mémoire et une interface physique, un circuit pour le rafraichissement mémoire et un circuit d'arbitrage. Par contre, ses sorties changent pas mal. Contrairement aux mémoires DRAM basiques, les mémoires SDRAM sont cadencées par un signal d'horloge. Et ce signal d'horloge vient bien de quelque part. Pour cela, deux solutions : soit le contrôleur mémoire génère la fréquence qui commande la mémoire, soit il prend en entrée une fréquence de base qu'il multiplie pour obtenir la fréquence désirée. Les deux solutions sont équivalentes, si ce n'est que les circuits impliqués ne sont pas les mêmes. Dans le premier cas, le contrôleur doit embarquer un circuit oscillateur, qui génère la fréquence demandée. Dans l'autre cas, un simple multiplieur/diviseur de fréquence suffit et c'est généralement une PLL qui est utilisée pour cela. : Notez qu'il ne faut pas confondre la fréquence de la SDRAM et celle du contrôleur mémoire. Le contrôleur mémoire fonctionne à une vitesse assez élevée, en interne. Le port relié au processeur fonctionne à haute fréquence, généralement la même que celle du processeur. A vrai dire, de nos jours, il est intégré dans le processeur. Pour le décodage d'adresse, tout est plus simple sur les SDRAM, DDR inclues. Les chips de mémoire SDRAM et DDR disposent d'une entrée ''Chip Select'', ce qui facilite grandement le décodage d'adresse. Les bits de ''Chip Select'' sont générés par le contrôleur mémoire, et sont transmis sur le bus de commande. Le contrôleur de DRAM peut adresser un certain nombre de rangées, dispersés sur une ou plusieurs barrettes. La limite maximale dépend du contrôleur de DRAM, elle est souvent proche de 8 ou 16 rangées. Si on combine plusieurs barrettes de mémoire, il est possible de dépasser cette limite. Par exemple, pour un contrôleur de DRAM supportant maximum 8 rangées, 4 barrettes de 4 rangées chacune dépassent la limite. ===Le séquenceurs mémoire pour les SDRAM/DDR=== Le séquenceur mémoire existe toujours pour les mémoires SDRAM, c'est toujours un circuit séquentiel qui implémente une machine à état. Il traduit toujours une requête processeur en une séquence de commandes envoyées à des timings bien précis. Les commandes mémoires peuvent provenir de l'extérieur, mais aussi d'un circuit de rafraichissement intégré dans le contrôleur mémoire, comme pour les autres DRAM. La seule différence est que la machine à état est plus complexe. Pour rappel, une requête de lecture/écriture se fait en trois étapes : une commande PRECHARGE pour précharger le tampon de ligne, une commande ACT qui fixe l'adresse de ligne, et enfin une commande READ/WRITE avec l'adresse de colonne. Et il faut tenir compte des timings mémoire, à savoir le fait que ces commandes sont séparées par des temps d'attentes bien précis. Par exemple, je prends des chiffres arbitraires : il faut attendre 2 cycles entre une commande ACT et une commande READ, 6 cycles avant deux commandes WRITE consécutives, etc. La gestion des ''timings'' rend la conception du séquenceur plus complexe. Les SDRAM sont parfois capables de démarrer une commande avant que la précédente soit terminée. Concrètement, pendant que la commande précédente envoie sa dernière donnée sur le bus de données, on peut envoyer la commande suivante avec quelques cycles d'avance. L'exemple ci-dessous devrait être assez clair : on envoie une seconde commande au neuvième cycle, alors qu'une rafale est en cours. Il s'agit d'une forme très limitée de pipeline, similaire à celui des mémoires SRAM, qui ne mérite pas d'être détaillée ici. Son existence est liée au fait que la donnée lue est mémorisée dans un registre, juste avant le bus de données, ce qui permet d’accéder à la mémoire sans écraser la donnée envoyée. Mais il faut préciser que la machine à état tient compte de cette possibilité. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | || bgcolor="#A0FFFF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || || || || bgcolor="#FFA0FF" | READ (2) || || || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 | bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 | |} Le séquenceur peut aussi profiter du fait que des accès simultanés peuvent se faire de préférence se faire dans des banques différentes. Prenons l'exemple d'une mémoire partagée entre plusieurs processeurs. Les deux processeurs ou cœurs exécutent deux programmes différents : le premier processeur exécute le navigateur web, alors que le second lit de la musique. Dans ce cas, les deux processeurs accèdent à des données très éloignées en mémoire (ce n'est pas toujours le cas, mais ça l'est dans cet exemple), qui sont potentiellement dans des banques séparées. Par contre, il y a une contrainte majeure : deux accès simultanés ne peuvent pas accéder au bus en même temps. Je rappelle que quand on démarre une lecture, il se passe quelques cycles d'horloge avant que la donnée atterrissent sur le bus de données. Le temps en question est fixe, mais sa durée n'est pas la même selon que la lecture fasse un succès de page ou un défaut de page. Le séquence mémoire doit en tenir compte avant de démarrer une lecture ou une écriture. Le séquenceur doit donc détecter les succès de page, et déterminer si le bus de donnée sera libre au moment où la lecture terminera. Si c'est le cas, il peut lancer a commande. Sinon, il doit la retarder. {|class="wikitable" |+ Exemple où deux processeurs accèdent à une SDRAM en même temps |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 |- ! rowspan="2" | Requêtes mémoires | bgcolor="#A0FFFF" | CPU 1 || || || bgcolor="#A0FFFF" | CPU 1 || || || || || || |- | bgcolor="#FFA0FF" | CPU 2 || || || || || bgcolor="#FFA0FF" | CPU 2 || || || || |- |- ! Bus de commande/adresse | || bgcolor="#A0FFFF" | Lecture (CPU 1) || bgcolor="#FFA0FF" | Lecture (CPU 2) || || bgcolor="#A0FFFF" | Lecture (CPU 1) || || bgcolor="#FFA0FF" | Lecture (CPU 2) || || |- ! Bus de données | || || || bgcolor="#A0FFFF" | Donnée lue CPU 1 || bgcolor="#FFA0FF" | Donnée lue CPU 2 || || bgcolor="#A0FFFF" | Donnée lue CPU 1 || || bgcolor="#FFA0FF" | Donnée lue CPU 2 |} Les DDR2 et 3 vont encore plus loin avec l'optimisation des '''CAS postés'''. L'idée est que le contrôleur mémoire peut envoyer une commande ACT et une commande READ/WRITE sans se soucier des ''timings''. En théorie, les deux commandes doivent être séparées par quelques cycles, sur une SDRAM ou une DDR1. Mais avec la DDR2, le contrôleur mémoire peut envoyer les deux l'une après l'autre, au cycle suivant. C'est la mémoire qui mettra en attente la commande READ/WRITE pour respecter les ''timings'' mémoire. Cela complexifie le fonctionnement interne de la DDR, mais simplifie grandement le travail du contrôleur mémoire. ===La politique de gestion du tampon de ligne=== Plus haut, nous avons parlé des mémoires FPM, qui ont introduit quelques optimisations pour utiliser au mieux le tampon de ligne. Il se trouve que les SDRAM conservent ces optimisations. Il y a toujours cette notion de succès de page et de défaut de page, suivant que deux accès se font dans la même ligne ou dans deux lignes différentes. Du moins, c'est le cas si le séquenceur mémoire est coopératif. En effet, il peut fonctionner de plusieurs manières, dont les plus extrêmes sont appelés la politique de la page fermée et la politique de la page ouverte. Voyons à quoi elles correspondent. Avec la '''politique de la page fermée''', chaque accès mémoire est suivi d'une commande PRECHARGE, qui ferme la ligne courante et précharge les lignes de bits. Même si deux accès consécutifs se font dans la même ligne, la ligne est fermée et ré-ouverte entre deux accès mémoire. En clair : l'optimisation introduite par les mémoires FPM est désactivée, le contrôleur mémoire fait exprès de ne pas en profiter. On appelle cette méthode la close ''page autoprecharge''. Cette méthode réduit grandement les performances pour les accès à des adresses consécutives, mais fonctionne à merveille si les accès sont "aléatoires", à savoir qu'ils se font sans régularités évidentes. Son seul avantage est que l'implémentation du séquenceur mémoire est très simple. En effet, le séquenceur mémoire se passe complétement de la table des banques, du comparateur de ligne, et de tous les circuits nécessaires pour vérifier les succès ou défauts de page. De plus, le séquenceur mémoire profite grandement des commandes READA et WRITEA, qui fusionnent une commande READ/WRITE avec une commande PRECHARGE. Le séquenceur mémoire a juste à envoyer des commandes ACT, READA, WRITEA et PREFETCH à la mémoire, pas besoin des commandes PRECHARGE, READ ou WRITE. A l'opposé, la '''politique de la page ouverte''' ne ferme pas automatiquement la ligne. Elle la laisse ouverte, en espérant que le prochain accès mémoire se fasse dans cette ligne. Lorsqu'un nouvel accès mémoire arrive, elle doit détecter les succès ou défauts de page et agir en fonction. En cas de défaut de page, la ligne est fermée, le séquenceur mémoire envoie une commande PRECHARGE, puis l'accès suivant effectue les deux commandes ACT + READ ou WRITE. En cas de succès de page, les commandes PRECHARGE et ACT ne sont pas envoyées, seules la commande READ ou WRITE l'est. Un désavantage est que le contrôleur mémoire doit inclure une table des banques et un comparateur, comme vu plus haut dans la section sur les mémoires FPM. un autre défaut est que garder une ligne ouverte consomme beaucoup d'énergie, comparé à un simple état de PRECHARGE. En conséquence, il est préférable de fermer les lignes dès que possible. Par contre, les performances sont d'autant meilleures que les accès mémoire consécutifs à une même ligne soient assez fréquents. Si les accès mémoire sont aléatoires, les performances sont moins bonnes. La politique de la page fermée fermait les lignes en avance, avec des commandes READA ou WRITEA, avant même que l'accès suivant démarre. Avec la politique de la page ouverte, on doit attendre pour détecter un défaut de page, puis fermer la ligne avec une commande PRECHARGE séparée. La ligne est donc fermée avec un peu temps de retard, et envoyer deux commandes au lieu d'une prend plus de temps. Les contrôleurs mémoires basiques utilisent une des deux solutions précédentes. Soit la page est toujours fermée, soit elle est toujours laissée ouverte jusqu'à ce qu'un accès mémoire la referme. Mais les contrôleurs plus évolués tentent de prédire s'il faut fermer ou laisser ouvertes les pages ouvertes. La méthode la plus simple attend un temps prédéterminé avant de fermer la ligne. Une autre solution regarde le tout dernier accès. On peut très bien décider de laisser la ligne ouverte si l'accès mémoire précédent était une rafale, et fermer sinon. Une solution plus complexe mémorise les N derniers accès et en déduit s'il faut fermer ou non la prochaine ligne. On peut mémoriser si l'accès en question a causé la fermeture d'une ligne avec un bit. Mémoriser les N derniers accès demande d'utiliser un simple registre à décalage. Pour chaque valeur de ce registre, il faut prédire si le prochain accès demandera une ouverture ou une fermeture. Une solution simple fait la moyenne des bits à 1 dans ce registre : si plus de la moitié des bits est à 1, on laisse la ligne ouverte et on ferme sinon. Pour améliorer un petit peu l'algorithme, on peut faire en sorte que les bits des accès mémoires les plus récents aient plus de poids dans le calcul de la moyenne. Il existe sans doute d'autres solutions plus évoluées, mais il est difficile de savoir ce qu'il y a dans les contrôleurs de SDRAM modernes. : Le fait de laisser ouverte une ligne ou au contraire de la fermer systématiquement, se fait pour chaque banque. ===Les banques et l'entrelacement mémoire des SDRAM=== Les SDRAM incorporent plusieurs banques à l'intérieur de leurs circuits. Pour rappel, une banque est une sous-mémoire, avec ses propres décodeurs, son tampon de ligne, ses multiplexeurs de colonnes, etc. Pour le dire autrement, une SDRAM regroupe plusieurs mémoires séparées dans un même circuit intégré, les mémoires en question étant des banques. [[File:Arrangement vertical.jpg|centre|vignette|upright=2.5|Mémoire multi-banques.]] La présence de plusieurs banques impacte la gestion des lignes ouvertes/fermées. En effet, chaque banque a son propre tampon de ligne, ce qui fait que la gestion des lignes se fait indépendamment dans chaque banque. Le séquenceur mémoire doit donc se souvenir des lignes actives dans chaque banque. Pour cela, il mémorise ces lignes dans une petite mémoire : la '''table des banques''', aussi appelée ''bank status memory''. Pour détecter un succès ou un défaut, le contrôleur doit extraire la ligne de l'adresse, mais aussi le numéro de banque. Il envoie alors le numéro de banque à la table des banques, sur son entrée d'adresse. Il récupère alors le numéro de la ligne active sur les sorties de données. Il compare alors ce numéro de ligne avec le numéro de ligne de l'adresse envoyée par le processeur. C'est un succès si les deux sont égales, un défaut sinon. [[File:Controleur mémoire FPM avec plusieurs banques.jpg|centre|vignette|upright=2|Contrôleur mémoire FPM avec plusieurs banques.]] <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les mémoires RAM dynamiques (DRAM) | prevText=Les mémoires RAM dynamiques (DRAM) | next=Les mémoires associatives | nextText=Les mémoires associatives }} </noinclude> 69yej4tgy9agh5boks1cv8rfi8agtt5 764665 764658 2026-04-23T15:26:09Z Mewtow 31375 /* Exemple : l'Intel 8202-8203 */ 764665 wikitext text/x-wiki Les mémoires ROM ou SRAM ont généralement une interface simple, à laquelle le processeur peut s'interfacer directement. Mais pour les DRAM, ce n'est pas le cas. Les DRAM utilisent un bus d'adresse multiplexé, où l'adresse est envoyée en deux fois. Connecter le processeur directement sur une DRAM n'est pas pratique : le bus d'adresse du processeur et celui de la mémoire ne collent pas. Les DRAM doivent aussi être rafraichies régulièrement. Le rafraichissement mémoire peut être délégué au processeur, mais c'est loin d'être idéal. Et il y a bien d'autres raisons qui font que le processeur ne peut pas s'interfacer facilement avec les mémoires DRAM. Pour gérer ces problèmes, les mémoires DRAM ne sont pas connectées directement au processeur. À la place, on ajoute un intermédiaire entre le processeur et la mémoire : le '''contrôleur mémoire externe'''. Son but est de montrer au processeur une interface simple, semblable à celle d'une SRAM classique, alors qu'il commande une mémoire DRAM à l'interface plus complexe. Il est placé sur la carte mère ou dans le processeur, et ne doit pas être confondu avec le contrôleur mémoire intégré dans la mémoire. Ce chapitre va expliquer quels sont les rôles du contrôleur mémoire, son interface et ce qu'il y a à l'intérieur. Dans ce chapitre, quand nous parlerons de ''contrôleur mémoire'', cela fera systématiquement référence au contrôleur mémoire externe. Et avant de poursuivre, sachez qu'il est difficile de faire des généralités sur les contrôleurs mémoire, car les mémoires DRAM elles-mêmes sont assez différentes les unes des autres. Entre une mémoire EDO, une mémoire SDR, une mémoire DDR et une DRAM asynchrone, les contrôleurs mémoires seront fortement différents. Aussi, il y a aura une différence entre un contrôleur pour une DRAM asynchrone et un contrôleur pour une mémoire EDO, une mémoire SDRAM, etc. J'ai choisit de vous séparer les contrôleurs mémoire pour les DRAM asynchrones de ceux pour les SDRAM/DRR. ==Le contrôleur d'une DRAM asynchrone== Les premières DRAM asynchrones avaient des contrôleurs mémoires dédiés, qui étaient séparés du processeur et du ''chipset'' de la carte mère. Par exemple, les composants Intel 8202, Intel 8203 et Intel 8207 étaient des contrôleurs mémoire pour DRAM asynchrones qui étaient vendus dans des boitiers DIP et étaient soudés sur la carte mère. Par la suite, ils ont été intégrés au ''chipset'' de la carte mère pendant les décennies 90-2000. Après les années 2000, ils ont été intégrés dans les processeurs. Leurs fonctions étaient le multiplexage de l'adresse et le rafraichissement mémoire. Ils recevaient une adresse mémoire complète, qu'ils découpaient une adresse de ligne et une adresse de colonne, envoyées l'une après l'autre sur le bus mémoire. Pour le rafraichissement mémoire, ils rafraichissaient la DRAM régulièrement, de manière automatique, entre deux accès mémoire normaux. Le processeur n'avait ainsi plus à rafraichir la mémoire lui-même, cette fonction était déléguée au contrôleur de DRAM. Ils étaient connectés au bus d'adresse et de commande, avec éventuellement des relations indirectes avec le bus de données. ===L'interface d'un contrôleur de DRAM asynchrone=== L'interface du contrôleur mémoire décrit ses broches d'entrées/sorties et leur signification. Elle est généralement très simple et contient deux ports : un connecté au processeur, un autre connecté à la DRAM. Cela trahit d'ailleurs son rôle principal, qui est de transformer les requêtes de lecture/écriture provenant du processeur en une suite de commandes acceptée par la mémoire. Le port connecté à la DRAM est connecté ua bus d'adresse et au bus de commande. Le bus de données est lui relié au processeur et/ou au bus système. Un accès mémoire provenant du processeur contient une adresse à lire/écrire, le bit R/W qui indique s'il faut faire une lecture ou une écriture, et éventuellement une donnée à écrire. Mais, nous avons vu que les accès mémoires sur une DRAM sont multiplexés : on envoie l'adresse en deux fois : la ligne d'abord, puis la colonne. De plus, il faut générer les signaux RAS, CAS et bien d'autres. Le tout est illustré ci-dessous. [[File:Contrôleur mémoire.png|centre|vignette|upright=2|Contrôleur mémoire externe.]] Un point important est que les DRAM asynchrones n'ont pas d'entrée ''Chip Select'' ou d'entrée ''Output Enable''. Les signaux RAS et CAS remplacent en quelque sorte ces deux signaux. Le bit RAS fait office de ''Chip Select'', le bit CAS fait office d'''Output Enable''. Pour certains contrôleurs de DRAM, il faut ajouter l''''interface électrique''', qui traduit les signaux du processeur en signaux compatibles avec la mémoire. Il est en effet très fréquent que la mémoire et le processeur n'utilisent pas les mêmes tensions pour coder un bit, ce qui fait qu'elles ne sont pas compatibles. Dans ce cas, le contrôleur mémoire fait la conversion. Le contrôleur DRAM peut fonctionner en mode synchrone ou asynchrone, du point de vue du processeur. Quand il fonctionne en mode synchrone, il permet d'interfacer un processeur synchrone avec une mémoire DRAM asynchrone. Un point important est que le contrôleur DRAM sert d'intermédiaire entre une mémoire DRAM et "le reste du monde". Il ne fait pas forcément office d'intermédiaire entre DRAM et processeur, mais peut aussi faire l'intermédiaire entre la DRAM et un bus système, entre une DRAM et le ''Video Display Controler'' d'une carte graphique, ou n'importe quel autre composant électronique qui utilise cette DRAM. ===Le générateur de ''timings'' et la traduction d'adresse=== Le contrôleur mémoire doit traduire les adresses du processeur en adresses compatibles avec la mémoire. Et la traduction est assez variable, suivant que le bus mémoire est un bus normal, un bus multiplexé, ou partiellement multiplexé. Nous avons vu ces trois types de bus mémoire dans le chapitre sur l'interface des mémoires, mais nous ferons quelques rappels rapides. Avec un ''bus totalement multiplexé'', le bus d'adresse et le bus de données sont fusionnés. Dans ce cas, on peut envoyer soit une adresse, soit lire/écrire une donnée sur le bus, mais on ne peut pas faire les deux en même temps. Un bit ALE indique si le bus est utilisé en tant que bus d'adresse ou bus de données. Le contrôleur mémoire gère cette situation, en fixant le bit ALE et en envoyant séparément adresse et donnée pour les écritures. [[File:Bus multiplexé avec bit ALE.png|centre|vignette|upright=2|Bus multiplexé avec bit ALE.]] Avec un ''bus d'adresse multiplexé'', l'adresse est découpée en une adresse de ligne et une adresse de colonne, envoyées l'une après l'autre. Le contrôleur mémoire prend en entrée une adresse mémoire complète, la découpe en deux, et envoie chaque morceau au bon moment. Pour cela, il suffit d'un registre pour mémoriser l'adresse et d'un multiplexeur. Le multiplexeur choisit soit les bits de poids fort de l'adresse, soit ceux de poids faible. Les premiers correspondent à l'adresse de ligne, les autres à l'adresse de colonne. La commande du multiplexeur est le fait d'un petit circuit séquentiel, qui génère aussi les signaux CAS et RAS. Au premier cycle, il met le signal RAS à 1, met le CAS à 0, et configure le MUX pour sélectionner les bits de poids fort. Au second cycle, il génère un signal CAS à 1, met le RAS à 0 et configure le MUX pour sélectionner les bits de poids faible. Le circuit en question est appelé le générateur de ''timings''. [[File:Controleur de DRAM simple, sans rafraichissement mémoire.png|centre|vignette|upright=2|Contrôleur de DRAM simple, sans rafraichissement mémoire.]] Le générateur de ''timings'' est un circuit séquentiel qui implémente une petite machine à état. Il est très simple sur une mémoire DRAM asynchrone basique, mais il est plus complexe sur les mémoires FPM, EDO, quartet, et autres. Le regroupement des multiplexeurs d'adresse et du générateur de ''timings'' est appelé le '''séquenceur mémoire'''. C'est le séquenceur mémoire qui traduit la requête processeur en commande DRAM, le reste du contrôleur est dédié au rafraichissement mémoire ou à d'autres fonctions facultatives. ===Le rafraichissement mémoire=== La gestion du rafraichissement mémoire est la fonction principale du contrôleur DRAM. Pour gérer le rafraichissement mémoire, le contrôleur mémoire intègre deux compteurs, un pour gérer l'adresse à rafraichir, l'autre pour gérer l'intervalle de temps entre deux rafraichissements. Le rafraichissement se fait à intervalle régulier, tous les x microsecondes. Pour déclencher le rafraichissement au bon moment, le contrôleur mémoire contient un ''Refresh Timer'', aussi appelé le '''compteur de rafraichissement'''. Il est initialisé avec le temps entre deux rafraichissements, une adresse est rafraichie quand ce compteur atteint 0. Le rafraichissement mémoire balaye la mémoire adresse par adresse. Pour savoir à quelle adresse il en est rendu, le contrôleur mémoire utilise un '''compteur d'adresse'''. Il contient la prochaine adresse à rafraichir, aussi appelée l'adresse de rafraichissement. Régulièrement, l'adresse dans ce compteur est envoyée à la RAM, pour une lecture. Mais la donnée lue n'est pas envoyée sur le bus de donnée, soit parce que la RAM est prévue pour, soit parce que le contrôleur désactive son bit ''output enable''. Dans le second cas, la RAM fait la lecture en interne, mais se déconnecte du bus de donnée, perdant la donnée lue dans le néant. Pour envoyer l'adresse de rafraichissement sur le bus d'adresse, il faut rajouter un multiplexeur, qui choisit entre l'adresse normale et l'adresse de rafraichissement. [[File:Controleur de DRAM avec rafraichissement mémoire.png|centre|vignette|upright=2|Controleur de DRAM avec rafraichissement mémoire.]] Le multiplexeur ne doit cependant pas être configuré si une adresse est déjà en cours de transfert. Pour cela, un circuit d'arbitrage se débrouille pour éviter qu'un accès mémoire soit interrompu par une demande de rafraichissement et inversement. Il peut être inclus dans le séquenceur mémoire ou séparé de celui-ci. [[File:Controleur mémoire, intérieur simplifié.png|centre|vignette|upright=2.5|Contrôleur mémoire, intérieur simplifié.]] Il faut noter que le rafraichissement mémoire peut être délégué non pas au contrôleur mémoire, mais au processeur où à la DRAM elle-même. Quand elle est le fait du processeur, celui-ci incorpore un ''refresh timer'' et un compteur d'adresse. Un exemple est celui du processeur Zilog Z80, qui implémentait des compteurs internes pour gérer le rafraichissement mémoire. On peut considérer que le processeur incorpore alors le contrôleur mémoire, au moins partiellement. Il est aussi possible de déléguer le rafraichissement au logiciel (certains jeux vidéos Amiga ou Commodore faisaient cela pour la mémoire vidéo). Quand la DRAM elle-même s'occupe de son propre rafraichissement, c'est elle qui intègre un ''refresh timer'' et le compteur d'adresse. ===Le décodage d'adresse=== Le contrôleur mémoire gère aussi le '''décodage d'adresse'''. pour rappel, celui-ci est utilisé quand on combine plusieurs chips mémoire ensemble, pour combiner leurs capacités mémoire. Par exemple, on peut combiner 4 chips mémoires de 1 mébioctet chacun, pour que le processeur voit comme 4 mébioctets de RAM unique. Le premier mébioctet est placé dans le premier chip mémoire, le second mébioctet dans le second chip, etc. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Pour cela, on active le chip mémoire adéquat, en fonction de l'adresse à consulter. Les autres chips mémoire sont désactivés pendant l'accès mémoire. En théorie, activer ou désactiver un chip mémoire se fait en utilisant son entrée ''Chip Select''. Il faut noter que si les SDRAM disposent bien d'un signal ''Chip Select'', ce n'est pas le cas des mémoires RAM asynchrones. A la place, ce sont les signaux RAS qui font office de ''Chip Select''. Une RAM asynchrone est activée quand son signal RAS lui demande de lire une ligne, elle est désactivée sinon. Mais c'est un détail. Toujours est-il que les signaux ''Chip Select'', ou leurs équivalents, sont générés par le contrôleur de DRAM, à partir des bits de poids fort de l'adresse. Par exemple, avec 4 chips mémoire, les deux bits de poids fort de l'adresse sont utilisés pour sélectionner le chip mémoire adéquat. Le contrôleur mémoire doit avoir plusieurs sorties ''Chip Select'', une par chip mémoire. Et le nombre de ces sorties limite le nombre de chips mémoire qu'on peut combiner. Par exemple, s'il y a seulement 4 sorties ''Chip Select'', on ne pourra brancher que 4 chips mémoire dessus. Sauf à ruser, avec un arrangement horizontal, mais cela n'est pas le ressort du contrôleur mémoire. [[File:Td6bfig3.png|centre|vignette|upright=2|Comparaison entre arrangement horizontal (à gauche) et arrangement vertical (à droite).]] ===Exemple : l'Intel 8202-8203=== L'Intel 8202 et le 8203 étaient des contrôleurs de mémoire DRAM, parmi les plus simples qui soient. Ils avaient une entrée d'adresse de 12 bits, ce qui permettait d'adresser 4 kibioctets de RAM. Ils fournissaient en sortie une adresse multiplexée sur 6 bits, envoyée en deux fois. Ils avaient donc 12 entrées d'adresse, 6 sorties d'adresse, un signal RAS, un signal CAS. Les adresses présentées en entrées n'étaient pas mémorisées dans des registres, ce qui fait qu'elles devaient être maintenues durant toute la durée de l'accès mémoire. Le processeur ne pouvait donc pas se déconnecter du bus d'adresse pendant l'accès mémoire, peu importe sa durée. Le 8202 contenait aussi un compteur de rafraichissement. Rappelons que sur les DRAM asynchrones, le rafraichissement se fait ligne par ligne. Le contrôleur mémoire a juste à présenter l'adresse de ligne, il n'a pas à envoyer l'adresse de colonne. La commande de rafraichissement se fait en mettant le signal RAS à 0, mais en laissant le CAS à 1 (je rappelle que les signaux sont actifs à 0). Le compteur d'adresse de rafraichissement a donc juste à mémoriser l'adresse de ligne. Le séquenceur mémoire était précédé par un circuit d'arbitrage, non-représenté dans le schéma ci-dessous. La microarchitecture de l'Intel 8202 est la suivante : [[File:Microarchitecture de l'Intel 8202.png|centre|vignette|upright=2|Microarchitecture de l'Intel 8202.]] Le 8202 avait une entrée pour un signal d'horloge, ainsi qu'un ''Chip Select'' un peu particulier. Si le signal CS passait à 0 lors d'un accès mémoire, le 8202/8203 ne se désactivait qu'une fois l'accès mémoire terminé. On ne pouvait pas l'interrompre pendant un accès mémoire, même en changeant le bit CS. Le signal d'horloge était utilisé pour commander le ''refresh timer''. Pour commander les lectures et écriture, il recevait en entrée un bit ''Write Request'' et un bit ''Read Request'', qui demandent respectivement une écriture et une lecture. En sortie, on trouvait un unique bit R/W qui valait 0 pour une lecture et 1 pour une écriture. Il avait aussi un bit d'entrée pour forcer le rafraichissement mémoire. S'il est à 1, la mémoire rafraichie l'adresse envoyée par le processeur. Le 8202 pouvait être connecté sur 1 à 4 chips mémoire, ce qui permettait d'adresser au maximum 16 kibioctets de RAM. Les 4 chips ne sont pas accédés en parallèle, un seul l'est à chaque fois. Pour le décodage d'adresse, le 8202 dispose de deux bits BO et B1 pour sélectionner le chip adéquat, ainsi que 4 sorties RAS pour activer la banque adéquate. On rappelle que le signal RAS remplace le signal ''Chip Select''. C'est le séquenceur mémoire qui se charge de générer ces signaux RAS, à partir des deux bits B0 et B1 (qui sont techniquement des bits d'adresse). Pour communiquer avec le processeur, l'Intel 8202 disposait de deux bits XACK et SACK. SACK indiquait au processeur que le 8202/8203 est en train de faire un accès mémoire et qu'il est indisponible pour un second accès mémoire. Cela permet de bloquer le processeur tant que le 8202 est indisponible. Le signal XACK indique que l'accès mémoire précédent est terminé et que : soit la donnée lue est présente sur le bus de données, soit que l'écriture s'est terminée. : Le signal SACK est très utile sur les configurations multiprocesseurs. Un processeur peut démarrer un accès mémoire, le signal SACK indiquera au second processeur qu'il doit attendre que l'accès soit terminé pour que ce soit son tour. ==Les contrôleurs de DRAM asynchrones évolués== L'Intel 8202 était un contrôleur mémoire basique, comme beaucoup d'autres à cette époque. Mais Intel a vendu des contrôleurs mémoires plus complexes. Par exemple, l'Intel 8207 était un contrôleur mémoire bien plus avancé que les deux précédents. Passons sur certains détails, comme le fait qu'il pouvait gérer jusqu'à 256 kibioctets de DRAM. Au-delà de ça, il y avait des fonctionnalités bien plus intéressantes, à savoir : un support de l'ECC, il était double port, il permettait de simuler une DRAM synchrone à partir d'une DRAM asynchrone. Il n'était pas le seul dans ce cas et des contrôleurs de DRAM très évolués étaient capables de faire des merveilles. Voyons comment cela était possible. ===Les contrôleurs mémoire synchrone=== Il est parfaitement possible d'utiliser un contrôleur mémoire synchrone avec une DRAM asynchrone. A vrai dire, le contrôleur DRAM peut complétement simuler une mémoire synchrone alors que la DRAM associée est asynchrone. La traduction asynchrone vers synchrone se fait en ajoutant des registres sur le bus mémoire, notamment sur le bus de données et le bus d'adresse/commande. Nous avions détaillé cela dans le chapitre sur les SRAM, c'est la même chose avec une mémoire DRAM. Sauf que cette fois-ci, le contrôleur mémoire doit aussi être prévu pour. [[File:Controleur mémoire synchrone utilisé avec une DRAM asynchrone.png|centre|vignette|upright=2|Contrôleur mémoire synchrone utilisé avec une DRAM asynchrone]] Les deux-trois registres illustrés plus haut peuvent être intégrés directement dans le contrôleur mémoire, que ce soit totalement ou en partie. Le strict minimum pour avoir un contrôleur mémoire synchrone est que celui-ci doit mémoriser adresse et commandes dans un registre. Par exemple, le 8207 d'Intel était capable de mémoriser les requêtes processeurs dans un registre interne, mais il fallait utiliser deux registres séparés pour le bus de données. Les deux registres étaient alors commandés par le contrôleur mémoire. Il est cependant possible d'aller plus loin et d'intégrer les registres du bus de données dans le contrôleur mémoire. [[File:Controleur mémoire DRAM synchrone.png|centre|vignette|upright=2|Contrôleur mémoire DRAM synchrone.]] : Il faut noter que cette fonctionnalité est parfois disponible sur les SRAM. En clair, on peut associer une SRAM asynchrone avec un contrôleur de SRAM synchrone. Le contrôleur de SRAM se charge alors de simuler une SRAM synchrone à partir de la SRAM asynchrone. Utiliser un contrôleur mémoire synchrone a de nombreux avantages, l'un d'entre eux étant lié aux ''wait state''. Quand le processeur envoie une requête de lecture/écriture à la mémoire RAM, celle-ci met plusieurs cycles d'horloge à répondre. Et pendant ce temps, le processeur... attend. Et pendant ce temps d'attente, il doit maintenir l'adresse mémoire sur le bus d'adresse. Les cycles d'horloge perdus à attendre la mémoire RAM étaient appelés des '''''Wait states'''''. Utiliser un contrôleur mémoire synchrone d'éliminer les ''wait state'', dans une certaine mesure. Avec un contrôleur mémoire synchrone, le processeur envoie l'adresse, mais c'est le contrôleur mémoire qui la maintient sur le bus d'adresse. Le processeur peut envoyer l'adresse et la donnée à écrire, elles sont recopiées dans les registres, et le controleur mémoire y a accès sans que le processeur doive les maintenir. Le processeur peut se déconnecter du bus mémoire et faire du travail dans son coin pendant que le contrôleur mémoire accède à la DRAM. Les ''wait state'' disparaissent alors, du moins du point de vue du processeur. ===La gestion de l'ECC=== L''''ECC''' peut être géré dans le contrôleur mémoire. Pour cela, on couple les registres mentionnés dans la section précédente, avec un circuit de détection et de correction d'erreur. Le circuit d'ECC peut, comme les registres synchrones, être intégré dans le contrôleur mémoire, ou au contraire être situé dans un circuit séparé. Si le circuit d'ECC est séparé du contrôleur mémoire, il communique avec lui, histoire que le contrôleur mémoire puisse signaler toute erreur de parité ou d'ECC au processeur. [[File:Controleur mémoire synchrone avec ECC intégré.png|centre|vignette|upright=2|Controleur mémoire synchrone avec ECC intégré]] Reprenons l'exemple du 8207 d'Intel. Le contrôleur mémoire 8207 gère le bus d'adresse et de commande, mais n'a pas de connexions directes avec le bus de données. Il ne peut donc pas prendre en charge l'ECC. Il avait besoin d'être couplé avec un circuit d'ECC séparé, relié au bus de données : l'Intel 8206. Le 8026 prenait en entrée : 16 bits de données et 8 bits d'ECC. Il fournissait en sortie 16 bits de données après correction d'erreur, les 8 bits d'ECC pour indiquer qu'une erreur a été détectée mais pas corrigée, ainsi que des bits de parité. Le 8206 détectait/corrigeait les erreurs et générait les bits d'ECC, mais il communiquait avec le contrôleur mémoire pour cela. [[File:8207 avec ECC.png|centre|vignette|upright=2|8207 avec ECC]] La détection/correction d'erreur était appliquée à la fois pour les accès mémoire et pour les rafraichissements mémoire. Lors d'un rafraichissement mémoire, la donnée rafraichie est lue et réécrite. Avec l'ECC activé et configuré correctement, le rafraichissement passe par le bus de données. Au lieu d'avoir un cycle de lecture-écriture interne à la DRAM, on a un cycle de lecture-correction-écriture qui utilise le 8206. La donnée lue est envoyée sur le bus de données, puis le 8206 corrige une éventuelle erreur, et la donnée corrigée est alors réécrite en mémoire. Au passage, si une erreur non-correctible est détectée, le 8206 ne fait rien, l'erreur est ignorée. La gestion de l'erreur sera retardée jusqu'à une lecture ultérieure. Et encore : si lecture ultérieure il y a. Si la donnée est écrasée par une écriture, la donnée corrompue sera simplement écrasée et disparaitra sans avoir pu faire le moindre dégât. Mais pour cela, le 8206 doit communiquer avec le contrôleur mémoire, pour savoir s'il est dans un cycle de rafraichissement ou un accès mémoire normal. Il prévient le 8207 lors d'une erreur, et c'est ce dernier qui décide si l'erreur doit être prise en compte ou ignorée. C'est seulement lors d'un accès mémoire normal que le processeur est prévenu qu'une erreur de parité/autre a eu lieu. ===Les contrôleurs mémoires multiports=== Les '''contrôleur mémoire multiport''' disposent de plusieurs ports, chacun permettant de traiter un accès mémoire. Ils peuvent simuler une mémoire multiport à partir d'une DRAM monoport. Évidemment, la simulation n'est pas parfaite. Des accès mémoire simultanés, envoyés en même temps sur différents ports, sont en réalité exécutés un par un, pas en même temps. Il y a donc une petite pénalité en termes de performances, mais elle est mineure. Encore une fois, nous allons reprendre l'exemple du 8207. Il avait deux ports séparés, et était prévu pour fonctionner dans un système à deux processeurs. L'usage de deux ports séparés permettait de partager une unique mémoire DRAM entre deux processeurs. Le partage se faisait en interfaçant deux processeurs sur le contrôleur mémoire, chacun étant connecté à un port. Lors d'une lecture, il redirigeait la donnée lue vers le bon processeur, en configurant le bus de données correctement. Le contrôleur mémoire recevait des requêtes mémoire de deux processeurs, mais il les exécutait une à la fois. S'il recevait deux requêtes en même temps, l'une d'entre elles était mise en attente. Le contrôleur mémoire doit arbitrer les accès à la mémoire, et faire en sorte que les deux processeurs aient accès à la mémoire à tour de rôle. Et non seulement il doit arbitrer les deux ports, mais il y a aussi un troisième port interne au contrôleur mémoire : le rafraichissement mémoire ! Pour cela, le circuit d'arbitrage qui choisissait entre rafraichissement mémoire et accès mémoire, est amélioré de manière à gérer un second port. Le circuit d'arbitrage donne l'accès au séquenceur mémoire à un port sélectionné. L'arbitrage était configurable, avec deux options : soit le port A est privilégié sur le port B, soit le port le plus récemment accédé à la priorité. Les deux ports pouvaient être configurés pour fonctionner soit de manière asynchrone, soit de manière synchrone. Il était aussi possible de configurer l'ECC, des options liées à la fréquence du processeur et de la RAM, ainsi que de nombreuses options liées au rafraichissement. Pour cela, le 8207 contenait un registre de configuration interne, programmable en fournissant les entrées adéquates. Tout ce qui vient d'être dit se généralise avec plus de deux processeurs. Le 8207 ne permettait pas ça, mais les contrôleurs mémoire des PC modernes en sont capables. Ils peuvent gérer plusieurs dizaines de processeurs facilement. ==Le contrôleur mémoire d'une DRAM ''Fast Page Mode''== Les mémoires DRAM classiques sont des mémoires à tampon de ligne, mais qui sont assez mal utilisées. Notamment, tout accès mémoire se fait en deux phases : un accès pour sélectionner la ligne, un autre pour sélectionner la colonne. Les mémoires ''Fast Page Mode'' permettent d'optimiser le tout. Elles permettent de faire plusieurs accès successifs à la même ligne, à des colonnes différentes. Et le contrôleur mémoire doit être adapté pour cela. [[File:Sélection d'une ligne sur une mémoire FPM ou EDO.png|centre|vignette|upright=2|Sélection d'une ligne sur une mémoire FPM ou EDO.]] Le contrôleur mémoire doit détecter que deux accès mémoire consécutifs se font dans la même ligne. Si deux accès consécutifs accèdent à la même ligne, on doit juste changer de colonne et altérer le signal CAS. C'est un ''succès de tampon de ligne'', aussi appelé un '''succès de page'''. Si deux accès consécutifs accèdent à une ligne différente, c'est un ''défaut de tampon de ligne'', aussi appelé un '''défaut de page'''. Il faut alors changer de ligne, en altérant les signaux RAS et en envoyant une adresse de ligne. Pour détecter les succès ou défauts de page, il faut ajouter un circuit spécialisé dans le contrôleur mémoire. Il mémorise la ligne ouverte, et plus précisément son adresse de ligne (numéro de banque inclut). A chaque requête processeur, il compare l'adresse de ligne recue avec celle déjà ouverte. C'est un succès si les deux sont égales, un défaut si elles sont différentes. Le circuit qui fait cette comparaison est appelé le '''décodeur de commande'''. Il prévient le séquenceur mémoire en cas de succès de page, grâce à un signal de un bit, qui vaut 0 en cas de défaut de page et 1 en cas de succès. Le séquenceur mémoire décide alors comment gérer les signaux RAS et CAS, ainsi que l'envoi des adresses de ligne/colonne. [[File:Controleur mémoire d'une FPM-DRAM.png|centre|vignette|upright=2|Controleur mémoire d'une FPM-DRAM]] ==Le contrôleur mémoire d'une SDRAM ou d'une DDR== l'intérieur d'un contrôleur de SDRAM ne change pas significativement d'un controleur de RAM asynchrone. Il regroupe toujours un séquenceur mémoire et une interface physique, un circuit pour le rafraichissement mémoire et un circuit d'arbitrage. Par contre, ses sorties changent pas mal. Contrairement aux mémoires DRAM basiques, les mémoires SDRAM sont cadencées par un signal d'horloge. Et ce signal d'horloge vient bien de quelque part. Pour cela, deux solutions : soit le contrôleur mémoire génère la fréquence qui commande la mémoire, soit il prend en entrée une fréquence de base qu'il multiplie pour obtenir la fréquence désirée. Les deux solutions sont équivalentes, si ce n'est que les circuits impliqués ne sont pas les mêmes. Dans le premier cas, le contrôleur doit embarquer un circuit oscillateur, qui génère la fréquence demandée. Dans l'autre cas, un simple multiplieur/diviseur de fréquence suffit et c'est généralement une PLL qui est utilisée pour cela. : Notez qu'il ne faut pas confondre la fréquence de la SDRAM et celle du contrôleur mémoire. Le contrôleur mémoire fonctionne à une vitesse assez élevée, en interne. Le port relié au processeur fonctionne à haute fréquence, généralement la même que celle du processeur. A vrai dire, de nos jours, il est intégré dans le processeur. Pour le décodage d'adresse, tout est plus simple sur les SDRAM, DDR inclues. Les chips de mémoire SDRAM et DDR disposent d'une entrée ''Chip Select'', ce qui facilite grandement le décodage d'adresse. Les bits de ''Chip Select'' sont générés par le contrôleur mémoire, et sont transmis sur le bus de commande. Le contrôleur de DRAM peut adresser un certain nombre de rangées, dispersés sur une ou plusieurs barrettes. La limite maximale dépend du contrôleur de DRAM, elle est souvent proche de 8 ou 16 rangées. Si on combine plusieurs barrettes de mémoire, il est possible de dépasser cette limite. Par exemple, pour un contrôleur de DRAM supportant maximum 8 rangées, 4 barrettes de 4 rangées chacune dépassent la limite. ===Le séquenceurs mémoire pour les SDRAM/DDR=== Le séquenceur mémoire existe toujours pour les mémoires SDRAM, c'est toujours un circuit séquentiel qui implémente une machine à état. Il traduit toujours une requête processeur en une séquence de commandes envoyées à des timings bien précis. Les commandes mémoires peuvent provenir de l'extérieur, mais aussi d'un circuit de rafraichissement intégré dans le contrôleur mémoire, comme pour les autres DRAM. La seule différence est que la machine à état est plus complexe. Pour rappel, une requête de lecture/écriture se fait en trois étapes : une commande PRECHARGE pour précharger le tampon de ligne, une commande ACT qui fixe l'adresse de ligne, et enfin une commande READ/WRITE avec l'adresse de colonne. Et il faut tenir compte des timings mémoire, à savoir le fait que ces commandes sont séparées par des temps d'attentes bien précis. Par exemple, je prends des chiffres arbitraires : il faut attendre 2 cycles entre une commande ACT et une commande READ, 6 cycles avant deux commandes WRITE consécutives, etc. La gestion des ''timings'' rend la conception du séquenceur plus complexe. Les SDRAM sont parfois capables de démarrer une commande avant que la précédente soit terminée. Concrètement, pendant que la commande précédente envoie sa dernière donnée sur le bus de données, on peut envoyer la commande suivante avec quelques cycles d'avance. L'exemple ci-dessous devrait être assez clair : on envoie une seconde commande au neuvième cycle, alors qu'une rafale est en cours. Il s'agit d'une forme très limitée de pipeline, similaire à celui des mémoires SRAM, qui ne mérite pas d'être détaillée ici. Son existence est liée au fait que la donnée lue est mémorisée dans un registre, juste avant le bus de données, ce qui permet d’accéder à la mémoire sans écraser la donnée envoyée. Mais il faut préciser que la machine à état tient compte de cette possibilité. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | || bgcolor="#A0FFFF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || || || || bgcolor="#FFA0FF" | READ (2) || || || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 | bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 | |} Le séquenceur peut aussi profiter du fait que des accès simultanés peuvent se faire de préférence se faire dans des banques différentes. Prenons l'exemple d'une mémoire partagée entre plusieurs processeurs. Les deux processeurs ou cœurs exécutent deux programmes différents : le premier processeur exécute le navigateur web, alors que le second lit de la musique. Dans ce cas, les deux processeurs accèdent à des données très éloignées en mémoire (ce n'est pas toujours le cas, mais ça l'est dans cet exemple), qui sont potentiellement dans des banques séparées. Par contre, il y a une contrainte majeure : deux accès simultanés ne peuvent pas accéder au bus en même temps. Je rappelle que quand on démarre une lecture, il se passe quelques cycles d'horloge avant que la donnée atterrissent sur le bus de données. Le temps en question est fixe, mais sa durée n'est pas la même selon que la lecture fasse un succès de page ou un défaut de page. Le séquence mémoire doit en tenir compte avant de démarrer une lecture ou une écriture. Le séquenceur doit donc détecter les succès de page, et déterminer si le bus de donnée sera libre au moment où la lecture terminera. Si c'est le cas, il peut lancer a commande. Sinon, il doit la retarder. {|class="wikitable" |+ Exemple où deux processeurs accèdent à une SDRAM en même temps |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 |- ! rowspan="2" | Requêtes mémoires | bgcolor="#A0FFFF" | CPU 1 || || || bgcolor="#A0FFFF" | CPU 1 || || || || || || |- | bgcolor="#FFA0FF" | CPU 2 || || || || || bgcolor="#FFA0FF" | CPU 2 || || || || |- |- ! Bus de commande/adresse | || bgcolor="#A0FFFF" | Lecture (CPU 1) || bgcolor="#FFA0FF" | Lecture (CPU 2) || || bgcolor="#A0FFFF" | Lecture (CPU 1) || || bgcolor="#FFA0FF" | Lecture (CPU 2) || || |- ! Bus de données | || || || bgcolor="#A0FFFF" | Donnée lue CPU 1 || bgcolor="#FFA0FF" | Donnée lue CPU 2 || || bgcolor="#A0FFFF" | Donnée lue CPU 1 || || bgcolor="#FFA0FF" | Donnée lue CPU 2 |} Les DDR2 et 3 vont encore plus loin avec l'optimisation des '''CAS postés'''. L'idée est que le contrôleur mémoire peut envoyer une commande ACT et une commande READ/WRITE sans se soucier des ''timings''. En théorie, les deux commandes doivent être séparées par quelques cycles, sur une SDRAM ou une DDR1. Mais avec la DDR2, le contrôleur mémoire peut envoyer les deux l'une après l'autre, au cycle suivant. C'est la mémoire qui mettra en attente la commande READ/WRITE pour respecter les ''timings'' mémoire. Cela complexifie le fonctionnement interne de la DDR, mais simplifie grandement le travail du contrôleur mémoire. ===La politique de gestion du tampon de ligne=== Plus haut, nous avons parlé des mémoires FPM, qui ont introduit quelques optimisations pour utiliser au mieux le tampon de ligne. Il se trouve que les SDRAM conservent ces optimisations. Il y a toujours cette notion de succès de page et de défaut de page, suivant que deux accès se font dans la même ligne ou dans deux lignes différentes. Du moins, c'est le cas si le séquenceur mémoire est coopératif. En effet, il peut fonctionner de plusieurs manières, dont les plus extrêmes sont appelés la politique de la page fermée et la politique de la page ouverte. Voyons à quoi elles correspondent. Avec la '''politique de la page fermée''', chaque accès mémoire est suivi d'une commande PRECHARGE, qui ferme la ligne courante et précharge les lignes de bits. Même si deux accès consécutifs se font dans la même ligne, la ligne est fermée et ré-ouverte entre deux accès mémoire. En clair : l'optimisation introduite par les mémoires FPM est désactivée, le contrôleur mémoire fait exprès de ne pas en profiter. On appelle cette méthode la close ''page autoprecharge''. Cette méthode réduit grandement les performances pour les accès à des adresses consécutives, mais fonctionne à merveille si les accès sont "aléatoires", à savoir qu'ils se font sans régularités évidentes. Son seul avantage est que l'implémentation du séquenceur mémoire est très simple. En effet, le séquenceur mémoire se passe complétement de la table des banques, du comparateur de ligne, et de tous les circuits nécessaires pour vérifier les succès ou défauts de page. De plus, le séquenceur mémoire profite grandement des commandes READA et WRITEA, qui fusionnent une commande READ/WRITE avec une commande PRECHARGE. Le séquenceur mémoire a juste à envoyer des commandes ACT, READA, WRITEA et PREFETCH à la mémoire, pas besoin des commandes PRECHARGE, READ ou WRITE. A l'opposé, la '''politique de la page ouverte''' ne ferme pas automatiquement la ligne. Elle la laisse ouverte, en espérant que le prochain accès mémoire se fasse dans cette ligne. Lorsqu'un nouvel accès mémoire arrive, elle doit détecter les succès ou défauts de page et agir en fonction. En cas de défaut de page, la ligne est fermée, le séquenceur mémoire envoie une commande PRECHARGE, puis l'accès suivant effectue les deux commandes ACT + READ ou WRITE. En cas de succès de page, les commandes PRECHARGE et ACT ne sont pas envoyées, seules la commande READ ou WRITE l'est. Un désavantage est que le contrôleur mémoire doit inclure une table des banques et un comparateur, comme vu plus haut dans la section sur les mémoires FPM. un autre défaut est que garder une ligne ouverte consomme beaucoup d'énergie, comparé à un simple état de PRECHARGE. En conséquence, il est préférable de fermer les lignes dès que possible. Par contre, les performances sont d'autant meilleures que les accès mémoire consécutifs à une même ligne soient assez fréquents. Si les accès mémoire sont aléatoires, les performances sont moins bonnes. La politique de la page fermée fermait les lignes en avance, avec des commandes READA ou WRITEA, avant même que l'accès suivant démarre. Avec la politique de la page ouverte, on doit attendre pour détecter un défaut de page, puis fermer la ligne avec une commande PRECHARGE séparée. La ligne est donc fermée avec un peu temps de retard, et envoyer deux commandes au lieu d'une prend plus de temps. Les contrôleurs mémoires basiques utilisent une des deux solutions précédentes. Soit la page est toujours fermée, soit elle est toujours laissée ouverte jusqu'à ce qu'un accès mémoire la referme. Mais les contrôleurs plus évolués tentent de prédire s'il faut fermer ou laisser ouvertes les pages ouvertes. La méthode la plus simple attend un temps prédéterminé avant de fermer la ligne. Une autre solution regarde le tout dernier accès. On peut très bien décider de laisser la ligne ouverte si l'accès mémoire précédent était une rafale, et fermer sinon. Une solution plus complexe mémorise les N derniers accès et en déduit s'il faut fermer ou non la prochaine ligne. On peut mémoriser si l'accès en question a causé la fermeture d'une ligne avec un bit. Mémoriser les N derniers accès demande d'utiliser un simple registre à décalage. Pour chaque valeur de ce registre, il faut prédire si le prochain accès demandera une ouverture ou une fermeture. Une solution simple fait la moyenne des bits à 1 dans ce registre : si plus de la moitié des bits est à 1, on laisse la ligne ouverte et on ferme sinon. Pour améliorer un petit peu l'algorithme, on peut faire en sorte que les bits des accès mémoires les plus récents aient plus de poids dans le calcul de la moyenne. Il existe sans doute d'autres solutions plus évoluées, mais il est difficile de savoir ce qu'il y a dans les contrôleurs de SDRAM modernes. : Le fait de laisser ouverte une ligne ou au contraire de la fermer systématiquement, se fait pour chaque banque. ===Les banques et l'entrelacement mémoire des SDRAM=== Les SDRAM incorporent plusieurs banques à l'intérieur de leurs circuits. Pour rappel, une banque est une sous-mémoire, avec ses propres décodeurs, son tampon de ligne, ses multiplexeurs de colonnes, etc. Pour le dire autrement, une SDRAM regroupe plusieurs mémoires séparées dans un même circuit intégré, les mémoires en question étant des banques. [[File:Arrangement vertical.jpg|centre|vignette|upright=2.5|Mémoire multi-banques.]] La présence de plusieurs banques impacte la gestion des lignes ouvertes/fermées. En effet, chaque banque a son propre tampon de ligne, ce qui fait que la gestion des lignes se fait indépendamment dans chaque banque. Le séquenceur mémoire doit donc se souvenir des lignes actives dans chaque banque. Pour cela, il mémorise ces lignes dans une petite mémoire : la '''table des banques''', aussi appelée ''bank status memory''. Pour détecter un succès ou un défaut, le contrôleur doit extraire la ligne de l'adresse, mais aussi le numéro de banque. Il envoie alors le numéro de banque à la table des banques, sur son entrée d'adresse. Il récupère alors le numéro de la ligne active sur les sorties de données. Il compare alors ce numéro de ligne avec le numéro de ligne de l'adresse envoyée par le processeur. C'est un succès si les deux sont égales, un défaut sinon. [[File:Controleur mémoire FPM avec plusieurs banques.jpg|centre|vignette|upright=2|Contrôleur mémoire FPM avec plusieurs banques.]] <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les mémoires RAM dynamiques (DRAM) | prevText=Les mémoires RAM dynamiques (DRAM) | next=Les mémoires associatives | nextText=Les mémoires associatives }} </noinclude> 0qk0fe94c5qf0c45tvn9ch6pdmxkmh5 764672 764665 2026-04-23T18:34:05Z Mewtow 31375 /* Le séquenceurs mémoire pour les SDRAM/DDR */ 764672 wikitext text/x-wiki Les mémoires ROM ou SRAM ont généralement une interface simple, à laquelle le processeur peut s'interfacer directement. Mais pour les DRAM, ce n'est pas le cas. Les DRAM utilisent un bus d'adresse multiplexé, où l'adresse est envoyée en deux fois. Connecter le processeur directement sur une DRAM n'est pas pratique : le bus d'adresse du processeur et celui de la mémoire ne collent pas. Les DRAM doivent aussi être rafraichies régulièrement. Le rafraichissement mémoire peut être délégué au processeur, mais c'est loin d'être idéal. Et il y a bien d'autres raisons qui font que le processeur ne peut pas s'interfacer facilement avec les mémoires DRAM. Pour gérer ces problèmes, les mémoires DRAM ne sont pas connectées directement au processeur. À la place, on ajoute un intermédiaire entre le processeur et la mémoire : le '''contrôleur mémoire externe'''. Son but est de montrer au processeur une interface simple, semblable à celle d'une SRAM classique, alors qu'il commande une mémoire DRAM à l'interface plus complexe. Il est placé sur la carte mère ou dans le processeur, et ne doit pas être confondu avec le contrôleur mémoire intégré dans la mémoire. Ce chapitre va expliquer quels sont les rôles du contrôleur mémoire, son interface et ce qu'il y a à l'intérieur. Dans ce chapitre, quand nous parlerons de ''contrôleur mémoire'', cela fera systématiquement référence au contrôleur mémoire externe. Et avant de poursuivre, sachez qu'il est difficile de faire des généralités sur les contrôleurs mémoire, car les mémoires DRAM elles-mêmes sont assez différentes les unes des autres. Entre une mémoire EDO, une mémoire SDR, une mémoire DDR et une DRAM asynchrone, les contrôleurs mémoires seront fortement différents. Aussi, il y a aura une différence entre un contrôleur pour une DRAM asynchrone et un contrôleur pour une mémoire EDO, une mémoire SDRAM, etc. J'ai choisit de vous séparer les contrôleurs mémoire pour les DRAM asynchrones de ceux pour les SDRAM/DRR. ==Le contrôleur d'une DRAM asynchrone== Les premières DRAM asynchrones avaient des contrôleurs mémoires dédiés, qui étaient séparés du processeur et du ''chipset'' de la carte mère. Par exemple, les composants Intel 8202, Intel 8203 et Intel 8207 étaient des contrôleurs mémoire pour DRAM asynchrones qui étaient vendus dans des boitiers DIP et étaient soudés sur la carte mère. Par la suite, ils ont été intégrés au ''chipset'' de la carte mère pendant les décennies 90-2000. Après les années 2000, ils ont été intégrés dans les processeurs. Leurs fonctions étaient le multiplexage de l'adresse et le rafraichissement mémoire. Ils recevaient une adresse mémoire complète, qu'ils découpaient une adresse de ligne et une adresse de colonne, envoyées l'une après l'autre sur le bus mémoire. Pour le rafraichissement mémoire, ils rafraichissaient la DRAM régulièrement, de manière automatique, entre deux accès mémoire normaux. Le processeur n'avait ainsi plus à rafraichir la mémoire lui-même, cette fonction était déléguée au contrôleur de DRAM. Ils étaient connectés au bus d'adresse et de commande, avec éventuellement des relations indirectes avec le bus de données. ===L'interface d'un contrôleur de DRAM asynchrone=== L'interface du contrôleur mémoire décrit ses broches d'entrées/sorties et leur signification. Elle est généralement très simple et contient deux ports : un connecté au processeur, un autre connecté à la DRAM. Cela trahit d'ailleurs son rôle principal, qui est de transformer les requêtes de lecture/écriture provenant du processeur en une suite de commandes acceptée par la mémoire. Le port connecté à la DRAM est connecté ua bus d'adresse et au bus de commande. Le bus de données est lui relié au processeur et/ou au bus système. Un accès mémoire provenant du processeur contient une adresse à lire/écrire, le bit R/W qui indique s'il faut faire une lecture ou une écriture, et éventuellement une donnée à écrire. Mais, nous avons vu que les accès mémoires sur une DRAM sont multiplexés : on envoie l'adresse en deux fois : la ligne d'abord, puis la colonne. De plus, il faut générer les signaux RAS, CAS et bien d'autres. Le tout est illustré ci-dessous. [[File:Contrôleur mémoire.png|centre|vignette|upright=2|Contrôleur mémoire externe.]] Un point important est que les DRAM asynchrones n'ont pas d'entrée ''Chip Select'' ou d'entrée ''Output Enable''. Les signaux RAS et CAS remplacent en quelque sorte ces deux signaux. Le bit RAS fait office de ''Chip Select'', le bit CAS fait office d'''Output Enable''. Pour certains contrôleurs de DRAM, il faut ajouter l''''interface électrique''', qui traduit les signaux du processeur en signaux compatibles avec la mémoire. Il est en effet très fréquent que la mémoire et le processeur n'utilisent pas les mêmes tensions pour coder un bit, ce qui fait qu'elles ne sont pas compatibles. Dans ce cas, le contrôleur mémoire fait la conversion. Le contrôleur DRAM peut fonctionner en mode synchrone ou asynchrone, du point de vue du processeur. Quand il fonctionne en mode synchrone, il permet d'interfacer un processeur synchrone avec une mémoire DRAM asynchrone. Un point important est que le contrôleur DRAM sert d'intermédiaire entre une mémoire DRAM et "le reste du monde". Il ne fait pas forcément office d'intermédiaire entre DRAM et processeur, mais peut aussi faire l'intermédiaire entre la DRAM et un bus système, entre une DRAM et le ''Video Display Controler'' d'une carte graphique, ou n'importe quel autre composant électronique qui utilise cette DRAM. ===Le générateur de ''timings'' et la traduction d'adresse=== Le contrôleur mémoire doit traduire les adresses du processeur en adresses compatibles avec la mémoire. Et la traduction est assez variable, suivant que le bus mémoire est un bus normal, un bus multiplexé, ou partiellement multiplexé. Nous avons vu ces trois types de bus mémoire dans le chapitre sur l'interface des mémoires, mais nous ferons quelques rappels rapides. Avec un ''bus totalement multiplexé'', le bus d'adresse et le bus de données sont fusionnés. Dans ce cas, on peut envoyer soit une adresse, soit lire/écrire une donnée sur le bus, mais on ne peut pas faire les deux en même temps. Un bit ALE indique si le bus est utilisé en tant que bus d'adresse ou bus de données. Le contrôleur mémoire gère cette situation, en fixant le bit ALE et en envoyant séparément adresse et donnée pour les écritures. [[File:Bus multiplexé avec bit ALE.png|centre|vignette|upright=2|Bus multiplexé avec bit ALE.]] Avec un ''bus d'adresse multiplexé'', l'adresse est découpée en une adresse de ligne et une adresse de colonne, envoyées l'une après l'autre. Le contrôleur mémoire prend en entrée une adresse mémoire complète, la découpe en deux, et envoie chaque morceau au bon moment. Pour cela, il suffit d'un registre pour mémoriser l'adresse et d'un multiplexeur. Le multiplexeur choisit soit les bits de poids fort de l'adresse, soit ceux de poids faible. Les premiers correspondent à l'adresse de ligne, les autres à l'adresse de colonne. La commande du multiplexeur est le fait d'un petit circuit séquentiel, qui génère aussi les signaux CAS et RAS. Au premier cycle, il met le signal RAS à 1, met le CAS à 0, et configure le MUX pour sélectionner les bits de poids fort. Au second cycle, il génère un signal CAS à 1, met le RAS à 0 et configure le MUX pour sélectionner les bits de poids faible. Le circuit en question est appelé le générateur de ''timings''. [[File:Controleur de DRAM simple, sans rafraichissement mémoire.png|centre|vignette|upright=2|Contrôleur de DRAM simple, sans rafraichissement mémoire.]] Le générateur de ''timings'' est un circuit séquentiel qui implémente une petite machine à état. Il est très simple sur une mémoire DRAM asynchrone basique, mais il est plus complexe sur les mémoires FPM, EDO, quartet, et autres. Le regroupement des multiplexeurs d'adresse et du générateur de ''timings'' est appelé le '''séquenceur mémoire'''. C'est le séquenceur mémoire qui traduit la requête processeur en commande DRAM, le reste du contrôleur est dédié au rafraichissement mémoire ou à d'autres fonctions facultatives. ===Le rafraichissement mémoire=== La gestion du rafraichissement mémoire est la fonction principale du contrôleur DRAM. Pour gérer le rafraichissement mémoire, le contrôleur mémoire intègre deux compteurs, un pour gérer l'adresse à rafraichir, l'autre pour gérer l'intervalle de temps entre deux rafraichissements. Le rafraichissement se fait à intervalle régulier, tous les x microsecondes. Pour déclencher le rafraichissement au bon moment, le contrôleur mémoire contient un ''Refresh Timer'', aussi appelé le '''compteur de rafraichissement'''. Il est initialisé avec le temps entre deux rafraichissements, une adresse est rafraichie quand ce compteur atteint 0. Le rafraichissement mémoire balaye la mémoire adresse par adresse. Pour savoir à quelle adresse il en est rendu, le contrôleur mémoire utilise un '''compteur d'adresse'''. Il contient la prochaine adresse à rafraichir, aussi appelée l'adresse de rafraichissement. Régulièrement, l'adresse dans ce compteur est envoyée à la RAM, pour une lecture. Mais la donnée lue n'est pas envoyée sur le bus de donnée, soit parce que la RAM est prévue pour, soit parce que le contrôleur désactive son bit ''output enable''. Dans le second cas, la RAM fait la lecture en interne, mais se déconnecte du bus de donnée, perdant la donnée lue dans le néant. Pour envoyer l'adresse de rafraichissement sur le bus d'adresse, il faut rajouter un multiplexeur, qui choisit entre l'adresse normale et l'adresse de rafraichissement. [[File:Controleur de DRAM avec rafraichissement mémoire.png|centre|vignette|upright=2|Controleur de DRAM avec rafraichissement mémoire.]] Le multiplexeur ne doit cependant pas être configuré si une adresse est déjà en cours de transfert. Pour cela, un circuit d'arbitrage se débrouille pour éviter qu'un accès mémoire soit interrompu par une demande de rafraichissement et inversement. Il peut être inclus dans le séquenceur mémoire ou séparé de celui-ci. [[File:Controleur mémoire, intérieur simplifié.png|centre|vignette|upright=2.5|Contrôleur mémoire, intérieur simplifié.]] Il faut noter que le rafraichissement mémoire peut être délégué non pas au contrôleur mémoire, mais au processeur où à la DRAM elle-même. Quand elle est le fait du processeur, celui-ci incorpore un ''refresh timer'' et un compteur d'adresse. Un exemple est celui du processeur Zilog Z80, qui implémentait des compteurs internes pour gérer le rafraichissement mémoire. On peut considérer que le processeur incorpore alors le contrôleur mémoire, au moins partiellement. Il est aussi possible de déléguer le rafraichissement au logiciel (certains jeux vidéos Amiga ou Commodore faisaient cela pour la mémoire vidéo). Quand la DRAM elle-même s'occupe de son propre rafraichissement, c'est elle qui intègre un ''refresh timer'' et le compteur d'adresse. ===Le décodage d'adresse=== Le contrôleur mémoire gère aussi le '''décodage d'adresse'''. pour rappel, celui-ci est utilisé quand on combine plusieurs chips mémoire ensemble, pour combiner leurs capacités mémoire. Par exemple, on peut combiner 4 chips mémoires de 1 mébioctet chacun, pour que le processeur voit comme 4 mébioctets de RAM unique. Le premier mébioctet est placé dans le premier chip mémoire, le second mébioctet dans le second chip, etc. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Pour cela, on active le chip mémoire adéquat, en fonction de l'adresse à consulter. Les autres chips mémoire sont désactivés pendant l'accès mémoire. En théorie, activer ou désactiver un chip mémoire se fait en utilisant son entrée ''Chip Select''. Il faut noter que si les SDRAM disposent bien d'un signal ''Chip Select'', ce n'est pas le cas des mémoires RAM asynchrones. A la place, ce sont les signaux RAS qui font office de ''Chip Select''. Une RAM asynchrone est activée quand son signal RAS lui demande de lire une ligne, elle est désactivée sinon. Mais c'est un détail. Toujours est-il que les signaux ''Chip Select'', ou leurs équivalents, sont générés par le contrôleur de DRAM, à partir des bits de poids fort de l'adresse. Par exemple, avec 4 chips mémoire, les deux bits de poids fort de l'adresse sont utilisés pour sélectionner le chip mémoire adéquat. Le contrôleur mémoire doit avoir plusieurs sorties ''Chip Select'', une par chip mémoire. Et le nombre de ces sorties limite le nombre de chips mémoire qu'on peut combiner. Par exemple, s'il y a seulement 4 sorties ''Chip Select'', on ne pourra brancher que 4 chips mémoire dessus. Sauf à ruser, avec un arrangement horizontal, mais cela n'est pas le ressort du contrôleur mémoire. [[File:Td6bfig3.png|centre|vignette|upright=2|Comparaison entre arrangement horizontal (à gauche) et arrangement vertical (à droite).]] ===Exemple : l'Intel 8202-8203=== L'Intel 8202 et le 8203 étaient des contrôleurs de mémoire DRAM, parmi les plus simples qui soient. Ils avaient une entrée d'adresse de 12 bits, ce qui permettait d'adresser 4 kibioctets de RAM. Ils fournissaient en sortie une adresse multiplexée sur 6 bits, envoyée en deux fois. Ils avaient donc 12 entrées d'adresse, 6 sorties d'adresse, un signal RAS, un signal CAS. Les adresses présentées en entrées n'étaient pas mémorisées dans des registres, ce qui fait qu'elles devaient être maintenues durant toute la durée de l'accès mémoire. Le processeur ne pouvait donc pas se déconnecter du bus d'adresse pendant l'accès mémoire, peu importe sa durée. Le 8202 contenait aussi un compteur de rafraichissement. Rappelons que sur les DRAM asynchrones, le rafraichissement se fait ligne par ligne. Le contrôleur mémoire a juste à présenter l'adresse de ligne, il n'a pas à envoyer l'adresse de colonne. La commande de rafraichissement se fait en mettant le signal RAS à 0, mais en laissant le CAS à 1 (je rappelle que les signaux sont actifs à 0). Le compteur d'adresse de rafraichissement a donc juste à mémoriser l'adresse de ligne. Le séquenceur mémoire était précédé par un circuit d'arbitrage, non-représenté dans le schéma ci-dessous. La microarchitecture de l'Intel 8202 est la suivante : [[File:Microarchitecture de l'Intel 8202.png|centre|vignette|upright=2|Microarchitecture de l'Intel 8202.]] Le 8202 avait une entrée pour un signal d'horloge, ainsi qu'un ''Chip Select'' un peu particulier. Si le signal CS passait à 0 lors d'un accès mémoire, le 8202/8203 ne se désactivait qu'une fois l'accès mémoire terminé. On ne pouvait pas l'interrompre pendant un accès mémoire, même en changeant le bit CS. Le signal d'horloge était utilisé pour commander le ''refresh timer''. Pour commander les lectures et écriture, il recevait en entrée un bit ''Write Request'' et un bit ''Read Request'', qui demandent respectivement une écriture et une lecture. En sortie, on trouvait un unique bit R/W qui valait 0 pour une lecture et 1 pour une écriture. Il avait aussi un bit d'entrée pour forcer le rafraichissement mémoire. S'il est à 1, la mémoire rafraichie l'adresse envoyée par le processeur. Le 8202 pouvait être connecté sur 1 à 4 chips mémoire, ce qui permettait d'adresser au maximum 16 kibioctets de RAM. Les 4 chips ne sont pas accédés en parallèle, un seul l'est à chaque fois. Pour le décodage d'adresse, le 8202 dispose de deux bits BO et B1 pour sélectionner le chip adéquat, ainsi que 4 sorties RAS pour activer la banque adéquate. On rappelle que le signal RAS remplace le signal ''Chip Select''. C'est le séquenceur mémoire qui se charge de générer ces signaux RAS, à partir des deux bits B0 et B1 (qui sont techniquement des bits d'adresse). Pour communiquer avec le processeur, l'Intel 8202 disposait de deux bits XACK et SACK. SACK indiquait au processeur que le 8202/8203 est en train de faire un accès mémoire et qu'il est indisponible pour un second accès mémoire. Cela permet de bloquer le processeur tant que le 8202 est indisponible. Le signal XACK indique que l'accès mémoire précédent est terminé et que : soit la donnée lue est présente sur le bus de données, soit que l'écriture s'est terminée. : Le signal SACK est très utile sur les configurations multiprocesseurs. Un processeur peut démarrer un accès mémoire, le signal SACK indiquera au second processeur qu'il doit attendre que l'accès soit terminé pour que ce soit son tour. ==Les contrôleurs de DRAM asynchrones évolués== L'Intel 8202 était un contrôleur mémoire basique, comme beaucoup d'autres à cette époque. Mais Intel a vendu des contrôleurs mémoires plus complexes. Par exemple, l'Intel 8207 était un contrôleur mémoire bien plus avancé que les deux précédents. Passons sur certains détails, comme le fait qu'il pouvait gérer jusqu'à 256 kibioctets de DRAM. Au-delà de ça, il y avait des fonctionnalités bien plus intéressantes, à savoir : un support de l'ECC, il était double port, il permettait de simuler une DRAM synchrone à partir d'une DRAM asynchrone. Il n'était pas le seul dans ce cas et des contrôleurs de DRAM très évolués étaient capables de faire des merveilles. Voyons comment cela était possible. ===Les contrôleurs mémoire synchrone=== Il est parfaitement possible d'utiliser un contrôleur mémoire synchrone avec une DRAM asynchrone. A vrai dire, le contrôleur DRAM peut complétement simuler une mémoire synchrone alors que la DRAM associée est asynchrone. La traduction asynchrone vers synchrone se fait en ajoutant des registres sur le bus mémoire, notamment sur le bus de données et le bus d'adresse/commande. Nous avions détaillé cela dans le chapitre sur les SRAM, c'est la même chose avec une mémoire DRAM. Sauf que cette fois-ci, le contrôleur mémoire doit aussi être prévu pour. [[File:Controleur mémoire synchrone utilisé avec une DRAM asynchrone.png|centre|vignette|upright=2|Contrôleur mémoire synchrone utilisé avec une DRAM asynchrone]] Les deux-trois registres illustrés plus haut peuvent être intégrés directement dans le contrôleur mémoire, que ce soit totalement ou en partie. Le strict minimum pour avoir un contrôleur mémoire synchrone est que celui-ci doit mémoriser adresse et commandes dans un registre. Par exemple, le 8207 d'Intel était capable de mémoriser les requêtes processeurs dans un registre interne, mais il fallait utiliser deux registres séparés pour le bus de données. Les deux registres étaient alors commandés par le contrôleur mémoire. Il est cependant possible d'aller plus loin et d'intégrer les registres du bus de données dans le contrôleur mémoire. [[File:Controleur mémoire DRAM synchrone.png|centre|vignette|upright=2|Contrôleur mémoire DRAM synchrone.]] : Il faut noter que cette fonctionnalité est parfois disponible sur les SRAM. En clair, on peut associer une SRAM asynchrone avec un contrôleur de SRAM synchrone. Le contrôleur de SRAM se charge alors de simuler une SRAM synchrone à partir de la SRAM asynchrone. Utiliser un contrôleur mémoire synchrone a de nombreux avantages, l'un d'entre eux étant lié aux ''wait state''. Quand le processeur envoie une requête de lecture/écriture à la mémoire RAM, celle-ci met plusieurs cycles d'horloge à répondre. Et pendant ce temps, le processeur... attend. Et pendant ce temps d'attente, il doit maintenir l'adresse mémoire sur le bus d'adresse. Les cycles d'horloge perdus à attendre la mémoire RAM étaient appelés des '''''Wait states'''''. Utiliser un contrôleur mémoire synchrone d'éliminer les ''wait state'', dans une certaine mesure. Avec un contrôleur mémoire synchrone, le processeur envoie l'adresse, mais c'est le contrôleur mémoire qui la maintient sur le bus d'adresse. Le processeur peut envoyer l'adresse et la donnée à écrire, elles sont recopiées dans les registres, et le controleur mémoire y a accès sans que le processeur doive les maintenir. Le processeur peut se déconnecter du bus mémoire et faire du travail dans son coin pendant que le contrôleur mémoire accède à la DRAM. Les ''wait state'' disparaissent alors, du moins du point de vue du processeur. ===La gestion de l'ECC=== L''''ECC''' peut être géré dans le contrôleur mémoire. Pour cela, on couple les registres mentionnés dans la section précédente, avec un circuit de détection et de correction d'erreur. Le circuit d'ECC peut, comme les registres synchrones, être intégré dans le contrôleur mémoire, ou au contraire être situé dans un circuit séparé. Si le circuit d'ECC est séparé du contrôleur mémoire, il communique avec lui, histoire que le contrôleur mémoire puisse signaler toute erreur de parité ou d'ECC au processeur. [[File:Controleur mémoire synchrone avec ECC intégré.png|centre|vignette|upright=2|Controleur mémoire synchrone avec ECC intégré]] Reprenons l'exemple du 8207 d'Intel. Le contrôleur mémoire 8207 gère le bus d'adresse et de commande, mais n'a pas de connexions directes avec le bus de données. Il ne peut donc pas prendre en charge l'ECC. Il avait besoin d'être couplé avec un circuit d'ECC séparé, relié au bus de données : l'Intel 8206. Le 8026 prenait en entrée : 16 bits de données et 8 bits d'ECC. Il fournissait en sortie 16 bits de données après correction d'erreur, les 8 bits d'ECC pour indiquer qu'une erreur a été détectée mais pas corrigée, ainsi que des bits de parité. Le 8206 détectait/corrigeait les erreurs et générait les bits d'ECC, mais il communiquait avec le contrôleur mémoire pour cela. [[File:8207 avec ECC.png|centre|vignette|upright=2|8207 avec ECC]] La détection/correction d'erreur était appliquée à la fois pour les accès mémoire et pour les rafraichissements mémoire. Lors d'un rafraichissement mémoire, la donnée rafraichie est lue et réécrite. Avec l'ECC activé et configuré correctement, le rafraichissement passe par le bus de données. Au lieu d'avoir un cycle de lecture-écriture interne à la DRAM, on a un cycle de lecture-correction-écriture qui utilise le 8206. La donnée lue est envoyée sur le bus de données, puis le 8206 corrige une éventuelle erreur, et la donnée corrigée est alors réécrite en mémoire. Au passage, si une erreur non-correctible est détectée, le 8206 ne fait rien, l'erreur est ignorée. La gestion de l'erreur sera retardée jusqu'à une lecture ultérieure. Et encore : si lecture ultérieure il y a. Si la donnée est écrasée par une écriture, la donnée corrompue sera simplement écrasée et disparaitra sans avoir pu faire le moindre dégât. Mais pour cela, le 8206 doit communiquer avec le contrôleur mémoire, pour savoir s'il est dans un cycle de rafraichissement ou un accès mémoire normal. Il prévient le 8207 lors d'une erreur, et c'est ce dernier qui décide si l'erreur doit être prise en compte ou ignorée. C'est seulement lors d'un accès mémoire normal que le processeur est prévenu qu'une erreur de parité/autre a eu lieu. ===Les contrôleurs mémoires multiports=== Les '''contrôleur mémoire multiport''' disposent de plusieurs ports, chacun permettant de traiter un accès mémoire. Ils peuvent simuler une mémoire multiport à partir d'une DRAM monoport. Évidemment, la simulation n'est pas parfaite. Des accès mémoire simultanés, envoyés en même temps sur différents ports, sont en réalité exécutés un par un, pas en même temps. Il y a donc une petite pénalité en termes de performances, mais elle est mineure. Encore une fois, nous allons reprendre l'exemple du 8207. Il avait deux ports séparés, et était prévu pour fonctionner dans un système à deux processeurs. L'usage de deux ports séparés permettait de partager une unique mémoire DRAM entre deux processeurs. Le partage se faisait en interfaçant deux processeurs sur le contrôleur mémoire, chacun étant connecté à un port. Lors d'une lecture, il redirigeait la donnée lue vers le bon processeur, en configurant le bus de données correctement. Le contrôleur mémoire recevait des requêtes mémoire de deux processeurs, mais il les exécutait une à la fois. S'il recevait deux requêtes en même temps, l'une d'entre elles était mise en attente. Le contrôleur mémoire doit arbitrer les accès à la mémoire, et faire en sorte que les deux processeurs aient accès à la mémoire à tour de rôle. Et non seulement il doit arbitrer les deux ports, mais il y a aussi un troisième port interne au contrôleur mémoire : le rafraichissement mémoire ! Pour cela, le circuit d'arbitrage qui choisissait entre rafraichissement mémoire et accès mémoire, est amélioré de manière à gérer un second port. Le circuit d'arbitrage donne l'accès au séquenceur mémoire à un port sélectionné. L'arbitrage était configurable, avec deux options : soit le port A est privilégié sur le port B, soit le port le plus récemment accédé à la priorité. Les deux ports pouvaient être configurés pour fonctionner soit de manière asynchrone, soit de manière synchrone. Il était aussi possible de configurer l'ECC, des options liées à la fréquence du processeur et de la RAM, ainsi que de nombreuses options liées au rafraichissement. Pour cela, le 8207 contenait un registre de configuration interne, programmable en fournissant les entrées adéquates. Tout ce qui vient d'être dit se généralise avec plus de deux processeurs. Le 8207 ne permettait pas ça, mais les contrôleurs mémoire des PC modernes en sont capables. Ils peuvent gérer plusieurs dizaines de processeurs facilement. ==Le contrôleur mémoire d'une DRAM ''Fast Page Mode''== Les mémoires DRAM classiques sont des mémoires à tampon de ligne, mais qui sont assez mal utilisées. Notamment, tout accès mémoire se fait en deux phases : un accès pour sélectionner la ligne, un autre pour sélectionner la colonne. Les mémoires ''Fast Page Mode'' permettent d'optimiser le tout. Elles permettent de faire plusieurs accès successifs à la même ligne, à des colonnes différentes. Et le contrôleur mémoire doit être adapté pour cela. [[File:Sélection d'une ligne sur une mémoire FPM ou EDO.png|centre|vignette|upright=2|Sélection d'une ligne sur une mémoire FPM ou EDO.]] Le contrôleur mémoire doit détecter que deux accès mémoire consécutifs se font dans la même ligne. Si deux accès consécutifs accèdent à la même ligne, on doit juste changer de colonne et altérer le signal CAS. C'est un ''succès de tampon de ligne'', aussi appelé un '''succès de page'''. Si deux accès consécutifs accèdent à une ligne différente, c'est un ''défaut de tampon de ligne'', aussi appelé un '''défaut de page'''. Il faut alors changer de ligne, en altérant les signaux RAS et en envoyant une adresse de ligne. Pour détecter les succès ou défauts de page, il faut ajouter un circuit spécialisé dans le contrôleur mémoire. Il mémorise la ligne ouverte, et plus précisément son adresse de ligne (numéro de banque inclut). A chaque requête processeur, il compare l'adresse de ligne recue avec celle déjà ouverte. C'est un succès si les deux sont égales, un défaut si elles sont différentes. Le circuit qui fait cette comparaison est appelé le '''décodeur de commande'''. Il prévient le séquenceur mémoire en cas de succès de page, grâce à un signal de un bit, qui vaut 0 en cas de défaut de page et 1 en cas de succès. Le séquenceur mémoire décide alors comment gérer les signaux RAS et CAS, ainsi que l'envoi des adresses de ligne/colonne. [[File:Controleur mémoire d'une FPM-DRAM.png|centre|vignette|upright=2|Controleur mémoire d'une FPM-DRAM]] ==Le contrôleur mémoire d'une SDRAM ou d'une DDR== l'intérieur d'un contrôleur de SDRAM ne change pas significativement d'un controleur de RAM asynchrone. Il regroupe toujours un séquenceur mémoire et une interface physique, un circuit pour le rafraichissement mémoire et un circuit d'arbitrage. Par contre, ses sorties changent pas mal. Contrairement aux mémoires DRAM basiques, les mémoires SDRAM sont cadencées par un signal d'horloge. Et ce signal d'horloge vient bien de quelque part. Pour cela, deux solutions : soit le contrôleur mémoire génère la fréquence qui commande la mémoire, soit il prend en entrée une fréquence de base qu'il multiplie pour obtenir la fréquence désirée. Les deux solutions sont équivalentes, si ce n'est que les circuits impliqués ne sont pas les mêmes. Dans le premier cas, le contrôleur doit embarquer un circuit oscillateur, qui génère la fréquence demandée. Dans l'autre cas, un simple multiplieur/diviseur de fréquence suffit et c'est généralement une PLL qui est utilisée pour cela. : Notez qu'il ne faut pas confondre la fréquence de la SDRAM et celle du contrôleur mémoire. Le contrôleur mémoire fonctionne à une vitesse assez élevée, en interne. Le port relié au processeur fonctionne à haute fréquence, généralement la même que celle du processeur. A vrai dire, de nos jours, il est intégré dans le processeur. Pour le décodage d'adresse, tout est plus simple sur les SDRAM, DDR inclues. Les chips de mémoire SDRAM et DDR disposent d'une entrée ''Chip Select'', ce qui facilite grandement le décodage d'adresse. Les bits de ''Chip Select'' sont générés par le contrôleur mémoire, et sont transmis sur le bus de commande. Le contrôleur de DRAM peut adresser un certain nombre de rangées, dispersés sur une ou plusieurs barrettes. La limite maximale dépend du contrôleur de DRAM, elle est souvent proche de 8 ou 16 rangées. Si on combine plusieurs barrettes de mémoire, il est possible de dépasser cette limite. Par exemple, pour un contrôleur de DRAM supportant maximum 8 rangées, 4 barrettes de 4 rangées chacune dépassent la limite. ===Le séquenceurs mémoire pour les SDRAM/DDR=== Le séquenceur mémoire existe toujours pour les mémoires SDRAM, c'est toujours un circuit séquentiel qui implémente une machine à état. Il traduit toujours une requête processeur en une séquence de commandes envoyées à des timings bien précis. Les commandes mémoires peuvent provenir de l'extérieur, mais aussi d'un circuit de rafraichissement intégré dans le contrôleur mémoire, comme pour les autres DRAM. La seule différence est que la machine à état est plus complexe. Pour rappel, une requête de lecture/écriture se fait en trois étapes : une commande PRECHARGE pour précharger le tampon de ligne, une commande ACT qui fixe l'adresse de ligne, et enfin une commande READ/WRITE avec l'adresse de colonne. Et il faut tenir compte des timings mémoire, à savoir le fait que ces commandes sont séparées par des temps d'attentes bien précis. Par exemple, je prends des chiffres arbitraires : il faut attendre 2 cycles entre une commande ACT et une commande READ, 6 cycles avant deux commandes WRITE consécutives, etc. La gestion des ''timings'' rend la conception du séquenceur plus complexe. Les SDRAM sont parfois capables de démarrer une commande avant que la précédente soit terminée. Concrètement, pendant que la commande précédente envoie sa dernière donnée sur le bus de données, on peut envoyer la commande suivante avec quelques cycles d'avance. L'exemple ci-dessous devrait être assez clair : on envoie une seconde commande au neuvième cycle, alors qu'une rafale est en cours. Il s'agit d'une forme très limitée de pipeline, similaire à celui des mémoires SRAM, qui ne mérite pas d'être détaillée ici. Mais il faut préciser que la machine à état tient compte de cette possibilité. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | || bgcolor="#A0FFFF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || || || bgcolor="#FFA0FF" | READ (2) || || || || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 | bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 | |} Le séquenceur peut aussi profiter du fait que des accès simultanés peuvent se faire de préférence se faire dans des banques différentes. Prenons l'exemple d'une mémoire partagée entre plusieurs processeurs. Les deux processeurs ou cœurs exécutent deux programmes différents : le premier processeur exécute le navigateur web, alors que le second lit de la musique. Dans ce cas, les deux processeurs accèdent à des données très éloignées en mémoire (ce n'est pas toujours le cas, mais ça l'est dans cet exemple), qui sont potentiellement dans des banques séparées. Par contre, il y a une contrainte majeure : deux accès simultanés ne peuvent pas accéder au bus en même temps. Je rappelle que quand on démarre une lecture, il se passe quelques cycles d'horloge avant que la donnée atterrissent sur le bus de données. Le temps en question est fixe, mais sa durée n'est pas la même selon que la lecture fasse un succès de page ou un défaut de page. Le séquence mémoire doit en tenir compte avant de démarrer une lecture ou une écriture. Le séquenceur doit donc détecter les succès de page, et déterminer si le bus de donnée sera libre au moment où la lecture terminera. Si c'est le cas, il peut lancer a commande. Sinon, il doit la retarder. {|class="wikitable" |+ Exemple où deux processeurs accèdent à une SDRAM en même temps |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 |- ! rowspan="2" | Requêtes mémoires | bgcolor="#A0FFFF" | CPU 1 || || || bgcolor="#A0FFFF" | CPU 1 || || || || || || |- | bgcolor="#FFA0FF" | CPU 2 || || || || || bgcolor="#FFA0FF" | CPU 2 || || || || |- |- ! Bus de commande/adresse | || bgcolor="#A0FFFF" | Lecture (CPU 1) || bgcolor="#FFA0FF" | Lecture (CPU 2) || || bgcolor="#A0FFFF" | Lecture (CPU 1) || || bgcolor="#FFA0FF" | Lecture (CPU 2) || || |- ! Bus de données | || || || bgcolor="#A0FFFF" | Donnée lue CPU 1 || bgcolor="#FFA0FF" | Donnée lue CPU 2 || || bgcolor="#A0FFFF" | Donnée lue CPU 1 || || bgcolor="#FFA0FF" | Donnée lue CPU 2 |} Les DDR2 et 3 vont encore plus loin avec l'optimisation des '''CAS postés'''. L'idée est que le contrôleur mémoire peut envoyer une commande ACT et une commande READ/WRITE sans se soucier des ''timings''. En théorie, les deux commandes doivent être séparées par quelques cycles, sur une SDRAM ou une DDR1. Mais avec la DDR2, le contrôleur mémoire peut envoyer les deux l'une après l'autre, au cycle suivant. C'est la mémoire qui mettra en attente la commande READ/WRITE pour respecter les ''timings'' mémoire. Cela complexifie le fonctionnement interne de la DDR, mais simplifie grandement le travail du contrôleur mémoire. ===La politique de gestion du tampon de ligne=== Plus haut, nous avons parlé des mémoires FPM, qui ont introduit quelques optimisations pour utiliser au mieux le tampon de ligne. Il se trouve que les SDRAM conservent ces optimisations. Il y a toujours cette notion de succès de page et de défaut de page, suivant que deux accès se font dans la même ligne ou dans deux lignes différentes. Du moins, c'est le cas si le séquenceur mémoire est coopératif. En effet, il peut fonctionner de plusieurs manières, dont les plus extrêmes sont appelés la politique de la page fermée et la politique de la page ouverte. Voyons à quoi elles correspondent. Avec la '''politique de la page fermée''', chaque accès mémoire est suivi d'une commande PRECHARGE, qui ferme la ligne courante et précharge les lignes de bits. Même si deux accès consécutifs se font dans la même ligne, la ligne est fermée et ré-ouverte entre deux accès mémoire. En clair : l'optimisation introduite par les mémoires FPM est désactivée, le contrôleur mémoire fait exprès de ne pas en profiter. On appelle cette méthode la close ''page autoprecharge''. Cette méthode réduit grandement les performances pour les accès à des adresses consécutives, mais fonctionne à merveille si les accès sont "aléatoires", à savoir qu'ils se font sans régularités évidentes. Son seul avantage est que l'implémentation du séquenceur mémoire est très simple. En effet, le séquenceur mémoire se passe complétement de la table des banques, du comparateur de ligne, et de tous les circuits nécessaires pour vérifier les succès ou défauts de page. De plus, le séquenceur mémoire profite grandement des commandes READA et WRITEA, qui fusionnent une commande READ/WRITE avec une commande PRECHARGE. Le séquenceur mémoire a juste à envoyer des commandes ACT, READA, WRITEA et PREFETCH à la mémoire, pas besoin des commandes PRECHARGE, READ ou WRITE. A l'opposé, la '''politique de la page ouverte''' ne ferme pas automatiquement la ligne. Elle la laisse ouverte, en espérant que le prochain accès mémoire se fasse dans cette ligne. Lorsqu'un nouvel accès mémoire arrive, elle doit détecter les succès ou défauts de page et agir en fonction. En cas de défaut de page, la ligne est fermée, le séquenceur mémoire envoie une commande PRECHARGE, puis l'accès suivant effectue les deux commandes ACT + READ ou WRITE. En cas de succès de page, les commandes PRECHARGE et ACT ne sont pas envoyées, seules la commande READ ou WRITE l'est. Un désavantage est que le contrôleur mémoire doit inclure une table des banques et un comparateur, comme vu plus haut dans la section sur les mémoires FPM. un autre défaut est que garder une ligne ouverte consomme beaucoup d'énergie, comparé à un simple état de PRECHARGE. En conséquence, il est préférable de fermer les lignes dès que possible. Par contre, les performances sont d'autant meilleures que les accès mémoire consécutifs à une même ligne soient assez fréquents. Si les accès mémoire sont aléatoires, les performances sont moins bonnes. La politique de la page fermée fermait les lignes en avance, avec des commandes READA ou WRITEA, avant même que l'accès suivant démarre. Avec la politique de la page ouverte, on doit attendre pour détecter un défaut de page, puis fermer la ligne avec une commande PRECHARGE séparée. La ligne est donc fermée avec un peu temps de retard, et envoyer deux commandes au lieu d'une prend plus de temps. Les contrôleurs mémoires basiques utilisent une des deux solutions précédentes. Soit la page est toujours fermée, soit elle est toujours laissée ouverte jusqu'à ce qu'un accès mémoire la referme. Mais les contrôleurs plus évolués tentent de prédire s'il faut fermer ou laisser ouvertes les pages ouvertes. La méthode la plus simple attend un temps prédéterminé avant de fermer la ligne. Une autre solution regarde le tout dernier accès. On peut très bien décider de laisser la ligne ouverte si l'accès mémoire précédent était une rafale, et fermer sinon. Une solution plus complexe mémorise les N derniers accès et en déduit s'il faut fermer ou non la prochaine ligne. On peut mémoriser si l'accès en question a causé la fermeture d'une ligne avec un bit. Mémoriser les N derniers accès demande d'utiliser un simple registre à décalage. Pour chaque valeur de ce registre, il faut prédire si le prochain accès demandera une ouverture ou une fermeture. Une solution simple fait la moyenne des bits à 1 dans ce registre : si plus de la moitié des bits est à 1, on laisse la ligne ouverte et on ferme sinon. Pour améliorer un petit peu l'algorithme, on peut faire en sorte que les bits des accès mémoires les plus récents aient plus de poids dans le calcul de la moyenne. Il existe sans doute d'autres solutions plus évoluées, mais il est difficile de savoir ce qu'il y a dans les contrôleurs de SDRAM modernes. : Le fait de laisser ouverte une ligne ou au contraire de la fermer systématiquement, se fait pour chaque banque. ===Les banques et l'entrelacement mémoire des SDRAM=== Les SDRAM incorporent plusieurs banques à l'intérieur de leurs circuits. Pour rappel, une banque est une sous-mémoire, avec ses propres décodeurs, son tampon de ligne, ses multiplexeurs de colonnes, etc. Pour le dire autrement, une SDRAM regroupe plusieurs mémoires séparées dans un même circuit intégré, les mémoires en question étant des banques. [[File:Arrangement vertical.jpg|centre|vignette|upright=2.5|Mémoire multi-banques.]] La présence de plusieurs banques impacte la gestion des lignes ouvertes/fermées. En effet, chaque banque a son propre tampon de ligne, ce qui fait que la gestion des lignes se fait indépendamment dans chaque banque. Le séquenceur mémoire doit donc se souvenir des lignes actives dans chaque banque. Pour cela, il mémorise ces lignes dans une petite mémoire : la '''table des banques''', aussi appelée ''bank status memory''. Pour détecter un succès ou un défaut, le contrôleur doit extraire la ligne de l'adresse, mais aussi le numéro de banque. Il envoie alors le numéro de banque à la table des banques, sur son entrée d'adresse. Il récupère alors le numéro de la ligne active sur les sorties de données. Il compare alors ce numéro de ligne avec le numéro de ligne de l'adresse envoyée par le processeur. C'est un succès si les deux sont égales, un défaut sinon. [[File:Controleur mémoire FPM avec plusieurs banques.jpg|centre|vignette|upright=2|Contrôleur mémoire FPM avec plusieurs banques.]] <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les mémoires RAM dynamiques (DRAM) | prevText=Les mémoires RAM dynamiques (DRAM) | next=Les mémoires associatives | nextText=Les mémoires associatives }} </noinclude> 8nblvm1dvbi5uhquunxnr87ehbrves1 764673 764672 2026-04-23T18:42:41Z Mewtow 31375 /* Le séquenceurs mémoire pour les SDRAM/DDR */ 764673 wikitext text/x-wiki Les mémoires ROM ou SRAM ont généralement une interface simple, à laquelle le processeur peut s'interfacer directement. Mais pour les DRAM, ce n'est pas le cas. Les DRAM utilisent un bus d'adresse multiplexé, où l'adresse est envoyée en deux fois. Connecter le processeur directement sur une DRAM n'est pas pratique : le bus d'adresse du processeur et celui de la mémoire ne collent pas. Les DRAM doivent aussi être rafraichies régulièrement. Le rafraichissement mémoire peut être délégué au processeur, mais c'est loin d'être idéal. Et il y a bien d'autres raisons qui font que le processeur ne peut pas s'interfacer facilement avec les mémoires DRAM. Pour gérer ces problèmes, les mémoires DRAM ne sont pas connectées directement au processeur. À la place, on ajoute un intermédiaire entre le processeur et la mémoire : le '''contrôleur mémoire externe'''. Son but est de montrer au processeur une interface simple, semblable à celle d'une SRAM classique, alors qu'il commande une mémoire DRAM à l'interface plus complexe. Il est placé sur la carte mère ou dans le processeur, et ne doit pas être confondu avec le contrôleur mémoire intégré dans la mémoire. Ce chapitre va expliquer quels sont les rôles du contrôleur mémoire, son interface et ce qu'il y a à l'intérieur. Dans ce chapitre, quand nous parlerons de ''contrôleur mémoire'', cela fera systématiquement référence au contrôleur mémoire externe. Et avant de poursuivre, sachez qu'il est difficile de faire des généralités sur les contrôleurs mémoire, car les mémoires DRAM elles-mêmes sont assez différentes les unes des autres. Entre une mémoire EDO, une mémoire SDR, une mémoire DDR et une DRAM asynchrone, les contrôleurs mémoires seront fortement différents. Aussi, il y a aura une différence entre un contrôleur pour une DRAM asynchrone et un contrôleur pour une mémoire EDO, une mémoire SDRAM, etc. J'ai choisit de vous séparer les contrôleurs mémoire pour les DRAM asynchrones de ceux pour les SDRAM/DRR. ==Le contrôleur d'une DRAM asynchrone== Les premières DRAM asynchrones avaient des contrôleurs mémoires dédiés, qui étaient séparés du processeur et du ''chipset'' de la carte mère. Par exemple, les composants Intel 8202, Intel 8203 et Intel 8207 étaient des contrôleurs mémoire pour DRAM asynchrones qui étaient vendus dans des boitiers DIP et étaient soudés sur la carte mère. Par la suite, ils ont été intégrés au ''chipset'' de la carte mère pendant les décennies 90-2000. Après les années 2000, ils ont été intégrés dans les processeurs. Leurs fonctions étaient le multiplexage de l'adresse et le rafraichissement mémoire. Ils recevaient une adresse mémoire complète, qu'ils découpaient une adresse de ligne et une adresse de colonne, envoyées l'une après l'autre sur le bus mémoire. Pour le rafraichissement mémoire, ils rafraichissaient la DRAM régulièrement, de manière automatique, entre deux accès mémoire normaux. Le processeur n'avait ainsi plus à rafraichir la mémoire lui-même, cette fonction était déléguée au contrôleur de DRAM. Ils étaient connectés au bus d'adresse et de commande, avec éventuellement des relations indirectes avec le bus de données. ===L'interface d'un contrôleur de DRAM asynchrone=== L'interface du contrôleur mémoire décrit ses broches d'entrées/sorties et leur signification. Elle est généralement très simple et contient deux ports : un connecté au processeur, un autre connecté à la DRAM. Cela trahit d'ailleurs son rôle principal, qui est de transformer les requêtes de lecture/écriture provenant du processeur en une suite de commandes acceptée par la mémoire. Le port connecté à la DRAM est connecté ua bus d'adresse et au bus de commande. Le bus de données est lui relié au processeur et/ou au bus système. Un accès mémoire provenant du processeur contient une adresse à lire/écrire, le bit R/W qui indique s'il faut faire une lecture ou une écriture, et éventuellement une donnée à écrire. Mais, nous avons vu que les accès mémoires sur une DRAM sont multiplexés : on envoie l'adresse en deux fois : la ligne d'abord, puis la colonne. De plus, il faut générer les signaux RAS, CAS et bien d'autres. Le tout est illustré ci-dessous. [[File:Contrôleur mémoire.png|centre|vignette|upright=2|Contrôleur mémoire externe.]] Un point important est que les DRAM asynchrones n'ont pas d'entrée ''Chip Select'' ou d'entrée ''Output Enable''. Les signaux RAS et CAS remplacent en quelque sorte ces deux signaux. Le bit RAS fait office de ''Chip Select'', le bit CAS fait office d'''Output Enable''. Pour certains contrôleurs de DRAM, il faut ajouter l''''interface électrique''', qui traduit les signaux du processeur en signaux compatibles avec la mémoire. Il est en effet très fréquent que la mémoire et le processeur n'utilisent pas les mêmes tensions pour coder un bit, ce qui fait qu'elles ne sont pas compatibles. Dans ce cas, le contrôleur mémoire fait la conversion. Le contrôleur DRAM peut fonctionner en mode synchrone ou asynchrone, du point de vue du processeur. Quand il fonctionne en mode synchrone, il permet d'interfacer un processeur synchrone avec une mémoire DRAM asynchrone. Un point important est que le contrôleur DRAM sert d'intermédiaire entre une mémoire DRAM et "le reste du monde". Il ne fait pas forcément office d'intermédiaire entre DRAM et processeur, mais peut aussi faire l'intermédiaire entre la DRAM et un bus système, entre une DRAM et le ''Video Display Controler'' d'une carte graphique, ou n'importe quel autre composant électronique qui utilise cette DRAM. ===Le générateur de ''timings'' et la traduction d'adresse=== Le contrôleur mémoire doit traduire les adresses du processeur en adresses compatibles avec la mémoire. Et la traduction est assez variable, suivant que le bus mémoire est un bus normal, un bus multiplexé, ou partiellement multiplexé. Nous avons vu ces trois types de bus mémoire dans le chapitre sur l'interface des mémoires, mais nous ferons quelques rappels rapides. Avec un ''bus totalement multiplexé'', le bus d'adresse et le bus de données sont fusionnés. Dans ce cas, on peut envoyer soit une adresse, soit lire/écrire une donnée sur le bus, mais on ne peut pas faire les deux en même temps. Un bit ALE indique si le bus est utilisé en tant que bus d'adresse ou bus de données. Le contrôleur mémoire gère cette situation, en fixant le bit ALE et en envoyant séparément adresse et donnée pour les écritures. [[File:Bus multiplexé avec bit ALE.png|centre|vignette|upright=2|Bus multiplexé avec bit ALE.]] Avec un ''bus d'adresse multiplexé'', l'adresse est découpée en une adresse de ligne et une adresse de colonne, envoyées l'une après l'autre. Le contrôleur mémoire prend en entrée une adresse mémoire complète, la découpe en deux, et envoie chaque morceau au bon moment. Pour cela, il suffit d'un registre pour mémoriser l'adresse et d'un multiplexeur. Le multiplexeur choisit soit les bits de poids fort de l'adresse, soit ceux de poids faible. Les premiers correspondent à l'adresse de ligne, les autres à l'adresse de colonne. La commande du multiplexeur est le fait d'un petit circuit séquentiel, qui génère aussi les signaux CAS et RAS. Au premier cycle, il met le signal RAS à 1, met le CAS à 0, et configure le MUX pour sélectionner les bits de poids fort. Au second cycle, il génère un signal CAS à 1, met le RAS à 0 et configure le MUX pour sélectionner les bits de poids faible. Le circuit en question est appelé le générateur de ''timings''. [[File:Controleur de DRAM simple, sans rafraichissement mémoire.png|centre|vignette|upright=2|Contrôleur de DRAM simple, sans rafraichissement mémoire.]] Le générateur de ''timings'' est un circuit séquentiel qui implémente une petite machine à état. Il est très simple sur une mémoire DRAM asynchrone basique, mais il est plus complexe sur les mémoires FPM, EDO, quartet, et autres. Le regroupement des multiplexeurs d'adresse et du générateur de ''timings'' est appelé le '''séquenceur mémoire'''. C'est le séquenceur mémoire qui traduit la requête processeur en commande DRAM, le reste du contrôleur est dédié au rafraichissement mémoire ou à d'autres fonctions facultatives. ===Le rafraichissement mémoire=== La gestion du rafraichissement mémoire est la fonction principale du contrôleur DRAM. Pour gérer le rafraichissement mémoire, le contrôleur mémoire intègre deux compteurs, un pour gérer l'adresse à rafraichir, l'autre pour gérer l'intervalle de temps entre deux rafraichissements. Le rafraichissement se fait à intervalle régulier, tous les x microsecondes. Pour déclencher le rafraichissement au bon moment, le contrôleur mémoire contient un ''Refresh Timer'', aussi appelé le '''compteur de rafraichissement'''. Il est initialisé avec le temps entre deux rafraichissements, une adresse est rafraichie quand ce compteur atteint 0. Le rafraichissement mémoire balaye la mémoire adresse par adresse. Pour savoir à quelle adresse il en est rendu, le contrôleur mémoire utilise un '''compteur d'adresse'''. Il contient la prochaine adresse à rafraichir, aussi appelée l'adresse de rafraichissement. Régulièrement, l'adresse dans ce compteur est envoyée à la RAM, pour une lecture. Mais la donnée lue n'est pas envoyée sur le bus de donnée, soit parce que la RAM est prévue pour, soit parce que le contrôleur désactive son bit ''output enable''. Dans le second cas, la RAM fait la lecture en interne, mais se déconnecte du bus de donnée, perdant la donnée lue dans le néant. Pour envoyer l'adresse de rafraichissement sur le bus d'adresse, il faut rajouter un multiplexeur, qui choisit entre l'adresse normale et l'adresse de rafraichissement. [[File:Controleur de DRAM avec rafraichissement mémoire.png|centre|vignette|upright=2|Controleur de DRAM avec rafraichissement mémoire.]] Le multiplexeur ne doit cependant pas être configuré si une adresse est déjà en cours de transfert. Pour cela, un circuit d'arbitrage se débrouille pour éviter qu'un accès mémoire soit interrompu par une demande de rafraichissement et inversement. Il peut être inclus dans le séquenceur mémoire ou séparé de celui-ci. [[File:Controleur mémoire, intérieur simplifié.png|centre|vignette|upright=2.5|Contrôleur mémoire, intérieur simplifié.]] Il faut noter que le rafraichissement mémoire peut être délégué non pas au contrôleur mémoire, mais au processeur où à la DRAM elle-même. Quand elle est le fait du processeur, celui-ci incorpore un ''refresh timer'' et un compteur d'adresse. Un exemple est celui du processeur Zilog Z80, qui implémentait des compteurs internes pour gérer le rafraichissement mémoire. On peut considérer que le processeur incorpore alors le contrôleur mémoire, au moins partiellement. Il est aussi possible de déléguer le rafraichissement au logiciel (certains jeux vidéos Amiga ou Commodore faisaient cela pour la mémoire vidéo). Quand la DRAM elle-même s'occupe de son propre rafraichissement, c'est elle qui intègre un ''refresh timer'' et le compteur d'adresse. ===Le décodage d'adresse=== Le contrôleur mémoire gère aussi le '''décodage d'adresse'''. pour rappel, celui-ci est utilisé quand on combine plusieurs chips mémoire ensemble, pour combiner leurs capacités mémoire. Par exemple, on peut combiner 4 chips mémoires de 1 mébioctet chacun, pour que le processeur voit comme 4 mébioctets de RAM unique. Le premier mébioctet est placé dans le premier chip mémoire, le second mébioctet dans le second chip, etc. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Pour cela, on active le chip mémoire adéquat, en fonction de l'adresse à consulter. Les autres chips mémoire sont désactivés pendant l'accès mémoire. En théorie, activer ou désactiver un chip mémoire se fait en utilisant son entrée ''Chip Select''. Il faut noter que si les SDRAM disposent bien d'un signal ''Chip Select'', ce n'est pas le cas des mémoires RAM asynchrones. A la place, ce sont les signaux RAS qui font office de ''Chip Select''. Une RAM asynchrone est activée quand son signal RAS lui demande de lire une ligne, elle est désactivée sinon. Mais c'est un détail. Toujours est-il que les signaux ''Chip Select'', ou leurs équivalents, sont générés par le contrôleur de DRAM, à partir des bits de poids fort de l'adresse. Par exemple, avec 4 chips mémoire, les deux bits de poids fort de l'adresse sont utilisés pour sélectionner le chip mémoire adéquat. Le contrôleur mémoire doit avoir plusieurs sorties ''Chip Select'', une par chip mémoire. Et le nombre de ces sorties limite le nombre de chips mémoire qu'on peut combiner. Par exemple, s'il y a seulement 4 sorties ''Chip Select'', on ne pourra brancher que 4 chips mémoire dessus. Sauf à ruser, avec un arrangement horizontal, mais cela n'est pas le ressort du contrôleur mémoire. [[File:Td6bfig3.png|centre|vignette|upright=2|Comparaison entre arrangement horizontal (à gauche) et arrangement vertical (à droite).]] ===Exemple : l'Intel 8202-8203=== L'Intel 8202 et le 8203 étaient des contrôleurs de mémoire DRAM, parmi les plus simples qui soient. Ils avaient une entrée d'adresse de 12 bits, ce qui permettait d'adresser 4 kibioctets de RAM. Ils fournissaient en sortie une adresse multiplexée sur 6 bits, envoyée en deux fois. Ils avaient donc 12 entrées d'adresse, 6 sorties d'adresse, un signal RAS, un signal CAS. Les adresses présentées en entrées n'étaient pas mémorisées dans des registres, ce qui fait qu'elles devaient être maintenues durant toute la durée de l'accès mémoire. Le processeur ne pouvait donc pas se déconnecter du bus d'adresse pendant l'accès mémoire, peu importe sa durée. Le 8202 contenait aussi un compteur de rafraichissement. Rappelons que sur les DRAM asynchrones, le rafraichissement se fait ligne par ligne. Le contrôleur mémoire a juste à présenter l'adresse de ligne, il n'a pas à envoyer l'adresse de colonne. La commande de rafraichissement se fait en mettant le signal RAS à 0, mais en laissant le CAS à 1 (je rappelle que les signaux sont actifs à 0). Le compteur d'adresse de rafraichissement a donc juste à mémoriser l'adresse de ligne. Le séquenceur mémoire était précédé par un circuit d'arbitrage, non-représenté dans le schéma ci-dessous. La microarchitecture de l'Intel 8202 est la suivante : [[File:Microarchitecture de l'Intel 8202.png|centre|vignette|upright=2|Microarchitecture de l'Intel 8202.]] Le 8202 avait une entrée pour un signal d'horloge, ainsi qu'un ''Chip Select'' un peu particulier. Si le signal CS passait à 0 lors d'un accès mémoire, le 8202/8203 ne se désactivait qu'une fois l'accès mémoire terminé. On ne pouvait pas l'interrompre pendant un accès mémoire, même en changeant le bit CS. Le signal d'horloge était utilisé pour commander le ''refresh timer''. Pour commander les lectures et écriture, il recevait en entrée un bit ''Write Request'' et un bit ''Read Request'', qui demandent respectivement une écriture et une lecture. En sortie, on trouvait un unique bit R/W qui valait 0 pour une lecture et 1 pour une écriture. Il avait aussi un bit d'entrée pour forcer le rafraichissement mémoire. S'il est à 1, la mémoire rafraichie l'adresse envoyée par le processeur. Le 8202 pouvait être connecté sur 1 à 4 chips mémoire, ce qui permettait d'adresser au maximum 16 kibioctets de RAM. Les 4 chips ne sont pas accédés en parallèle, un seul l'est à chaque fois. Pour le décodage d'adresse, le 8202 dispose de deux bits BO et B1 pour sélectionner le chip adéquat, ainsi que 4 sorties RAS pour activer la banque adéquate. On rappelle que le signal RAS remplace le signal ''Chip Select''. C'est le séquenceur mémoire qui se charge de générer ces signaux RAS, à partir des deux bits B0 et B1 (qui sont techniquement des bits d'adresse). Pour communiquer avec le processeur, l'Intel 8202 disposait de deux bits XACK et SACK. SACK indiquait au processeur que le 8202/8203 est en train de faire un accès mémoire et qu'il est indisponible pour un second accès mémoire. Cela permet de bloquer le processeur tant que le 8202 est indisponible. Le signal XACK indique que l'accès mémoire précédent est terminé et que : soit la donnée lue est présente sur le bus de données, soit que l'écriture s'est terminée. : Le signal SACK est très utile sur les configurations multiprocesseurs. Un processeur peut démarrer un accès mémoire, le signal SACK indiquera au second processeur qu'il doit attendre que l'accès soit terminé pour que ce soit son tour. ==Les contrôleurs de DRAM asynchrones évolués== L'Intel 8202 était un contrôleur mémoire basique, comme beaucoup d'autres à cette époque. Mais Intel a vendu des contrôleurs mémoires plus complexes. Par exemple, l'Intel 8207 était un contrôleur mémoire bien plus avancé que les deux précédents. Passons sur certains détails, comme le fait qu'il pouvait gérer jusqu'à 256 kibioctets de DRAM. Au-delà de ça, il y avait des fonctionnalités bien plus intéressantes, à savoir : un support de l'ECC, il était double port, il permettait de simuler une DRAM synchrone à partir d'une DRAM asynchrone. Il n'était pas le seul dans ce cas et des contrôleurs de DRAM très évolués étaient capables de faire des merveilles. Voyons comment cela était possible. ===Les contrôleurs mémoire synchrone=== Il est parfaitement possible d'utiliser un contrôleur mémoire synchrone avec une DRAM asynchrone. A vrai dire, le contrôleur DRAM peut complétement simuler une mémoire synchrone alors que la DRAM associée est asynchrone. La traduction asynchrone vers synchrone se fait en ajoutant des registres sur le bus mémoire, notamment sur le bus de données et le bus d'adresse/commande. Nous avions détaillé cela dans le chapitre sur les SRAM, c'est la même chose avec une mémoire DRAM. Sauf que cette fois-ci, le contrôleur mémoire doit aussi être prévu pour. [[File:Controleur mémoire synchrone utilisé avec une DRAM asynchrone.png|centre|vignette|upright=2|Contrôleur mémoire synchrone utilisé avec une DRAM asynchrone]] Les deux-trois registres illustrés plus haut peuvent être intégrés directement dans le contrôleur mémoire, que ce soit totalement ou en partie. Le strict minimum pour avoir un contrôleur mémoire synchrone est que celui-ci doit mémoriser adresse et commandes dans un registre. Par exemple, le 8207 d'Intel était capable de mémoriser les requêtes processeurs dans un registre interne, mais il fallait utiliser deux registres séparés pour le bus de données. Les deux registres étaient alors commandés par le contrôleur mémoire. Il est cependant possible d'aller plus loin et d'intégrer les registres du bus de données dans le contrôleur mémoire. [[File:Controleur mémoire DRAM synchrone.png|centre|vignette|upright=2|Contrôleur mémoire DRAM synchrone.]] : Il faut noter que cette fonctionnalité est parfois disponible sur les SRAM. En clair, on peut associer une SRAM asynchrone avec un contrôleur de SRAM synchrone. Le contrôleur de SRAM se charge alors de simuler une SRAM synchrone à partir de la SRAM asynchrone. Utiliser un contrôleur mémoire synchrone a de nombreux avantages, l'un d'entre eux étant lié aux ''wait state''. Quand le processeur envoie une requête de lecture/écriture à la mémoire RAM, celle-ci met plusieurs cycles d'horloge à répondre. Et pendant ce temps, le processeur... attend. Et pendant ce temps d'attente, il doit maintenir l'adresse mémoire sur le bus d'adresse. Les cycles d'horloge perdus à attendre la mémoire RAM étaient appelés des '''''Wait states'''''. Utiliser un contrôleur mémoire synchrone d'éliminer les ''wait state'', dans une certaine mesure. Avec un contrôleur mémoire synchrone, le processeur envoie l'adresse, mais c'est le contrôleur mémoire qui la maintient sur le bus d'adresse. Le processeur peut envoyer l'adresse et la donnée à écrire, elles sont recopiées dans les registres, et le controleur mémoire y a accès sans que le processeur doive les maintenir. Le processeur peut se déconnecter du bus mémoire et faire du travail dans son coin pendant que le contrôleur mémoire accède à la DRAM. Les ''wait state'' disparaissent alors, du moins du point de vue du processeur. ===La gestion de l'ECC=== L''''ECC''' peut être géré dans le contrôleur mémoire. Pour cela, on couple les registres mentionnés dans la section précédente, avec un circuit de détection et de correction d'erreur. Le circuit d'ECC peut, comme les registres synchrones, être intégré dans le contrôleur mémoire, ou au contraire être situé dans un circuit séparé. Si le circuit d'ECC est séparé du contrôleur mémoire, il communique avec lui, histoire que le contrôleur mémoire puisse signaler toute erreur de parité ou d'ECC au processeur. [[File:Controleur mémoire synchrone avec ECC intégré.png|centre|vignette|upright=2|Controleur mémoire synchrone avec ECC intégré]] Reprenons l'exemple du 8207 d'Intel. Le contrôleur mémoire 8207 gère le bus d'adresse et de commande, mais n'a pas de connexions directes avec le bus de données. Il ne peut donc pas prendre en charge l'ECC. Il avait besoin d'être couplé avec un circuit d'ECC séparé, relié au bus de données : l'Intel 8206. Le 8026 prenait en entrée : 16 bits de données et 8 bits d'ECC. Il fournissait en sortie 16 bits de données après correction d'erreur, les 8 bits d'ECC pour indiquer qu'une erreur a été détectée mais pas corrigée, ainsi que des bits de parité. Le 8206 détectait/corrigeait les erreurs et générait les bits d'ECC, mais il communiquait avec le contrôleur mémoire pour cela. [[File:8207 avec ECC.png|centre|vignette|upright=2|8207 avec ECC]] La détection/correction d'erreur était appliquée à la fois pour les accès mémoire et pour les rafraichissements mémoire. Lors d'un rafraichissement mémoire, la donnée rafraichie est lue et réécrite. Avec l'ECC activé et configuré correctement, le rafraichissement passe par le bus de données. Au lieu d'avoir un cycle de lecture-écriture interne à la DRAM, on a un cycle de lecture-correction-écriture qui utilise le 8206. La donnée lue est envoyée sur le bus de données, puis le 8206 corrige une éventuelle erreur, et la donnée corrigée est alors réécrite en mémoire. Au passage, si une erreur non-correctible est détectée, le 8206 ne fait rien, l'erreur est ignorée. La gestion de l'erreur sera retardée jusqu'à une lecture ultérieure. Et encore : si lecture ultérieure il y a. Si la donnée est écrasée par une écriture, la donnée corrompue sera simplement écrasée et disparaitra sans avoir pu faire le moindre dégât. Mais pour cela, le 8206 doit communiquer avec le contrôleur mémoire, pour savoir s'il est dans un cycle de rafraichissement ou un accès mémoire normal. Il prévient le 8207 lors d'une erreur, et c'est ce dernier qui décide si l'erreur doit être prise en compte ou ignorée. C'est seulement lors d'un accès mémoire normal que le processeur est prévenu qu'une erreur de parité/autre a eu lieu. ===Les contrôleurs mémoires multiports=== Les '''contrôleur mémoire multiport''' disposent de plusieurs ports, chacun permettant de traiter un accès mémoire. Ils peuvent simuler une mémoire multiport à partir d'une DRAM monoport. Évidemment, la simulation n'est pas parfaite. Des accès mémoire simultanés, envoyés en même temps sur différents ports, sont en réalité exécutés un par un, pas en même temps. Il y a donc une petite pénalité en termes de performances, mais elle est mineure. Encore une fois, nous allons reprendre l'exemple du 8207. Il avait deux ports séparés, et était prévu pour fonctionner dans un système à deux processeurs. L'usage de deux ports séparés permettait de partager une unique mémoire DRAM entre deux processeurs. Le partage se faisait en interfaçant deux processeurs sur le contrôleur mémoire, chacun étant connecté à un port. Lors d'une lecture, il redirigeait la donnée lue vers le bon processeur, en configurant le bus de données correctement. Le contrôleur mémoire recevait des requêtes mémoire de deux processeurs, mais il les exécutait une à la fois. S'il recevait deux requêtes en même temps, l'une d'entre elles était mise en attente. Le contrôleur mémoire doit arbitrer les accès à la mémoire, et faire en sorte que les deux processeurs aient accès à la mémoire à tour de rôle. Et non seulement il doit arbitrer les deux ports, mais il y a aussi un troisième port interne au contrôleur mémoire : le rafraichissement mémoire ! Pour cela, le circuit d'arbitrage qui choisissait entre rafraichissement mémoire et accès mémoire, est amélioré de manière à gérer un second port. Le circuit d'arbitrage donne l'accès au séquenceur mémoire à un port sélectionné. L'arbitrage était configurable, avec deux options : soit le port A est privilégié sur le port B, soit le port le plus récemment accédé à la priorité. Les deux ports pouvaient être configurés pour fonctionner soit de manière asynchrone, soit de manière synchrone. Il était aussi possible de configurer l'ECC, des options liées à la fréquence du processeur et de la RAM, ainsi que de nombreuses options liées au rafraichissement. Pour cela, le 8207 contenait un registre de configuration interne, programmable en fournissant les entrées adéquates. Tout ce qui vient d'être dit se généralise avec plus de deux processeurs. Le 8207 ne permettait pas ça, mais les contrôleurs mémoire des PC modernes en sont capables. Ils peuvent gérer plusieurs dizaines de processeurs facilement. ==Le contrôleur mémoire d'une DRAM ''Fast Page Mode''== Les mémoires DRAM classiques sont des mémoires à tampon de ligne, mais qui sont assez mal utilisées. Notamment, tout accès mémoire se fait en deux phases : un accès pour sélectionner la ligne, un autre pour sélectionner la colonne. Les mémoires ''Fast Page Mode'' permettent d'optimiser le tout. Elles permettent de faire plusieurs accès successifs à la même ligne, à des colonnes différentes. Et le contrôleur mémoire doit être adapté pour cela. [[File:Sélection d'une ligne sur une mémoire FPM ou EDO.png|centre|vignette|upright=2|Sélection d'une ligne sur une mémoire FPM ou EDO.]] Le contrôleur mémoire doit détecter que deux accès mémoire consécutifs se font dans la même ligne. Si deux accès consécutifs accèdent à la même ligne, on doit juste changer de colonne et altérer le signal CAS. C'est un ''succès de tampon de ligne'', aussi appelé un '''succès de page'''. Si deux accès consécutifs accèdent à une ligne différente, c'est un ''défaut de tampon de ligne'', aussi appelé un '''défaut de page'''. Il faut alors changer de ligne, en altérant les signaux RAS et en envoyant une adresse de ligne. Pour détecter les succès ou défauts de page, il faut ajouter un circuit spécialisé dans le contrôleur mémoire. Il mémorise la ligne ouverte, et plus précisément son adresse de ligne (numéro de banque inclut). A chaque requête processeur, il compare l'adresse de ligne recue avec celle déjà ouverte. C'est un succès si les deux sont égales, un défaut si elles sont différentes. Le circuit qui fait cette comparaison est appelé le '''décodeur de commande'''. Il prévient le séquenceur mémoire en cas de succès de page, grâce à un signal de un bit, qui vaut 0 en cas de défaut de page et 1 en cas de succès. Le séquenceur mémoire décide alors comment gérer les signaux RAS et CAS, ainsi que l'envoi des adresses de ligne/colonne. [[File:Controleur mémoire d'une FPM-DRAM.png|centre|vignette|upright=2|Controleur mémoire d'une FPM-DRAM]] ==Le contrôleur mémoire d'une SDRAM ou d'une DDR== l'intérieur d'un contrôleur de SDRAM ne change pas significativement d'un controleur de RAM asynchrone. Il regroupe toujours un séquenceur mémoire et une interface physique, un circuit pour le rafraichissement mémoire et un circuit d'arbitrage. Par contre, ses sorties changent pas mal. Contrairement aux mémoires DRAM basiques, les mémoires SDRAM sont cadencées par un signal d'horloge. Et ce signal d'horloge vient bien de quelque part. Pour cela, deux solutions : soit le contrôleur mémoire génère la fréquence qui commande la mémoire, soit il prend en entrée une fréquence de base qu'il multiplie pour obtenir la fréquence désirée. Les deux solutions sont équivalentes, si ce n'est que les circuits impliqués ne sont pas les mêmes. Dans le premier cas, le contrôleur doit embarquer un circuit oscillateur, qui génère la fréquence demandée. Dans l'autre cas, un simple multiplieur/diviseur de fréquence suffit et c'est généralement une PLL qui est utilisée pour cela. : Notez qu'il ne faut pas confondre la fréquence de la SDRAM et celle du contrôleur mémoire. Le contrôleur mémoire fonctionne à une vitesse assez élevée, en interne. Le port relié au processeur fonctionne à haute fréquence, généralement la même que celle du processeur. A vrai dire, de nos jours, il est intégré dans le processeur. Pour le décodage d'adresse, tout est plus simple sur les SDRAM, DDR inclues. Les chips de mémoire SDRAM et DDR disposent d'une entrée ''Chip Select'', ce qui facilite grandement le décodage d'adresse. Les bits de ''Chip Select'' sont générés par le contrôleur mémoire, et sont transmis sur le bus de commande. Le contrôleur de DRAM peut adresser un certain nombre de rangées, dispersés sur une ou plusieurs barrettes. La limite maximale dépend du contrôleur de DRAM, elle est souvent proche de 8 ou 16 rangées. Si on combine plusieurs barrettes de mémoire, il est possible de dépasser cette limite. Par exemple, pour un contrôleur de DRAM supportant maximum 8 rangées, 4 barrettes de 4 rangées chacune dépassent la limite. ===Le séquenceurs mémoire pour les SDRAM/DDR=== Le séquenceur mémoire existe toujours pour les mémoires SDRAM, c'est toujours un circuit séquentiel qui implémente une machine à état. Il traduit toujours une requête processeur en une séquence de commandes envoyées à des timings bien précis. Les commandes mémoires peuvent provenir de l'extérieur, mais aussi d'un circuit de rafraichissement intégré dans le contrôleur mémoire, comme pour les autres DRAM. La seule différence est que la machine à état est plus complexe. Pour rappel, une requête de lecture/écriture se fait en trois étapes : une commande PRECHARGE pour précharger le tampon de ligne, une commande ACT qui fixe l'adresse de ligne, et enfin une commande READ/WRITE avec l'adresse de colonne. Et il faut tenir compte des timings mémoire, à savoir le fait que ces commandes sont séparées par des temps d'attentes bien précis. Par exemple, je prends des chiffres arbitraires : il faut attendre 2 cycles entre une commande ACT et une commande READ, 6 cycles avant deux commandes WRITE consécutives, etc. La gestion des ''timings'' rend la conception du séquenceur plus complexe. Les SDRAM sont parfois capables de démarrer une commande avant que la précédente soit terminée. Concrètement, pendant que la commande précédente envoie sa dernière donnée sur le bus de données, on peut envoyer la commande suivante avec quelques cycles d'avance. L'exemple ci-dessous devrait être assez clair : on envoie une seconde commande au neuvième cycle, alors qu'une rafale est en cours. La machine à état tient compte de cette possibilité. La possibilité est très limitée, car il faut tenir compte des délais mémoire. Il s'agit d'une forme très limitée de pipeline, similaire à celui des mémoires SRAM. Les SDRAM sont formées en entourant une RAM asynchrone de registres. Il est possible d'écrire dans les registres de données/commandes, pendant qu'un autre accès mémoire accède au cœur asynchrone. Les délais mémoire sont conçus pour éviter qu'une commande accède au coeur asynchrone en même temps que la suivante ou la précédente. C'est pour cela que les délais mémoire sont assez différents entre écritures et lectures, d'ailleurs. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | || bgcolor="#A0FFFF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || || || bgcolor="#FFA0FF" | READ (2) || || || || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 | bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 | |} Le séquenceur peut aussi profiter du fait que des accès simultanés peuvent se faire de préférence se faire dans des banques différentes. Prenons l'exemple d'une mémoire partagée entre plusieurs processeurs. Les deux processeurs ou cœurs exécutent deux programmes différents : le premier processeur exécute le navigateur web, alors que le second lit de la musique. Dans ce cas, les deux processeurs accèdent à des données très éloignées en mémoire (ce n'est pas toujours le cas, mais ça l'est dans cet exemple), qui sont potentiellement dans des banques séparées. Par contre, il y a une contrainte majeure : deux accès simultanés ne peuvent pas accéder au bus en même temps. Je rappelle que quand on démarre une lecture, il se passe quelques cycles d'horloge avant que la donnée atterrissent sur le bus de données. Le temps en question est fixe, mais sa durée n'est pas la même selon que la lecture fasse un succès de page ou un défaut de page. Le séquence mémoire doit en tenir compte avant de démarrer une lecture ou une écriture. Le séquenceur doit donc détecter les succès de page, et déterminer si le bus de donnée sera libre au moment où la lecture terminera. Si c'est le cas, il peut lancer a commande. Sinon, il doit la retarder. {|class="wikitable" |+ Exemple où deux processeurs accèdent à une SDRAM en même temps |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 |- ! rowspan="2" | Requêtes mémoires | bgcolor="#A0FFFF" | CPU 1 || || || bgcolor="#A0FFFF" | CPU 1 || || || || || || |- | bgcolor="#FFA0FF" | CPU 2 || || || || || bgcolor="#FFA0FF" | CPU 2 || || || || |- |- ! Bus de commande/adresse | || bgcolor="#A0FFFF" | Lecture (CPU 1) || bgcolor="#FFA0FF" | Lecture (CPU 2) || || bgcolor="#A0FFFF" | Lecture (CPU 1) || || bgcolor="#FFA0FF" | Lecture (CPU 2) || || |- ! Bus de données | || || || bgcolor="#A0FFFF" | Donnée lue CPU 1 || bgcolor="#FFA0FF" | Donnée lue CPU 2 || || bgcolor="#A0FFFF" | Donnée lue CPU 1 || || bgcolor="#FFA0FF" | Donnée lue CPU 2 |} Les DDR2 et 3 vont encore plus loin avec l'optimisation des '''CAS postés'''. L'idée est que le contrôleur mémoire peut envoyer une commande ACT et une commande READ/WRITE sans se soucier des ''timings''. En théorie, les deux commandes doivent être séparées par quelques cycles, sur une SDRAM ou une DDR1. Mais avec la DDR2, le contrôleur mémoire peut envoyer les deux l'une après l'autre, au cycle suivant. C'est la mémoire qui mettra en attente la commande READ/WRITE pour respecter les ''timings'' mémoire. Cela complexifie le fonctionnement interne de la DDR, mais simplifie grandement le travail du contrôleur mémoire. ===La politique de gestion du tampon de ligne=== Plus haut, nous avons parlé des mémoires FPM, qui ont introduit quelques optimisations pour utiliser au mieux le tampon de ligne. Il se trouve que les SDRAM conservent ces optimisations. Il y a toujours cette notion de succès de page et de défaut de page, suivant que deux accès se font dans la même ligne ou dans deux lignes différentes. Du moins, c'est le cas si le séquenceur mémoire est coopératif. En effet, il peut fonctionner de plusieurs manières, dont les plus extrêmes sont appelés la politique de la page fermée et la politique de la page ouverte. Voyons à quoi elles correspondent. Avec la '''politique de la page fermée''', chaque accès mémoire est suivi d'une commande PRECHARGE, qui ferme la ligne courante et précharge les lignes de bits. Même si deux accès consécutifs se font dans la même ligne, la ligne est fermée et ré-ouverte entre deux accès mémoire. En clair : l'optimisation introduite par les mémoires FPM est désactivée, le contrôleur mémoire fait exprès de ne pas en profiter. On appelle cette méthode la close ''page autoprecharge''. Cette méthode réduit grandement les performances pour les accès à des adresses consécutives, mais fonctionne à merveille si les accès sont "aléatoires", à savoir qu'ils se font sans régularités évidentes. Son seul avantage est que l'implémentation du séquenceur mémoire est très simple. En effet, le séquenceur mémoire se passe complétement de la table des banques, du comparateur de ligne, et de tous les circuits nécessaires pour vérifier les succès ou défauts de page. De plus, le séquenceur mémoire profite grandement des commandes READA et WRITEA, qui fusionnent une commande READ/WRITE avec une commande PRECHARGE. Le séquenceur mémoire a juste à envoyer des commandes ACT, READA, WRITEA et PREFETCH à la mémoire, pas besoin des commandes PRECHARGE, READ ou WRITE. A l'opposé, la '''politique de la page ouverte''' ne ferme pas automatiquement la ligne. Elle la laisse ouverte, en espérant que le prochain accès mémoire se fasse dans cette ligne. Lorsqu'un nouvel accès mémoire arrive, elle doit détecter les succès ou défauts de page et agir en fonction. En cas de défaut de page, la ligne est fermée, le séquenceur mémoire envoie une commande PRECHARGE, puis l'accès suivant effectue les deux commandes ACT + READ ou WRITE. En cas de succès de page, les commandes PRECHARGE et ACT ne sont pas envoyées, seules la commande READ ou WRITE l'est. Un désavantage est que le contrôleur mémoire doit inclure une table des banques et un comparateur, comme vu plus haut dans la section sur les mémoires FPM. un autre défaut est que garder une ligne ouverte consomme beaucoup d'énergie, comparé à un simple état de PRECHARGE. En conséquence, il est préférable de fermer les lignes dès que possible. Par contre, les performances sont d'autant meilleures que les accès mémoire consécutifs à une même ligne soient assez fréquents. Si les accès mémoire sont aléatoires, les performances sont moins bonnes. La politique de la page fermée fermait les lignes en avance, avec des commandes READA ou WRITEA, avant même que l'accès suivant démarre. Avec la politique de la page ouverte, on doit attendre pour détecter un défaut de page, puis fermer la ligne avec une commande PRECHARGE séparée. La ligne est donc fermée avec un peu temps de retard, et envoyer deux commandes au lieu d'une prend plus de temps. Les contrôleurs mémoires basiques utilisent une des deux solutions précédentes. Soit la page est toujours fermée, soit elle est toujours laissée ouverte jusqu'à ce qu'un accès mémoire la referme. Mais les contrôleurs plus évolués tentent de prédire s'il faut fermer ou laisser ouvertes les pages ouvertes. La méthode la plus simple attend un temps prédéterminé avant de fermer la ligne. Une autre solution regarde le tout dernier accès. On peut très bien décider de laisser la ligne ouverte si l'accès mémoire précédent était une rafale, et fermer sinon. Une solution plus complexe mémorise les N derniers accès et en déduit s'il faut fermer ou non la prochaine ligne. On peut mémoriser si l'accès en question a causé la fermeture d'une ligne avec un bit. Mémoriser les N derniers accès demande d'utiliser un simple registre à décalage. Pour chaque valeur de ce registre, il faut prédire si le prochain accès demandera une ouverture ou une fermeture. Une solution simple fait la moyenne des bits à 1 dans ce registre : si plus de la moitié des bits est à 1, on laisse la ligne ouverte et on ferme sinon. Pour améliorer un petit peu l'algorithme, on peut faire en sorte que les bits des accès mémoires les plus récents aient plus de poids dans le calcul de la moyenne. Il existe sans doute d'autres solutions plus évoluées, mais il est difficile de savoir ce qu'il y a dans les contrôleurs de SDRAM modernes. : Le fait de laisser ouverte une ligne ou au contraire de la fermer systématiquement, se fait pour chaque banque. ===Les banques et l'entrelacement mémoire des SDRAM=== Les SDRAM incorporent plusieurs banques à l'intérieur de leurs circuits. Pour rappel, une banque est une sous-mémoire, avec ses propres décodeurs, son tampon de ligne, ses multiplexeurs de colonnes, etc. Pour le dire autrement, une SDRAM regroupe plusieurs mémoires séparées dans un même circuit intégré, les mémoires en question étant des banques. [[File:Arrangement vertical.jpg|centre|vignette|upright=2.5|Mémoire multi-banques.]] La présence de plusieurs banques impacte la gestion des lignes ouvertes/fermées. En effet, chaque banque a son propre tampon de ligne, ce qui fait que la gestion des lignes se fait indépendamment dans chaque banque. Le séquenceur mémoire doit donc se souvenir des lignes actives dans chaque banque. Pour cela, il mémorise ces lignes dans une petite mémoire : la '''table des banques''', aussi appelée ''bank status memory''. Pour détecter un succès ou un défaut, le contrôleur doit extraire la ligne de l'adresse, mais aussi le numéro de banque. Il envoie alors le numéro de banque à la table des banques, sur son entrée d'adresse. Il récupère alors le numéro de la ligne active sur les sorties de données. Il compare alors ce numéro de ligne avec le numéro de ligne de l'adresse envoyée par le processeur. C'est un succès si les deux sont égales, un défaut sinon. [[File:Controleur mémoire FPM avec plusieurs banques.jpg|centre|vignette|upright=2|Contrôleur mémoire FPM avec plusieurs banques.]] <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les mémoires RAM dynamiques (DRAM) | prevText=Les mémoires RAM dynamiques (DRAM) | next=Les mémoires associatives | nextText=Les mémoires associatives }} </noinclude> g38xxtr4gjsj4pjb690694lupbtptww 764675 764673 2026-04-23T18:43:52Z Mewtow 31375 /* Le séquenceurs mémoire pour les SDRAM/DDR */ 764675 wikitext text/x-wiki Les mémoires ROM ou SRAM ont généralement une interface simple, à laquelle le processeur peut s'interfacer directement. Mais pour les DRAM, ce n'est pas le cas. Les DRAM utilisent un bus d'adresse multiplexé, où l'adresse est envoyée en deux fois. Connecter le processeur directement sur une DRAM n'est pas pratique : le bus d'adresse du processeur et celui de la mémoire ne collent pas. Les DRAM doivent aussi être rafraichies régulièrement. Le rafraichissement mémoire peut être délégué au processeur, mais c'est loin d'être idéal. Et il y a bien d'autres raisons qui font que le processeur ne peut pas s'interfacer facilement avec les mémoires DRAM. Pour gérer ces problèmes, les mémoires DRAM ne sont pas connectées directement au processeur. À la place, on ajoute un intermédiaire entre le processeur et la mémoire : le '''contrôleur mémoire externe'''. Son but est de montrer au processeur une interface simple, semblable à celle d'une SRAM classique, alors qu'il commande une mémoire DRAM à l'interface plus complexe. Il est placé sur la carte mère ou dans le processeur, et ne doit pas être confondu avec le contrôleur mémoire intégré dans la mémoire. Ce chapitre va expliquer quels sont les rôles du contrôleur mémoire, son interface et ce qu'il y a à l'intérieur. Dans ce chapitre, quand nous parlerons de ''contrôleur mémoire'', cela fera systématiquement référence au contrôleur mémoire externe. Et avant de poursuivre, sachez qu'il est difficile de faire des généralités sur les contrôleurs mémoire, car les mémoires DRAM elles-mêmes sont assez différentes les unes des autres. Entre une mémoire EDO, une mémoire SDR, une mémoire DDR et une DRAM asynchrone, les contrôleurs mémoires seront fortement différents. Aussi, il y a aura une différence entre un contrôleur pour une DRAM asynchrone et un contrôleur pour une mémoire EDO, une mémoire SDRAM, etc. J'ai choisit de vous séparer les contrôleurs mémoire pour les DRAM asynchrones de ceux pour les SDRAM/DRR. ==Le contrôleur d'une DRAM asynchrone== Les premières DRAM asynchrones avaient des contrôleurs mémoires dédiés, qui étaient séparés du processeur et du ''chipset'' de la carte mère. Par exemple, les composants Intel 8202, Intel 8203 et Intel 8207 étaient des contrôleurs mémoire pour DRAM asynchrones qui étaient vendus dans des boitiers DIP et étaient soudés sur la carte mère. Par la suite, ils ont été intégrés au ''chipset'' de la carte mère pendant les décennies 90-2000. Après les années 2000, ils ont été intégrés dans les processeurs. Leurs fonctions étaient le multiplexage de l'adresse et le rafraichissement mémoire. Ils recevaient une adresse mémoire complète, qu'ils découpaient une adresse de ligne et une adresse de colonne, envoyées l'une après l'autre sur le bus mémoire. Pour le rafraichissement mémoire, ils rafraichissaient la DRAM régulièrement, de manière automatique, entre deux accès mémoire normaux. Le processeur n'avait ainsi plus à rafraichir la mémoire lui-même, cette fonction était déléguée au contrôleur de DRAM. Ils étaient connectés au bus d'adresse et de commande, avec éventuellement des relations indirectes avec le bus de données. ===L'interface d'un contrôleur de DRAM asynchrone=== L'interface du contrôleur mémoire décrit ses broches d'entrées/sorties et leur signification. Elle est généralement très simple et contient deux ports : un connecté au processeur, un autre connecté à la DRAM. Cela trahit d'ailleurs son rôle principal, qui est de transformer les requêtes de lecture/écriture provenant du processeur en une suite de commandes acceptée par la mémoire. Le port connecté à la DRAM est connecté ua bus d'adresse et au bus de commande. Le bus de données est lui relié au processeur et/ou au bus système. Un accès mémoire provenant du processeur contient une adresse à lire/écrire, le bit R/W qui indique s'il faut faire une lecture ou une écriture, et éventuellement une donnée à écrire. Mais, nous avons vu que les accès mémoires sur une DRAM sont multiplexés : on envoie l'adresse en deux fois : la ligne d'abord, puis la colonne. De plus, il faut générer les signaux RAS, CAS et bien d'autres. Le tout est illustré ci-dessous. [[File:Contrôleur mémoire.png|centre|vignette|upright=2|Contrôleur mémoire externe.]] Un point important est que les DRAM asynchrones n'ont pas d'entrée ''Chip Select'' ou d'entrée ''Output Enable''. Les signaux RAS et CAS remplacent en quelque sorte ces deux signaux. Le bit RAS fait office de ''Chip Select'', le bit CAS fait office d'''Output Enable''. Pour certains contrôleurs de DRAM, il faut ajouter l''''interface électrique''', qui traduit les signaux du processeur en signaux compatibles avec la mémoire. Il est en effet très fréquent que la mémoire et le processeur n'utilisent pas les mêmes tensions pour coder un bit, ce qui fait qu'elles ne sont pas compatibles. Dans ce cas, le contrôleur mémoire fait la conversion. Le contrôleur DRAM peut fonctionner en mode synchrone ou asynchrone, du point de vue du processeur. Quand il fonctionne en mode synchrone, il permet d'interfacer un processeur synchrone avec une mémoire DRAM asynchrone. Un point important est que le contrôleur DRAM sert d'intermédiaire entre une mémoire DRAM et "le reste du monde". Il ne fait pas forcément office d'intermédiaire entre DRAM et processeur, mais peut aussi faire l'intermédiaire entre la DRAM et un bus système, entre une DRAM et le ''Video Display Controler'' d'une carte graphique, ou n'importe quel autre composant électronique qui utilise cette DRAM. ===Le générateur de ''timings'' et la traduction d'adresse=== Le contrôleur mémoire doit traduire les adresses du processeur en adresses compatibles avec la mémoire. Et la traduction est assez variable, suivant que le bus mémoire est un bus normal, un bus multiplexé, ou partiellement multiplexé. Nous avons vu ces trois types de bus mémoire dans le chapitre sur l'interface des mémoires, mais nous ferons quelques rappels rapides. Avec un ''bus totalement multiplexé'', le bus d'adresse et le bus de données sont fusionnés. Dans ce cas, on peut envoyer soit une adresse, soit lire/écrire une donnée sur le bus, mais on ne peut pas faire les deux en même temps. Un bit ALE indique si le bus est utilisé en tant que bus d'adresse ou bus de données. Le contrôleur mémoire gère cette situation, en fixant le bit ALE et en envoyant séparément adresse et donnée pour les écritures. [[File:Bus multiplexé avec bit ALE.png|centre|vignette|upright=2|Bus multiplexé avec bit ALE.]] Avec un ''bus d'adresse multiplexé'', l'adresse est découpée en une adresse de ligne et une adresse de colonne, envoyées l'une après l'autre. Le contrôleur mémoire prend en entrée une adresse mémoire complète, la découpe en deux, et envoie chaque morceau au bon moment. Pour cela, il suffit d'un registre pour mémoriser l'adresse et d'un multiplexeur. Le multiplexeur choisit soit les bits de poids fort de l'adresse, soit ceux de poids faible. Les premiers correspondent à l'adresse de ligne, les autres à l'adresse de colonne. La commande du multiplexeur est le fait d'un petit circuit séquentiel, qui génère aussi les signaux CAS et RAS. Au premier cycle, il met le signal RAS à 1, met le CAS à 0, et configure le MUX pour sélectionner les bits de poids fort. Au second cycle, il génère un signal CAS à 1, met le RAS à 0 et configure le MUX pour sélectionner les bits de poids faible. Le circuit en question est appelé le générateur de ''timings''. [[File:Controleur de DRAM simple, sans rafraichissement mémoire.png|centre|vignette|upright=2|Contrôleur de DRAM simple, sans rafraichissement mémoire.]] Le générateur de ''timings'' est un circuit séquentiel qui implémente une petite machine à état. Il est très simple sur une mémoire DRAM asynchrone basique, mais il est plus complexe sur les mémoires FPM, EDO, quartet, et autres. Le regroupement des multiplexeurs d'adresse et du générateur de ''timings'' est appelé le '''séquenceur mémoire'''. C'est le séquenceur mémoire qui traduit la requête processeur en commande DRAM, le reste du contrôleur est dédié au rafraichissement mémoire ou à d'autres fonctions facultatives. ===Le rafraichissement mémoire=== La gestion du rafraichissement mémoire est la fonction principale du contrôleur DRAM. Pour gérer le rafraichissement mémoire, le contrôleur mémoire intègre deux compteurs, un pour gérer l'adresse à rafraichir, l'autre pour gérer l'intervalle de temps entre deux rafraichissements. Le rafraichissement se fait à intervalle régulier, tous les x microsecondes. Pour déclencher le rafraichissement au bon moment, le contrôleur mémoire contient un ''Refresh Timer'', aussi appelé le '''compteur de rafraichissement'''. Il est initialisé avec le temps entre deux rafraichissements, une adresse est rafraichie quand ce compteur atteint 0. Le rafraichissement mémoire balaye la mémoire adresse par adresse. Pour savoir à quelle adresse il en est rendu, le contrôleur mémoire utilise un '''compteur d'adresse'''. Il contient la prochaine adresse à rafraichir, aussi appelée l'adresse de rafraichissement. Régulièrement, l'adresse dans ce compteur est envoyée à la RAM, pour une lecture. Mais la donnée lue n'est pas envoyée sur le bus de donnée, soit parce que la RAM est prévue pour, soit parce que le contrôleur désactive son bit ''output enable''. Dans le second cas, la RAM fait la lecture en interne, mais se déconnecte du bus de donnée, perdant la donnée lue dans le néant. Pour envoyer l'adresse de rafraichissement sur le bus d'adresse, il faut rajouter un multiplexeur, qui choisit entre l'adresse normale et l'adresse de rafraichissement. [[File:Controleur de DRAM avec rafraichissement mémoire.png|centre|vignette|upright=2|Controleur de DRAM avec rafraichissement mémoire.]] Le multiplexeur ne doit cependant pas être configuré si une adresse est déjà en cours de transfert. Pour cela, un circuit d'arbitrage se débrouille pour éviter qu'un accès mémoire soit interrompu par une demande de rafraichissement et inversement. Il peut être inclus dans le séquenceur mémoire ou séparé de celui-ci. [[File:Controleur mémoire, intérieur simplifié.png|centre|vignette|upright=2.5|Contrôleur mémoire, intérieur simplifié.]] Il faut noter que le rafraichissement mémoire peut être délégué non pas au contrôleur mémoire, mais au processeur où à la DRAM elle-même. Quand elle est le fait du processeur, celui-ci incorpore un ''refresh timer'' et un compteur d'adresse. Un exemple est celui du processeur Zilog Z80, qui implémentait des compteurs internes pour gérer le rafraichissement mémoire. On peut considérer que le processeur incorpore alors le contrôleur mémoire, au moins partiellement. Il est aussi possible de déléguer le rafraichissement au logiciel (certains jeux vidéos Amiga ou Commodore faisaient cela pour la mémoire vidéo). Quand la DRAM elle-même s'occupe de son propre rafraichissement, c'est elle qui intègre un ''refresh timer'' et le compteur d'adresse. ===Le décodage d'adresse=== Le contrôleur mémoire gère aussi le '''décodage d'adresse'''. pour rappel, celui-ci est utilisé quand on combine plusieurs chips mémoire ensemble, pour combiner leurs capacités mémoire. Par exemple, on peut combiner 4 chips mémoires de 1 mébioctet chacun, pour que le processeur voit comme 4 mébioctets de RAM unique. Le premier mébioctet est placé dans le premier chip mémoire, le second mébioctet dans le second chip, etc. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Pour cela, on active le chip mémoire adéquat, en fonction de l'adresse à consulter. Les autres chips mémoire sont désactivés pendant l'accès mémoire. En théorie, activer ou désactiver un chip mémoire se fait en utilisant son entrée ''Chip Select''. Il faut noter que si les SDRAM disposent bien d'un signal ''Chip Select'', ce n'est pas le cas des mémoires RAM asynchrones. A la place, ce sont les signaux RAS qui font office de ''Chip Select''. Une RAM asynchrone est activée quand son signal RAS lui demande de lire une ligne, elle est désactivée sinon. Mais c'est un détail. Toujours est-il que les signaux ''Chip Select'', ou leurs équivalents, sont générés par le contrôleur de DRAM, à partir des bits de poids fort de l'adresse. Par exemple, avec 4 chips mémoire, les deux bits de poids fort de l'adresse sont utilisés pour sélectionner le chip mémoire adéquat. Le contrôleur mémoire doit avoir plusieurs sorties ''Chip Select'', une par chip mémoire. Et le nombre de ces sorties limite le nombre de chips mémoire qu'on peut combiner. Par exemple, s'il y a seulement 4 sorties ''Chip Select'', on ne pourra brancher que 4 chips mémoire dessus. Sauf à ruser, avec un arrangement horizontal, mais cela n'est pas le ressort du contrôleur mémoire. [[File:Td6bfig3.png|centre|vignette|upright=2|Comparaison entre arrangement horizontal (à gauche) et arrangement vertical (à droite).]] ===Exemple : l'Intel 8202-8203=== L'Intel 8202 et le 8203 étaient des contrôleurs de mémoire DRAM, parmi les plus simples qui soient. Ils avaient une entrée d'adresse de 12 bits, ce qui permettait d'adresser 4 kibioctets de RAM. Ils fournissaient en sortie une adresse multiplexée sur 6 bits, envoyée en deux fois. Ils avaient donc 12 entrées d'adresse, 6 sorties d'adresse, un signal RAS, un signal CAS. Les adresses présentées en entrées n'étaient pas mémorisées dans des registres, ce qui fait qu'elles devaient être maintenues durant toute la durée de l'accès mémoire. Le processeur ne pouvait donc pas se déconnecter du bus d'adresse pendant l'accès mémoire, peu importe sa durée. Le 8202 contenait aussi un compteur de rafraichissement. Rappelons que sur les DRAM asynchrones, le rafraichissement se fait ligne par ligne. Le contrôleur mémoire a juste à présenter l'adresse de ligne, il n'a pas à envoyer l'adresse de colonne. La commande de rafraichissement se fait en mettant le signal RAS à 0, mais en laissant le CAS à 1 (je rappelle que les signaux sont actifs à 0). Le compteur d'adresse de rafraichissement a donc juste à mémoriser l'adresse de ligne. Le séquenceur mémoire était précédé par un circuit d'arbitrage, non-représenté dans le schéma ci-dessous. La microarchitecture de l'Intel 8202 est la suivante : [[File:Microarchitecture de l'Intel 8202.png|centre|vignette|upright=2|Microarchitecture de l'Intel 8202.]] Le 8202 avait une entrée pour un signal d'horloge, ainsi qu'un ''Chip Select'' un peu particulier. Si le signal CS passait à 0 lors d'un accès mémoire, le 8202/8203 ne se désactivait qu'une fois l'accès mémoire terminé. On ne pouvait pas l'interrompre pendant un accès mémoire, même en changeant le bit CS. Le signal d'horloge était utilisé pour commander le ''refresh timer''. Pour commander les lectures et écriture, il recevait en entrée un bit ''Write Request'' et un bit ''Read Request'', qui demandent respectivement une écriture et une lecture. En sortie, on trouvait un unique bit R/W qui valait 0 pour une lecture et 1 pour une écriture. Il avait aussi un bit d'entrée pour forcer le rafraichissement mémoire. S'il est à 1, la mémoire rafraichie l'adresse envoyée par le processeur. Le 8202 pouvait être connecté sur 1 à 4 chips mémoire, ce qui permettait d'adresser au maximum 16 kibioctets de RAM. Les 4 chips ne sont pas accédés en parallèle, un seul l'est à chaque fois. Pour le décodage d'adresse, le 8202 dispose de deux bits BO et B1 pour sélectionner le chip adéquat, ainsi que 4 sorties RAS pour activer la banque adéquate. On rappelle que le signal RAS remplace le signal ''Chip Select''. C'est le séquenceur mémoire qui se charge de générer ces signaux RAS, à partir des deux bits B0 et B1 (qui sont techniquement des bits d'adresse). Pour communiquer avec le processeur, l'Intel 8202 disposait de deux bits XACK et SACK. SACK indiquait au processeur que le 8202/8203 est en train de faire un accès mémoire et qu'il est indisponible pour un second accès mémoire. Cela permet de bloquer le processeur tant que le 8202 est indisponible. Le signal XACK indique que l'accès mémoire précédent est terminé et que : soit la donnée lue est présente sur le bus de données, soit que l'écriture s'est terminée. : Le signal SACK est très utile sur les configurations multiprocesseurs. Un processeur peut démarrer un accès mémoire, le signal SACK indiquera au second processeur qu'il doit attendre que l'accès soit terminé pour que ce soit son tour. ==Les contrôleurs de DRAM asynchrones évolués== L'Intel 8202 était un contrôleur mémoire basique, comme beaucoup d'autres à cette époque. Mais Intel a vendu des contrôleurs mémoires plus complexes. Par exemple, l'Intel 8207 était un contrôleur mémoire bien plus avancé que les deux précédents. Passons sur certains détails, comme le fait qu'il pouvait gérer jusqu'à 256 kibioctets de DRAM. Au-delà de ça, il y avait des fonctionnalités bien plus intéressantes, à savoir : un support de l'ECC, il était double port, il permettait de simuler une DRAM synchrone à partir d'une DRAM asynchrone. Il n'était pas le seul dans ce cas et des contrôleurs de DRAM très évolués étaient capables de faire des merveilles. Voyons comment cela était possible. ===Les contrôleurs mémoire synchrone=== Il est parfaitement possible d'utiliser un contrôleur mémoire synchrone avec une DRAM asynchrone. A vrai dire, le contrôleur DRAM peut complétement simuler une mémoire synchrone alors que la DRAM associée est asynchrone. La traduction asynchrone vers synchrone se fait en ajoutant des registres sur le bus mémoire, notamment sur le bus de données et le bus d'adresse/commande. Nous avions détaillé cela dans le chapitre sur les SRAM, c'est la même chose avec une mémoire DRAM. Sauf que cette fois-ci, le contrôleur mémoire doit aussi être prévu pour. [[File:Controleur mémoire synchrone utilisé avec une DRAM asynchrone.png|centre|vignette|upright=2|Contrôleur mémoire synchrone utilisé avec une DRAM asynchrone]] Les deux-trois registres illustrés plus haut peuvent être intégrés directement dans le contrôleur mémoire, que ce soit totalement ou en partie. Le strict minimum pour avoir un contrôleur mémoire synchrone est que celui-ci doit mémoriser adresse et commandes dans un registre. Par exemple, le 8207 d'Intel était capable de mémoriser les requêtes processeurs dans un registre interne, mais il fallait utiliser deux registres séparés pour le bus de données. Les deux registres étaient alors commandés par le contrôleur mémoire. Il est cependant possible d'aller plus loin et d'intégrer les registres du bus de données dans le contrôleur mémoire. [[File:Controleur mémoire DRAM synchrone.png|centre|vignette|upright=2|Contrôleur mémoire DRAM synchrone.]] : Il faut noter que cette fonctionnalité est parfois disponible sur les SRAM. En clair, on peut associer une SRAM asynchrone avec un contrôleur de SRAM synchrone. Le contrôleur de SRAM se charge alors de simuler une SRAM synchrone à partir de la SRAM asynchrone. Utiliser un contrôleur mémoire synchrone a de nombreux avantages, l'un d'entre eux étant lié aux ''wait state''. Quand le processeur envoie une requête de lecture/écriture à la mémoire RAM, celle-ci met plusieurs cycles d'horloge à répondre. Et pendant ce temps, le processeur... attend. Et pendant ce temps d'attente, il doit maintenir l'adresse mémoire sur le bus d'adresse. Les cycles d'horloge perdus à attendre la mémoire RAM étaient appelés des '''''Wait states'''''. Utiliser un contrôleur mémoire synchrone d'éliminer les ''wait state'', dans une certaine mesure. Avec un contrôleur mémoire synchrone, le processeur envoie l'adresse, mais c'est le contrôleur mémoire qui la maintient sur le bus d'adresse. Le processeur peut envoyer l'adresse et la donnée à écrire, elles sont recopiées dans les registres, et le controleur mémoire y a accès sans que le processeur doive les maintenir. Le processeur peut se déconnecter du bus mémoire et faire du travail dans son coin pendant que le contrôleur mémoire accède à la DRAM. Les ''wait state'' disparaissent alors, du moins du point de vue du processeur. ===La gestion de l'ECC=== L''''ECC''' peut être géré dans le contrôleur mémoire. Pour cela, on couple les registres mentionnés dans la section précédente, avec un circuit de détection et de correction d'erreur. Le circuit d'ECC peut, comme les registres synchrones, être intégré dans le contrôleur mémoire, ou au contraire être situé dans un circuit séparé. Si le circuit d'ECC est séparé du contrôleur mémoire, il communique avec lui, histoire que le contrôleur mémoire puisse signaler toute erreur de parité ou d'ECC au processeur. [[File:Controleur mémoire synchrone avec ECC intégré.png|centre|vignette|upright=2|Controleur mémoire synchrone avec ECC intégré]] Reprenons l'exemple du 8207 d'Intel. Le contrôleur mémoire 8207 gère le bus d'adresse et de commande, mais n'a pas de connexions directes avec le bus de données. Il ne peut donc pas prendre en charge l'ECC. Il avait besoin d'être couplé avec un circuit d'ECC séparé, relié au bus de données : l'Intel 8206. Le 8026 prenait en entrée : 16 bits de données et 8 bits d'ECC. Il fournissait en sortie 16 bits de données après correction d'erreur, les 8 bits d'ECC pour indiquer qu'une erreur a été détectée mais pas corrigée, ainsi que des bits de parité. Le 8206 détectait/corrigeait les erreurs et générait les bits d'ECC, mais il communiquait avec le contrôleur mémoire pour cela. [[File:8207 avec ECC.png|centre|vignette|upright=2|8207 avec ECC]] La détection/correction d'erreur était appliquée à la fois pour les accès mémoire et pour les rafraichissements mémoire. Lors d'un rafraichissement mémoire, la donnée rafraichie est lue et réécrite. Avec l'ECC activé et configuré correctement, le rafraichissement passe par le bus de données. Au lieu d'avoir un cycle de lecture-écriture interne à la DRAM, on a un cycle de lecture-correction-écriture qui utilise le 8206. La donnée lue est envoyée sur le bus de données, puis le 8206 corrige une éventuelle erreur, et la donnée corrigée est alors réécrite en mémoire. Au passage, si une erreur non-correctible est détectée, le 8206 ne fait rien, l'erreur est ignorée. La gestion de l'erreur sera retardée jusqu'à une lecture ultérieure. Et encore : si lecture ultérieure il y a. Si la donnée est écrasée par une écriture, la donnée corrompue sera simplement écrasée et disparaitra sans avoir pu faire le moindre dégât. Mais pour cela, le 8206 doit communiquer avec le contrôleur mémoire, pour savoir s'il est dans un cycle de rafraichissement ou un accès mémoire normal. Il prévient le 8207 lors d'une erreur, et c'est ce dernier qui décide si l'erreur doit être prise en compte ou ignorée. C'est seulement lors d'un accès mémoire normal que le processeur est prévenu qu'une erreur de parité/autre a eu lieu. ===Les contrôleurs mémoires multiports=== Les '''contrôleur mémoire multiport''' disposent de plusieurs ports, chacun permettant de traiter un accès mémoire. Ils peuvent simuler une mémoire multiport à partir d'une DRAM monoport. Évidemment, la simulation n'est pas parfaite. Des accès mémoire simultanés, envoyés en même temps sur différents ports, sont en réalité exécutés un par un, pas en même temps. Il y a donc une petite pénalité en termes de performances, mais elle est mineure. Encore une fois, nous allons reprendre l'exemple du 8207. Il avait deux ports séparés, et était prévu pour fonctionner dans un système à deux processeurs. L'usage de deux ports séparés permettait de partager une unique mémoire DRAM entre deux processeurs. Le partage se faisait en interfaçant deux processeurs sur le contrôleur mémoire, chacun étant connecté à un port. Lors d'une lecture, il redirigeait la donnée lue vers le bon processeur, en configurant le bus de données correctement. Le contrôleur mémoire recevait des requêtes mémoire de deux processeurs, mais il les exécutait une à la fois. S'il recevait deux requêtes en même temps, l'une d'entre elles était mise en attente. Le contrôleur mémoire doit arbitrer les accès à la mémoire, et faire en sorte que les deux processeurs aient accès à la mémoire à tour de rôle. Et non seulement il doit arbitrer les deux ports, mais il y a aussi un troisième port interne au contrôleur mémoire : le rafraichissement mémoire ! Pour cela, le circuit d'arbitrage qui choisissait entre rafraichissement mémoire et accès mémoire, est amélioré de manière à gérer un second port. Le circuit d'arbitrage donne l'accès au séquenceur mémoire à un port sélectionné. L'arbitrage était configurable, avec deux options : soit le port A est privilégié sur le port B, soit le port le plus récemment accédé à la priorité. Les deux ports pouvaient être configurés pour fonctionner soit de manière asynchrone, soit de manière synchrone. Il était aussi possible de configurer l'ECC, des options liées à la fréquence du processeur et de la RAM, ainsi que de nombreuses options liées au rafraichissement. Pour cela, le 8207 contenait un registre de configuration interne, programmable en fournissant les entrées adéquates. Tout ce qui vient d'être dit se généralise avec plus de deux processeurs. Le 8207 ne permettait pas ça, mais les contrôleurs mémoire des PC modernes en sont capables. Ils peuvent gérer plusieurs dizaines de processeurs facilement. ==Le contrôleur mémoire d'une DRAM ''Fast Page Mode''== Les mémoires DRAM classiques sont des mémoires à tampon de ligne, mais qui sont assez mal utilisées. Notamment, tout accès mémoire se fait en deux phases : un accès pour sélectionner la ligne, un autre pour sélectionner la colonne. Les mémoires ''Fast Page Mode'' permettent d'optimiser le tout. Elles permettent de faire plusieurs accès successifs à la même ligne, à des colonnes différentes. Et le contrôleur mémoire doit être adapté pour cela. [[File:Sélection d'une ligne sur une mémoire FPM ou EDO.png|centre|vignette|upright=2|Sélection d'une ligne sur une mémoire FPM ou EDO.]] Le contrôleur mémoire doit détecter que deux accès mémoire consécutifs se font dans la même ligne. Si deux accès consécutifs accèdent à la même ligne, on doit juste changer de colonne et altérer le signal CAS. C'est un ''succès de tampon de ligne'', aussi appelé un '''succès de page'''. Si deux accès consécutifs accèdent à une ligne différente, c'est un ''défaut de tampon de ligne'', aussi appelé un '''défaut de page'''. Il faut alors changer de ligne, en altérant les signaux RAS et en envoyant une adresse de ligne. Pour détecter les succès ou défauts de page, il faut ajouter un circuit spécialisé dans le contrôleur mémoire. Il mémorise la ligne ouverte, et plus précisément son adresse de ligne (numéro de banque inclut). A chaque requête processeur, il compare l'adresse de ligne recue avec celle déjà ouverte. C'est un succès si les deux sont égales, un défaut si elles sont différentes. Le circuit qui fait cette comparaison est appelé le '''décodeur de commande'''. Il prévient le séquenceur mémoire en cas de succès de page, grâce à un signal de un bit, qui vaut 0 en cas de défaut de page et 1 en cas de succès. Le séquenceur mémoire décide alors comment gérer les signaux RAS et CAS, ainsi que l'envoi des adresses de ligne/colonne. [[File:Controleur mémoire d'une FPM-DRAM.png|centre|vignette|upright=2|Controleur mémoire d'une FPM-DRAM]] ==Le contrôleur mémoire d'une SDRAM ou d'une DDR== l'intérieur d'un contrôleur de SDRAM ne change pas significativement d'un controleur de RAM asynchrone. Il regroupe toujours un séquenceur mémoire et une interface physique, un circuit pour le rafraichissement mémoire et un circuit d'arbitrage. Par contre, ses sorties changent pas mal. Contrairement aux mémoires DRAM basiques, les mémoires SDRAM sont cadencées par un signal d'horloge. Et ce signal d'horloge vient bien de quelque part. Pour cela, deux solutions : soit le contrôleur mémoire génère la fréquence qui commande la mémoire, soit il prend en entrée une fréquence de base qu'il multiplie pour obtenir la fréquence désirée. Les deux solutions sont équivalentes, si ce n'est que les circuits impliqués ne sont pas les mêmes. Dans le premier cas, le contrôleur doit embarquer un circuit oscillateur, qui génère la fréquence demandée. Dans l'autre cas, un simple multiplieur/diviseur de fréquence suffit et c'est généralement une PLL qui est utilisée pour cela. : Notez qu'il ne faut pas confondre la fréquence de la SDRAM et celle du contrôleur mémoire. Le contrôleur mémoire fonctionne à une vitesse assez élevée, en interne. Le port relié au processeur fonctionne à haute fréquence, généralement la même que celle du processeur. A vrai dire, de nos jours, il est intégré dans le processeur. Pour le décodage d'adresse, tout est plus simple sur les SDRAM, DDR inclues. Les chips de mémoire SDRAM et DDR disposent d'une entrée ''Chip Select'', ce qui facilite grandement le décodage d'adresse. Les bits de ''Chip Select'' sont générés par le contrôleur mémoire, et sont transmis sur le bus de commande. Le contrôleur de DRAM peut adresser un certain nombre de rangées, dispersés sur une ou plusieurs barrettes. La limite maximale dépend du contrôleur de DRAM, elle est souvent proche de 8 ou 16 rangées. Si on combine plusieurs barrettes de mémoire, il est possible de dépasser cette limite. Par exemple, pour un contrôleur de DRAM supportant maximum 8 rangées, 4 barrettes de 4 rangées chacune dépassent la limite. ===Le séquenceurs mémoire pour les SDRAM/DDR=== Le séquenceur mémoire existe toujours pour les mémoires SDRAM, c'est toujours un circuit séquentiel qui implémente une machine à état. Il traduit toujours une requête processeur en une séquence de commandes envoyées à des timings bien précis. Les commandes mémoires peuvent provenir de l'extérieur, mais aussi d'un circuit de rafraichissement intégré dans le contrôleur mémoire, comme pour les autres DRAM. La seule différence est que la machine à état est plus complexe. Pour rappel, une requête de lecture/écriture se fait en trois étapes : une commande PRECHARGE pour précharger le tampon de ligne, une commande ACT qui fixe l'adresse de ligne, et enfin une commande READ/WRITE avec l'adresse de colonne. Et il faut tenir compte des timings mémoire, à savoir le fait que ces commandes sont séparées par des temps d'attentes bien précis. Par exemple, je prends des chiffres arbitraires : il faut attendre 2 cycles entre une commande ACT et une commande READ, 6 cycles avant deux commandes WRITE consécutives, etc. La gestion des ''timings'' rend la conception du séquenceur plus complexe. Le séquenceur peut profiter du fait que des accès simultanés peuvent se faire de préférence se faire dans des banques différentes. Prenons l'exemple d'une mémoire partagée entre plusieurs processeurs. Les deux processeurs ou cœurs exécutent deux programmes différents : le premier processeur exécute le navigateur web, alors que le second lit de la musique. Dans ce cas, les deux processeurs accèdent à des données très éloignées en mémoire (ce n'est pas toujours le cas, mais ça l'est dans cet exemple), qui sont potentiellement dans des banques séparées. Par contre, il y a une contrainte majeure : deux accès simultanés ne peuvent pas accéder au bus en même temps. Je rappelle que quand on démarre une lecture, il se passe quelques cycles d'horloge avant que la donnée atterrissent sur le bus de données. Le temps en question est fixe, mais sa durée n'est pas la même selon que la lecture fasse un succès de page ou un défaut de page. Le séquence mémoire doit en tenir compte avant de démarrer une lecture ou une écriture. Le séquenceur doit donc détecter les succès de page, et déterminer si le bus de donnée sera libre au moment où la lecture terminera. Si c'est le cas, il peut lancer a commande. Sinon, il doit la retarder. {|class="wikitable" |+ Exemple où deux processeurs accèdent à une SDRAM en même temps |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 |- ! rowspan="2" | Requêtes mémoires | bgcolor="#A0FFFF" | CPU 1 || || || bgcolor="#A0FFFF" | CPU 1 || || || || || || |- | bgcolor="#FFA0FF" | CPU 2 || || || || || bgcolor="#FFA0FF" | CPU 2 || || || || |- |- ! Bus de commande/adresse | || bgcolor="#A0FFFF" | Lecture (CPU 1) || bgcolor="#FFA0FF" | Lecture (CPU 2) || || bgcolor="#A0FFFF" | Lecture (CPU 1) || || bgcolor="#FFA0FF" | Lecture (CPU 2) || || |- ! Bus de données | || || || bgcolor="#A0FFFF" | Donnée lue CPU 1 || bgcolor="#FFA0FF" | Donnée lue CPU 2 || || bgcolor="#A0FFFF" | Donnée lue CPU 1 || || bgcolor="#FFA0FF" | Donnée lue CPU 2 |} Les DDR2 et 3 vont encore plus loin avec l'optimisation des '''CAS postés'''. L'idée est que le contrôleur mémoire peut envoyer une commande ACT et une commande READ/WRITE sans se soucier des ''timings''. En théorie, les deux commandes doivent être séparées par quelques cycles, sur une SDRAM ou une DDR1. Mais avec la DDR2, le contrôleur mémoire peut envoyer les deux l'une après l'autre, au cycle suivant. C'est la mémoire qui mettra en attente la commande READ/WRITE pour respecter les ''timings'' mémoire. Cela complexifie le fonctionnement interne de la DDR, mais simplifie grandement le travail du contrôleur mémoire. ===La politique de gestion du tampon de ligne=== Plus haut, nous avons parlé des mémoires FPM, qui ont introduit quelques optimisations pour utiliser au mieux le tampon de ligne. Il se trouve que les SDRAM conservent ces optimisations. Il y a toujours cette notion de succès de page et de défaut de page, suivant que deux accès se font dans la même ligne ou dans deux lignes différentes. Du moins, c'est le cas si le séquenceur mémoire est coopératif. En effet, il peut fonctionner de plusieurs manières, dont les plus extrêmes sont appelés la politique de la page fermée et la politique de la page ouverte. Voyons à quoi elles correspondent. Avec la '''politique de la page fermée''', chaque accès mémoire est suivi d'une commande PRECHARGE, qui ferme la ligne courante et précharge les lignes de bits. Même si deux accès consécutifs se font dans la même ligne, la ligne est fermée et ré-ouverte entre deux accès mémoire. En clair : l'optimisation introduite par les mémoires FPM est désactivée, le contrôleur mémoire fait exprès de ne pas en profiter. On appelle cette méthode la close ''page autoprecharge''. Cette méthode réduit grandement les performances pour les accès à des adresses consécutives, mais fonctionne à merveille si les accès sont "aléatoires", à savoir qu'ils se font sans régularités évidentes. Son seul avantage est que l'implémentation du séquenceur mémoire est très simple. En effet, le séquenceur mémoire se passe complétement de la table des banques, du comparateur de ligne, et de tous les circuits nécessaires pour vérifier les succès ou défauts de page. De plus, le séquenceur mémoire profite grandement des commandes READA et WRITEA, qui fusionnent une commande READ/WRITE avec une commande PRECHARGE. Le séquenceur mémoire a juste à envoyer des commandes ACT, READA, WRITEA et PREFETCH à la mémoire, pas besoin des commandes PRECHARGE, READ ou WRITE. A l'opposé, la '''politique de la page ouverte''' ne ferme pas automatiquement la ligne. Elle la laisse ouverte, en espérant que le prochain accès mémoire se fasse dans cette ligne. Lorsqu'un nouvel accès mémoire arrive, elle doit détecter les succès ou défauts de page et agir en fonction. En cas de défaut de page, la ligne est fermée, le séquenceur mémoire envoie une commande PRECHARGE, puis l'accès suivant effectue les deux commandes ACT + READ ou WRITE. En cas de succès de page, les commandes PRECHARGE et ACT ne sont pas envoyées, seules la commande READ ou WRITE l'est. Un désavantage est que le contrôleur mémoire doit inclure une table des banques et un comparateur, comme vu plus haut dans la section sur les mémoires FPM. un autre défaut est que garder une ligne ouverte consomme beaucoup d'énergie, comparé à un simple état de PRECHARGE. En conséquence, il est préférable de fermer les lignes dès que possible. Par contre, les performances sont d'autant meilleures que les accès mémoire consécutifs à une même ligne soient assez fréquents. Si les accès mémoire sont aléatoires, les performances sont moins bonnes. La politique de la page fermée fermait les lignes en avance, avec des commandes READA ou WRITEA, avant même que l'accès suivant démarre. Avec la politique de la page ouverte, on doit attendre pour détecter un défaut de page, puis fermer la ligne avec une commande PRECHARGE séparée. La ligne est donc fermée avec un peu temps de retard, et envoyer deux commandes au lieu d'une prend plus de temps. Les contrôleurs mémoires basiques utilisent une des deux solutions précédentes. Soit la page est toujours fermée, soit elle est toujours laissée ouverte jusqu'à ce qu'un accès mémoire la referme. Mais les contrôleurs plus évolués tentent de prédire s'il faut fermer ou laisser ouvertes les pages ouvertes. La méthode la plus simple attend un temps prédéterminé avant de fermer la ligne. Une autre solution regarde le tout dernier accès. On peut très bien décider de laisser la ligne ouverte si l'accès mémoire précédent était une rafale, et fermer sinon. Une solution plus complexe mémorise les N derniers accès et en déduit s'il faut fermer ou non la prochaine ligne. On peut mémoriser si l'accès en question a causé la fermeture d'une ligne avec un bit. Mémoriser les N derniers accès demande d'utiliser un simple registre à décalage. Pour chaque valeur de ce registre, il faut prédire si le prochain accès demandera une ouverture ou une fermeture. Une solution simple fait la moyenne des bits à 1 dans ce registre : si plus de la moitié des bits est à 1, on laisse la ligne ouverte et on ferme sinon. Pour améliorer un petit peu l'algorithme, on peut faire en sorte que les bits des accès mémoires les plus récents aient plus de poids dans le calcul de la moyenne. Il existe sans doute d'autres solutions plus évoluées, mais il est difficile de savoir ce qu'il y a dans les contrôleurs de SDRAM modernes. : Le fait de laisser ouverte une ligne ou au contraire de la fermer systématiquement, se fait pour chaque banque. ===Les banques et l'entrelacement mémoire des SDRAM=== Les SDRAM incorporent plusieurs banques à l'intérieur de leurs circuits. Pour rappel, une banque est une sous-mémoire, avec ses propres décodeurs, son tampon de ligne, ses multiplexeurs de colonnes, etc. Pour le dire autrement, une SDRAM regroupe plusieurs mémoires séparées dans un même circuit intégré, les mémoires en question étant des banques. [[File:Arrangement vertical.jpg|centre|vignette|upright=2.5|Mémoire multi-banques.]] La présence de plusieurs banques impacte la gestion des lignes ouvertes/fermées. En effet, chaque banque a son propre tampon de ligne, ce qui fait que la gestion des lignes se fait indépendamment dans chaque banque. Le séquenceur mémoire doit donc se souvenir des lignes actives dans chaque banque. Pour cela, il mémorise ces lignes dans une petite mémoire : la '''table des banques''', aussi appelée ''bank status memory''. Pour détecter un succès ou un défaut, le contrôleur doit extraire la ligne de l'adresse, mais aussi le numéro de banque. Il envoie alors le numéro de banque à la table des banques, sur son entrée d'adresse. Il récupère alors le numéro de la ligne active sur les sorties de données. Il compare alors ce numéro de ligne avec le numéro de ligne de l'adresse envoyée par le processeur. C'est un succès si les deux sont égales, un défaut sinon. [[File:Controleur mémoire FPM avec plusieurs banques.jpg|centre|vignette|upright=2|Contrôleur mémoire FPM avec plusieurs banques.]] <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les mémoires RAM dynamiques (DRAM) | prevText=Les mémoires RAM dynamiques (DRAM) | next=Les mémoires associatives | nextText=Les mémoires associatives }} </noinclude> 5qezajgpbjrl99sxr8e7hyr132etbx5 764679 764675 2026-04-23T18:54:07Z Mewtow 31375 /* Le séquenceurs mémoire pour les SDRAM/DDR */ 764679 wikitext text/x-wiki Les mémoires ROM ou SRAM ont généralement une interface simple, à laquelle le processeur peut s'interfacer directement. Mais pour les DRAM, ce n'est pas le cas. Les DRAM utilisent un bus d'adresse multiplexé, où l'adresse est envoyée en deux fois. Connecter le processeur directement sur une DRAM n'est pas pratique : le bus d'adresse du processeur et celui de la mémoire ne collent pas. Les DRAM doivent aussi être rafraichies régulièrement. Le rafraichissement mémoire peut être délégué au processeur, mais c'est loin d'être idéal. Et il y a bien d'autres raisons qui font que le processeur ne peut pas s'interfacer facilement avec les mémoires DRAM. Pour gérer ces problèmes, les mémoires DRAM ne sont pas connectées directement au processeur. À la place, on ajoute un intermédiaire entre le processeur et la mémoire : le '''contrôleur mémoire externe'''. Son but est de montrer au processeur une interface simple, semblable à celle d'une SRAM classique, alors qu'il commande une mémoire DRAM à l'interface plus complexe. Il est placé sur la carte mère ou dans le processeur, et ne doit pas être confondu avec le contrôleur mémoire intégré dans la mémoire. Ce chapitre va expliquer quels sont les rôles du contrôleur mémoire, son interface et ce qu'il y a à l'intérieur. Dans ce chapitre, quand nous parlerons de ''contrôleur mémoire'', cela fera systématiquement référence au contrôleur mémoire externe. Et avant de poursuivre, sachez qu'il est difficile de faire des généralités sur les contrôleurs mémoire, car les mémoires DRAM elles-mêmes sont assez différentes les unes des autres. Entre une mémoire EDO, une mémoire SDR, une mémoire DDR et une DRAM asynchrone, les contrôleurs mémoires seront fortement différents. Aussi, il y a aura une différence entre un contrôleur pour une DRAM asynchrone et un contrôleur pour une mémoire EDO, une mémoire SDRAM, etc. J'ai choisit de vous séparer les contrôleurs mémoire pour les DRAM asynchrones de ceux pour les SDRAM/DRR. ==Le contrôleur d'une DRAM asynchrone== Les premières DRAM asynchrones avaient des contrôleurs mémoires dédiés, qui étaient séparés du processeur et du ''chipset'' de la carte mère. Par exemple, les composants Intel 8202, Intel 8203 et Intel 8207 étaient des contrôleurs mémoire pour DRAM asynchrones qui étaient vendus dans des boitiers DIP et étaient soudés sur la carte mère. Par la suite, ils ont été intégrés au ''chipset'' de la carte mère pendant les décennies 90-2000. Après les années 2000, ils ont été intégrés dans les processeurs. Leurs fonctions étaient le multiplexage de l'adresse et le rafraichissement mémoire. Ils recevaient une adresse mémoire complète, qu'ils découpaient une adresse de ligne et une adresse de colonne, envoyées l'une après l'autre sur le bus mémoire. Pour le rafraichissement mémoire, ils rafraichissaient la DRAM régulièrement, de manière automatique, entre deux accès mémoire normaux. Le processeur n'avait ainsi plus à rafraichir la mémoire lui-même, cette fonction était déléguée au contrôleur de DRAM. Ils étaient connectés au bus d'adresse et de commande, avec éventuellement des relations indirectes avec le bus de données. ===L'interface d'un contrôleur de DRAM asynchrone=== L'interface du contrôleur mémoire décrit ses broches d'entrées/sorties et leur signification. Elle est généralement très simple et contient deux ports : un connecté au processeur, un autre connecté à la DRAM. Cela trahit d'ailleurs son rôle principal, qui est de transformer les requêtes de lecture/écriture provenant du processeur en une suite de commandes acceptée par la mémoire. Le port connecté à la DRAM est connecté ua bus d'adresse et au bus de commande. Le bus de données est lui relié au processeur et/ou au bus système. Un accès mémoire provenant du processeur contient une adresse à lire/écrire, le bit R/W qui indique s'il faut faire une lecture ou une écriture, et éventuellement une donnée à écrire. Mais, nous avons vu que les accès mémoires sur une DRAM sont multiplexés : on envoie l'adresse en deux fois : la ligne d'abord, puis la colonne. De plus, il faut générer les signaux RAS, CAS et bien d'autres. Le tout est illustré ci-dessous. [[File:Contrôleur mémoire.png|centre|vignette|upright=2|Contrôleur mémoire externe.]] Un point important est que les DRAM asynchrones n'ont pas d'entrée ''Chip Select'' ou d'entrée ''Output Enable''. Les signaux RAS et CAS remplacent en quelque sorte ces deux signaux. Le bit RAS fait office de ''Chip Select'', le bit CAS fait office d'''Output Enable''. Pour certains contrôleurs de DRAM, il faut ajouter l''''interface électrique''', qui traduit les signaux du processeur en signaux compatibles avec la mémoire. Il est en effet très fréquent que la mémoire et le processeur n'utilisent pas les mêmes tensions pour coder un bit, ce qui fait qu'elles ne sont pas compatibles. Dans ce cas, le contrôleur mémoire fait la conversion. Le contrôleur DRAM peut fonctionner en mode synchrone ou asynchrone, du point de vue du processeur. Quand il fonctionne en mode synchrone, il permet d'interfacer un processeur synchrone avec une mémoire DRAM asynchrone. Un point important est que le contrôleur DRAM sert d'intermédiaire entre une mémoire DRAM et "le reste du monde". Il ne fait pas forcément office d'intermédiaire entre DRAM et processeur, mais peut aussi faire l'intermédiaire entre la DRAM et un bus système, entre une DRAM et le ''Video Display Controler'' d'une carte graphique, ou n'importe quel autre composant électronique qui utilise cette DRAM. ===Le générateur de ''timings'' et la traduction d'adresse=== Le contrôleur mémoire doit traduire les adresses du processeur en adresses compatibles avec la mémoire. Et la traduction est assez variable, suivant que le bus mémoire est un bus normal, un bus multiplexé, ou partiellement multiplexé. Nous avons vu ces trois types de bus mémoire dans le chapitre sur l'interface des mémoires, mais nous ferons quelques rappels rapides. Avec un ''bus totalement multiplexé'', le bus d'adresse et le bus de données sont fusionnés. Dans ce cas, on peut envoyer soit une adresse, soit lire/écrire une donnée sur le bus, mais on ne peut pas faire les deux en même temps. Un bit ALE indique si le bus est utilisé en tant que bus d'adresse ou bus de données. Le contrôleur mémoire gère cette situation, en fixant le bit ALE et en envoyant séparément adresse et donnée pour les écritures. [[File:Bus multiplexé avec bit ALE.png|centre|vignette|upright=2|Bus multiplexé avec bit ALE.]] Avec un ''bus d'adresse multiplexé'', l'adresse est découpée en une adresse de ligne et une adresse de colonne, envoyées l'une après l'autre. Le contrôleur mémoire prend en entrée une adresse mémoire complète, la découpe en deux, et envoie chaque morceau au bon moment. Pour cela, il suffit d'un registre pour mémoriser l'adresse et d'un multiplexeur. Le multiplexeur choisit soit les bits de poids fort de l'adresse, soit ceux de poids faible. Les premiers correspondent à l'adresse de ligne, les autres à l'adresse de colonne. La commande du multiplexeur est le fait d'un petit circuit séquentiel, qui génère aussi les signaux CAS et RAS. Au premier cycle, il met le signal RAS à 1, met le CAS à 0, et configure le MUX pour sélectionner les bits de poids fort. Au second cycle, il génère un signal CAS à 1, met le RAS à 0 et configure le MUX pour sélectionner les bits de poids faible. Le circuit en question est appelé le générateur de ''timings''. [[File:Controleur de DRAM simple, sans rafraichissement mémoire.png|centre|vignette|upright=2|Contrôleur de DRAM simple, sans rafraichissement mémoire.]] Le générateur de ''timings'' est un circuit séquentiel qui implémente une petite machine à état. Il est très simple sur une mémoire DRAM asynchrone basique, mais il est plus complexe sur les mémoires FPM, EDO, quartet, et autres. Le regroupement des multiplexeurs d'adresse et du générateur de ''timings'' est appelé le '''séquenceur mémoire'''. C'est le séquenceur mémoire qui traduit la requête processeur en commande DRAM, le reste du contrôleur est dédié au rafraichissement mémoire ou à d'autres fonctions facultatives. ===Le rafraichissement mémoire=== La gestion du rafraichissement mémoire est la fonction principale du contrôleur DRAM. Pour gérer le rafraichissement mémoire, le contrôleur mémoire intègre deux compteurs, un pour gérer l'adresse à rafraichir, l'autre pour gérer l'intervalle de temps entre deux rafraichissements. Le rafraichissement se fait à intervalle régulier, tous les x microsecondes. Pour déclencher le rafraichissement au bon moment, le contrôleur mémoire contient un ''Refresh Timer'', aussi appelé le '''compteur de rafraichissement'''. Il est initialisé avec le temps entre deux rafraichissements, une adresse est rafraichie quand ce compteur atteint 0. Le rafraichissement mémoire balaye la mémoire adresse par adresse. Pour savoir à quelle adresse il en est rendu, le contrôleur mémoire utilise un '''compteur d'adresse'''. Il contient la prochaine adresse à rafraichir, aussi appelée l'adresse de rafraichissement. Régulièrement, l'adresse dans ce compteur est envoyée à la RAM, pour une lecture. Mais la donnée lue n'est pas envoyée sur le bus de donnée, soit parce que la RAM est prévue pour, soit parce que le contrôleur désactive son bit ''output enable''. Dans le second cas, la RAM fait la lecture en interne, mais se déconnecte du bus de donnée, perdant la donnée lue dans le néant. Pour envoyer l'adresse de rafraichissement sur le bus d'adresse, il faut rajouter un multiplexeur, qui choisit entre l'adresse normale et l'adresse de rafraichissement. [[File:Controleur de DRAM avec rafraichissement mémoire.png|centre|vignette|upright=2|Controleur de DRAM avec rafraichissement mémoire.]] Le multiplexeur ne doit cependant pas être configuré si une adresse est déjà en cours de transfert. Pour cela, un circuit d'arbitrage se débrouille pour éviter qu'un accès mémoire soit interrompu par une demande de rafraichissement et inversement. Il peut être inclus dans le séquenceur mémoire ou séparé de celui-ci. [[File:Controleur mémoire, intérieur simplifié.png|centre|vignette|upright=2.5|Contrôleur mémoire, intérieur simplifié.]] Il faut noter que le rafraichissement mémoire peut être délégué non pas au contrôleur mémoire, mais au processeur où à la DRAM elle-même. Quand elle est le fait du processeur, celui-ci incorpore un ''refresh timer'' et un compteur d'adresse. Un exemple est celui du processeur Zilog Z80, qui implémentait des compteurs internes pour gérer le rafraichissement mémoire. On peut considérer que le processeur incorpore alors le contrôleur mémoire, au moins partiellement. Il est aussi possible de déléguer le rafraichissement au logiciel (certains jeux vidéos Amiga ou Commodore faisaient cela pour la mémoire vidéo). Quand la DRAM elle-même s'occupe de son propre rafraichissement, c'est elle qui intègre un ''refresh timer'' et le compteur d'adresse. ===Le décodage d'adresse=== Le contrôleur mémoire gère aussi le '''décodage d'adresse'''. pour rappel, celui-ci est utilisé quand on combine plusieurs chips mémoire ensemble, pour combiner leurs capacités mémoire. Par exemple, on peut combiner 4 chips mémoires de 1 mébioctet chacun, pour que le processeur voit comme 4 mébioctets de RAM unique. Le premier mébioctet est placé dans le premier chip mémoire, le second mébioctet dans le second chip, etc. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Pour cela, on active le chip mémoire adéquat, en fonction de l'adresse à consulter. Les autres chips mémoire sont désactivés pendant l'accès mémoire. En théorie, activer ou désactiver un chip mémoire se fait en utilisant son entrée ''Chip Select''. Il faut noter que si les SDRAM disposent bien d'un signal ''Chip Select'', ce n'est pas le cas des mémoires RAM asynchrones. A la place, ce sont les signaux RAS qui font office de ''Chip Select''. Une RAM asynchrone est activée quand son signal RAS lui demande de lire une ligne, elle est désactivée sinon. Mais c'est un détail. Toujours est-il que les signaux ''Chip Select'', ou leurs équivalents, sont générés par le contrôleur de DRAM, à partir des bits de poids fort de l'adresse. Par exemple, avec 4 chips mémoire, les deux bits de poids fort de l'adresse sont utilisés pour sélectionner le chip mémoire adéquat. Le contrôleur mémoire doit avoir plusieurs sorties ''Chip Select'', une par chip mémoire. Et le nombre de ces sorties limite le nombre de chips mémoire qu'on peut combiner. Par exemple, s'il y a seulement 4 sorties ''Chip Select'', on ne pourra brancher que 4 chips mémoire dessus. Sauf à ruser, avec un arrangement horizontal, mais cela n'est pas le ressort du contrôleur mémoire. [[File:Td6bfig3.png|centre|vignette|upright=2|Comparaison entre arrangement horizontal (à gauche) et arrangement vertical (à droite).]] ===Exemple : l'Intel 8202-8203=== L'Intel 8202 et le 8203 étaient des contrôleurs de mémoire DRAM, parmi les plus simples qui soient. Ils avaient une entrée d'adresse de 12 bits, ce qui permettait d'adresser 4 kibioctets de RAM. Ils fournissaient en sortie une adresse multiplexée sur 6 bits, envoyée en deux fois. Ils avaient donc 12 entrées d'adresse, 6 sorties d'adresse, un signal RAS, un signal CAS. Les adresses présentées en entrées n'étaient pas mémorisées dans des registres, ce qui fait qu'elles devaient être maintenues durant toute la durée de l'accès mémoire. Le processeur ne pouvait donc pas se déconnecter du bus d'adresse pendant l'accès mémoire, peu importe sa durée. Le 8202 contenait aussi un compteur de rafraichissement. Rappelons que sur les DRAM asynchrones, le rafraichissement se fait ligne par ligne. Le contrôleur mémoire a juste à présenter l'adresse de ligne, il n'a pas à envoyer l'adresse de colonne. La commande de rafraichissement se fait en mettant le signal RAS à 0, mais en laissant le CAS à 1 (je rappelle que les signaux sont actifs à 0). Le compteur d'adresse de rafraichissement a donc juste à mémoriser l'adresse de ligne. Le séquenceur mémoire était précédé par un circuit d'arbitrage, non-représenté dans le schéma ci-dessous. La microarchitecture de l'Intel 8202 est la suivante : [[File:Microarchitecture de l'Intel 8202.png|centre|vignette|upright=2|Microarchitecture de l'Intel 8202.]] Le 8202 avait une entrée pour un signal d'horloge, ainsi qu'un ''Chip Select'' un peu particulier. Si le signal CS passait à 0 lors d'un accès mémoire, le 8202/8203 ne se désactivait qu'une fois l'accès mémoire terminé. On ne pouvait pas l'interrompre pendant un accès mémoire, même en changeant le bit CS. Le signal d'horloge était utilisé pour commander le ''refresh timer''. Pour commander les lectures et écriture, il recevait en entrée un bit ''Write Request'' et un bit ''Read Request'', qui demandent respectivement une écriture et une lecture. En sortie, on trouvait un unique bit R/W qui valait 0 pour une lecture et 1 pour une écriture. Il avait aussi un bit d'entrée pour forcer le rafraichissement mémoire. S'il est à 1, la mémoire rafraichie l'adresse envoyée par le processeur. Le 8202 pouvait être connecté sur 1 à 4 chips mémoire, ce qui permettait d'adresser au maximum 16 kibioctets de RAM. Les 4 chips ne sont pas accédés en parallèle, un seul l'est à chaque fois. Pour le décodage d'adresse, le 8202 dispose de deux bits BO et B1 pour sélectionner le chip adéquat, ainsi que 4 sorties RAS pour activer la banque adéquate. On rappelle que le signal RAS remplace le signal ''Chip Select''. C'est le séquenceur mémoire qui se charge de générer ces signaux RAS, à partir des deux bits B0 et B1 (qui sont techniquement des bits d'adresse). Pour communiquer avec le processeur, l'Intel 8202 disposait de deux bits XACK et SACK. SACK indiquait au processeur que le 8202/8203 est en train de faire un accès mémoire et qu'il est indisponible pour un second accès mémoire. Cela permet de bloquer le processeur tant que le 8202 est indisponible. Le signal XACK indique que l'accès mémoire précédent est terminé et que : soit la donnée lue est présente sur le bus de données, soit que l'écriture s'est terminée. : Le signal SACK est très utile sur les configurations multiprocesseurs. Un processeur peut démarrer un accès mémoire, le signal SACK indiquera au second processeur qu'il doit attendre que l'accès soit terminé pour que ce soit son tour. ==Les contrôleurs de DRAM asynchrones évolués== L'Intel 8202 était un contrôleur mémoire basique, comme beaucoup d'autres à cette époque. Mais Intel a vendu des contrôleurs mémoires plus complexes. Par exemple, l'Intel 8207 était un contrôleur mémoire bien plus avancé que les deux précédents. Passons sur certains détails, comme le fait qu'il pouvait gérer jusqu'à 256 kibioctets de DRAM. Au-delà de ça, il y avait des fonctionnalités bien plus intéressantes, à savoir : un support de l'ECC, il était double port, il permettait de simuler une DRAM synchrone à partir d'une DRAM asynchrone. Il n'était pas le seul dans ce cas et des contrôleurs de DRAM très évolués étaient capables de faire des merveilles. Voyons comment cela était possible. ===Les contrôleurs mémoire synchrone=== Il est parfaitement possible d'utiliser un contrôleur mémoire synchrone avec une DRAM asynchrone. A vrai dire, le contrôleur DRAM peut complétement simuler une mémoire synchrone alors que la DRAM associée est asynchrone. La traduction asynchrone vers synchrone se fait en ajoutant des registres sur le bus mémoire, notamment sur le bus de données et le bus d'adresse/commande. Nous avions détaillé cela dans le chapitre sur les SRAM, c'est la même chose avec une mémoire DRAM. Sauf que cette fois-ci, le contrôleur mémoire doit aussi être prévu pour. [[File:Controleur mémoire synchrone utilisé avec une DRAM asynchrone.png|centre|vignette|upright=2|Contrôleur mémoire synchrone utilisé avec une DRAM asynchrone]] Les deux-trois registres illustrés plus haut peuvent être intégrés directement dans le contrôleur mémoire, que ce soit totalement ou en partie. Le strict minimum pour avoir un contrôleur mémoire synchrone est que celui-ci doit mémoriser adresse et commandes dans un registre. Par exemple, le 8207 d'Intel était capable de mémoriser les requêtes processeurs dans un registre interne, mais il fallait utiliser deux registres séparés pour le bus de données. Les deux registres étaient alors commandés par le contrôleur mémoire. Il est cependant possible d'aller plus loin et d'intégrer les registres du bus de données dans le contrôleur mémoire. [[File:Controleur mémoire DRAM synchrone.png|centre|vignette|upright=2|Contrôleur mémoire DRAM synchrone.]] : Il faut noter que cette fonctionnalité est parfois disponible sur les SRAM. En clair, on peut associer une SRAM asynchrone avec un contrôleur de SRAM synchrone. Le contrôleur de SRAM se charge alors de simuler une SRAM synchrone à partir de la SRAM asynchrone. Utiliser un contrôleur mémoire synchrone a de nombreux avantages, l'un d'entre eux étant lié aux ''wait state''. Quand le processeur envoie une requête de lecture/écriture à la mémoire RAM, celle-ci met plusieurs cycles d'horloge à répondre. Et pendant ce temps, le processeur... attend. Et pendant ce temps d'attente, il doit maintenir l'adresse mémoire sur le bus d'adresse. Les cycles d'horloge perdus à attendre la mémoire RAM étaient appelés des '''''Wait states'''''. Utiliser un contrôleur mémoire synchrone d'éliminer les ''wait state'', dans une certaine mesure. Avec un contrôleur mémoire synchrone, le processeur envoie l'adresse, mais c'est le contrôleur mémoire qui la maintient sur le bus d'adresse. Le processeur peut envoyer l'adresse et la donnée à écrire, elles sont recopiées dans les registres, et le controleur mémoire y a accès sans que le processeur doive les maintenir. Le processeur peut se déconnecter du bus mémoire et faire du travail dans son coin pendant que le contrôleur mémoire accède à la DRAM. Les ''wait state'' disparaissent alors, du moins du point de vue du processeur. ===La gestion de l'ECC=== L''''ECC''' peut être géré dans le contrôleur mémoire. Pour cela, on couple les registres mentionnés dans la section précédente, avec un circuit de détection et de correction d'erreur. Le circuit d'ECC peut, comme les registres synchrones, être intégré dans le contrôleur mémoire, ou au contraire être situé dans un circuit séparé. Si le circuit d'ECC est séparé du contrôleur mémoire, il communique avec lui, histoire que le contrôleur mémoire puisse signaler toute erreur de parité ou d'ECC au processeur. [[File:Controleur mémoire synchrone avec ECC intégré.png|centre|vignette|upright=2|Controleur mémoire synchrone avec ECC intégré]] Reprenons l'exemple du 8207 d'Intel. Le contrôleur mémoire 8207 gère le bus d'adresse et de commande, mais n'a pas de connexions directes avec le bus de données. Il ne peut donc pas prendre en charge l'ECC. Il avait besoin d'être couplé avec un circuit d'ECC séparé, relié au bus de données : l'Intel 8206. Le 8026 prenait en entrée : 16 bits de données et 8 bits d'ECC. Il fournissait en sortie 16 bits de données après correction d'erreur, les 8 bits d'ECC pour indiquer qu'une erreur a été détectée mais pas corrigée, ainsi que des bits de parité. Le 8206 détectait/corrigeait les erreurs et générait les bits d'ECC, mais il communiquait avec le contrôleur mémoire pour cela. [[File:8207 avec ECC.png|centre|vignette|upright=2|8207 avec ECC]] La détection/correction d'erreur était appliquée à la fois pour les accès mémoire et pour les rafraichissements mémoire. Lors d'un rafraichissement mémoire, la donnée rafraichie est lue et réécrite. Avec l'ECC activé et configuré correctement, le rafraichissement passe par le bus de données. Au lieu d'avoir un cycle de lecture-écriture interne à la DRAM, on a un cycle de lecture-correction-écriture qui utilise le 8206. La donnée lue est envoyée sur le bus de données, puis le 8206 corrige une éventuelle erreur, et la donnée corrigée est alors réécrite en mémoire. Au passage, si une erreur non-correctible est détectée, le 8206 ne fait rien, l'erreur est ignorée. La gestion de l'erreur sera retardée jusqu'à une lecture ultérieure. Et encore : si lecture ultérieure il y a. Si la donnée est écrasée par une écriture, la donnée corrompue sera simplement écrasée et disparaitra sans avoir pu faire le moindre dégât. Mais pour cela, le 8206 doit communiquer avec le contrôleur mémoire, pour savoir s'il est dans un cycle de rafraichissement ou un accès mémoire normal. Il prévient le 8207 lors d'une erreur, et c'est ce dernier qui décide si l'erreur doit être prise en compte ou ignorée. C'est seulement lors d'un accès mémoire normal que le processeur est prévenu qu'une erreur de parité/autre a eu lieu. ===Les contrôleurs mémoires multiports=== Les '''contrôleur mémoire multiport''' disposent de plusieurs ports, chacun permettant de traiter un accès mémoire. Ils peuvent simuler une mémoire multiport à partir d'une DRAM monoport. Évidemment, la simulation n'est pas parfaite. Des accès mémoire simultanés, envoyés en même temps sur différents ports, sont en réalité exécutés un par un, pas en même temps. Il y a donc une petite pénalité en termes de performances, mais elle est mineure. Encore une fois, nous allons reprendre l'exemple du 8207. Il avait deux ports séparés, et était prévu pour fonctionner dans un système à deux processeurs. L'usage de deux ports séparés permettait de partager une unique mémoire DRAM entre deux processeurs. Le partage se faisait en interfaçant deux processeurs sur le contrôleur mémoire, chacun étant connecté à un port. Lors d'une lecture, il redirigeait la donnée lue vers le bon processeur, en configurant le bus de données correctement. Le contrôleur mémoire recevait des requêtes mémoire de deux processeurs, mais il les exécutait une à la fois. S'il recevait deux requêtes en même temps, l'une d'entre elles était mise en attente. Le contrôleur mémoire doit arbitrer les accès à la mémoire, et faire en sorte que les deux processeurs aient accès à la mémoire à tour de rôle. Et non seulement il doit arbitrer les deux ports, mais il y a aussi un troisième port interne au contrôleur mémoire : le rafraichissement mémoire ! Pour cela, le circuit d'arbitrage qui choisissait entre rafraichissement mémoire et accès mémoire, est amélioré de manière à gérer un second port. Le circuit d'arbitrage donne l'accès au séquenceur mémoire à un port sélectionné. L'arbitrage était configurable, avec deux options : soit le port A est privilégié sur le port B, soit le port le plus récemment accédé à la priorité. Les deux ports pouvaient être configurés pour fonctionner soit de manière asynchrone, soit de manière synchrone. Il était aussi possible de configurer l'ECC, des options liées à la fréquence du processeur et de la RAM, ainsi que de nombreuses options liées au rafraichissement. Pour cela, le 8207 contenait un registre de configuration interne, programmable en fournissant les entrées adéquates. Tout ce qui vient d'être dit se généralise avec plus de deux processeurs. Le 8207 ne permettait pas ça, mais les contrôleurs mémoire des PC modernes en sont capables. Ils peuvent gérer plusieurs dizaines de processeurs facilement. ==Le contrôleur mémoire d'une DRAM ''Fast Page Mode''== Les mémoires DRAM classiques sont des mémoires à tampon de ligne, mais qui sont assez mal utilisées. Notamment, tout accès mémoire se fait en deux phases : un accès pour sélectionner la ligne, un autre pour sélectionner la colonne. Les mémoires ''Fast Page Mode'' permettent d'optimiser le tout. Elles permettent de faire plusieurs accès successifs à la même ligne, à des colonnes différentes. Et le contrôleur mémoire doit être adapté pour cela. [[File:Sélection d'une ligne sur une mémoire FPM ou EDO.png|centre|vignette|upright=2|Sélection d'une ligne sur une mémoire FPM ou EDO.]] Le contrôleur mémoire doit détecter que deux accès mémoire consécutifs se font dans la même ligne. Si deux accès consécutifs accèdent à la même ligne, on doit juste changer de colonne et altérer le signal CAS. C'est un ''succès de tampon de ligne'', aussi appelé un '''succès de page'''. Si deux accès consécutifs accèdent à une ligne différente, c'est un ''défaut de tampon de ligne'', aussi appelé un '''défaut de page'''. Il faut alors changer de ligne, en altérant les signaux RAS et en envoyant une adresse de ligne. Pour détecter les succès ou défauts de page, il faut ajouter un circuit spécialisé dans le contrôleur mémoire. Il mémorise la ligne ouverte, et plus précisément son adresse de ligne (numéro de banque inclut). A chaque requête processeur, il compare l'adresse de ligne recue avec celle déjà ouverte. C'est un succès si les deux sont égales, un défaut si elles sont différentes. Le circuit qui fait cette comparaison est appelé le '''décodeur de commande'''. Il prévient le séquenceur mémoire en cas de succès de page, grâce à un signal de un bit, qui vaut 0 en cas de défaut de page et 1 en cas de succès. Le séquenceur mémoire décide alors comment gérer les signaux RAS et CAS, ainsi que l'envoi des adresses de ligne/colonne. [[File:Controleur mémoire d'une FPM-DRAM.png|centre|vignette|upright=2|Controleur mémoire d'une FPM-DRAM]] ==Le contrôleur mémoire d'une SDRAM ou d'une DDR== l'intérieur d'un contrôleur de SDRAM ne change pas significativement d'un controleur de RAM asynchrone. Il regroupe toujours un séquenceur mémoire et une interface physique, un circuit pour le rafraichissement mémoire et un circuit d'arbitrage. Par contre, ses sorties changent pas mal. Contrairement aux mémoires DRAM basiques, les mémoires SDRAM sont cadencées par un signal d'horloge. Et ce signal d'horloge vient bien de quelque part. Pour cela, deux solutions : soit le contrôleur mémoire génère la fréquence qui commande la mémoire, soit il prend en entrée une fréquence de base qu'il multiplie pour obtenir la fréquence désirée. Les deux solutions sont équivalentes, si ce n'est que les circuits impliqués ne sont pas les mêmes. Dans le premier cas, le contrôleur doit embarquer un circuit oscillateur, qui génère la fréquence demandée. Dans l'autre cas, un simple multiplieur/diviseur de fréquence suffit et c'est généralement une PLL qui est utilisée pour cela. : Notez qu'il ne faut pas confondre la fréquence de la SDRAM et celle du contrôleur mémoire. Le contrôleur mémoire fonctionne à une vitesse assez élevée, en interne. Le port relié au processeur fonctionne à haute fréquence, généralement la même que celle du processeur. A vrai dire, de nos jours, il est intégré dans le processeur. Pour le décodage d'adresse, tout est plus simple sur les SDRAM, DDR inclues. Les chips de mémoire SDRAM et DDR disposent d'une entrée ''Chip Select'', ce qui facilite grandement le décodage d'adresse. Les bits de ''Chip Select'' sont générés par le contrôleur mémoire, et sont transmis sur le bus de commande. Le contrôleur de DRAM peut adresser un certain nombre de rangées, dispersés sur une ou plusieurs barrettes. La limite maximale dépend du contrôleur de DRAM, elle est souvent proche de 8 ou 16 rangées. Si on combine plusieurs barrettes de mémoire, il est possible de dépasser cette limite. Par exemple, pour un contrôleur de DRAM supportant maximum 8 rangées, 4 barrettes de 4 rangées chacune dépassent la limite. ===Le séquenceurs mémoire pour les SDRAM/DDR=== Le séquenceur mémoire existe toujours pour les mémoires SDRAM, c'est toujours un circuit séquentiel qui implémente une machine à état. Il traduit toujours une requête processeur en une séquence de commandes envoyées à des timings bien précis. Les commandes mémoires peuvent provenir de l'extérieur, mais aussi d'un circuit de rafraichissement intégré dans le contrôleur mémoire, comme pour les autres DRAM. La seule différence est que la machine à état est plus complexe. Pour rappel, une requête de lecture/écriture se fait en trois étapes : une commande PRECHARGE pour précharger le tampon de ligne, une commande ACT qui fixe l'adresse de ligne, et enfin une commande READ/WRITE avec l'adresse de colonne. Et il faut tenir compte des timings mémoire, à savoir le fait que ces commandes sont séparées par des temps d'attentes bien précis. Par exemple, je prends des chiffres arbitraires : il faut attendre 2 cycles entre une commande ACT et une commande READ, 6 cycles avant deux commandes WRITE consécutives, etc. La gestion des ''timings'' rend la conception du séquenceur plus complexe. Il faut aussi tenir compte des commandes SDRAM anticipées, à savoir que l'on peut envoyer des commandes avant que la précédente soit terminée. Et cela complexifie le séquenceur. Le séquenceur peut profiter du fait que des accès simultanés peuvent se faire de préférence se faire dans des banques différentes. Prenons l'exemple d'une mémoire partagée entre plusieurs processeurs. Les deux processeurs ou cœurs exécutent deux programmes différents : le premier processeur exécute le navigateur web, alors que le second lit de la musique. Dans ce cas, les deux processeurs accèdent à des données très éloignées en mémoire (ce n'est pas toujours le cas, mais ça l'est dans cet exemple), qui sont potentiellement dans des banques séparées. Par contre, il y a une contrainte majeure : deux accès simultanés ne peuvent pas accéder au bus en même temps. Je rappelle que quand on démarre une lecture, il se passe quelques cycles d'horloge avant que la donnée atterrissent sur le bus de données. Le temps en question est fixe, mais sa durée n'est pas la même selon que la lecture fasse un succès de page ou un défaut de page. Le séquence mémoire doit en tenir compte avant de démarrer une lecture ou une écriture. Le séquenceur doit donc détecter les succès de page, et déterminer si le bus de donnée sera libre au moment où la lecture terminera. Si c'est le cas, il peut lancer a commande. Sinon, il doit la retarder. {|class="wikitable" |+ Exemple où deux processeurs accèdent à une SDRAM en même temps |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 |- ! rowspan="2" | Requêtes mémoires | bgcolor="#A0FFFF" | CPU 1 || || || bgcolor="#A0FFFF" | CPU 1 || || || || || || |- | bgcolor="#FFA0FF" | CPU 2 || || || || || bgcolor="#FFA0FF" | CPU 2 || || || || |- |- ! Bus de commande/adresse | || bgcolor="#A0FFFF" | Lecture (CPU 1) || bgcolor="#FFA0FF" | Lecture (CPU 2) || || bgcolor="#A0FFFF" | Lecture (CPU 1) || || bgcolor="#FFA0FF" | Lecture (CPU 2) || || |- ! Bus de données | || || || bgcolor="#A0FFFF" | Donnée lue CPU 1 || bgcolor="#FFA0FF" | Donnée lue CPU 2 || || bgcolor="#A0FFFF" | Donnée lue CPU 1 || || bgcolor="#FFA0FF" | Donnée lue CPU 2 |} ===La politique de gestion du tampon de ligne=== Plus haut, nous avons parlé des mémoires FPM, qui ont introduit quelques optimisations pour utiliser au mieux le tampon de ligne. Il se trouve que les SDRAM conservent ces optimisations. Il y a toujours cette notion de succès de page et de défaut de page, suivant que deux accès se font dans la même ligne ou dans deux lignes différentes. Du moins, c'est le cas si le séquenceur mémoire est coopératif. En effet, il peut fonctionner de plusieurs manières, dont les plus extrêmes sont appelés la politique de la page fermée et la politique de la page ouverte. Voyons à quoi elles correspondent. Avec la '''politique de la page fermée''', chaque accès mémoire est suivi d'une commande PRECHARGE, qui ferme la ligne courante et précharge les lignes de bits. Même si deux accès consécutifs se font dans la même ligne, la ligne est fermée et ré-ouverte entre deux accès mémoire. En clair : l'optimisation introduite par les mémoires FPM est désactivée, le contrôleur mémoire fait exprès de ne pas en profiter. On appelle cette méthode la close ''page autoprecharge''. Cette méthode réduit grandement les performances pour les accès à des adresses consécutives, mais fonctionne à merveille si les accès sont "aléatoires", à savoir qu'ils se font sans régularités évidentes. Son seul avantage est que l'implémentation du séquenceur mémoire est très simple. En effet, le séquenceur mémoire se passe complétement de la table des banques, du comparateur de ligne, et de tous les circuits nécessaires pour vérifier les succès ou défauts de page. De plus, le séquenceur mémoire profite grandement des commandes READA et WRITEA, qui fusionnent une commande READ/WRITE avec une commande PRECHARGE. Le séquenceur mémoire a juste à envoyer des commandes ACT, READA, WRITEA et PREFETCH à la mémoire, pas besoin des commandes PRECHARGE, READ ou WRITE. A l'opposé, la '''politique de la page ouverte''' ne ferme pas automatiquement la ligne. Elle la laisse ouverte, en espérant que le prochain accès mémoire se fasse dans cette ligne. Lorsqu'un nouvel accès mémoire arrive, elle doit détecter les succès ou défauts de page et agir en fonction. En cas de défaut de page, la ligne est fermée, le séquenceur mémoire envoie une commande PRECHARGE, puis l'accès suivant effectue les deux commandes ACT + READ ou WRITE. En cas de succès de page, les commandes PRECHARGE et ACT ne sont pas envoyées, seules la commande READ ou WRITE l'est. Un désavantage est que le contrôleur mémoire doit inclure une table des banques et un comparateur, comme vu plus haut dans la section sur les mémoires FPM. un autre défaut est que garder une ligne ouverte consomme beaucoup d'énergie, comparé à un simple état de PRECHARGE. En conséquence, il est préférable de fermer les lignes dès que possible. Par contre, les performances sont d'autant meilleures que les accès mémoire consécutifs à une même ligne soient assez fréquents. Si les accès mémoire sont aléatoires, les performances sont moins bonnes. La politique de la page fermée fermait les lignes en avance, avec des commandes READA ou WRITEA, avant même que l'accès suivant démarre. Avec la politique de la page ouverte, on doit attendre pour détecter un défaut de page, puis fermer la ligne avec une commande PRECHARGE séparée. La ligne est donc fermée avec un peu temps de retard, et envoyer deux commandes au lieu d'une prend plus de temps. Les contrôleurs mémoires basiques utilisent une des deux solutions précédentes. Soit la page est toujours fermée, soit elle est toujours laissée ouverte jusqu'à ce qu'un accès mémoire la referme. Mais les contrôleurs plus évolués tentent de prédire s'il faut fermer ou laisser ouvertes les pages ouvertes. La méthode la plus simple attend un temps prédéterminé avant de fermer la ligne. Une autre solution regarde le tout dernier accès. On peut très bien décider de laisser la ligne ouverte si l'accès mémoire précédent était une rafale, et fermer sinon. Une solution plus complexe mémorise les N derniers accès et en déduit s'il faut fermer ou non la prochaine ligne. On peut mémoriser si l'accès en question a causé la fermeture d'une ligne avec un bit. Mémoriser les N derniers accès demande d'utiliser un simple registre à décalage. Pour chaque valeur de ce registre, il faut prédire si le prochain accès demandera une ouverture ou une fermeture. Une solution simple fait la moyenne des bits à 1 dans ce registre : si plus de la moitié des bits est à 1, on laisse la ligne ouverte et on ferme sinon. Pour améliorer un petit peu l'algorithme, on peut faire en sorte que les bits des accès mémoires les plus récents aient plus de poids dans le calcul de la moyenne. Il existe sans doute d'autres solutions plus évoluées, mais il est difficile de savoir ce qu'il y a dans les contrôleurs de SDRAM modernes. : Le fait de laisser ouverte une ligne ou au contraire de la fermer systématiquement, se fait pour chaque banque. ===Les banques et l'entrelacement mémoire des SDRAM=== Les SDRAM incorporent plusieurs banques à l'intérieur de leurs circuits. Pour rappel, une banque est une sous-mémoire, avec ses propres décodeurs, son tampon de ligne, ses multiplexeurs de colonnes, etc. Pour le dire autrement, une SDRAM regroupe plusieurs mémoires séparées dans un même circuit intégré, les mémoires en question étant des banques. [[File:Arrangement vertical.jpg|centre|vignette|upright=2.5|Mémoire multi-banques.]] La présence de plusieurs banques impacte la gestion des lignes ouvertes/fermées. En effet, chaque banque a son propre tampon de ligne, ce qui fait que la gestion des lignes se fait indépendamment dans chaque banque. Le séquenceur mémoire doit donc se souvenir des lignes actives dans chaque banque. Pour cela, il mémorise ces lignes dans une petite mémoire : la '''table des banques''', aussi appelée ''bank status memory''. Pour détecter un succès ou un défaut, le contrôleur doit extraire la ligne de l'adresse, mais aussi le numéro de banque. Il envoie alors le numéro de banque à la table des banques, sur son entrée d'adresse. Il récupère alors le numéro de la ligne active sur les sorties de données. Il compare alors ce numéro de ligne avec le numéro de ligne de l'adresse envoyée par le processeur. C'est un succès si les deux sont égales, un défaut sinon. [[File:Controleur mémoire FPM avec plusieurs banques.jpg|centre|vignette|upright=2|Contrôleur mémoire FPM avec plusieurs banques.]] <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les mémoires RAM dynamiques (DRAM) | prevText=Les mémoires RAM dynamiques (DRAM) | next=Les mémoires associatives | nextText=Les mémoires associatives }} </noinclude> n2pnlvbm0xnmzns3jp4zg21rdsmm9dt 764682 764679 2026-04-23T19:02:13Z Mewtow 31375 /* Le séquenceurs mémoire pour les SDRAM/DDR */ 764682 wikitext text/x-wiki Les mémoires ROM ou SRAM ont généralement une interface simple, à laquelle le processeur peut s'interfacer directement. Mais pour les DRAM, ce n'est pas le cas. Les DRAM utilisent un bus d'adresse multiplexé, où l'adresse est envoyée en deux fois. Connecter le processeur directement sur une DRAM n'est pas pratique : le bus d'adresse du processeur et celui de la mémoire ne collent pas. Les DRAM doivent aussi être rafraichies régulièrement. Le rafraichissement mémoire peut être délégué au processeur, mais c'est loin d'être idéal. Et il y a bien d'autres raisons qui font que le processeur ne peut pas s'interfacer facilement avec les mémoires DRAM. Pour gérer ces problèmes, les mémoires DRAM ne sont pas connectées directement au processeur. À la place, on ajoute un intermédiaire entre le processeur et la mémoire : le '''contrôleur mémoire externe'''. Son but est de montrer au processeur une interface simple, semblable à celle d'une SRAM classique, alors qu'il commande une mémoire DRAM à l'interface plus complexe. Il est placé sur la carte mère ou dans le processeur, et ne doit pas être confondu avec le contrôleur mémoire intégré dans la mémoire. Ce chapitre va expliquer quels sont les rôles du contrôleur mémoire, son interface et ce qu'il y a à l'intérieur. Dans ce chapitre, quand nous parlerons de ''contrôleur mémoire'', cela fera systématiquement référence au contrôleur mémoire externe. Et avant de poursuivre, sachez qu'il est difficile de faire des généralités sur les contrôleurs mémoire, car les mémoires DRAM elles-mêmes sont assez différentes les unes des autres. Entre une mémoire EDO, une mémoire SDR, une mémoire DDR et une DRAM asynchrone, les contrôleurs mémoires seront fortement différents. Aussi, il y a aura une différence entre un contrôleur pour une DRAM asynchrone et un contrôleur pour une mémoire EDO, une mémoire SDRAM, etc. J'ai choisit de vous séparer les contrôleurs mémoire pour les DRAM asynchrones de ceux pour les SDRAM/DRR. ==Le contrôleur d'une DRAM asynchrone== Les premières DRAM asynchrones avaient des contrôleurs mémoires dédiés, qui étaient séparés du processeur et du ''chipset'' de la carte mère. Par exemple, les composants Intel 8202, Intel 8203 et Intel 8207 étaient des contrôleurs mémoire pour DRAM asynchrones qui étaient vendus dans des boitiers DIP et étaient soudés sur la carte mère. Par la suite, ils ont été intégrés au ''chipset'' de la carte mère pendant les décennies 90-2000. Après les années 2000, ils ont été intégrés dans les processeurs. Leurs fonctions étaient le multiplexage de l'adresse et le rafraichissement mémoire. Ils recevaient une adresse mémoire complète, qu'ils découpaient une adresse de ligne et une adresse de colonne, envoyées l'une après l'autre sur le bus mémoire. Pour le rafraichissement mémoire, ils rafraichissaient la DRAM régulièrement, de manière automatique, entre deux accès mémoire normaux. Le processeur n'avait ainsi plus à rafraichir la mémoire lui-même, cette fonction était déléguée au contrôleur de DRAM. Ils étaient connectés au bus d'adresse et de commande, avec éventuellement des relations indirectes avec le bus de données. ===L'interface d'un contrôleur de DRAM asynchrone=== L'interface du contrôleur mémoire décrit ses broches d'entrées/sorties et leur signification. Elle est généralement très simple et contient deux ports : un connecté au processeur, un autre connecté à la DRAM. Cela trahit d'ailleurs son rôle principal, qui est de transformer les requêtes de lecture/écriture provenant du processeur en une suite de commandes acceptée par la mémoire. Le port connecté à la DRAM est connecté ua bus d'adresse et au bus de commande. Le bus de données est lui relié au processeur et/ou au bus système. Un accès mémoire provenant du processeur contient une adresse à lire/écrire, le bit R/W qui indique s'il faut faire une lecture ou une écriture, et éventuellement une donnée à écrire. Mais, nous avons vu que les accès mémoires sur une DRAM sont multiplexés : on envoie l'adresse en deux fois : la ligne d'abord, puis la colonne. De plus, il faut générer les signaux RAS, CAS et bien d'autres. Le tout est illustré ci-dessous. [[File:Contrôleur mémoire.png|centre|vignette|upright=2|Contrôleur mémoire externe.]] Un point important est que les DRAM asynchrones n'ont pas d'entrée ''Chip Select'' ou d'entrée ''Output Enable''. Les signaux RAS et CAS remplacent en quelque sorte ces deux signaux. Le bit RAS fait office de ''Chip Select'', le bit CAS fait office d'''Output Enable''. Pour certains contrôleurs de DRAM, il faut ajouter l''''interface électrique''', qui traduit les signaux du processeur en signaux compatibles avec la mémoire. Il est en effet très fréquent que la mémoire et le processeur n'utilisent pas les mêmes tensions pour coder un bit, ce qui fait qu'elles ne sont pas compatibles. Dans ce cas, le contrôleur mémoire fait la conversion. Le contrôleur DRAM peut fonctionner en mode synchrone ou asynchrone, du point de vue du processeur. Quand il fonctionne en mode synchrone, il permet d'interfacer un processeur synchrone avec une mémoire DRAM asynchrone. Un point important est que le contrôleur DRAM sert d'intermédiaire entre une mémoire DRAM et "le reste du monde". Il ne fait pas forcément office d'intermédiaire entre DRAM et processeur, mais peut aussi faire l'intermédiaire entre la DRAM et un bus système, entre une DRAM et le ''Video Display Controler'' d'une carte graphique, ou n'importe quel autre composant électronique qui utilise cette DRAM. ===Le générateur de ''timings'' et la traduction d'adresse=== Le contrôleur mémoire doit traduire les adresses du processeur en adresses compatibles avec la mémoire. Et la traduction est assez variable, suivant que le bus mémoire est un bus normal, un bus multiplexé, ou partiellement multiplexé. Nous avons vu ces trois types de bus mémoire dans le chapitre sur l'interface des mémoires, mais nous ferons quelques rappels rapides. Avec un ''bus totalement multiplexé'', le bus d'adresse et le bus de données sont fusionnés. Dans ce cas, on peut envoyer soit une adresse, soit lire/écrire une donnée sur le bus, mais on ne peut pas faire les deux en même temps. Un bit ALE indique si le bus est utilisé en tant que bus d'adresse ou bus de données. Le contrôleur mémoire gère cette situation, en fixant le bit ALE et en envoyant séparément adresse et donnée pour les écritures. [[File:Bus multiplexé avec bit ALE.png|centre|vignette|upright=2|Bus multiplexé avec bit ALE.]] Avec un ''bus d'adresse multiplexé'', l'adresse est découpée en une adresse de ligne et une adresse de colonne, envoyées l'une après l'autre. Le contrôleur mémoire prend en entrée une adresse mémoire complète, la découpe en deux, et envoie chaque morceau au bon moment. Pour cela, il suffit d'un registre pour mémoriser l'adresse et d'un multiplexeur. Le multiplexeur choisit soit les bits de poids fort de l'adresse, soit ceux de poids faible. Les premiers correspondent à l'adresse de ligne, les autres à l'adresse de colonne. La commande du multiplexeur est le fait d'un petit circuit séquentiel, qui génère aussi les signaux CAS et RAS. Au premier cycle, il met le signal RAS à 1, met le CAS à 0, et configure le MUX pour sélectionner les bits de poids fort. Au second cycle, il génère un signal CAS à 1, met le RAS à 0 et configure le MUX pour sélectionner les bits de poids faible. Le circuit en question est appelé le générateur de ''timings''. [[File:Controleur de DRAM simple, sans rafraichissement mémoire.png|centre|vignette|upright=2|Contrôleur de DRAM simple, sans rafraichissement mémoire.]] Le générateur de ''timings'' est un circuit séquentiel qui implémente une petite machine à état. Il est très simple sur une mémoire DRAM asynchrone basique, mais il est plus complexe sur les mémoires FPM, EDO, quartet, et autres. Le regroupement des multiplexeurs d'adresse et du générateur de ''timings'' est appelé le '''séquenceur mémoire'''. C'est le séquenceur mémoire qui traduit la requête processeur en commande DRAM, le reste du contrôleur est dédié au rafraichissement mémoire ou à d'autres fonctions facultatives. ===Le rafraichissement mémoire=== La gestion du rafraichissement mémoire est la fonction principale du contrôleur DRAM. Pour gérer le rafraichissement mémoire, le contrôleur mémoire intègre deux compteurs, un pour gérer l'adresse à rafraichir, l'autre pour gérer l'intervalle de temps entre deux rafraichissements. Le rafraichissement se fait à intervalle régulier, tous les x microsecondes. Pour déclencher le rafraichissement au bon moment, le contrôleur mémoire contient un ''Refresh Timer'', aussi appelé le '''compteur de rafraichissement'''. Il est initialisé avec le temps entre deux rafraichissements, une adresse est rafraichie quand ce compteur atteint 0. Le rafraichissement mémoire balaye la mémoire adresse par adresse. Pour savoir à quelle adresse il en est rendu, le contrôleur mémoire utilise un '''compteur d'adresse'''. Il contient la prochaine adresse à rafraichir, aussi appelée l'adresse de rafraichissement. Régulièrement, l'adresse dans ce compteur est envoyée à la RAM, pour une lecture. Mais la donnée lue n'est pas envoyée sur le bus de donnée, soit parce que la RAM est prévue pour, soit parce que le contrôleur désactive son bit ''output enable''. Dans le second cas, la RAM fait la lecture en interne, mais se déconnecte du bus de donnée, perdant la donnée lue dans le néant. Pour envoyer l'adresse de rafraichissement sur le bus d'adresse, il faut rajouter un multiplexeur, qui choisit entre l'adresse normale et l'adresse de rafraichissement. [[File:Controleur de DRAM avec rafraichissement mémoire.png|centre|vignette|upright=2|Controleur de DRAM avec rafraichissement mémoire.]] Le multiplexeur ne doit cependant pas être configuré si une adresse est déjà en cours de transfert. Pour cela, un circuit d'arbitrage se débrouille pour éviter qu'un accès mémoire soit interrompu par une demande de rafraichissement et inversement. Il peut être inclus dans le séquenceur mémoire ou séparé de celui-ci. [[File:Controleur mémoire, intérieur simplifié.png|centre|vignette|upright=2.5|Contrôleur mémoire, intérieur simplifié.]] Il faut noter que le rafraichissement mémoire peut être délégué non pas au contrôleur mémoire, mais au processeur où à la DRAM elle-même. Quand elle est le fait du processeur, celui-ci incorpore un ''refresh timer'' et un compteur d'adresse. Un exemple est celui du processeur Zilog Z80, qui implémentait des compteurs internes pour gérer le rafraichissement mémoire. On peut considérer que le processeur incorpore alors le contrôleur mémoire, au moins partiellement. Il est aussi possible de déléguer le rafraichissement au logiciel (certains jeux vidéos Amiga ou Commodore faisaient cela pour la mémoire vidéo). Quand la DRAM elle-même s'occupe de son propre rafraichissement, c'est elle qui intègre un ''refresh timer'' et le compteur d'adresse. ===Le décodage d'adresse=== Le contrôleur mémoire gère aussi le '''décodage d'adresse'''. pour rappel, celui-ci est utilisé quand on combine plusieurs chips mémoire ensemble, pour combiner leurs capacités mémoire. Par exemple, on peut combiner 4 chips mémoires de 1 mébioctet chacun, pour que le processeur voit comme 4 mébioctets de RAM unique. Le premier mébioctet est placé dans le premier chip mémoire, le second mébioctet dans le second chip, etc. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Pour cela, on active le chip mémoire adéquat, en fonction de l'adresse à consulter. Les autres chips mémoire sont désactivés pendant l'accès mémoire. En théorie, activer ou désactiver un chip mémoire se fait en utilisant son entrée ''Chip Select''. Il faut noter que si les SDRAM disposent bien d'un signal ''Chip Select'', ce n'est pas le cas des mémoires RAM asynchrones. A la place, ce sont les signaux RAS qui font office de ''Chip Select''. Une RAM asynchrone est activée quand son signal RAS lui demande de lire une ligne, elle est désactivée sinon. Mais c'est un détail. Toujours est-il que les signaux ''Chip Select'', ou leurs équivalents, sont générés par le contrôleur de DRAM, à partir des bits de poids fort de l'adresse. Par exemple, avec 4 chips mémoire, les deux bits de poids fort de l'adresse sont utilisés pour sélectionner le chip mémoire adéquat. Le contrôleur mémoire doit avoir plusieurs sorties ''Chip Select'', une par chip mémoire. Et le nombre de ces sorties limite le nombre de chips mémoire qu'on peut combiner. Par exemple, s'il y a seulement 4 sorties ''Chip Select'', on ne pourra brancher que 4 chips mémoire dessus. Sauf à ruser, avec un arrangement horizontal, mais cela n'est pas le ressort du contrôleur mémoire. [[File:Td6bfig3.png|centre|vignette|upright=2|Comparaison entre arrangement horizontal (à gauche) et arrangement vertical (à droite).]] ===Exemple : l'Intel 8202-8203=== L'Intel 8202 et le 8203 étaient des contrôleurs de mémoire DRAM, parmi les plus simples qui soient. Ils avaient une entrée d'adresse de 12 bits, ce qui permettait d'adresser 4 kibioctets de RAM. Ils fournissaient en sortie une adresse multiplexée sur 6 bits, envoyée en deux fois. Ils avaient donc 12 entrées d'adresse, 6 sorties d'adresse, un signal RAS, un signal CAS. Les adresses présentées en entrées n'étaient pas mémorisées dans des registres, ce qui fait qu'elles devaient être maintenues durant toute la durée de l'accès mémoire. Le processeur ne pouvait donc pas se déconnecter du bus d'adresse pendant l'accès mémoire, peu importe sa durée. Le 8202 contenait aussi un compteur de rafraichissement. Rappelons que sur les DRAM asynchrones, le rafraichissement se fait ligne par ligne. Le contrôleur mémoire a juste à présenter l'adresse de ligne, il n'a pas à envoyer l'adresse de colonne. La commande de rafraichissement se fait en mettant le signal RAS à 0, mais en laissant le CAS à 1 (je rappelle que les signaux sont actifs à 0). Le compteur d'adresse de rafraichissement a donc juste à mémoriser l'adresse de ligne. Le séquenceur mémoire était précédé par un circuit d'arbitrage, non-représenté dans le schéma ci-dessous. La microarchitecture de l'Intel 8202 est la suivante : [[File:Microarchitecture de l'Intel 8202.png|centre|vignette|upright=2|Microarchitecture de l'Intel 8202.]] Le 8202 avait une entrée pour un signal d'horloge, ainsi qu'un ''Chip Select'' un peu particulier. Si le signal CS passait à 0 lors d'un accès mémoire, le 8202/8203 ne se désactivait qu'une fois l'accès mémoire terminé. On ne pouvait pas l'interrompre pendant un accès mémoire, même en changeant le bit CS. Le signal d'horloge était utilisé pour commander le ''refresh timer''. Pour commander les lectures et écriture, il recevait en entrée un bit ''Write Request'' et un bit ''Read Request'', qui demandent respectivement une écriture et une lecture. En sortie, on trouvait un unique bit R/W qui valait 0 pour une lecture et 1 pour une écriture. Il avait aussi un bit d'entrée pour forcer le rafraichissement mémoire. S'il est à 1, la mémoire rafraichie l'adresse envoyée par le processeur. Le 8202 pouvait être connecté sur 1 à 4 chips mémoire, ce qui permettait d'adresser au maximum 16 kibioctets de RAM. Les 4 chips ne sont pas accédés en parallèle, un seul l'est à chaque fois. Pour le décodage d'adresse, le 8202 dispose de deux bits BO et B1 pour sélectionner le chip adéquat, ainsi que 4 sorties RAS pour activer la banque adéquate. On rappelle que le signal RAS remplace le signal ''Chip Select''. C'est le séquenceur mémoire qui se charge de générer ces signaux RAS, à partir des deux bits B0 et B1 (qui sont techniquement des bits d'adresse). Pour communiquer avec le processeur, l'Intel 8202 disposait de deux bits XACK et SACK. SACK indiquait au processeur que le 8202/8203 est en train de faire un accès mémoire et qu'il est indisponible pour un second accès mémoire. Cela permet de bloquer le processeur tant que le 8202 est indisponible. Le signal XACK indique que l'accès mémoire précédent est terminé et que : soit la donnée lue est présente sur le bus de données, soit que l'écriture s'est terminée. : Le signal SACK est très utile sur les configurations multiprocesseurs. Un processeur peut démarrer un accès mémoire, le signal SACK indiquera au second processeur qu'il doit attendre que l'accès soit terminé pour que ce soit son tour. ==Les contrôleurs de DRAM asynchrones évolués== L'Intel 8202 était un contrôleur mémoire basique, comme beaucoup d'autres à cette époque. Mais Intel a vendu des contrôleurs mémoires plus complexes. Par exemple, l'Intel 8207 était un contrôleur mémoire bien plus avancé que les deux précédents. Passons sur certains détails, comme le fait qu'il pouvait gérer jusqu'à 256 kibioctets de DRAM. Au-delà de ça, il y avait des fonctionnalités bien plus intéressantes, à savoir : un support de l'ECC, il était double port, il permettait de simuler une DRAM synchrone à partir d'une DRAM asynchrone. Il n'était pas le seul dans ce cas et des contrôleurs de DRAM très évolués étaient capables de faire des merveilles. Voyons comment cela était possible. ===Les contrôleurs mémoire synchrone=== Il est parfaitement possible d'utiliser un contrôleur mémoire synchrone avec une DRAM asynchrone. A vrai dire, le contrôleur DRAM peut complétement simuler une mémoire synchrone alors que la DRAM associée est asynchrone. La traduction asynchrone vers synchrone se fait en ajoutant des registres sur le bus mémoire, notamment sur le bus de données et le bus d'adresse/commande. Nous avions détaillé cela dans le chapitre sur les SRAM, c'est la même chose avec une mémoire DRAM. Sauf que cette fois-ci, le contrôleur mémoire doit aussi être prévu pour. [[File:Controleur mémoire synchrone utilisé avec une DRAM asynchrone.png|centre|vignette|upright=2|Contrôleur mémoire synchrone utilisé avec une DRAM asynchrone]] Les deux-trois registres illustrés plus haut peuvent être intégrés directement dans le contrôleur mémoire, que ce soit totalement ou en partie. Le strict minimum pour avoir un contrôleur mémoire synchrone est que celui-ci doit mémoriser adresse et commandes dans un registre. Par exemple, le 8207 d'Intel était capable de mémoriser les requêtes processeurs dans un registre interne, mais il fallait utiliser deux registres séparés pour le bus de données. Les deux registres étaient alors commandés par le contrôleur mémoire. Il est cependant possible d'aller plus loin et d'intégrer les registres du bus de données dans le contrôleur mémoire. [[File:Controleur mémoire DRAM synchrone.png|centre|vignette|upright=2|Contrôleur mémoire DRAM synchrone.]] : Il faut noter que cette fonctionnalité est parfois disponible sur les SRAM. En clair, on peut associer une SRAM asynchrone avec un contrôleur de SRAM synchrone. Le contrôleur de SRAM se charge alors de simuler une SRAM synchrone à partir de la SRAM asynchrone. Utiliser un contrôleur mémoire synchrone a de nombreux avantages, l'un d'entre eux étant lié aux ''wait state''. Quand le processeur envoie une requête de lecture/écriture à la mémoire RAM, celle-ci met plusieurs cycles d'horloge à répondre. Et pendant ce temps, le processeur... attend. Et pendant ce temps d'attente, il doit maintenir l'adresse mémoire sur le bus d'adresse. Les cycles d'horloge perdus à attendre la mémoire RAM étaient appelés des '''''Wait states'''''. Utiliser un contrôleur mémoire synchrone d'éliminer les ''wait state'', dans une certaine mesure. Avec un contrôleur mémoire synchrone, le processeur envoie l'adresse, mais c'est le contrôleur mémoire qui la maintient sur le bus d'adresse. Le processeur peut envoyer l'adresse et la donnée à écrire, elles sont recopiées dans les registres, et le controleur mémoire y a accès sans que le processeur doive les maintenir. Le processeur peut se déconnecter du bus mémoire et faire du travail dans son coin pendant que le contrôleur mémoire accède à la DRAM. Les ''wait state'' disparaissent alors, du moins du point de vue du processeur. ===La gestion de l'ECC=== L''''ECC''' peut être géré dans le contrôleur mémoire. Pour cela, on couple les registres mentionnés dans la section précédente, avec un circuit de détection et de correction d'erreur. Le circuit d'ECC peut, comme les registres synchrones, être intégré dans le contrôleur mémoire, ou au contraire être situé dans un circuit séparé. Si le circuit d'ECC est séparé du contrôleur mémoire, il communique avec lui, histoire que le contrôleur mémoire puisse signaler toute erreur de parité ou d'ECC au processeur. [[File:Controleur mémoire synchrone avec ECC intégré.png|centre|vignette|upright=2|Controleur mémoire synchrone avec ECC intégré]] Reprenons l'exemple du 8207 d'Intel. Le contrôleur mémoire 8207 gère le bus d'adresse et de commande, mais n'a pas de connexions directes avec le bus de données. Il ne peut donc pas prendre en charge l'ECC. Il avait besoin d'être couplé avec un circuit d'ECC séparé, relié au bus de données : l'Intel 8206. Le 8026 prenait en entrée : 16 bits de données et 8 bits d'ECC. Il fournissait en sortie 16 bits de données après correction d'erreur, les 8 bits d'ECC pour indiquer qu'une erreur a été détectée mais pas corrigée, ainsi que des bits de parité. Le 8206 détectait/corrigeait les erreurs et générait les bits d'ECC, mais il communiquait avec le contrôleur mémoire pour cela. [[File:8207 avec ECC.png|centre|vignette|upright=2|8207 avec ECC]] La détection/correction d'erreur était appliquée à la fois pour les accès mémoire et pour les rafraichissements mémoire. Lors d'un rafraichissement mémoire, la donnée rafraichie est lue et réécrite. Avec l'ECC activé et configuré correctement, le rafraichissement passe par le bus de données. Au lieu d'avoir un cycle de lecture-écriture interne à la DRAM, on a un cycle de lecture-correction-écriture qui utilise le 8206. La donnée lue est envoyée sur le bus de données, puis le 8206 corrige une éventuelle erreur, et la donnée corrigée est alors réécrite en mémoire. Au passage, si une erreur non-correctible est détectée, le 8206 ne fait rien, l'erreur est ignorée. La gestion de l'erreur sera retardée jusqu'à une lecture ultérieure. Et encore : si lecture ultérieure il y a. Si la donnée est écrasée par une écriture, la donnée corrompue sera simplement écrasée et disparaitra sans avoir pu faire le moindre dégât. Mais pour cela, le 8206 doit communiquer avec le contrôleur mémoire, pour savoir s'il est dans un cycle de rafraichissement ou un accès mémoire normal. Il prévient le 8207 lors d'une erreur, et c'est ce dernier qui décide si l'erreur doit être prise en compte ou ignorée. C'est seulement lors d'un accès mémoire normal que le processeur est prévenu qu'une erreur de parité/autre a eu lieu. ===Les contrôleurs mémoires multiports=== Les '''contrôleur mémoire multiport''' disposent de plusieurs ports, chacun permettant de traiter un accès mémoire. Ils peuvent simuler une mémoire multiport à partir d'une DRAM monoport. Évidemment, la simulation n'est pas parfaite. Des accès mémoire simultanés, envoyés en même temps sur différents ports, sont en réalité exécutés un par un, pas en même temps. Il y a donc une petite pénalité en termes de performances, mais elle est mineure. Encore une fois, nous allons reprendre l'exemple du 8207. Il avait deux ports séparés, et était prévu pour fonctionner dans un système à deux processeurs. L'usage de deux ports séparés permettait de partager une unique mémoire DRAM entre deux processeurs. Le partage se faisait en interfaçant deux processeurs sur le contrôleur mémoire, chacun étant connecté à un port. Lors d'une lecture, il redirigeait la donnée lue vers le bon processeur, en configurant le bus de données correctement. Le contrôleur mémoire recevait des requêtes mémoire de deux processeurs, mais il les exécutait une à la fois. S'il recevait deux requêtes en même temps, l'une d'entre elles était mise en attente. Le contrôleur mémoire doit arbitrer les accès à la mémoire, et faire en sorte que les deux processeurs aient accès à la mémoire à tour de rôle. Et non seulement il doit arbitrer les deux ports, mais il y a aussi un troisième port interne au contrôleur mémoire : le rafraichissement mémoire ! Pour cela, le circuit d'arbitrage qui choisissait entre rafraichissement mémoire et accès mémoire, est amélioré de manière à gérer un second port. Le circuit d'arbitrage donne l'accès au séquenceur mémoire à un port sélectionné. L'arbitrage était configurable, avec deux options : soit le port A est privilégié sur le port B, soit le port le plus récemment accédé à la priorité. Les deux ports pouvaient être configurés pour fonctionner soit de manière asynchrone, soit de manière synchrone. Il était aussi possible de configurer l'ECC, des options liées à la fréquence du processeur et de la RAM, ainsi que de nombreuses options liées au rafraichissement. Pour cela, le 8207 contenait un registre de configuration interne, programmable en fournissant les entrées adéquates. Tout ce qui vient d'être dit se généralise avec plus de deux processeurs. Le 8207 ne permettait pas ça, mais les contrôleurs mémoire des PC modernes en sont capables. Ils peuvent gérer plusieurs dizaines de processeurs facilement. ==Le contrôleur mémoire d'une DRAM ''Fast Page Mode''== Les mémoires DRAM classiques sont des mémoires à tampon de ligne, mais qui sont assez mal utilisées. Notamment, tout accès mémoire se fait en deux phases : un accès pour sélectionner la ligne, un autre pour sélectionner la colonne. Les mémoires ''Fast Page Mode'' permettent d'optimiser le tout. Elles permettent de faire plusieurs accès successifs à la même ligne, à des colonnes différentes. Et le contrôleur mémoire doit être adapté pour cela. [[File:Sélection d'une ligne sur une mémoire FPM ou EDO.png|centre|vignette|upright=2|Sélection d'une ligne sur une mémoire FPM ou EDO.]] Le contrôleur mémoire doit détecter que deux accès mémoire consécutifs se font dans la même ligne. Si deux accès consécutifs accèdent à la même ligne, on doit juste changer de colonne et altérer le signal CAS. C'est un ''succès de tampon de ligne'', aussi appelé un '''succès de page'''. Si deux accès consécutifs accèdent à une ligne différente, c'est un ''défaut de tampon de ligne'', aussi appelé un '''défaut de page'''. Il faut alors changer de ligne, en altérant les signaux RAS et en envoyant une adresse de ligne. Pour détecter les succès ou défauts de page, il faut ajouter un circuit spécialisé dans le contrôleur mémoire. Il mémorise la ligne ouverte, et plus précisément son adresse de ligne (numéro de banque inclut). A chaque requête processeur, il compare l'adresse de ligne recue avec celle déjà ouverte. C'est un succès si les deux sont égales, un défaut si elles sont différentes. Le circuit qui fait cette comparaison est appelé le '''décodeur de commande'''. Il prévient le séquenceur mémoire en cas de succès de page, grâce à un signal de un bit, qui vaut 0 en cas de défaut de page et 1 en cas de succès. Le séquenceur mémoire décide alors comment gérer les signaux RAS et CAS, ainsi que l'envoi des adresses de ligne/colonne. [[File:Controleur mémoire d'une FPM-DRAM.png|centre|vignette|upright=2|Controleur mémoire d'une FPM-DRAM]] ==Le contrôleur mémoire d'une SDRAM ou d'une DDR== l'intérieur d'un contrôleur de SDRAM ne change pas significativement d'un controleur de RAM asynchrone. Il regroupe toujours un séquenceur mémoire et une interface physique, un circuit pour le rafraichissement mémoire et un circuit d'arbitrage. Par contre, ses sorties changent pas mal. Contrairement aux mémoires DRAM basiques, les mémoires SDRAM sont cadencées par un signal d'horloge. Et ce signal d'horloge vient bien de quelque part. Pour cela, deux solutions : soit le contrôleur mémoire génère la fréquence qui commande la mémoire, soit il prend en entrée une fréquence de base qu'il multiplie pour obtenir la fréquence désirée. Les deux solutions sont équivalentes, si ce n'est que les circuits impliqués ne sont pas les mêmes. Dans le premier cas, le contrôleur doit embarquer un circuit oscillateur, qui génère la fréquence demandée. Dans l'autre cas, un simple multiplieur/diviseur de fréquence suffit et c'est généralement une PLL qui est utilisée pour cela. : Notez qu'il ne faut pas confondre la fréquence de la SDRAM et celle du contrôleur mémoire. Le contrôleur mémoire fonctionne à une vitesse assez élevée, en interne. Le port relié au processeur fonctionne à haute fréquence, généralement la même que celle du processeur. A vrai dire, de nos jours, il est intégré dans le processeur. Pour le décodage d'adresse, tout est plus simple sur les SDRAM, DDR inclues. Les chips de mémoire SDRAM et DDR disposent d'une entrée ''Chip Select'', ce qui facilite grandement le décodage d'adresse. Les bits de ''Chip Select'' sont générés par le contrôleur mémoire, et sont transmis sur le bus de commande. Le contrôleur de DRAM peut adresser un certain nombre de rangées, dispersés sur une ou plusieurs barrettes. La limite maximale dépend du contrôleur de DRAM, elle est souvent proche de 8 ou 16 rangées. Si on combine plusieurs barrettes de mémoire, il est possible de dépasser cette limite. Par exemple, pour un contrôleur de DRAM supportant maximum 8 rangées, 4 barrettes de 4 rangées chacune dépassent la limite. ===Le séquenceurs mémoire pour les SDRAM/DDR=== Le séquenceur mémoire existe toujours pour les mémoires SDRAM, c'est toujours un circuit séquentiel qui implémente une machine à état. Il traduit toujours une requête processeur en une séquence de commandes envoyées à des timings bien précis. Les commandes mémoires peuvent provenir de l'extérieur, mais aussi d'un circuit de rafraichissement intégré dans le contrôleur mémoire, comme pour les autres DRAM. La seule différence est que la machine à état est plus complexe. Pour rappel, une requête de lecture/écriture se fait en trois étapes maximum : une commande PRECHARGE pour précharger le tampon de ligne, une commande ACT qui fixe l'adresse de ligne, et enfin une commande READ/WRITE avec l'adresse de colonne. Le pire est que certaines de ces commandes peuvent être facultatives. Par exemple, en cas de succès de page, les commandes PRECHARGE et ACT ne doivent pas être envoyées, seule les commandes READ/WRITE le sont. Le controleur doit toujours détecter les succès et défaut de page et agir en fonction. Et il faut tenir compte des timings mémoire, à savoir le fait que ces commandes sont séparées par des temps d'attentes bien précis. Par exemple, je prends des chiffres arbitraires : il faut attendre 2 cycles entre une commande ACT et une commande READ, 6 cycles avant deux commandes WRITE consécutives, etc. Il faut aussi tenir compte des commandes SDRAM anticipées, à savoir que l'on peut envoyer des commandes avant que la précédente soit terminée. Et cela complexifie le séquenceur. La gestion des ''timings'' rend la conception du séquenceur plus complexe. Le séquenceur peut profiter du fait que des accès simultanés peuvent se faire de préférence se faire dans des banques différentes. Prenons l'exemple d'une mémoire partagée entre plusieurs processeurs. Les deux processeurs ou cœurs exécutent deux programmes différents : le premier processeur exécute le navigateur web, alors que le second lit de la musique. Dans ce cas, les deux processeurs accèdent à des données très éloignées en mémoire (ce n'est pas toujours le cas, mais ça l'est dans cet exemple), qui sont potentiellement dans des banques séparées. Par contre, il y a une contrainte majeure : deux accès simultanés ne peuvent pas accéder au bus en même temps. Je rappelle que quand on démarre une lecture, il se passe quelques cycles d'horloge avant que la donnée atterrissent sur le bus de données. Le temps en question est fixe, mais sa durée n'est pas la même selon que la lecture fasse un succès de page ou un défaut de page. Le séquence mémoire doit en tenir compte avant de démarrer une lecture ou une écriture. Le séquenceur doit donc détecter les succès de page, et déterminer si le bus de donnée sera libre au moment où la lecture terminera. Si c'est le cas, il peut lancer a commande. Sinon, il doit la retarder. {|class="wikitable" |+ Exemple où deux processeurs accèdent à une SDRAM en même temps |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 |- ! rowspan="2" | Requêtes mémoires | bgcolor="#A0FFFF" | CPU 1 || || || bgcolor="#A0FFFF" | CPU 1 || || || || || || |- | bgcolor="#FFA0FF" | CPU 2 || || || || || bgcolor="#FFA0FF" | CPU 2 || || || || |- |- ! Bus de commande/adresse | || bgcolor="#A0FFFF" | Lecture (CPU 1) || bgcolor="#FFA0FF" | Lecture (CPU 2) || || bgcolor="#A0FFFF" | Lecture (CPU 1) || || bgcolor="#FFA0FF" | Lecture (CPU 2) || || |- ! Bus de données | || || || bgcolor="#A0FFFF" | Donnée lue CPU 1 || bgcolor="#FFA0FF" | Donnée lue CPU 2 || || bgcolor="#A0FFFF" | Donnée lue CPU 1 || || bgcolor="#FFA0FF" | Donnée lue CPU 2 |} ===La politique de gestion du tampon de ligne=== Plus haut, nous avons parlé des mémoires FPM, qui ont introduit quelques optimisations pour utiliser au mieux le tampon de ligne. Il se trouve que les SDRAM conservent ces optimisations. Il y a toujours cette notion de succès de page et de défaut de page, suivant que deux accès se font dans la même ligne ou dans deux lignes différentes. Du moins, c'est le cas si le séquenceur mémoire est coopératif. En effet, il peut fonctionner de plusieurs manières, dont les plus extrêmes sont appelés la politique de la page fermée et la politique de la page ouverte. Voyons à quoi elles correspondent. Avec la '''politique de la page fermée''', chaque accès mémoire est suivi d'une commande PRECHARGE, qui ferme la ligne courante et précharge les lignes de bits. Même si deux accès consécutifs se font dans la même ligne, la ligne est fermée et ré-ouverte entre deux accès mémoire. En clair : l'optimisation introduite par les mémoires FPM est désactivée, le contrôleur mémoire fait exprès de ne pas en profiter. On appelle cette méthode la close ''page autoprecharge''. Cette méthode réduit grandement les performances pour les accès à des adresses consécutives, mais fonctionne à merveille si les accès sont "aléatoires", à savoir qu'ils se font sans régularités évidentes. Son seul avantage est que l'implémentation du séquenceur mémoire est très simple. En effet, le séquenceur mémoire se passe complétement de la table des banques, du comparateur de ligne, et de tous les circuits nécessaires pour vérifier les succès ou défauts de page. De plus, le séquenceur mémoire profite grandement des commandes READA et WRITEA, qui fusionnent une commande READ/WRITE avec une commande PRECHARGE. Le séquenceur mémoire a juste à envoyer des commandes ACT, READA, WRITEA et PREFETCH à la mémoire, pas besoin des commandes PRECHARGE, READ ou WRITE. A l'opposé, la '''politique de la page ouverte''' ne ferme pas automatiquement la ligne. Elle la laisse ouverte, en espérant que le prochain accès mémoire se fasse dans cette ligne. Lorsqu'un nouvel accès mémoire arrive, elle doit détecter les succès ou défauts de page et agir en fonction. En cas de défaut de page, la ligne est fermée, le séquenceur mémoire envoie une commande PRECHARGE, puis l'accès suivant effectue les deux commandes ACT + READ ou WRITE. En cas de succès de page, les commandes PRECHARGE et ACT ne sont pas envoyées, seules la commande READ ou WRITE l'est. Un désavantage est que le contrôleur mémoire doit inclure une table des banques et un comparateur, comme vu plus haut dans la section sur les mémoires FPM. un autre défaut est que garder une ligne ouverte consomme beaucoup d'énergie, comparé à un simple état de PRECHARGE. En conséquence, il est préférable de fermer les lignes dès que possible. Par contre, les performances sont d'autant meilleures que les accès mémoire consécutifs à une même ligne soient assez fréquents. Si les accès mémoire sont aléatoires, les performances sont moins bonnes. La politique de la page fermée fermait les lignes en avance, avec des commandes READA ou WRITEA, avant même que l'accès suivant démarre. Avec la politique de la page ouverte, on doit attendre pour détecter un défaut de page, puis fermer la ligne avec une commande PRECHARGE séparée. La ligne est donc fermée avec un peu temps de retard, et envoyer deux commandes au lieu d'une prend plus de temps. Les contrôleurs mémoires basiques utilisent une des deux solutions précédentes. Soit la page est toujours fermée, soit elle est toujours laissée ouverte jusqu'à ce qu'un accès mémoire la referme. Mais les contrôleurs plus évolués tentent de prédire s'il faut fermer ou laisser ouvertes les pages ouvertes. La méthode la plus simple attend un temps prédéterminé avant de fermer la ligne. Une autre solution regarde le tout dernier accès. On peut très bien décider de laisser la ligne ouverte si l'accès mémoire précédent était une rafale, et fermer sinon. Une solution plus complexe mémorise les N derniers accès et en déduit s'il faut fermer ou non la prochaine ligne. On peut mémoriser si l'accès en question a causé la fermeture d'une ligne avec un bit. Mémoriser les N derniers accès demande d'utiliser un simple registre à décalage. Pour chaque valeur de ce registre, il faut prédire si le prochain accès demandera une ouverture ou une fermeture. Une solution simple fait la moyenne des bits à 1 dans ce registre : si plus de la moitié des bits est à 1, on laisse la ligne ouverte et on ferme sinon. Pour améliorer un petit peu l'algorithme, on peut faire en sorte que les bits des accès mémoires les plus récents aient plus de poids dans le calcul de la moyenne. Il existe sans doute d'autres solutions plus évoluées, mais il est difficile de savoir ce qu'il y a dans les contrôleurs de SDRAM modernes. : Le fait de laisser ouverte une ligne ou au contraire de la fermer systématiquement, se fait pour chaque banque. ===Les banques et l'entrelacement mémoire des SDRAM=== Les SDRAM incorporent plusieurs banques à l'intérieur de leurs circuits. Pour rappel, une banque est une sous-mémoire, avec ses propres décodeurs, son tampon de ligne, ses multiplexeurs de colonnes, etc. Pour le dire autrement, une SDRAM regroupe plusieurs mémoires séparées dans un même circuit intégré, les mémoires en question étant des banques. [[File:Arrangement vertical.jpg|centre|vignette|upright=2.5|Mémoire multi-banques.]] La présence de plusieurs banques impacte la gestion des lignes ouvertes/fermées. En effet, chaque banque a son propre tampon de ligne, ce qui fait que la gestion des lignes se fait indépendamment dans chaque banque. Le séquenceur mémoire doit donc se souvenir des lignes actives dans chaque banque. Pour cela, il mémorise ces lignes dans une petite mémoire : la '''table des banques''', aussi appelée ''bank status memory''. Pour détecter un succès ou un défaut, le contrôleur doit extraire la ligne de l'adresse, mais aussi le numéro de banque. Il envoie alors le numéro de banque à la table des banques, sur son entrée d'adresse. Il récupère alors le numéro de la ligne active sur les sorties de données. Il compare alors ce numéro de ligne avec le numéro de ligne de l'adresse envoyée par le processeur. C'est un succès si les deux sont égales, un défaut sinon. [[File:Controleur mémoire FPM avec plusieurs banques.jpg|centre|vignette|upright=2|Contrôleur mémoire FPM avec plusieurs banques.]] <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les mémoires RAM dynamiques (DRAM) | prevText=Les mémoires RAM dynamiques (DRAM) | next=Les mémoires associatives | nextText=Les mémoires associatives }} </noinclude> 02pof2hbkr4w4zx0487as04ihcgt7hh 764683 764682 2026-04-23T19:07:27Z Mewtow 31375 /* Le séquenceurs mémoire pour les SDRAM/DDR */ 764683 wikitext text/x-wiki Les mémoires ROM ou SRAM ont généralement une interface simple, à laquelle le processeur peut s'interfacer directement. Mais pour les DRAM, ce n'est pas le cas. Les DRAM utilisent un bus d'adresse multiplexé, où l'adresse est envoyée en deux fois. Connecter le processeur directement sur une DRAM n'est pas pratique : le bus d'adresse du processeur et celui de la mémoire ne collent pas. Les DRAM doivent aussi être rafraichies régulièrement. Le rafraichissement mémoire peut être délégué au processeur, mais c'est loin d'être idéal. Et il y a bien d'autres raisons qui font que le processeur ne peut pas s'interfacer facilement avec les mémoires DRAM. Pour gérer ces problèmes, les mémoires DRAM ne sont pas connectées directement au processeur. À la place, on ajoute un intermédiaire entre le processeur et la mémoire : le '''contrôleur mémoire externe'''. Son but est de montrer au processeur une interface simple, semblable à celle d'une SRAM classique, alors qu'il commande une mémoire DRAM à l'interface plus complexe. Il est placé sur la carte mère ou dans le processeur, et ne doit pas être confondu avec le contrôleur mémoire intégré dans la mémoire. Ce chapitre va expliquer quels sont les rôles du contrôleur mémoire, son interface et ce qu'il y a à l'intérieur. Dans ce chapitre, quand nous parlerons de ''contrôleur mémoire'', cela fera systématiquement référence au contrôleur mémoire externe. Et avant de poursuivre, sachez qu'il est difficile de faire des généralités sur les contrôleurs mémoire, car les mémoires DRAM elles-mêmes sont assez différentes les unes des autres. Entre une mémoire EDO, une mémoire SDR, une mémoire DDR et une DRAM asynchrone, les contrôleurs mémoires seront fortement différents. Aussi, il y a aura une différence entre un contrôleur pour une DRAM asynchrone et un contrôleur pour une mémoire EDO, une mémoire SDRAM, etc. J'ai choisit de vous séparer les contrôleurs mémoire pour les DRAM asynchrones de ceux pour les SDRAM/DRR. ==Le contrôleur d'une DRAM asynchrone== Les premières DRAM asynchrones avaient des contrôleurs mémoires dédiés, qui étaient séparés du processeur et du ''chipset'' de la carte mère. Par exemple, les composants Intel 8202, Intel 8203 et Intel 8207 étaient des contrôleurs mémoire pour DRAM asynchrones qui étaient vendus dans des boitiers DIP et étaient soudés sur la carte mère. Par la suite, ils ont été intégrés au ''chipset'' de la carte mère pendant les décennies 90-2000. Après les années 2000, ils ont été intégrés dans les processeurs. Leurs fonctions étaient le multiplexage de l'adresse et le rafraichissement mémoire. Ils recevaient une adresse mémoire complète, qu'ils découpaient une adresse de ligne et une adresse de colonne, envoyées l'une après l'autre sur le bus mémoire. Pour le rafraichissement mémoire, ils rafraichissaient la DRAM régulièrement, de manière automatique, entre deux accès mémoire normaux. Le processeur n'avait ainsi plus à rafraichir la mémoire lui-même, cette fonction était déléguée au contrôleur de DRAM. Ils étaient connectés au bus d'adresse et de commande, avec éventuellement des relations indirectes avec le bus de données. ===L'interface d'un contrôleur de DRAM asynchrone=== L'interface du contrôleur mémoire décrit ses broches d'entrées/sorties et leur signification. Elle est généralement très simple et contient deux ports : un connecté au processeur, un autre connecté à la DRAM. Cela trahit d'ailleurs son rôle principal, qui est de transformer les requêtes de lecture/écriture provenant du processeur en une suite de commandes acceptée par la mémoire. Le port connecté à la DRAM est connecté ua bus d'adresse et au bus de commande. Le bus de données est lui relié au processeur et/ou au bus système. Un accès mémoire provenant du processeur contient une adresse à lire/écrire, le bit R/W qui indique s'il faut faire une lecture ou une écriture, et éventuellement une donnée à écrire. Mais, nous avons vu que les accès mémoires sur une DRAM sont multiplexés : on envoie l'adresse en deux fois : la ligne d'abord, puis la colonne. De plus, il faut générer les signaux RAS, CAS et bien d'autres. Le tout est illustré ci-dessous. [[File:Contrôleur mémoire.png|centre|vignette|upright=2|Contrôleur mémoire externe.]] Un point important est que les DRAM asynchrones n'ont pas d'entrée ''Chip Select'' ou d'entrée ''Output Enable''. Les signaux RAS et CAS remplacent en quelque sorte ces deux signaux. Le bit RAS fait office de ''Chip Select'', le bit CAS fait office d'''Output Enable''. Pour certains contrôleurs de DRAM, il faut ajouter l''''interface électrique''', qui traduit les signaux du processeur en signaux compatibles avec la mémoire. Il est en effet très fréquent que la mémoire et le processeur n'utilisent pas les mêmes tensions pour coder un bit, ce qui fait qu'elles ne sont pas compatibles. Dans ce cas, le contrôleur mémoire fait la conversion. Le contrôleur DRAM peut fonctionner en mode synchrone ou asynchrone, du point de vue du processeur. Quand il fonctionne en mode synchrone, il permet d'interfacer un processeur synchrone avec une mémoire DRAM asynchrone. Un point important est que le contrôleur DRAM sert d'intermédiaire entre une mémoire DRAM et "le reste du monde". Il ne fait pas forcément office d'intermédiaire entre DRAM et processeur, mais peut aussi faire l'intermédiaire entre la DRAM et un bus système, entre une DRAM et le ''Video Display Controler'' d'une carte graphique, ou n'importe quel autre composant électronique qui utilise cette DRAM. ===Le générateur de ''timings'' et la traduction d'adresse=== Le contrôleur mémoire doit traduire les adresses du processeur en adresses compatibles avec la mémoire. Et la traduction est assez variable, suivant que le bus mémoire est un bus normal, un bus multiplexé, ou partiellement multiplexé. Nous avons vu ces trois types de bus mémoire dans le chapitre sur l'interface des mémoires, mais nous ferons quelques rappels rapides. Avec un ''bus totalement multiplexé'', le bus d'adresse et le bus de données sont fusionnés. Dans ce cas, on peut envoyer soit une adresse, soit lire/écrire une donnée sur le bus, mais on ne peut pas faire les deux en même temps. Un bit ALE indique si le bus est utilisé en tant que bus d'adresse ou bus de données. Le contrôleur mémoire gère cette situation, en fixant le bit ALE et en envoyant séparément adresse et donnée pour les écritures. [[File:Bus multiplexé avec bit ALE.png|centre|vignette|upright=2|Bus multiplexé avec bit ALE.]] Avec un ''bus d'adresse multiplexé'', l'adresse est découpée en une adresse de ligne et une adresse de colonne, envoyées l'une après l'autre. Le contrôleur mémoire prend en entrée une adresse mémoire complète, la découpe en deux, et envoie chaque morceau au bon moment. Pour cela, il suffit d'un registre pour mémoriser l'adresse et d'un multiplexeur. Le multiplexeur choisit soit les bits de poids fort de l'adresse, soit ceux de poids faible. Les premiers correspondent à l'adresse de ligne, les autres à l'adresse de colonne. La commande du multiplexeur est le fait d'un petit circuit séquentiel, qui génère aussi les signaux CAS et RAS. Au premier cycle, il met le signal RAS à 1, met le CAS à 0, et configure le MUX pour sélectionner les bits de poids fort. Au second cycle, il génère un signal CAS à 1, met le RAS à 0 et configure le MUX pour sélectionner les bits de poids faible. Le circuit en question est appelé le générateur de ''timings''. [[File:Controleur de DRAM simple, sans rafraichissement mémoire.png|centre|vignette|upright=2|Contrôleur de DRAM simple, sans rafraichissement mémoire.]] Le générateur de ''timings'' est un circuit séquentiel qui implémente une petite machine à état. Il est très simple sur une mémoire DRAM asynchrone basique, mais il est plus complexe sur les mémoires FPM, EDO, quartet, et autres. Le regroupement des multiplexeurs d'adresse et du générateur de ''timings'' est appelé le '''séquenceur mémoire'''. C'est le séquenceur mémoire qui traduit la requête processeur en commande DRAM, le reste du contrôleur est dédié au rafraichissement mémoire ou à d'autres fonctions facultatives. ===Le rafraichissement mémoire=== La gestion du rafraichissement mémoire est la fonction principale du contrôleur DRAM. Pour gérer le rafraichissement mémoire, le contrôleur mémoire intègre deux compteurs, un pour gérer l'adresse à rafraichir, l'autre pour gérer l'intervalle de temps entre deux rafraichissements. Le rafraichissement se fait à intervalle régulier, tous les x microsecondes. Pour déclencher le rafraichissement au bon moment, le contrôleur mémoire contient un ''Refresh Timer'', aussi appelé le '''compteur de rafraichissement'''. Il est initialisé avec le temps entre deux rafraichissements, une adresse est rafraichie quand ce compteur atteint 0. Le rafraichissement mémoire balaye la mémoire adresse par adresse. Pour savoir à quelle adresse il en est rendu, le contrôleur mémoire utilise un '''compteur d'adresse'''. Il contient la prochaine adresse à rafraichir, aussi appelée l'adresse de rafraichissement. Régulièrement, l'adresse dans ce compteur est envoyée à la RAM, pour une lecture. Mais la donnée lue n'est pas envoyée sur le bus de donnée, soit parce que la RAM est prévue pour, soit parce que le contrôleur désactive son bit ''output enable''. Dans le second cas, la RAM fait la lecture en interne, mais se déconnecte du bus de donnée, perdant la donnée lue dans le néant. Pour envoyer l'adresse de rafraichissement sur le bus d'adresse, il faut rajouter un multiplexeur, qui choisit entre l'adresse normale et l'adresse de rafraichissement. [[File:Controleur de DRAM avec rafraichissement mémoire.png|centre|vignette|upright=2|Controleur de DRAM avec rafraichissement mémoire.]] Le multiplexeur ne doit cependant pas être configuré si une adresse est déjà en cours de transfert. Pour cela, un circuit d'arbitrage se débrouille pour éviter qu'un accès mémoire soit interrompu par une demande de rafraichissement et inversement. Il peut être inclus dans le séquenceur mémoire ou séparé de celui-ci. [[File:Controleur mémoire, intérieur simplifié.png|centre|vignette|upright=2.5|Contrôleur mémoire, intérieur simplifié.]] Il faut noter que le rafraichissement mémoire peut être délégué non pas au contrôleur mémoire, mais au processeur où à la DRAM elle-même. Quand elle est le fait du processeur, celui-ci incorpore un ''refresh timer'' et un compteur d'adresse. Un exemple est celui du processeur Zilog Z80, qui implémentait des compteurs internes pour gérer le rafraichissement mémoire. On peut considérer que le processeur incorpore alors le contrôleur mémoire, au moins partiellement. Il est aussi possible de déléguer le rafraichissement au logiciel (certains jeux vidéos Amiga ou Commodore faisaient cela pour la mémoire vidéo). Quand la DRAM elle-même s'occupe de son propre rafraichissement, c'est elle qui intègre un ''refresh timer'' et le compteur d'adresse. ===Le décodage d'adresse=== Le contrôleur mémoire gère aussi le '''décodage d'adresse'''. pour rappel, celui-ci est utilisé quand on combine plusieurs chips mémoire ensemble, pour combiner leurs capacités mémoire. Par exemple, on peut combiner 4 chips mémoires de 1 mébioctet chacun, pour que le processeur voit comme 4 mébioctets de RAM unique. Le premier mébioctet est placé dans le premier chip mémoire, le second mébioctet dans le second chip, etc. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Pour cela, on active le chip mémoire adéquat, en fonction de l'adresse à consulter. Les autres chips mémoire sont désactivés pendant l'accès mémoire. En théorie, activer ou désactiver un chip mémoire se fait en utilisant son entrée ''Chip Select''. Il faut noter que si les SDRAM disposent bien d'un signal ''Chip Select'', ce n'est pas le cas des mémoires RAM asynchrones. A la place, ce sont les signaux RAS qui font office de ''Chip Select''. Une RAM asynchrone est activée quand son signal RAS lui demande de lire une ligne, elle est désactivée sinon. Mais c'est un détail. Toujours est-il que les signaux ''Chip Select'', ou leurs équivalents, sont générés par le contrôleur de DRAM, à partir des bits de poids fort de l'adresse. Par exemple, avec 4 chips mémoire, les deux bits de poids fort de l'adresse sont utilisés pour sélectionner le chip mémoire adéquat. Le contrôleur mémoire doit avoir plusieurs sorties ''Chip Select'', une par chip mémoire. Et le nombre de ces sorties limite le nombre de chips mémoire qu'on peut combiner. Par exemple, s'il y a seulement 4 sorties ''Chip Select'', on ne pourra brancher que 4 chips mémoire dessus. Sauf à ruser, avec un arrangement horizontal, mais cela n'est pas le ressort du contrôleur mémoire. [[File:Td6bfig3.png|centre|vignette|upright=2|Comparaison entre arrangement horizontal (à gauche) et arrangement vertical (à droite).]] ===Exemple : l'Intel 8202-8203=== L'Intel 8202 et le 8203 étaient des contrôleurs de mémoire DRAM, parmi les plus simples qui soient. Ils avaient une entrée d'adresse de 12 bits, ce qui permettait d'adresser 4 kibioctets de RAM. Ils fournissaient en sortie une adresse multiplexée sur 6 bits, envoyée en deux fois. Ils avaient donc 12 entrées d'adresse, 6 sorties d'adresse, un signal RAS, un signal CAS. Les adresses présentées en entrées n'étaient pas mémorisées dans des registres, ce qui fait qu'elles devaient être maintenues durant toute la durée de l'accès mémoire. Le processeur ne pouvait donc pas se déconnecter du bus d'adresse pendant l'accès mémoire, peu importe sa durée. Le 8202 contenait aussi un compteur de rafraichissement. Rappelons que sur les DRAM asynchrones, le rafraichissement se fait ligne par ligne. Le contrôleur mémoire a juste à présenter l'adresse de ligne, il n'a pas à envoyer l'adresse de colonne. La commande de rafraichissement se fait en mettant le signal RAS à 0, mais en laissant le CAS à 1 (je rappelle que les signaux sont actifs à 0). Le compteur d'adresse de rafraichissement a donc juste à mémoriser l'adresse de ligne. Le séquenceur mémoire était précédé par un circuit d'arbitrage, non-représenté dans le schéma ci-dessous. La microarchitecture de l'Intel 8202 est la suivante : [[File:Microarchitecture de l'Intel 8202.png|centre|vignette|upright=2|Microarchitecture de l'Intel 8202.]] Le 8202 avait une entrée pour un signal d'horloge, ainsi qu'un ''Chip Select'' un peu particulier. Si le signal CS passait à 0 lors d'un accès mémoire, le 8202/8203 ne se désactivait qu'une fois l'accès mémoire terminé. On ne pouvait pas l'interrompre pendant un accès mémoire, même en changeant le bit CS. Le signal d'horloge était utilisé pour commander le ''refresh timer''. Pour commander les lectures et écriture, il recevait en entrée un bit ''Write Request'' et un bit ''Read Request'', qui demandent respectivement une écriture et une lecture. En sortie, on trouvait un unique bit R/W qui valait 0 pour une lecture et 1 pour une écriture. Il avait aussi un bit d'entrée pour forcer le rafraichissement mémoire. S'il est à 1, la mémoire rafraichie l'adresse envoyée par le processeur. Le 8202 pouvait être connecté sur 1 à 4 chips mémoire, ce qui permettait d'adresser au maximum 16 kibioctets de RAM. Les 4 chips ne sont pas accédés en parallèle, un seul l'est à chaque fois. Pour le décodage d'adresse, le 8202 dispose de deux bits BO et B1 pour sélectionner le chip adéquat, ainsi que 4 sorties RAS pour activer la banque adéquate. On rappelle que le signal RAS remplace le signal ''Chip Select''. C'est le séquenceur mémoire qui se charge de générer ces signaux RAS, à partir des deux bits B0 et B1 (qui sont techniquement des bits d'adresse). Pour communiquer avec le processeur, l'Intel 8202 disposait de deux bits XACK et SACK. SACK indiquait au processeur que le 8202/8203 est en train de faire un accès mémoire et qu'il est indisponible pour un second accès mémoire. Cela permet de bloquer le processeur tant que le 8202 est indisponible. Le signal XACK indique que l'accès mémoire précédent est terminé et que : soit la donnée lue est présente sur le bus de données, soit que l'écriture s'est terminée. : Le signal SACK est très utile sur les configurations multiprocesseurs. Un processeur peut démarrer un accès mémoire, le signal SACK indiquera au second processeur qu'il doit attendre que l'accès soit terminé pour que ce soit son tour. ==Les contrôleurs de DRAM asynchrones évolués== L'Intel 8202 était un contrôleur mémoire basique, comme beaucoup d'autres à cette époque. Mais Intel a vendu des contrôleurs mémoires plus complexes. Par exemple, l'Intel 8207 était un contrôleur mémoire bien plus avancé que les deux précédents. Passons sur certains détails, comme le fait qu'il pouvait gérer jusqu'à 256 kibioctets de DRAM. Au-delà de ça, il y avait des fonctionnalités bien plus intéressantes, à savoir : un support de l'ECC, il était double port, il permettait de simuler une DRAM synchrone à partir d'une DRAM asynchrone. Il n'était pas le seul dans ce cas et des contrôleurs de DRAM très évolués étaient capables de faire des merveilles. Voyons comment cela était possible. ===Les contrôleurs mémoire synchrone=== Il est parfaitement possible d'utiliser un contrôleur mémoire synchrone avec une DRAM asynchrone. A vrai dire, le contrôleur DRAM peut complétement simuler une mémoire synchrone alors que la DRAM associée est asynchrone. La traduction asynchrone vers synchrone se fait en ajoutant des registres sur le bus mémoire, notamment sur le bus de données et le bus d'adresse/commande. Nous avions détaillé cela dans le chapitre sur les SRAM, c'est la même chose avec une mémoire DRAM. Sauf que cette fois-ci, le contrôleur mémoire doit aussi être prévu pour. [[File:Controleur mémoire synchrone utilisé avec une DRAM asynchrone.png|centre|vignette|upright=2|Contrôleur mémoire synchrone utilisé avec une DRAM asynchrone]] Les deux-trois registres illustrés plus haut peuvent être intégrés directement dans le contrôleur mémoire, que ce soit totalement ou en partie. Le strict minimum pour avoir un contrôleur mémoire synchrone est que celui-ci doit mémoriser adresse et commandes dans un registre. Par exemple, le 8207 d'Intel était capable de mémoriser les requêtes processeurs dans un registre interne, mais il fallait utiliser deux registres séparés pour le bus de données. Les deux registres étaient alors commandés par le contrôleur mémoire. Il est cependant possible d'aller plus loin et d'intégrer les registres du bus de données dans le contrôleur mémoire. [[File:Controleur mémoire DRAM synchrone.png|centre|vignette|upright=2|Contrôleur mémoire DRAM synchrone.]] : Il faut noter que cette fonctionnalité est parfois disponible sur les SRAM. En clair, on peut associer une SRAM asynchrone avec un contrôleur de SRAM synchrone. Le contrôleur de SRAM se charge alors de simuler une SRAM synchrone à partir de la SRAM asynchrone. Utiliser un contrôleur mémoire synchrone a de nombreux avantages, l'un d'entre eux étant lié aux ''wait state''. Quand le processeur envoie une requête de lecture/écriture à la mémoire RAM, celle-ci met plusieurs cycles d'horloge à répondre. Et pendant ce temps, le processeur... attend. Et pendant ce temps d'attente, il doit maintenir l'adresse mémoire sur le bus d'adresse. Les cycles d'horloge perdus à attendre la mémoire RAM étaient appelés des '''''Wait states'''''. Utiliser un contrôleur mémoire synchrone d'éliminer les ''wait state'', dans une certaine mesure. Avec un contrôleur mémoire synchrone, le processeur envoie l'adresse, mais c'est le contrôleur mémoire qui la maintient sur le bus d'adresse. Le processeur peut envoyer l'adresse et la donnée à écrire, elles sont recopiées dans les registres, et le controleur mémoire y a accès sans que le processeur doive les maintenir. Le processeur peut se déconnecter du bus mémoire et faire du travail dans son coin pendant que le contrôleur mémoire accède à la DRAM. Les ''wait state'' disparaissent alors, du moins du point de vue du processeur. ===La gestion de l'ECC=== L''''ECC''' peut être géré dans le contrôleur mémoire. Pour cela, on couple les registres mentionnés dans la section précédente, avec un circuit de détection et de correction d'erreur. Le circuit d'ECC peut, comme les registres synchrones, être intégré dans le contrôleur mémoire, ou au contraire être situé dans un circuit séparé. Si le circuit d'ECC est séparé du contrôleur mémoire, il communique avec lui, histoire que le contrôleur mémoire puisse signaler toute erreur de parité ou d'ECC au processeur. [[File:Controleur mémoire synchrone avec ECC intégré.png|centre|vignette|upright=2|Controleur mémoire synchrone avec ECC intégré]] Reprenons l'exemple du 8207 d'Intel. Le contrôleur mémoire 8207 gère le bus d'adresse et de commande, mais n'a pas de connexions directes avec le bus de données. Il ne peut donc pas prendre en charge l'ECC. Il avait besoin d'être couplé avec un circuit d'ECC séparé, relié au bus de données : l'Intel 8206. Le 8026 prenait en entrée : 16 bits de données et 8 bits d'ECC. Il fournissait en sortie 16 bits de données après correction d'erreur, les 8 bits d'ECC pour indiquer qu'une erreur a été détectée mais pas corrigée, ainsi que des bits de parité. Le 8206 détectait/corrigeait les erreurs et générait les bits d'ECC, mais il communiquait avec le contrôleur mémoire pour cela. [[File:8207 avec ECC.png|centre|vignette|upright=2|8207 avec ECC]] La détection/correction d'erreur était appliquée à la fois pour les accès mémoire et pour les rafraichissements mémoire. Lors d'un rafraichissement mémoire, la donnée rafraichie est lue et réécrite. Avec l'ECC activé et configuré correctement, le rafraichissement passe par le bus de données. Au lieu d'avoir un cycle de lecture-écriture interne à la DRAM, on a un cycle de lecture-correction-écriture qui utilise le 8206. La donnée lue est envoyée sur le bus de données, puis le 8206 corrige une éventuelle erreur, et la donnée corrigée est alors réécrite en mémoire. Au passage, si une erreur non-correctible est détectée, le 8206 ne fait rien, l'erreur est ignorée. La gestion de l'erreur sera retardée jusqu'à une lecture ultérieure. Et encore : si lecture ultérieure il y a. Si la donnée est écrasée par une écriture, la donnée corrompue sera simplement écrasée et disparaitra sans avoir pu faire le moindre dégât. Mais pour cela, le 8206 doit communiquer avec le contrôleur mémoire, pour savoir s'il est dans un cycle de rafraichissement ou un accès mémoire normal. Il prévient le 8207 lors d'une erreur, et c'est ce dernier qui décide si l'erreur doit être prise en compte ou ignorée. C'est seulement lors d'un accès mémoire normal que le processeur est prévenu qu'une erreur de parité/autre a eu lieu. ===Les contrôleurs mémoires multiports=== Les '''contrôleur mémoire multiport''' disposent de plusieurs ports, chacun permettant de traiter un accès mémoire. Ils peuvent simuler une mémoire multiport à partir d'une DRAM monoport. Évidemment, la simulation n'est pas parfaite. Des accès mémoire simultanés, envoyés en même temps sur différents ports, sont en réalité exécutés un par un, pas en même temps. Il y a donc une petite pénalité en termes de performances, mais elle est mineure. Encore une fois, nous allons reprendre l'exemple du 8207. Il avait deux ports séparés, et était prévu pour fonctionner dans un système à deux processeurs. L'usage de deux ports séparés permettait de partager une unique mémoire DRAM entre deux processeurs. Le partage se faisait en interfaçant deux processeurs sur le contrôleur mémoire, chacun étant connecté à un port. Lors d'une lecture, il redirigeait la donnée lue vers le bon processeur, en configurant le bus de données correctement. Le contrôleur mémoire recevait des requêtes mémoire de deux processeurs, mais il les exécutait une à la fois. S'il recevait deux requêtes en même temps, l'une d'entre elles était mise en attente. Le contrôleur mémoire doit arbitrer les accès à la mémoire, et faire en sorte que les deux processeurs aient accès à la mémoire à tour de rôle. Et non seulement il doit arbitrer les deux ports, mais il y a aussi un troisième port interne au contrôleur mémoire : le rafraichissement mémoire ! Pour cela, le circuit d'arbitrage qui choisissait entre rafraichissement mémoire et accès mémoire, est amélioré de manière à gérer un second port. Le circuit d'arbitrage donne l'accès au séquenceur mémoire à un port sélectionné. L'arbitrage était configurable, avec deux options : soit le port A est privilégié sur le port B, soit le port le plus récemment accédé à la priorité. Les deux ports pouvaient être configurés pour fonctionner soit de manière asynchrone, soit de manière synchrone. Il était aussi possible de configurer l'ECC, des options liées à la fréquence du processeur et de la RAM, ainsi que de nombreuses options liées au rafraichissement. Pour cela, le 8207 contenait un registre de configuration interne, programmable en fournissant les entrées adéquates. Tout ce qui vient d'être dit se généralise avec plus de deux processeurs. Le 8207 ne permettait pas ça, mais les contrôleurs mémoire des PC modernes en sont capables. Ils peuvent gérer plusieurs dizaines de processeurs facilement. ==Le contrôleur mémoire d'une DRAM ''Fast Page Mode''== Les mémoires DRAM classiques sont des mémoires à tampon de ligne, mais qui sont assez mal utilisées. Notamment, tout accès mémoire se fait en deux phases : un accès pour sélectionner la ligne, un autre pour sélectionner la colonne. Les mémoires ''Fast Page Mode'' permettent d'optimiser le tout. Elles permettent de faire plusieurs accès successifs à la même ligne, à des colonnes différentes. Et le contrôleur mémoire doit être adapté pour cela. [[File:Sélection d'une ligne sur une mémoire FPM ou EDO.png|centre|vignette|upright=2|Sélection d'une ligne sur une mémoire FPM ou EDO.]] Le contrôleur mémoire doit détecter que deux accès mémoire consécutifs se font dans la même ligne. Si deux accès consécutifs accèdent à la même ligne, on doit juste changer de colonne et altérer le signal CAS. C'est un ''succès de tampon de ligne'', aussi appelé un '''succès de page'''. Si deux accès consécutifs accèdent à une ligne différente, c'est un ''défaut de tampon de ligne'', aussi appelé un '''défaut de page'''. Il faut alors changer de ligne, en altérant les signaux RAS et en envoyant une adresse de ligne. Pour détecter les succès ou défauts de page, il faut ajouter un circuit spécialisé dans le contrôleur mémoire. Il mémorise la ligne ouverte, et plus précisément son adresse de ligne (numéro de banque inclut). A chaque requête processeur, il compare l'adresse de ligne recue avec celle déjà ouverte. C'est un succès si les deux sont égales, un défaut si elles sont différentes. Le circuit qui fait cette comparaison est appelé le '''décodeur de commande'''. Il prévient le séquenceur mémoire en cas de succès de page, grâce à un signal de un bit, qui vaut 0 en cas de défaut de page et 1 en cas de succès. Le séquenceur mémoire décide alors comment gérer les signaux RAS et CAS, ainsi que l'envoi des adresses de ligne/colonne. [[File:Controleur mémoire d'une FPM-DRAM.png|centre|vignette|upright=2|Controleur mémoire d'une FPM-DRAM]] ==Le contrôleur mémoire d'une SDRAM ou d'une DDR== l'intérieur d'un contrôleur de SDRAM ne change pas significativement d'un controleur de RAM asynchrone. Il regroupe toujours un séquenceur mémoire et une interface physique, un circuit pour le rafraichissement mémoire et un circuit d'arbitrage. Par contre, ses sorties changent pas mal. Contrairement aux mémoires DRAM basiques, les mémoires SDRAM sont cadencées par un signal d'horloge. Et ce signal d'horloge vient bien de quelque part. Pour cela, deux solutions : soit le contrôleur mémoire génère la fréquence qui commande la mémoire, soit il prend en entrée une fréquence de base qu'il multiplie pour obtenir la fréquence désirée. Les deux solutions sont équivalentes, si ce n'est que les circuits impliqués ne sont pas les mêmes. Dans le premier cas, le contrôleur doit embarquer un circuit oscillateur, qui génère la fréquence demandée. Dans l'autre cas, un simple multiplieur/diviseur de fréquence suffit et c'est généralement une PLL qui est utilisée pour cela. : Notez qu'il ne faut pas confondre la fréquence de la SDRAM et celle du contrôleur mémoire. Le contrôleur mémoire fonctionne à une vitesse assez élevée, en interne. Le port relié au processeur fonctionne à haute fréquence, généralement la même que celle du processeur. A vrai dire, de nos jours, il est intégré dans le processeur. Pour le décodage d'adresse, tout est plus simple sur les SDRAM, DDR inclues. Les chips de mémoire SDRAM et DDR disposent d'une entrée ''Chip Select'', ce qui facilite grandement le décodage d'adresse. Les bits de ''Chip Select'' sont générés par le contrôleur mémoire, et sont transmis sur le bus de commande. Le contrôleur de DRAM peut adresser un certain nombre de rangées, dispersés sur une ou plusieurs barrettes. La limite maximale dépend du contrôleur de DRAM, elle est souvent proche de 8 ou 16 rangées. Si on combine plusieurs barrettes de mémoire, il est possible de dépasser cette limite. Par exemple, pour un contrôleur de DRAM supportant maximum 8 rangées, 4 barrettes de 4 rangées chacune dépassent la limite. ===Le séquenceurs mémoire pour les SDRAM/DDR=== Le séquenceur mémoire existe toujours pour les mémoires SDRAM, c'est toujours un circuit séquentiel qui implémente une machine à état. Il traduit toujours une requête processeur en une séquence de commandes envoyées à des timings bien précis. Les commandes mémoires peuvent provenir de l'extérieur, mais aussi d'un circuit de rafraichissement intégré dans le contrôleur mémoire, comme pour les autres DRAM. La seule différence est que la machine à état est plus complexe. Pour rappel, une requête de lecture/écriture se fait en trois étapes maximum : une commande PRECHARGE pour précharger le tampon de ligne, une commande ACT qui fixe l'adresse de ligne, et enfin une commande READ/WRITE avec l'adresse de colonne. Et ces commandes sont séparées par des '''délais mémoire''' bien précis. Par exemple, je prends des chiffres arbitraires : il faut attendre 2 cycles entre une commande ACT et une commande READ, 6 cycles avant deux commandes WRITE consécutives, etc. La gestion des délais mémoire rend la conception du séquenceur plus complexe. Et certaines de ces commandes peuvent être facultatives. Par exemple, en cas de succès de page, les commandes PRECHARGE et ACT ne doivent pas être envoyées, seule les commandes READ/WRITE le sont. Le contrôleur doit toujours '''détecter les succès et défauts de page''' et agir en fonction. Il faut aussi tenir compte des commandes SDRAM anticipées, à savoir que l'on peut envoyer des commandes avant que la précédente soit terminée. Les commandes anticipées sont idéales dans le cas où des accès successifs se font dans des banques différentes. Pour les exploiter au mieux, le contrôleur mémoire doit donc détecter si des accès successifs se font dans des banques différentes, ou dans la même banque, pour décider d'envoyer des commandes anticipées ou non. Cette '''détection des conflits de banque''' complexifie le séquenceur. ===La politique de gestion du tampon de ligne=== Plus haut, nous avons parlé des mémoires FPM, qui ont introduit quelques optimisations pour utiliser au mieux le tampon de ligne. Il se trouve que les SDRAM conservent ces optimisations. Il y a toujours cette notion de succès de page et de défaut de page, suivant que deux accès se font dans la même ligne ou dans deux lignes différentes. Du moins, c'est le cas si le séquenceur mémoire est coopératif. En effet, il peut fonctionner de plusieurs manières, dont les plus extrêmes sont appelés la politique de la page fermée et la politique de la page ouverte. Voyons à quoi elles correspondent. Avec la '''politique de la page fermée''', chaque accès mémoire est suivi d'une commande PRECHARGE, qui ferme la ligne courante et précharge les lignes de bits. Même si deux accès consécutifs se font dans la même ligne, la ligne est fermée et ré-ouverte entre deux accès mémoire. En clair : l'optimisation introduite par les mémoires FPM est désactivée, le contrôleur mémoire fait exprès de ne pas en profiter. On appelle cette méthode la close ''page autoprecharge''. Cette méthode réduit grandement les performances pour les accès à des adresses consécutives, mais fonctionne à merveille si les accès sont "aléatoires", à savoir qu'ils se font sans régularités évidentes. Son seul avantage est que l'implémentation du séquenceur mémoire est très simple. En effet, le séquenceur mémoire se passe complétement de la table des banques, du comparateur de ligne, et de tous les circuits nécessaires pour vérifier les succès ou défauts de page. De plus, le séquenceur mémoire profite grandement des commandes READA et WRITEA, qui fusionnent une commande READ/WRITE avec une commande PRECHARGE. Le séquenceur mémoire a juste à envoyer des commandes ACT, READA, WRITEA et PREFETCH à la mémoire, pas besoin des commandes PRECHARGE, READ ou WRITE. A l'opposé, la '''politique de la page ouverte''' ne ferme pas automatiquement la ligne. Elle la laisse ouverte, en espérant que le prochain accès mémoire se fasse dans cette ligne. Lorsqu'un nouvel accès mémoire arrive, elle doit détecter les succès ou défauts de page et agir en fonction. En cas de défaut de page, la ligne est fermée, le séquenceur mémoire envoie une commande PRECHARGE, puis l'accès suivant effectue les deux commandes ACT + READ ou WRITE. En cas de succès de page, les commandes PRECHARGE et ACT ne sont pas envoyées, seules la commande READ ou WRITE l'est. Un désavantage est que le contrôleur mémoire doit inclure une table des banques et un comparateur, comme vu plus haut dans la section sur les mémoires FPM. un autre défaut est que garder une ligne ouverte consomme beaucoup d'énergie, comparé à un simple état de PRECHARGE. En conséquence, il est préférable de fermer les lignes dès que possible. Par contre, les performances sont d'autant meilleures que les accès mémoire consécutifs à une même ligne soient assez fréquents. Si les accès mémoire sont aléatoires, les performances sont moins bonnes. La politique de la page fermée fermait les lignes en avance, avec des commandes READA ou WRITEA, avant même que l'accès suivant démarre. Avec la politique de la page ouverte, on doit attendre pour détecter un défaut de page, puis fermer la ligne avec une commande PRECHARGE séparée. La ligne est donc fermée avec un peu temps de retard, et envoyer deux commandes au lieu d'une prend plus de temps. Les contrôleurs mémoires basiques utilisent une des deux solutions précédentes. Soit la page est toujours fermée, soit elle est toujours laissée ouverte jusqu'à ce qu'un accès mémoire la referme. Mais les contrôleurs plus évolués tentent de prédire s'il faut fermer ou laisser ouvertes les pages ouvertes. La méthode la plus simple attend un temps prédéterminé avant de fermer la ligne. Une autre solution regarde le tout dernier accès. On peut très bien décider de laisser la ligne ouverte si l'accès mémoire précédent était une rafale, et fermer sinon. Une solution plus complexe mémorise les N derniers accès et en déduit s'il faut fermer ou non la prochaine ligne. On peut mémoriser si l'accès en question a causé la fermeture d'une ligne avec un bit. Mémoriser les N derniers accès demande d'utiliser un simple registre à décalage. Pour chaque valeur de ce registre, il faut prédire si le prochain accès demandera une ouverture ou une fermeture. Une solution simple fait la moyenne des bits à 1 dans ce registre : si plus de la moitié des bits est à 1, on laisse la ligne ouverte et on ferme sinon. Pour améliorer un petit peu l'algorithme, on peut faire en sorte que les bits des accès mémoires les plus récents aient plus de poids dans le calcul de la moyenne. Il existe sans doute d'autres solutions plus évoluées, mais il est difficile de savoir ce qu'il y a dans les contrôleurs de SDRAM modernes. : Le fait de laisser ouverte une ligne ou au contraire de la fermer systématiquement, se fait pour chaque banque. ===Les banques et l'entrelacement mémoire des SDRAM=== Les SDRAM incorporent plusieurs banques à l'intérieur de leurs circuits. Pour rappel, une banque est une sous-mémoire, avec ses propres décodeurs, son tampon de ligne, ses multiplexeurs de colonnes, etc. Pour le dire autrement, une SDRAM regroupe plusieurs mémoires séparées dans un même circuit intégré, les mémoires en question étant des banques. [[File:Arrangement vertical.jpg|centre|vignette|upright=2.5|Mémoire multi-banques.]] La présence de plusieurs banques impacte la gestion des lignes ouvertes/fermées. En effet, chaque banque a son propre tampon de ligne, ce qui fait que la gestion des lignes se fait indépendamment dans chaque banque. Le séquenceur mémoire doit donc se souvenir des lignes actives dans chaque banque. Pour cela, il mémorise ces lignes dans une petite mémoire : la '''table des banques''', aussi appelée ''bank status memory''. Pour détecter un succès ou un défaut, le contrôleur doit extraire la ligne de l'adresse, mais aussi le numéro de banque. Il envoie alors le numéro de banque à la table des banques, sur son entrée d'adresse. Il récupère alors le numéro de la ligne active sur les sorties de données. Il compare alors ce numéro de ligne avec le numéro de ligne de l'adresse envoyée par le processeur. C'est un succès si les deux sont égales, un défaut sinon. [[File:Controleur mémoire FPM avec plusieurs banques.jpg|centre|vignette|upright=2|Contrôleur mémoire FPM avec plusieurs banques.]] <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les mémoires RAM dynamiques (DRAM) | prevText=Les mémoires RAM dynamiques (DRAM) | next=Les mémoires associatives | nextText=Les mémoires associatives }} </noinclude> 93vg70tcsacm3aj9znbss1788mo04ri 764684 764683 2026-04-23T19:15:06Z Mewtow 31375 /* Le séquenceurs mémoire pour les SDRAM/DDR */ 764684 wikitext text/x-wiki Les mémoires ROM ou SRAM ont généralement une interface simple, à laquelle le processeur peut s'interfacer directement. Mais pour les DRAM, ce n'est pas le cas. Les DRAM utilisent un bus d'adresse multiplexé, où l'adresse est envoyée en deux fois. Connecter le processeur directement sur une DRAM n'est pas pratique : le bus d'adresse du processeur et celui de la mémoire ne collent pas. Les DRAM doivent aussi être rafraichies régulièrement. Le rafraichissement mémoire peut être délégué au processeur, mais c'est loin d'être idéal. Et il y a bien d'autres raisons qui font que le processeur ne peut pas s'interfacer facilement avec les mémoires DRAM. Pour gérer ces problèmes, les mémoires DRAM ne sont pas connectées directement au processeur. À la place, on ajoute un intermédiaire entre le processeur et la mémoire : le '''contrôleur mémoire externe'''. Son but est de montrer au processeur une interface simple, semblable à celle d'une SRAM classique, alors qu'il commande une mémoire DRAM à l'interface plus complexe. Il est placé sur la carte mère ou dans le processeur, et ne doit pas être confondu avec le contrôleur mémoire intégré dans la mémoire. Ce chapitre va expliquer quels sont les rôles du contrôleur mémoire, son interface et ce qu'il y a à l'intérieur. Dans ce chapitre, quand nous parlerons de ''contrôleur mémoire'', cela fera systématiquement référence au contrôleur mémoire externe. Et avant de poursuivre, sachez qu'il est difficile de faire des généralités sur les contrôleurs mémoire, car les mémoires DRAM elles-mêmes sont assez différentes les unes des autres. Entre une mémoire EDO, une mémoire SDR, une mémoire DDR et une DRAM asynchrone, les contrôleurs mémoires seront fortement différents. Aussi, il y a aura une différence entre un contrôleur pour une DRAM asynchrone et un contrôleur pour une mémoire EDO, une mémoire SDRAM, etc. J'ai choisit de vous séparer les contrôleurs mémoire pour les DRAM asynchrones de ceux pour les SDRAM/DRR. ==Le contrôleur d'une DRAM asynchrone== Les premières DRAM asynchrones avaient des contrôleurs mémoires dédiés, qui étaient séparés du processeur et du ''chipset'' de la carte mère. Par exemple, les composants Intel 8202, Intel 8203 et Intel 8207 étaient des contrôleurs mémoire pour DRAM asynchrones qui étaient vendus dans des boitiers DIP et étaient soudés sur la carte mère. Par la suite, ils ont été intégrés au ''chipset'' de la carte mère pendant les décennies 90-2000. Après les années 2000, ils ont été intégrés dans les processeurs. Leurs fonctions étaient le multiplexage de l'adresse et le rafraichissement mémoire. Ils recevaient une adresse mémoire complète, qu'ils découpaient une adresse de ligne et une adresse de colonne, envoyées l'une après l'autre sur le bus mémoire. Pour le rafraichissement mémoire, ils rafraichissaient la DRAM régulièrement, de manière automatique, entre deux accès mémoire normaux. Le processeur n'avait ainsi plus à rafraichir la mémoire lui-même, cette fonction était déléguée au contrôleur de DRAM. Ils étaient connectés au bus d'adresse et de commande, avec éventuellement des relations indirectes avec le bus de données. ===L'interface d'un contrôleur de DRAM asynchrone=== L'interface du contrôleur mémoire décrit ses broches d'entrées/sorties et leur signification. Elle est généralement très simple et contient deux ports : un connecté au processeur, un autre connecté à la DRAM. Cela trahit d'ailleurs son rôle principal, qui est de transformer les requêtes de lecture/écriture provenant du processeur en une suite de commandes acceptée par la mémoire. Le port connecté à la DRAM est connecté ua bus d'adresse et au bus de commande. Le bus de données est lui relié au processeur et/ou au bus système. Un accès mémoire provenant du processeur contient une adresse à lire/écrire, le bit R/W qui indique s'il faut faire une lecture ou une écriture, et éventuellement une donnée à écrire. Mais, nous avons vu que les accès mémoires sur une DRAM sont multiplexés : on envoie l'adresse en deux fois : la ligne d'abord, puis la colonne. De plus, il faut générer les signaux RAS, CAS et bien d'autres. Le tout est illustré ci-dessous. [[File:Contrôleur mémoire.png|centre|vignette|upright=2|Contrôleur mémoire externe.]] Un point important est que les DRAM asynchrones n'ont pas d'entrée ''Chip Select'' ou d'entrée ''Output Enable''. Les signaux RAS et CAS remplacent en quelque sorte ces deux signaux. Le bit RAS fait office de ''Chip Select'', le bit CAS fait office d'''Output Enable''. Pour certains contrôleurs de DRAM, il faut ajouter l''''interface électrique''', qui traduit les signaux du processeur en signaux compatibles avec la mémoire. Il est en effet très fréquent que la mémoire et le processeur n'utilisent pas les mêmes tensions pour coder un bit, ce qui fait qu'elles ne sont pas compatibles. Dans ce cas, le contrôleur mémoire fait la conversion. Le contrôleur DRAM peut fonctionner en mode synchrone ou asynchrone, du point de vue du processeur. Quand il fonctionne en mode synchrone, il permet d'interfacer un processeur synchrone avec une mémoire DRAM asynchrone. Un point important est que le contrôleur DRAM sert d'intermédiaire entre une mémoire DRAM et "le reste du monde". Il ne fait pas forcément office d'intermédiaire entre DRAM et processeur, mais peut aussi faire l'intermédiaire entre la DRAM et un bus système, entre une DRAM et le ''Video Display Controler'' d'une carte graphique, ou n'importe quel autre composant électronique qui utilise cette DRAM. ===Le générateur de ''timings'' et la traduction d'adresse=== Le contrôleur mémoire doit traduire les adresses du processeur en adresses compatibles avec la mémoire. Et la traduction est assez variable, suivant que le bus mémoire est un bus normal, un bus multiplexé, ou partiellement multiplexé. Nous avons vu ces trois types de bus mémoire dans le chapitre sur l'interface des mémoires, mais nous ferons quelques rappels rapides. Avec un ''bus totalement multiplexé'', le bus d'adresse et le bus de données sont fusionnés. Dans ce cas, on peut envoyer soit une adresse, soit lire/écrire une donnée sur le bus, mais on ne peut pas faire les deux en même temps. Un bit ALE indique si le bus est utilisé en tant que bus d'adresse ou bus de données. Le contrôleur mémoire gère cette situation, en fixant le bit ALE et en envoyant séparément adresse et donnée pour les écritures. [[File:Bus multiplexé avec bit ALE.png|centre|vignette|upright=2|Bus multiplexé avec bit ALE.]] Avec un ''bus d'adresse multiplexé'', l'adresse est découpée en une adresse de ligne et une adresse de colonne, envoyées l'une après l'autre. Le contrôleur mémoire prend en entrée une adresse mémoire complète, la découpe en deux, et envoie chaque morceau au bon moment. Pour cela, il suffit d'un registre pour mémoriser l'adresse et d'un multiplexeur. Le multiplexeur choisit soit les bits de poids fort de l'adresse, soit ceux de poids faible. Les premiers correspondent à l'adresse de ligne, les autres à l'adresse de colonne. La commande du multiplexeur est le fait d'un petit circuit séquentiel, qui génère aussi les signaux CAS et RAS. Au premier cycle, il met le signal RAS à 1, met le CAS à 0, et configure le MUX pour sélectionner les bits de poids fort. Au second cycle, il génère un signal CAS à 1, met le RAS à 0 et configure le MUX pour sélectionner les bits de poids faible. Le circuit en question est appelé le générateur de ''timings''. [[File:Controleur de DRAM simple, sans rafraichissement mémoire.png|centre|vignette|upright=2|Contrôleur de DRAM simple, sans rafraichissement mémoire.]] Le générateur de ''timings'' est un circuit séquentiel qui implémente une petite machine à état. Il est très simple sur une mémoire DRAM asynchrone basique, mais il est plus complexe sur les mémoires FPM, EDO, quartet, et autres. Le regroupement des multiplexeurs d'adresse et du générateur de ''timings'' est appelé le '''séquenceur mémoire'''. C'est le séquenceur mémoire qui traduit la requête processeur en commande DRAM, le reste du contrôleur est dédié au rafraichissement mémoire ou à d'autres fonctions facultatives. ===Le rafraichissement mémoire=== La gestion du rafraichissement mémoire est la fonction principale du contrôleur DRAM. Pour gérer le rafraichissement mémoire, le contrôleur mémoire intègre deux compteurs, un pour gérer l'adresse à rafraichir, l'autre pour gérer l'intervalle de temps entre deux rafraichissements. Le rafraichissement se fait à intervalle régulier, tous les x microsecondes. Pour déclencher le rafraichissement au bon moment, le contrôleur mémoire contient un ''Refresh Timer'', aussi appelé le '''compteur de rafraichissement'''. Il est initialisé avec le temps entre deux rafraichissements, une adresse est rafraichie quand ce compteur atteint 0. Le rafraichissement mémoire balaye la mémoire adresse par adresse. Pour savoir à quelle adresse il en est rendu, le contrôleur mémoire utilise un '''compteur d'adresse'''. Il contient la prochaine adresse à rafraichir, aussi appelée l'adresse de rafraichissement. Régulièrement, l'adresse dans ce compteur est envoyée à la RAM, pour une lecture. Mais la donnée lue n'est pas envoyée sur le bus de donnée, soit parce que la RAM est prévue pour, soit parce que le contrôleur désactive son bit ''output enable''. Dans le second cas, la RAM fait la lecture en interne, mais se déconnecte du bus de donnée, perdant la donnée lue dans le néant. Pour envoyer l'adresse de rafraichissement sur le bus d'adresse, il faut rajouter un multiplexeur, qui choisit entre l'adresse normale et l'adresse de rafraichissement. [[File:Controleur de DRAM avec rafraichissement mémoire.png|centre|vignette|upright=2|Controleur de DRAM avec rafraichissement mémoire.]] Le multiplexeur ne doit cependant pas être configuré si une adresse est déjà en cours de transfert. Pour cela, un circuit d'arbitrage se débrouille pour éviter qu'un accès mémoire soit interrompu par une demande de rafraichissement et inversement. Il peut être inclus dans le séquenceur mémoire ou séparé de celui-ci. [[File:Controleur mémoire, intérieur simplifié.png|centre|vignette|upright=2.5|Contrôleur mémoire, intérieur simplifié.]] Il faut noter que le rafraichissement mémoire peut être délégué non pas au contrôleur mémoire, mais au processeur où à la DRAM elle-même. Quand elle est le fait du processeur, celui-ci incorpore un ''refresh timer'' et un compteur d'adresse. Un exemple est celui du processeur Zilog Z80, qui implémentait des compteurs internes pour gérer le rafraichissement mémoire. On peut considérer que le processeur incorpore alors le contrôleur mémoire, au moins partiellement. Il est aussi possible de déléguer le rafraichissement au logiciel (certains jeux vidéos Amiga ou Commodore faisaient cela pour la mémoire vidéo). Quand la DRAM elle-même s'occupe de son propre rafraichissement, c'est elle qui intègre un ''refresh timer'' et le compteur d'adresse. ===Le décodage d'adresse=== Le contrôleur mémoire gère aussi le '''décodage d'adresse'''. pour rappel, celui-ci est utilisé quand on combine plusieurs chips mémoire ensemble, pour combiner leurs capacités mémoire. Par exemple, on peut combiner 4 chips mémoires de 1 mébioctet chacun, pour que le processeur voit comme 4 mébioctets de RAM unique. Le premier mébioctet est placé dans le premier chip mémoire, le second mébioctet dans le second chip, etc. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Pour cela, on active le chip mémoire adéquat, en fonction de l'adresse à consulter. Les autres chips mémoire sont désactivés pendant l'accès mémoire. En théorie, activer ou désactiver un chip mémoire se fait en utilisant son entrée ''Chip Select''. Il faut noter que si les SDRAM disposent bien d'un signal ''Chip Select'', ce n'est pas le cas des mémoires RAM asynchrones. A la place, ce sont les signaux RAS qui font office de ''Chip Select''. Une RAM asynchrone est activée quand son signal RAS lui demande de lire une ligne, elle est désactivée sinon. Mais c'est un détail. Toujours est-il que les signaux ''Chip Select'', ou leurs équivalents, sont générés par le contrôleur de DRAM, à partir des bits de poids fort de l'adresse. Par exemple, avec 4 chips mémoire, les deux bits de poids fort de l'adresse sont utilisés pour sélectionner le chip mémoire adéquat. Le contrôleur mémoire doit avoir plusieurs sorties ''Chip Select'', une par chip mémoire. Et le nombre de ces sorties limite le nombre de chips mémoire qu'on peut combiner. Par exemple, s'il y a seulement 4 sorties ''Chip Select'', on ne pourra brancher que 4 chips mémoire dessus. Sauf à ruser, avec un arrangement horizontal, mais cela n'est pas le ressort du contrôleur mémoire. [[File:Td6bfig3.png|centre|vignette|upright=2|Comparaison entre arrangement horizontal (à gauche) et arrangement vertical (à droite).]] ===Exemple : l'Intel 8202-8203=== L'Intel 8202 et le 8203 étaient des contrôleurs de mémoire DRAM, parmi les plus simples qui soient. Ils avaient une entrée d'adresse de 12 bits, ce qui permettait d'adresser 4 kibioctets de RAM. Ils fournissaient en sortie une adresse multiplexée sur 6 bits, envoyée en deux fois. Ils avaient donc 12 entrées d'adresse, 6 sorties d'adresse, un signal RAS, un signal CAS. Les adresses présentées en entrées n'étaient pas mémorisées dans des registres, ce qui fait qu'elles devaient être maintenues durant toute la durée de l'accès mémoire. Le processeur ne pouvait donc pas se déconnecter du bus d'adresse pendant l'accès mémoire, peu importe sa durée. Le 8202 contenait aussi un compteur de rafraichissement. Rappelons que sur les DRAM asynchrones, le rafraichissement se fait ligne par ligne. Le contrôleur mémoire a juste à présenter l'adresse de ligne, il n'a pas à envoyer l'adresse de colonne. La commande de rafraichissement se fait en mettant le signal RAS à 0, mais en laissant le CAS à 1 (je rappelle que les signaux sont actifs à 0). Le compteur d'adresse de rafraichissement a donc juste à mémoriser l'adresse de ligne. Le séquenceur mémoire était précédé par un circuit d'arbitrage, non-représenté dans le schéma ci-dessous. La microarchitecture de l'Intel 8202 est la suivante : [[File:Microarchitecture de l'Intel 8202.png|centre|vignette|upright=2|Microarchitecture de l'Intel 8202.]] Le 8202 avait une entrée pour un signal d'horloge, ainsi qu'un ''Chip Select'' un peu particulier. Si le signal CS passait à 0 lors d'un accès mémoire, le 8202/8203 ne se désactivait qu'une fois l'accès mémoire terminé. On ne pouvait pas l'interrompre pendant un accès mémoire, même en changeant le bit CS. Le signal d'horloge était utilisé pour commander le ''refresh timer''. Pour commander les lectures et écriture, il recevait en entrée un bit ''Write Request'' et un bit ''Read Request'', qui demandent respectivement une écriture et une lecture. En sortie, on trouvait un unique bit R/W qui valait 0 pour une lecture et 1 pour une écriture. Il avait aussi un bit d'entrée pour forcer le rafraichissement mémoire. S'il est à 1, la mémoire rafraichie l'adresse envoyée par le processeur. Le 8202 pouvait être connecté sur 1 à 4 chips mémoire, ce qui permettait d'adresser au maximum 16 kibioctets de RAM. Les 4 chips ne sont pas accédés en parallèle, un seul l'est à chaque fois. Pour le décodage d'adresse, le 8202 dispose de deux bits BO et B1 pour sélectionner le chip adéquat, ainsi que 4 sorties RAS pour activer la banque adéquate. On rappelle que le signal RAS remplace le signal ''Chip Select''. C'est le séquenceur mémoire qui se charge de générer ces signaux RAS, à partir des deux bits B0 et B1 (qui sont techniquement des bits d'adresse). Pour communiquer avec le processeur, l'Intel 8202 disposait de deux bits XACK et SACK. SACK indiquait au processeur que le 8202/8203 est en train de faire un accès mémoire et qu'il est indisponible pour un second accès mémoire. Cela permet de bloquer le processeur tant que le 8202 est indisponible. Le signal XACK indique que l'accès mémoire précédent est terminé et que : soit la donnée lue est présente sur le bus de données, soit que l'écriture s'est terminée. : Le signal SACK est très utile sur les configurations multiprocesseurs. Un processeur peut démarrer un accès mémoire, le signal SACK indiquera au second processeur qu'il doit attendre que l'accès soit terminé pour que ce soit son tour. ==Les contrôleurs de DRAM asynchrones évolués== L'Intel 8202 était un contrôleur mémoire basique, comme beaucoup d'autres à cette époque. Mais Intel a vendu des contrôleurs mémoires plus complexes. Par exemple, l'Intel 8207 était un contrôleur mémoire bien plus avancé que les deux précédents. Passons sur certains détails, comme le fait qu'il pouvait gérer jusqu'à 256 kibioctets de DRAM. Au-delà de ça, il y avait des fonctionnalités bien plus intéressantes, à savoir : un support de l'ECC, il était double port, il permettait de simuler une DRAM synchrone à partir d'une DRAM asynchrone. Il n'était pas le seul dans ce cas et des contrôleurs de DRAM très évolués étaient capables de faire des merveilles. Voyons comment cela était possible. ===Les contrôleurs mémoire synchrone=== Il est parfaitement possible d'utiliser un contrôleur mémoire synchrone avec une DRAM asynchrone. A vrai dire, le contrôleur DRAM peut complétement simuler une mémoire synchrone alors que la DRAM associée est asynchrone. La traduction asynchrone vers synchrone se fait en ajoutant des registres sur le bus mémoire, notamment sur le bus de données et le bus d'adresse/commande. Nous avions détaillé cela dans le chapitre sur les SRAM, c'est la même chose avec une mémoire DRAM. Sauf que cette fois-ci, le contrôleur mémoire doit aussi être prévu pour. [[File:Controleur mémoire synchrone utilisé avec une DRAM asynchrone.png|centre|vignette|upright=2|Contrôleur mémoire synchrone utilisé avec une DRAM asynchrone]] Les deux-trois registres illustrés plus haut peuvent être intégrés directement dans le contrôleur mémoire, que ce soit totalement ou en partie. Le strict minimum pour avoir un contrôleur mémoire synchrone est que celui-ci doit mémoriser adresse et commandes dans un registre. Par exemple, le 8207 d'Intel était capable de mémoriser les requêtes processeurs dans un registre interne, mais il fallait utiliser deux registres séparés pour le bus de données. Les deux registres étaient alors commandés par le contrôleur mémoire. Il est cependant possible d'aller plus loin et d'intégrer les registres du bus de données dans le contrôleur mémoire. [[File:Controleur mémoire DRAM synchrone.png|centre|vignette|upright=2|Contrôleur mémoire DRAM synchrone.]] : Il faut noter que cette fonctionnalité est parfois disponible sur les SRAM. En clair, on peut associer une SRAM asynchrone avec un contrôleur de SRAM synchrone. Le contrôleur de SRAM se charge alors de simuler une SRAM synchrone à partir de la SRAM asynchrone. Utiliser un contrôleur mémoire synchrone a de nombreux avantages, l'un d'entre eux étant lié aux ''wait state''. Quand le processeur envoie une requête de lecture/écriture à la mémoire RAM, celle-ci met plusieurs cycles d'horloge à répondre. Et pendant ce temps, le processeur... attend. Et pendant ce temps d'attente, il doit maintenir l'adresse mémoire sur le bus d'adresse. Les cycles d'horloge perdus à attendre la mémoire RAM étaient appelés des '''''Wait states'''''. Utiliser un contrôleur mémoire synchrone d'éliminer les ''wait state'', dans une certaine mesure. Avec un contrôleur mémoire synchrone, le processeur envoie l'adresse, mais c'est le contrôleur mémoire qui la maintient sur le bus d'adresse. Le processeur peut envoyer l'adresse et la donnée à écrire, elles sont recopiées dans les registres, et le controleur mémoire y a accès sans que le processeur doive les maintenir. Le processeur peut se déconnecter du bus mémoire et faire du travail dans son coin pendant que le contrôleur mémoire accède à la DRAM. Les ''wait state'' disparaissent alors, du moins du point de vue du processeur. ===La gestion de l'ECC=== L''''ECC''' peut être géré dans le contrôleur mémoire. Pour cela, on couple les registres mentionnés dans la section précédente, avec un circuit de détection et de correction d'erreur. Le circuit d'ECC peut, comme les registres synchrones, être intégré dans le contrôleur mémoire, ou au contraire être situé dans un circuit séparé. Si le circuit d'ECC est séparé du contrôleur mémoire, il communique avec lui, histoire que le contrôleur mémoire puisse signaler toute erreur de parité ou d'ECC au processeur. [[File:Controleur mémoire synchrone avec ECC intégré.png|centre|vignette|upright=2|Controleur mémoire synchrone avec ECC intégré]] Reprenons l'exemple du 8207 d'Intel. Le contrôleur mémoire 8207 gère le bus d'adresse et de commande, mais n'a pas de connexions directes avec le bus de données. Il ne peut donc pas prendre en charge l'ECC. Il avait besoin d'être couplé avec un circuit d'ECC séparé, relié au bus de données : l'Intel 8206. Le 8026 prenait en entrée : 16 bits de données et 8 bits d'ECC. Il fournissait en sortie 16 bits de données après correction d'erreur, les 8 bits d'ECC pour indiquer qu'une erreur a été détectée mais pas corrigée, ainsi que des bits de parité. Le 8206 détectait/corrigeait les erreurs et générait les bits d'ECC, mais il communiquait avec le contrôleur mémoire pour cela. [[File:8207 avec ECC.png|centre|vignette|upright=2|8207 avec ECC]] La détection/correction d'erreur était appliquée à la fois pour les accès mémoire et pour les rafraichissements mémoire. Lors d'un rafraichissement mémoire, la donnée rafraichie est lue et réécrite. Avec l'ECC activé et configuré correctement, le rafraichissement passe par le bus de données. Au lieu d'avoir un cycle de lecture-écriture interne à la DRAM, on a un cycle de lecture-correction-écriture qui utilise le 8206. La donnée lue est envoyée sur le bus de données, puis le 8206 corrige une éventuelle erreur, et la donnée corrigée est alors réécrite en mémoire. Au passage, si une erreur non-correctible est détectée, le 8206 ne fait rien, l'erreur est ignorée. La gestion de l'erreur sera retardée jusqu'à une lecture ultérieure. Et encore : si lecture ultérieure il y a. Si la donnée est écrasée par une écriture, la donnée corrompue sera simplement écrasée et disparaitra sans avoir pu faire le moindre dégât. Mais pour cela, le 8206 doit communiquer avec le contrôleur mémoire, pour savoir s'il est dans un cycle de rafraichissement ou un accès mémoire normal. Il prévient le 8207 lors d'une erreur, et c'est ce dernier qui décide si l'erreur doit être prise en compte ou ignorée. C'est seulement lors d'un accès mémoire normal que le processeur est prévenu qu'une erreur de parité/autre a eu lieu. ===Les contrôleurs mémoires multiports=== Les '''contrôleur mémoire multiport''' disposent de plusieurs ports, chacun permettant de traiter un accès mémoire. Ils peuvent simuler une mémoire multiport à partir d'une DRAM monoport. Évidemment, la simulation n'est pas parfaite. Des accès mémoire simultanés, envoyés en même temps sur différents ports, sont en réalité exécutés un par un, pas en même temps. Il y a donc une petite pénalité en termes de performances, mais elle est mineure. Encore une fois, nous allons reprendre l'exemple du 8207. Il avait deux ports séparés, et était prévu pour fonctionner dans un système à deux processeurs. L'usage de deux ports séparés permettait de partager une unique mémoire DRAM entre deux processeurs. Le partage se faisait en interfaçant deux processeurs sur le contrôleur mémoire, chacun étant connecté à un port. Lors d'une lecture, il redirigeait la donnée lue vers le bon processeur, en configurant le bus de données correctement. Le contrôleur mémoire recevait des requêtes mémoire de deux processeurs, mais il les exécutait une à la fois. S'il recevait deux requêtes en même temps, l'une d'entre elles était mise en attente. Le contrôleur mémoire doit arbitrer les accès à la mémoire, et faire en sorte que les deux processeurs aient accès à la mémoire à tour de rôle. Et non seulement il doit arbitrer les deux ports, mais il y a aussi un troisième port interne au contrôleur mémoire : le rafraichissement mémoire ! Pour cela, le circuit d'arbitrage qui choisissait entre rafraichissement mémoire et accès mémoire, est amélioré de manière à gérer un second port. Le circuit d'arbitrage donne l'accès au séquenceur mémoire à un port sélectionné. L'arbitrage était configurable, avec deux options : soit le port A est privilégié sur le port B, soit le port le plus récemment accédé à la priorité. Les deux ports pouvaient être configurés pour fonctionner soit de manière asynchrone, soit de manière synchrone. Il était aussi possible de configurer l'ECC, des options liées à la fréquence du processeur et de la RAM, ainsi que de nombreuses options liées au rafraichissement. Pour cela, le 8207 contenait un registre de configuration interne, programmable en fournissant les entrées adéquates. Tout ce qui vient d'être dit se généralise avec plus de deux processeurs. Le 8207 ne permettait pas ça, mais les contrôleurs mémoire des PC modernes en sont capables. Ils peuvent gérer plusieurs dizaines de processeurs facilement. ==Le contrôleur mémoire d'une DRAM ''Fast Page Mode''== Les mémoires DRAM classiques sont des mémoires à tampon de ligne, mais qui sont assez mal utilisées. Notamment, tout accès mémoire se fait en deux phases : un accès pour sélectionner la ligne, un autre pour sélectionner la colonne. Les mémoires ''Fast Page Mode'' permettent d'optimiser le tout. Elles permettent de faire plusieurs accès successifs à la même ligne, à des colonnes différentes. Et le contrôleur mémoire doit être adapté pour cela. [[File:Sélection d'une ligne sur une mémoire FPM ou EDO.png|centre|vignette|upright=2|Sélection d'une ligne sur une mémoire FPM ou EDO.]] Le contrôleur mémoire doit détecter que deux accès mémoire consécutifs se font dans la même ligne. Si deux accès consécutifs accèdent à la même ligne, on doit juste changer de colonne et altérer le signal CAS. C'est un ''succès de tampon de ligne'', aussi appelé un '''succès de page'''. Si deux accès consécutifs accèdent à une ligne différente, c'est un ''défaut de tampon de ligne'', aussi appelé un '''défaut de page'''. Il faut alors changer de ligne, en altérant les signaux RAS et en envoyant une adresse de ligne. Pour détecter les succès ou défauts de page, il faut ajouter un circuit spécialisé dans le contrôleur mémoire. Il mémorise la ligne ouverte, et plus précisément son adresse de ligne (numéro de banque inclut). A chaque requête processeur, il compare l'adresse de ligne recue avec celle déjà ouverte. C'est un succès si les deux sont égales, un défaut si elles sont différentes. Le circuit qui fait cette comparaison est appelé le '''décodeur de commande'''. Il prévient le séquenceur mémoire en cas de succès de page, grâce à un signal de un bit, qui vaut 0 en cas de défaut de page et 1 en cas de succès. Le séquenceur mémoire décide alors comment gérer les signaux RAS et CAS, ainsi que l'envoi des adresses de ligne/colonne. [[File:Controleur mémoire d'une FPM-DRAM.png|centre|vignette|upright=2|Controleur mémoire d'une FPM-DRAM]] ==Le contrôleur mémoire d'une SDRAM ou d'une DDR== l'intérieur d'un contrôleur de SDRAM ne change pas significativement d'un controleur de RAM asynchrone. Il regroupe toujours un séquenceur mémoire et une interface physique, un circuit pour le rafraichissement mémoire et un circuit d'arbitrage. Par contre, ses sorties changent pas mal. Contrairement aux mémoires DRAM basiques, les mémoires SDRAM sont cadencées par un signal d'horloge. Et ce signal d'horloge vient bien de quelque part. Pour cela, deux solutions : soit le contrôleur mémoire génère la fréquence qui commande la mémoire, soit il prend en entrée une fréquence de base qu'il multiplie pour obtenir la fréquence désirée. Les deux solutions sont équivalentes, si ce n'est que les circuits impliqués ne sont pas les mêmes. Dans le premier cas, le contrôleur doit embarquer un circuit oscillateur, qui génère la fréquence demandée. Dans l'autre cas, un simple multiplieur/diviseur de fréquence suffit et c'est généralement une PLL qui est utilisée pour cela. : Notez qu'il ne faut pas confondre la fréquence de la SDRAM et celle du contrôleur mémoire. Le contrôleur mémoire fonctionne à une vitesse assez élevée, en interne. Le port relié au processeur fonctionne à haute fréquence, généralement la même que celle du processeur. A vrai dire, de nos jours, il est intégré dans le processeur. Pour le décodage d'adresse, tout est plus simple sur les SDRAM, DDR inclues. Les chips de mémoire SDRAM et DDR disposent d'une entrée ''Chip Select'', ce qui facilite grandement le décodage d'adresse. Les bits de ''Chip Select'' sont générés par le contrôleur mémoire, et sont transmis sur le bus de commande. Le contrôleur de DRAM peut adresser un certain nombre de rangées, dispersés sur une ou plusieurs barrettes. La limite maximale dépend du contrôleur de DRAM, elle est souvent proche de 8 ou 16 rangées. Si on combine plusieurs barrettes de mémoire, il est possible de dépasser cette limite. Par exemple, pour un contrôleur de DRAM supportant maximum 8 rangées, 4 barrettes de 4 rangées chacune dépassent la limite. ===Le séquenceurs mémoire pour les SDRAM/DDR=== Le séquenceur mémoire existe toujours pour les mémoires SDRAM, c'est toujours un circuit séquentiel qui implémente une machine à état. Il traduit toujours une requête processeur en une séquence de commandes envoyées à des timings bien précis. Les commandes mémoires peuvent provenir de l'extérieur, mais aussi d'un circuit de rafraichissement intégré dans le contrôleur mémoire, comme pour les autres DRAM. La seule différence est que la machine à état est plus complexe. Pour rappel, une requête de lecture/écriture se fait en trois étapes maximum : une commande PRECHARGE pour précharger le tampon de ligne, une commande ACT qui fixe l'adresse de ligne, et enfin une commande READ/WRITE avec l'adresse de colonne. Et ces commandes sont séparées par des '''délais mémoire''' bien précis. Par exemple, je prends des chiffres arbitraires : il faut attendre 2 cycles entre une commande ACT et une commande READ, 6 cycles avant deux commandes WRITE consécutives, etc. La gestion des délais mémoire rend la conception du séquenceur plus complexe. Un point important est que dans certaines conditions, certaines de ces commandes peuvent être omises. Par exemple, en cas de succès de page, les commandes PRECHARGE et ACT ne doivent pas être envoyées, seule les commandes READ/WRITE le sont. Le contrôleur doit toujours '''détecter les succès et défauts de page''' et agir en fonction. La solution utilisée est la même que pour les mémoires FPM : il faut mémoriser quelle ligne est ouverte ou fermée. La seule différence avec les FPM est qu'il faut faire cela pour chaque banque mémoire ! En effet, il est possible d'ouvrir une ligne dans chaque banque. Il faut aussi tenir compte des commandes SDRAM anticipées, à savoir que l'on peut envoyer des commandes avant que la précédente soit terminée. Les commandes anticipées sont idéales dans le cas où des accès successifs se font dans des banques différentes. Pour les exploiter au mieux, le contrôleur mémoire doit donc détecter si des accès successifs se font dans des banques différentes, ou dans la même banque, pour décider d'envoyer des commandes anticipées ou non. Cette '''détection des conflits de banque''' complexifie le séquenceur. ===La politique de gestion du tampon de ligne=== Plus haut, nous avons parlé des mémoires FPM, qui ont introduit quelques optimisations pour utiliser au mieux le tampon de ligne. Il se trouve que les SDRAM conservent ces optimisations. Il y a toujours cette notion de succès de page et de défaut de page, suivant que deux accès se font dans la même ligne ou dans deux lignes différentes. Du moins, c'est le cas si le séquenceur mémoire est coopératif. En effet, il peut fonctionner de plusieurs manières, dont les plus extrêmes sont appelés la politique de la page fermée et la politique de la page ouverte. Voyons à quoi elles correspondent. Avec la '''politique de la page fermée''', chaque accès mémoire est suivi d'une commande PRECHARGE, qui ferme la ligne courante et précharge les lignes de bits. Même si deux accès consécutifs se font dans la même ligne, la ligne est fermée et ré-ouverte entre deux accès mémoire. En clair : l'optimisation introduite par les mémoires FPM est désactivée, le contrôleur mémoire fait exprès de ne pas en profiter. On appelle cette méthode la close ''page autoprecharge''. Cette méthode réduit grandement les performances pour les accès à des adresses consécutives, mais fonctionne à merveille si les accès sont "aléatoires", à savoir qu'ils se font sans régularités évidentes. Son seul avantage est que l'implémentation du séquenceur mémoire est très simple. En effet, le séquenceur mémoire se passe complétement de la table des banques, du comparateur de ligne, et de tous les circuits nécessaires pour vérifier les succès ou défauts de page. De plus, le séquenceur mémoire profite grandement des commandes READA et WRITEA, qui fusionnent une commande READ/WRITE avec une commande PRECHARGE. Le séquenceur mémoire a juste à envoyer des commandes ACT, READA, WRITEA et PREFETCH à la mémoire, pas besoin des commandes PRECHARGE, READ ou WRITE. A l'opposé, la '''politique de la page ouverte''' ne ferme pas automatiquement la ligne. Elle la laisse ouverte, en espérant que le prochain accès mémoire se fasse dans cette ligne. Lorsqu'un nouvel accès mémoire arrive, elle doit détecter les succès ou défauts de page et agir en fonction. En cas de défaut de page, la ligne est fermée, le séquenceur mémoire envoie une commande PRECHARGE, puis l'accès suivant effectue les deux commandes ACT + READ ou WRITE. En cas de succès de page, les commandes PRECHARGE et ACT ne sont pas envoyées, seules la commande READ ou WRITE l'est. Un désavantage est que le contrôleur mémoire doit inclure une table des banques et un comparateur, comme vu plus haut dans la section sur les mémoires FPM. un autre défaut est que garder une ligne ouverte consomme beaucoup d'énergie, comparé à un simple état de PRECHARGE. En conséquence, il est préférable de fermer les lignes dès que possible. Par contre, les performances sont d'autant meilleures que les accès mémoire consécutifs à une même ligne soient assez fréquents. Si les accès mémoire sont aléatoires, les performances sont moins bonnes. La politique de la page fermée fermait les lignes en avance, avec des commandes READA ou WRITEA, avant même que l'accès suivant démarre. Avec la politique de la page ouverte, on doit attendre pour détecter un défaut de page, puis fermer la ligne avec une commande PRECHARGE séparée. La ligne est donc fermée avec un peu temps de retard, et envoyer deux commandes au lieu d'une prend plus de temps. Les contrôleurs mémoires basiques utilisent une des deux solutions précédentes. Soit la page est toujours fermée, soit elle est toujours laissée ouverte jusqu'à ce qu'un accès mémoire la referme. Mais les contrôleurs plus évolués tentent de prédire s'il faut fermer ou laisser ouvertes les pages ouvertes. La méthode la plus simple attend un temps prédéterminé avant de fermer la ligne. Une autre solution regarde le tout dernier accès. On peut très bien décider de laisser la ligne ouverte si l'accès mémoire précédent était une rafale, et fermer sinon. Une solution plus complexe mémorise les N derniers accès et en déduit s'il faut fermer ou non la prochaine ligne. On peut mémoriser si l'accès en question a causé la fermeture d'une ligne avec un bit. Mémoriser les N derniers accès demande d'utiliser un simple registre à décalage. Pour chaque valeur de ce registre, il faut prédire si le prochain accès demandera une ouverture ou une fermeture. Une solution simple fait la moyenne des bits à 1 dans ce registre : si plus de la moitié des bits est à 1, on laisse la ligne ouverte et on ferme sinon. Pour améliorer un petit peu l'algorithme, on peut faire en sorte que les bits des accès mémoires les plus récents aient plus de poids dans le calcul de la moyenne. Il existe sans doute d'autres solutions plus évoluées, mais il est difficile de savoir ce qu'il y a dans les contrôleurs de SDRAM modernes. : Le fait de laisser ouverte une ligne ou au contraire de la fermer systématiquement, se fait pour chaque banque. ===Les banques et l'entrelacement mémoire des SDRAM=== Les SDRAM incorporent plusieurs banques à l'intérieur de leurs circuits. Pour rappel, une banque est une sous-mémoire, avec ses propres décodeurs, son tampon de ligne, ses multiplexeurs de colonnes, etc. Pour le dire autrement, une SDRAM regroupe plusieurs mémoires séparées dans un même circuit intégré, les mémoires en question étant des banques. [[File:Arrangement vertical.jpg|centre|vignette|upright=2.5|Mémoire multi-banques.]] La présence de plusieurs banques impacte la gestion des lignes ouvertes/fermées. En effet, chaque banque a son propre tampon de ligne, ce qui fait que la gestion des lignes se fait indépendamment dans chaque banque. Le séquenceur mémoire doit donc se souvenir des lignes actives dans chaque banque. Pour cela, il mémorise ces lignes dans une petite mémoire : la '''table des banques''', aussi appelée ''bank status memory''. Pour détecter un succès ou un défaut, le contrôleur doit extraire la ligne de l'adresse, mais aussi le numéro de banque. Il envoie alors le numéro de banque à la table des banques, sur son entrée d'adresse. Il récupère alors le numéro de la ligne active sur les sorties de données. Il compare alors ce numéro de ligne avec le numéro de ligne de l'adresse envoyée par le processeur. C'est un succès si les deux sont égales, un défaut sinon. [[File:Controleur mémoire FPM avec plusieurs banques.jpg|centre|vignette|upright=2|Contrôleur mémoire FPM avec plusieurs banques.]] <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les mémoires RAM dynamiques (DRAM) | prevText=Les mémoires RAM dynamiques (DRAM) | next=Les mémoires associatives | nextText=Les mémoires associatives }} </noinclude> appuk8joe2vgcepmkoazgk33xwj0jc3 764685 764684 2026-04-23T19:16:13Z Mewtow 31375 /* Le contrôleur mémoire d'une SDRAM ou d'une DDR */ 764685 wikitext text/x-wiki Les mémoires ROM ou SRAM ont généralement une interface simple, à laquelle le processeur peut s'interfacer directement. Mais pour les DRAM, ce n'est pas le cas. Les DRAM utilisent un bus d'adresse multiplexé, où l'adresse est envoyée en deux fois. Connecter le processeur directement sur une DRAM n'est pas pratique : le bus d'adresse du processeur et celui de la mémoire ne collent pas. Les DRAM doivent aussi être rafraichies régulièrement. Le rafraichissement mémoire peut être délégué au processeur, mais c'est loin d'être idéal. Et il y a bien d'autres raisons qui font que le processeur ne peut pas s'interfacer facilement avec les mémoires DRAM. Pour gérer ces problèmes, les mémoires DRAM ne sont pas connectées directement au processeur. À la place, on ajoute un intermédiaire entre le processeur et la mémoire : le '''contrôleur mémoire externe'''. Son but est de montrer au processeur une interface simple, semblable à celle d'une SRAM classique, alors qu'il commande une mémoire DRAM à l'interface plus complexe. Il est placé sur la carte mère ou dans le processeur, et ne doit pas être confondu avec le contrôleur mémoire intégré dans la mémoire. Ce chapitre va expliquer quels sont les rôles du contrôleur mémoire, son interface et ce qu'il y a à l'intérieur. Dans ce chapitre, quand nous parlerons de ''contrôleur mémoire'', cela fera systématiquement référence au contrôleur mémoire externe. Et avant de poursuivre, sachez qu'il est difficile de faire des généralités sur les contrôleurs mémoire, car les mémoires DRAM elles-mêmes sont assez différentes les unes des autres. Entre une mémoire EDO, une mémoire SDR, une mémoire DDR et une DRAM asynchrone, les contrôleurs mémoires seront fortement différents. Aussi, il y a aura une différence entre un contrôleur pour une DRAM asynchrone et un contrôleur pour une mémoire EDO, une mémoire SDRAM, etc. J'ai choisit de vous séparer les contrôleurs mémoire pour les DRAM asynchrones de ceux pour les SDRAM/DRR. ==Le contrôleur d'une DRAM asynchrone== Les premières DRAM asynchrones avaient des contrôleurs mémoires dédiés, qui étaient séparés du processeur et du ''chipset'' de la carte mère. Par exemple, les composants Intel 8202, Intel 8203 et Intel 8207 étaient des contrôleurs mémoire pour DRAM asynchrones qui étaient vendus dans des boitiers DIP et étaient soudés sur la carte mère. Par la suite, ils ont été intégrés au ''chipset'' de la carte mère pendant les décennies 90-2000. Après les années 2000, ils ont été intégrés dans les processeurs. Leurs fonctions étaient le multiplexage de l'adresse et le rafraichissement mémoire. Ils recevaient une adresse mémoire complète, qu'ils découpaient une adresse de ligne et une adresse de colonne, envoyées l'une après l'autre sur le bus mémoire. Pour le rafraichissement mémoire, ils rafraichissaient la DRAM régulièrement, de manière automatique, entre deux accès mémoire normaux. Le processeur n'avait ainsi plus à rafraichir la mémoire lui-même, cette fonction était déléguée au contrôleur de DRAM. Ils étaient connectés au bus d'adresse et de commande, avec éventuellement des relations indirectes avec le bus de données. ===L'interface d'un contrôleur de DRAM asynchrone=== L'interface du contrôleur mémoire décrit ses broches d'entrées/sorties et leur signification. Elle est généralement très simple et contient deux ports : un connecté au processeur, un autre connecté à la DRAM. Cela trahit d'ailleurs son rôle principal, qui est de transformer les requêtes de lecture/écriture provenant du processeur en une suite de commandes acceptée par la mémoire. Le port connecté à la DRAM est connecté ua bus d'adresse et au bus de commande. Le bus de données est lui relié au processeur et/ou au bus système. Un accès mémoire provenant du processeur contient une adresse à lire/écrire, le bit R/W qui indique s'il faut faire une lecture ou une écriture, et éventuellement une donnée à écrire. Mais, nous avons vu que les accès mémoires sur une DRAM sont multiplexés : on envoie l'adresse en deux fois : la ligne d'abord, puis la colonne. De plus, il faut générer les signaux RAS, CAS et bien d'autres. Le tout est illustré ci-dessous. [[File:Contrôleur mémoire.png|centre|vignette|upright=2|Contrôleur mémoire externe.]] Un point important est que les DRAM asynchrones n'ont pas d'entrée ''Chip Select'' ou d'entrée ''Output Enable''. Les signaux RAS et CAS remplacent en quelque sorte ces deux signaux. Le bit RAS fait office de ''Chip Select'', le bit CAS fait office d'''Output Enable''. Pour certains contrôleurs de DRAM, il faut ajouter l''''interface électrique''', qui traduit les signaux du processeur en signaux compatibles avec la mémoire. Il est en effet très fréquent que la mémoire et le processeur n'utilisent pas les mêmes tensions pour coder un bit, ce qui fait qu'elles ne sont pas compatibles. Dans ce cas, le contrôleur mémoire fait la conversion. Le contrôleur DRAM peut fonctionner en mode synchrone ou asynchrone, du point de vue du processeur. Quand il fonctionne en mode synchrone, il permet d'interfacer un processeur synchrone avec une mémoire DRAM asynchrone. Un point important est que le contrôleur DRAM sert d'intermédiaire entre une mémoire DRAM et "le reste du monde". Il ne fait pas forcément office d'intermédiaire entre DRAM et processeur, mais peut aussi faire l'intermédiaire entre la DRAM et un bus système, entre une DRAM et le ''Video Display Controler'' d'une carte graphique, ou n'importe quel autre composant électronique qui utilise cette DRAM. ===Le générateur de ''timings'' et la traduction d'adresse=== Le contrôleur mémoire doit traduire les adresses du processeur en adresses compatibles avec la mémoire. Et la traduction est assez variable, suivant que le bus mémoire est un bus normal, un bus multiplexé, ou partiellement multiplexé. Nous avons vu ces trois types de bus mémoire dans le chapitre sur l'interface des mémoires, mais nous ferons quelques rappels rapides. Avec un ''bus totalement multiplexé'', le bus d'adresse et le bus de données sont fusionnés. Dans ce cas, on peut envoyer soit une adresse, soit lire/écrire une donnée sur le bus, mais on ne peut pas faire les deux en même temps. Un bit ALE indique si le bus est utilisé en tant que bus d'adresse ou bus de données. Le contrôleur mémoire gère cette situation, en fixant le bit ALE et en envoyant séparément adresse et donnée pour les écritures. [[File:Bus multiplexé avec bit ALE.png|centre|vignette|upright=2|Bus multiplexé avec bit ALE.]] Avec un ''bus d'adresse multiplexé'', l'adresse est découpée en une adresse de ligne et une adresse de colonne, envoyées l'une après l'autre. Le contrôleur mémoire prend en entrée une adresse mémoire complète, la découpe en deux, et envoie chaque morceau au bon moment. Pour cela, il suffit d'un registre pour mémoriser l'adresse et d'un multiplexeur. Le multiplexeur choisit soit les bits de poids fort de l'adresse, soit ceux de poids faible. Les premiers correspondent à l'adresse de ligne, les autres à l'adresse de colonne. La commande du multiplexeur est le fait d'un petit circuit séquentiel, qui génère aussi les signaux CAS et RAS. Au premier cycle, il met le signal RAS à 1, met le CAS à 0, et configure le MUX pour sélectionner les bits de poids fort. Au second cycle, il génère un signal CAS à 1, met le RAS à 0 et configure le MUX pour sélectionner les bits de poids faible. Le circuit en question est appelé le générateur de ''timings''. [[File:Controleur de DRAM simple, sans rafraichissement mémoire.png|centre|vignette|upright=2|Contrôleur de DRAM simple, sans rafraichissement mémoire.]] Le générateur de ''timings'' est un circuit séquentiel qui implémente une petite machine à état. Il est très simple sur une mémoire DRAM asynchrone basique, mais il est plus complexe sur les mémoires FPM, EDO, quartet, et autres. Le regroupement des multiplexeurs d'adresse et du générateur de ''timings'' est appelé le '''séquenceur mémoire'''. C'est le séquenceur mémoire qui traduit la requête processeur en commande DRAM, le reste du contrôleur est dédié au rafraichissement mémoire ou à d'autres fonctions facultatives. ===Le rafraichissement mémoire=== La gestion du rafraichissement mémoire est la fonction principale du contrôleur DRAM. Pour gérer le rafraichissement mémoire, le contrôleur mémoire intègre deux compteurs, un pour gérer l'adresse à rafraichir, l'autre pour gérer l'intervalle de temps entre deux rafraichissements. Le rafraichissement se fait à intervalle régulier, tous les x microsecondes. Pour déclencher le rafraichissement au bon moment, le contrôleur mémoire contient un ''Refresh Timer'', aussi appelé le '''compteur de rafraichissement'''. Il est initialisé avec le temps entre deux rafraichissements, une adresse est rafraichie quand ce compteur atteint 0. Le rafraichissement mémoire balaye la mémoire adresse par adresse. Pour savoir à quelle adresse il en est rendu, le contrôleur mémoire utilise un '''compteur d'adresse'''. Il contient la prochaine adresse à rafraichir, aussi appelée l'adresse de rafraichissement. Régulièrement, l'adresse dans ce compteur est envoyée à la RAM, pour une lecture. Mais la donnée lue n'est pas envoyée sur le bus de donnée, soit parce que la RAM est prévue pour, soit parce que le contrôleur désactive son bit ''output enable''. Dans le second cas, la RAM fait la lecture en interne, mais se déconnecte du bus de donnée, perdant la donnée lue dans le néant. Pour envoyer l'adresse de rafraichissement sur le bus d'adresse, il faut rajouter un multiplexeur, qui choisit entre l'adresse normale et l'adresse de rafraichissement. [[File:Controleur de DRAM avec rafraichissement mémoire.png|centre|vignette|upright=2|Controleur de DRAM avec rafraichissement mémoire.]] Le multiplexeur ne doit cependant pas être configuré si une adresse est déjà en cours de transfert. Pour cela, un circuit d'arbitrage se débrouille pour éviter qu'un accès mémoire soit interrompu par une demande de rafraichissement et inversement. Il peut être inclus dans le séquenceur mémoire ou séparé de celui-ci. [[File:Controleur mémoire, intérieur simplifié.png|centre|vignette|upright=2.5|Contrôleur mémoire, intérieur simplifié.]] Il faut noter que le rafraichissement mémoire peut être délégué non pas au contrôleur mémoire, mais au processeur où à la DRAM elle-même. Quand elle est le fait du processeur, celui-ci incorpore un ''refresh timer'' et un compteur d'adresse. Un exemple est celui du processeur Zilog Z80, qui implémentait des compteurs internes pour gérer le rafraichissement mémoire. On peut considérer que le processeur incorpore alors le contrôleur mémoire, au moins partiellement. Il est aussi possible de déléguer le rafraichissement au logiciel (certains jeux vidéos Amiga ou Commodore faisaient cela pour la mémoire vidéo). Quand la DRAM elle-même s'occupe de son propre rafraichissement, c'est elle qui intègre un ''refresh timer'' et le compteur d'adresse. ===Le décodage d'adresse=== Le contrôleur mémoire gère aussi le '''décodage d'adresse'''. pour rappel, celui-ci est utilisé quand on combine plusieurs chips mémoire ensemble, pour combiner leurs capacités mémoire. Par exemple, on peut combiner 4 chips mémoires de 1 mébioctet chacun, pour que le processeur voit comme 4 mébioctets de RAM unique. Le premier mébioctet est placé dans le premier chip mémoire, le second mébioctet dans le second chip, etc. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Pour cela, on active le chip mémoire adéquat, en fonction de l'adresse à consulter. Les autres chips mémoire sont désactivés pendant l'accès mémoire. En théorie, activer ou désactiver un chip mémoire se fait en utilisant son entrée ''Chip Select''. Il faut noter que si les SDRAM disposent bien d'un signal ''Chip Select'', ce n'est pas le cas des mémoires RAM asynchrones. A la place, ce sont les signaux RAS qui font office de ''Chip Select''. Une RAM asynchrone est activée quand son signal RAS lui demande de lire une ligne, elle est désactivée sinon. Mais c'est un détail. Toujours est-il que les signaux ''Chip Select'', ou leurs équivalents, sont générés par le contrôleur de DRAM, à partir des bits de poids fort de l'adresse. Par exemple, avec 4 chips mémoire, les deux bits de poids fort de l'adresse sont utilisés pour sélectionner le chip mémoire adéquat. Le contrôleur mémoire doit avoir plusieurs sorties ''Chip Select'', une par chip mémoire. Et le nombre de ces sorties limite le nombre de chips mémoire qu'on peut combiner. Par exemple, s'il y a seulement 4 sorties ''Chip Select'', on ne pourra brancher que 4 chips mémoire dessus. Sauf à ruser, avec un arrangement horizontal, mais cela n'est pas le ressort du contrôleur mémoire. [[File:Td6bfig3.png|centre|vignette|upright=2|Comparaison entre arrangement horizontal (à gauche) et arrangement vertical (à droite).]] ===Exemple : l'Intel 8202-8203=== L'Intel 8202 et le 8203 étaient des contrôleurs de mémoire DRAM, parmi les plus simples qui soient. Ils avaient une entrée d'adresse de 12 bits, ce qui permettait d'adresser 4 kibioctets de RAM. Ils fournissaient en sortie une adresse multiplexée sur 6 bits, envoyée en deux fois. Ils avaient donc 12 entrées d'adresse, 6 sorties d'adresse, un signal RAS, un signal CAS. Les adresses présentées en entrées n'étaient pas mémorisées dans des registres, ce qui fait qu'elles devaient être maintenues durant toute la durée de l'accès mémoire. Le processeur ne pouvait donc pas se déconnecter du bus d'adresse pendant l'accès mémoire, peu importe sa durée. Le 8202 contenait aussi un compteur de rafraichissement. Rappelons que sur les DRAM asynchrones, le rafraichissement se fait ligne par ligne. Le contrôleur mémoire a juste à présenter l'adresse de ligne, il n'a pas à envoyer l'adresse de colonne. La commande de rafraichissement se fait en mettant le signal RAS à 0, mais en laissant le CAS à 1 (je rappelle que les signaux sont actifs à 0). Le compteur d'adresse de rafraichissement a donc juste à mémoriser l'adresse de ligne. Le séquenceur mémoire était précédé par un circuit d'arbitrage, non-représenté dans le schéma ci-dessous. La microarchitecture de l'Intel 8202 est la suivante : [[File:Microarchitecture de l'Intel 8202.png|centre|vignette|upright=2|Microarchitecture de l'Intel 8202.]] Le 8202 avait une entrée pour un signal d'horloge, ainsi qu'un ''Chip Select'' un peu particulier. Si le signal CS passait à 0 lors d'un accès mémoire, le 8202/8203 ne se désactivait qu'une fois l'accès mémoire terminé. On ne pouvait pas l'interrompre pendant un accès mémoire, même en changeant le bit CS. Le signal d'horloge était utilisé pour commander le ''refresh timer''. Pour commander les lectures et écriture, il recevait en entrée un bit ''Write Request'' et un bit ''Read Request'', qui demandent respectivement une écriture et une lecture. En sortie, on trouvait un unique bit R/W qui valait 0 pour une lecture et 1 pour une écriture. Il avait aussi un bit d'entrée pour forcer le rafraichissement mémoire. S'il est à 1, la mémoire rafraichie l'adresse envoyée par le processeur. Le 8202 pouvait être connecté sur 1 à 4 chips mémoire, ce qui permettait d'adresser au maximum 16 kibioctets de RAM. Les 4 chips ne sont pas accédés en parallèle, un seul l'est à chaque fois. Pour le décodage d'adresse, le 8202 dispose de deux bits BO et B1 pour sélectionner le chip adéquat, ainsi que 4 sorties RAS pour activer la banque adéquate. On rappelle que le signal RAS remplace le signal ''Chip Select''. C'est le séquenceur mémoire qui se charge de générer ces signaux RAS, à partir des deux bits B0 et B1 (qui sont techniquement des bits d'adresse). Pour communiquer avec le processeur, l'Intel 8202 disposait de deux bits XACK et SACK. SACK indiquait au processeur que le 8202/8203 est en train de faire un accès mémoire et qu'il est indisponible pour un second accès mémoire. Cela permet de bloquer le processeur tant que le 8202 est indisponible. Le signal XACK indique que l'accès mémoire précédent est terminé et que : soit la donnée lue est présente sur le bus de données, soit que l'écriture s'est terminée. : Le signal SACK est très utile sur les configurations multiprocesseurs. Un processeur peut démarrer un accès mémoire, le signal SACK indiquera au second processeur qu'il doit attendre que l'accès soit terminé pour que ce soit son tour. ==Les contrôleurs de DRAM asynchrones évolués== L'Intel 8202 était un contrôleur mémoire basique, comme beaucoup d'autres à cette époque. Mais Intel a vendu des contrôleurs mémoires plus complexes. Par exemple, l'Intel 8207 était un contrôleur mémoire bien plus avancé que les deux précédents. Passons sur certains détails, comme le fait qu'il pouvait gérer jusqu'à 256 kibioctets de DRAM. Au-delà de ça, il y avait des fonctionnalités bien plus intéressantes, à savoir : un support de l'ECC, il était double port, il permettait de simuler une DRAM synchrone à partir d'une DRAM asynchrone. Il n'était pas le seul dans ce cas et des contrôleurs de DRAM très évolués étaient capables de faire des merveilles. Voyons comment cela était possible. ===Les contrôleurs mémoire synchrone=== Il est parfaitement possible d'utiliser un contrôleur mémoire synchrone avec une DRAM asynchrone. A vrai dire, le contrôleur DRAM peut complétement simuler une mémoire synchrone alors que la DRAM associée est asynchrone. La traduction asynchrone vers synchrone se fait en ajoutant des registres sur le bus mémoire, notamment sur le bus de données et le bus d'adresse/commande. Nous avions détaillé cela dans le chapitre sur les SRAM, c'est la même chose avec une mémoire DRAM. Sauf que cette fois-ci, le contrôleur mémoire doit aussi être prévu pour. [[File:Controleur mémoire synchrone utilisé avec une DRAM asynchrone.png|centre|vignette|upright=2|Contrôleur mémoire synchrone utilisé avec une DRAM asynchrone]] Les deux-trois registres illustrés plus haut peuvent être intégrés directement dans le contrôleur mémoire, que ce soit totalement ou en partie. Le strict minimum pour avoir un contrôleur mémoire synchrone est que celui-ci doit mémoriser adresse et commandes dans un registre. Par exemple, le 8207 d'Intel était capable de mémoriser les requêtes processeurs dans un registre interne, mais il fallait utiliser deux registres séparés pour le bus de données. Les deux registres étaient alors commandés par le contrôleur mémoire. Il est cependant possible d'aller plus loin et d'intégrer les registres du bus de données dans le contrôleur mémoire. [[File:Controleur mémoire DRAM synchrone.png|centre|vignette|upright=2|Contrôleur mémoire DRAM synchrone.]] : Il faut noter que cette fonctionnalité est parfois disponible sur les SRAM. En clair, on peut associer une SRAM asynchrone avec un contrôleur de SRAM synchrone. Le contrôleur de SRAM se charge alors de simuler une SRAM synchrone à partir de la SRAM asynchrone. Utiliser un contrôleur mémoire synchrone a de nombreux avantages, l'un d'entre eux étant lié aux ''wait state''. Quand le processeur envoie une requête de lecture/écriture à la mémoire RAM, celle-ci met plusieurs cycles d'horloge à répondre. Et pendant ce temps, le processeur... attend. Et pendant ce temps d'attente, il doit maintenir l'adresse mémoire sur le bus d'adresse. Les cycles d'horloge perdus à attendre la mémoire RAM étaient appelés des '''''Wait states'''''. Utiliser un contrôleur mémoire synchrone d'éliminer les ''wait state'', dans une certaine mesure. Avec un contrôleur mémoire synchrone, le processeur envoie l'adresse, mais c'est le contrôleur mémoire qui la maintient sur le bus d'adresse. Le processeur peut envoyer l'adresse et la donnée à écrire, elles sont recopiées dans les registres, et le controleur mémoire y a accès sans que le processeur doive les maintenir. Le processeur peut se déconnecter du bus mémoire et faire du travail dans son coin pendant que le contrôleur mémoire accède à la DRAM. Les ''wait state'' disparaissent alors, du moins du point de vue du processeur. ===La gestion de l'ECC=== L''''ECC''' peut être géré dans le contrôleur mémoire. Pour cela, on couple les registres mentionnés dans la section précédente, avec un circuit de détection et de correction d'erreur. Le circuit d'ECC peut, comme les registres synchrones, être intégré dans le contrôleur mémoire, ou au contraire être situé dans un circuit séparé. Si le circuit d'ECC est séparé du contrôleur mémoire, il communique avec lui, histoire que le contrôleur mémoire puisse signaler toute erreur de parité ou d'ECC au processeur. [[File:Controleur mémoire synchrone avec ECC intégré.png|centre|vignette|upright=2|Controleur mémoire synchrone avec ECC intégré]] Reprenons l'exemple du 8207 d'Intel. Le contrôleur mémoire 8207 gère le bus d'adresse et de commande, mais n'a pas de connexions directes avec le bus de données. Il ne peut donc pas prendre en charge l'ECC. Il avait besoin d'être couplé avec un circuit d'ECC séparé, relié au bus de données : l'Intel 8206. Le 8026 prenait en entrée : 16 bits de données et 8 bits d'ECC. Il fournissait en sortie 16 bits de données après correction d'erreur, les 8 bits d'ECC pour indiquer qu'une erreur a été détectée mais pas corrigée, ainsi que des bits de parité. Le 8206 détectait/corrigeait les erreurs et générait les bits d'ECC, mais il communiquait avec le contrôleur mémoire pour cela. [[File:8207 avec ECC.png|centre|vignette|upright=2|8207 avec ECC]] La détection/correction d'erreur était appliquée à la fois pour les accès mémoire et pour les rafraichissements mémoire. Lors d'un rafraichissement mémoire, la donnée rafraichie est lue et réécrite. Avec l'ECC activé et configuré correctement, le rafraichissement passe par le bus de données. Au lieu d'avoir un cycle de lecture-écriture interne à la DRAM, on a un cycle de lecture-correction-écriture qui utilise le 8206. La donnée lue est envoyée sur le bus de données, puis le 8206 corrige une éventuelle erreur, et la donnée corrigée est alors réécrite en mémoire. Au passage, si une erreur non-correctible est détectée, le 8206 ne fait rien, l'erreur est ignorée. La gestion de l'erreur sera retardée jusqu'à une lecture ultérieure. Et encore : si lecture ultérieure il y a. Si la donnée est écrasée par une écriture, la donnée corrompue sera simplement écrasée et disparaitra sans avoir pu faire le moindre dégât. Mais pour cela, le 8206 doit communiquer avec le contrôleur mémoire, pour savoir s'il est dans un cycle de rafraichissement ou un accès mémoire normal. Il prévient le 8207 lors d'une erreur, et c'est ce dernier qui décide si l'erreur doit être prise en compte ou ignorée. C'est seulement lors d'un accès mémoire normal que le processeur est prévenu qu'une erreur de parité/autre a eu lieu. ===Les contrôleurs mémoires multiports=== Les '''contrôleur mémoire multiport''' disposent de plusieurs ports, chacun permettant de traiter un accès mémoire. Ils peuvent simuler une mémoire multiport à partir d'une DRAM monoport. Évidemment, la simulation n'est pas parfaite. Des accès mémoire simultanés, envoyés en même temps sur différents ports, sont en réalité exécutés un par un, pas en même temps. Il y a donc une petite pénalité en termes de performances, mais elle est mineure. Encore une fois, nous allons reprendre l'exemple du 8207. Il avait deux ports séparés, et était prévu pour fonctionner dans un système à deux processeurs. L'usage de deux ports séparés permettait de partager une unique mémoire DRAM entre deux processeurs. Le partage se faisait en interfaçant deux processeurs sur le contrôleur mémoire, chacun étant connecté à un port. Lors d'une lecture, il redirigeait la donnée lue vers le bon processeur, en configurant le bus de données correctement. Le contrôleur mémoire recevait des requêtes mémoire de deux processeurs, mais il les exécutait une à la fois. S'il recevait deux requêtes en même temps, l'une d'entre elles était mise en attente. Le contrôleur mémoire doit arbitrer les accès à la mémoire, et faire en sorte que les deux processeurs aient accès à la mémoire à tour de rôle. Et non seulement il doit arbitrer les deux ports, mais il y a aussi un troisième port interne au contrôleur mémoire : le rafraichissement mémoire ! Pour cela, le circuit d'arbitrage qui choisissait entre rafraichissement mémoire et accès mémoire, est amélioré de manière à gérer un second port. Le circuit d'arbitrage donne l'accès au séquenceur mémoire à un port sélectionné. L'arbitrage était configurable, avec deux options : soit le port A est privilégié sur le port B, soit le port le plus récemment accédé à la priorité. Les deux ports pouvaient être configurés pour fonctionner soit de manière asynchrone, soit de manière synchrone. Il était aussi possible de configurer l'ECC, des options liées à la fréquence du processeur et de la RAM, ainsi que de nombreuses options liées au rafraichissement. Pour cela, le 8207 contenait un registre de configuration interne, programmable en fournissant les entrées adéquates. Tout ce qui vient d'être dit se généralise avec plus de deux processeurs. Le 8207 ne permettait pas ça, mais les contrôleurs mémoire des PC modernes en sont capables. Ils peuvent gérer plusieurs dizaines de processeurs facilement. ==Le contrôleur mémoire d'une DRAM ''Fast Page Mode''== Les mémoires DRAM classiques sont des mémoires à tampon de ligne, mais qui sont assez mal utilisées. Notamment, tout accès mémoire se fait en deux phases : un accès pour sélectionner la ligne, un autre pour sélectionner la colonne. Les mémoires ''Fast Page Mode'' permettent d'optimiser le tout. Elles permettent de faire plusieurs accès successifs à la même ligne, à des colonnes différentes. Et le contrôleur mémoire doit être adapté pour cela. [[File:Sélection d'une ligne sur une mémoire FPM ou EDO.png|centre|vignette|upright=2|Sélection d'une ligne sur une mémoire FPM ou EDO.]] Le contrôleur mémoire doit détecter que deux accès mémoire consécutifs se font dans la même ligne. Si deux accès consécutifs accèdent à la même ligne, on doit juste changer de colonne et altérer le signal CAS. C'est un ''succès de tampon de ligne'', aussi appelé un '''succès de page'''. Si deux accès consécutifs accèdent à une ligne différente, c'est un ''défaut de tampon de ligne'', aussi appelé un '''défaut de page'''. Il faut alors changer de ligne, en altérant les signaux RAS et en envoyant une adresse de ligne. Pour détecter les succès ou défauts de page, il faut ajouter un circuit spécialisé dans le contrôleur mémoire. Il mémorise la ligne ouverte, et plus précisément son adresse de ligne (numéro de banque inclut). A chaque requête processeur, il compare l'adresse de ligne recue avec celle déjà ouverte. C'est un succès si les deux sont égales, un défaut si elles sont différentes. Le circuit qui fait cette comparaison est appelé le '''décodeur de commande'''. Il prévient le séquenceur mémoire en cas de succès de page, grâce à un signal de un bit, qui vaut 0 en cas de défaut de page et 1 en cas de succès. Le séquenceur mémoire décide alors comment gérer les signaux RAS et CAS, ainsi que l'envoi des adresses de ligne/colonne. [[File:Controleur mémoire d'une FPM-DRAM.png|centre|vignette|upright=2|Controleur mémoire d'une FPM-DRAM]] ==Le contrôleur mémoire d'une SDRAM ou d'une DDR== l'intérieur d'un contrôleur de SDRAM ne change pas significativement d'un controleur de RAM asynchrone. Il regroupe toujours un séquenceur mémoire et une interface physique, un circuit pour le rafraichissement mémoire et un circuit d'arbitrage. Par contre, ses sorties changent pas mal. Contrairement aux mémoires DRAM basiques, les mémoires SDRAM sont cadencées par un signal d'horloge. Et ce signal d'horloge vient bien de quelque part. Pour cela, deux solutions : soit le contrôleur mémoire génère la fréquence qui commande la mémoire, soit il prend en entrée une fréquence de base qu'il multiplie pour obtenir la fréquence désirée. Les deux solutions sont équivalentes, si ce n'est que les circuits impliqués ne sont pas les mêmes. Dans le premier cas, le contrôleur doit embarquer un circuit oscillateur, qui génère la fréquence demandée. Dans l'autre cas, un simple multiplieur/diviseur de fréquence suffit et c'est généralement une PLL qui est utilisée pour cela. : Notez qu'il ne faut pas confondre la fréquence de la SDRAM et celle du contrôleur mémoire. Le contrôleur mémoire fonctionne à une vitesse assez élevée, en interne. Le port relié au processeur fonctionne à haute fréquence, généralement la même que celle du processeur. A vrai dire, de nos jours, il est intégré dans le processeur. Pour le décodage d'adresse, tout est plus simple sur les SDRAM, DDR inclues. Les chips de mémoire SDRAM et DDR disposent d'une entrée ''Chip Select'', ce qui facilite grandement le décodage d'adresse. Les bits de ''Chip Select'' sont générés par le contrôleur mémoire, et sont transmis sur le bus de commande. Le contrôleur de DRAM peut adresser un certain nombre de rangées, dispersés sur une ou plusieurs barrettes. La limite maximale dépend du contrôleur de DRAM, elle est souvent proche de 8 ou 16 rangées. Si on combine plusieurs barrettes de mémoire, il est possible de dépasser cette limite. Par exemple, pour un contrôleur de DRAM supportant maximum 8 rangées, 4 barrettes de 4 rangées chacune dépassent la limite. ===Le séquenceurs mémoire pour les SDRAM/DDR=== Le séquenceur mémoire existe toujours pour les mémoires SDRAM, c'est toujours un circuit séquentiel qui implémente une machine à état. Il traduit toujours une requête processeur en une séquence de commandes envoyées à des timings bien précis. Les commandes mémoires peuvent provenir de l'extérieur, mais aussi d'un circuit de rafraichissement intégré dans le contrôleur mémoire, comme pour les autres DRAM. La seule différence est que la machine à état est plus complexe. Pour rappel, une requête de lecture/écriture se fait en trois étapes maximum : une commande PRECHARGE pour précharger le tampon de ligne, une commande ACT qui fixe l'adresse de ligne, et enfin une commande READ/WRITE avec l'adresse de colonne. Et ces commandes sont séparées par des '''délais mémoire''' bien précis. Par exemple, je prends des chiffres arbitraires : il faut attendre 2 cycles entre une commande ACT et une commande READ, 6 cycles avant deux commandes WRITE consécutives, etc. La gestion des délais mémoire rend la conception du séquenceur plus complexe. Il faut aussi tenir compte des commandes SDRAM anticipées, à savoir que l'on peut envoyer des commandes avant que la précédente soit terminée. Les commandes anticipées sont idéales dans le cas où des accès successifs se font dans des banques différentes. Pour les exploiter au mieux, le contrôleur mémoire doit donc détecter si des accès successifs se font dans des banques différentes, ou dans la même banque, pour décider d'envoyer des commandes anticipées ou non. Cette '''détection des conflits de banque''' complexifie le séquenceur. Un point important est que dans certaines conditions, certaines de ces commandes peuvent être omises. Par exemple, en cas de succès de page, les commandes PRECHARGE et ACT ne doivent pas être envoyées, seule les commandes READ/WRITE le sont. Le contrôleur doit toujours '''détecter les succès et défauts de page''' et agir en fonction. La solution utilisée est la même que pour les mémoires FPM : il faut mémoriser quelle ligne est ouverte ou fermée. La seule différence avec les FPM est qu'il faut faire cela pour chaque banque mémoire ! En effet, il est possible d'ouvrir une ligne dans chaque banque. En effet, chaque banque a son propre tampon de ligne, ce qui fait que la gestion des lignes se fait indépendamment dans chaque banque. Le séquenceur mémoire doit donc se souvenir des lignes actives dans chaque banque. Pour cela, il mémorise ces lignes dans une petite mémoire : la '''table des banques''', aussi appelée ''bank status memory''. Pour détecter un succès ou un défaut, le contrôleur doit extraire la ligne de l'adresse, mais aussi le numéro de banque. Il envoie alors le numéro de banque à la table des banques, sur son entrée d'adresse. Il récupère alors le numéro de la ligne active sur les sorties de données. Il compare alors ce numéro de ligne avec le numéro de ligne de l'adresse envoyée par le processeur. C'est un succès si les deux sont égales, un défaut sinon. [[File:Controleur mémoire FPM avec plusieurs banques.jpg|centre|vignette|upright=2|Contrôleur mémoire FPM avec plusieurs banques.]] ===La politique de gestion du tampon de ligne=== Plus haut, nous avons parlé des mémoires FPM, qui ont introduit quelques optimisations pour utiliser au mieux le tampon de ligne. Il se trouve que les SDRAM conservent ces optimisations. Il y a toujours cette notion de succès de page et de défaut de page, suivant que deux accès se font dans la même ligne ou dans deux lignes différentes. Du moins, c'est le cas si le séquenceur mémoire est coopératif. En effet, il peut fonctionner de plusieurs manières, dont les plus extrêmes sont appelés la politique de la page fermée et la politique de la page ouverte. Voyons à quoi elles correspondent. Avec la '''politique de la page fermée''', chaque accès mémoire est suivi d'une commande PRECHARGE, qui ferme la ligne courante et précharge les lignes de bits. Même si deux accès consécutifs se font dans la même ligne, la ligne est fermée et ré-ouverte entre deux accès mémoire. En clair : l'optimisation introduite par les mémoires FPM est désactivée, le contrôleur mémoire fait exprès de ne pas en profiter. On appelle cette méthode la close ''page autoprecharge''. Cette méthode réduit grandement les performances pour les accès à des adresses consécutives, mais fonctionne à merveille si les accès sont "aléatoires", à savoir qu'ils se font sans régularités évidentes. Son seul avantage est que l'implémentation du séquenceur mémoire est très simple. En effet, le séquenceur mémoire se passe complétement de la table des banques, du comparateur de ligne, et de tous les circuits nécessaires pour vérifier les succès ou défauts de page. De plus, le séquenceur mémoire profite grandement des commandes READA et WRITEA, qui fusionnent une commande READ/WRITE avec une commande PRECHARGE. Le séquenceur mémoire a juste à envoyer des commandes ACT, READA, WRITEA et PREFETCH à la mémoire, pas besoin des commandes PRECHARGE, READ ou WRITE. A l'opposé, la '''politique de la page ouverte''' ne ferme pas automatiquement la ligne. Elle la laisse ouverte, en espérant que le prochain accès mémoire se fasse dans cette ligne. Lorsqu'un nouvel accès mémoire arrive, elle doit détecter les succès ou défauts de page et agir en fonction. En cas de défaut de page, la ligne est fermée, le séquenceur mémoire envoie une commande PRECHARGE, puis l'accès suivant effectue les deux commandes ACT + READ ou WRITE. En cas de succès de page, les commandes PRECHARGE et ACT ne sont pas envoyées, seules la commande READ ou WRITE l'est. Un désavantage est que le contrôleur mémoire doit inclure une table des banques et un comparateur, comme vu plus haut dans la section sur les mémoires FPM. un autre défaut est que garder une ligne ouverte consomme beaucoup d'énergie, comparé à un simple état de PRECHARGE. En conséquence, il est préférable de fermer les lignes dès que possible. Par contre, les performances sont d'autant meilleures que les accès mémoire consécutifs à une même ligne soient assez fréquents. Si les accès mémoire sont aléatoires, les performances sont moins bonnes. La politique de la page fermée fermait les lignes en avance, avec des commandes READA ou WRITEA, avant même que l'accès suivant démarre. Avec la politique de la page ouverte, on doit attendre pour détecter un défaut de page, puis fermer la ligne avec une commande PRECHARGE séparée. La ligne est donc fermée avec un peu temps de retard, et envoyer deux commandes au lieu d'une prend plus de temps. Les contrôleurs mémoires basiques utilisent une des deux solutions précédentes. Soit la page est toujours fermée, soit elle est toujours laissée ouverte jusqu'à ce qu'un accès mémoire la referme. Mais les contrôleurs plus évolués tentent de prédire s'il faut fermer ou laisser ouvertes les pages ouvertes. La méthode la plus simple attend un temps prédéterminé avant de fermer la ligne. Une autre solution regarde le tout dernier accès. On peut très bien décider de laisser la ligne ouverte si l'accès mémoire précédent était une rafale, et fermer sinon. Une solution plus complexe mémorise les N derniers accès et en déduit s'il faut fermer ou non la prochaine ligne. On peut mémoriser si l'accès en question a causé la fermeture d'une ligne avec un bit. Mémoriser les N derniers accès demande d'utiliser un simple registre à décalage. Pour chaque valeur de ce registre, il faut prédire si le prochain accès demandera une ouverture ou une fermeture. Une solution simple fait la moyenne des bits à 1 dans ce registre : si plus de la moitié des bits est à 1, on laisse la ligne ouverte et on ferme sinon. Pour améliorer un petit peu l'algorithme, on peut faire en sorte que les bits des accès mémoires les plus récents aient plus de poids dans le calcul de la moyenne. Il existe sans doute d'autres solutions plus évoluées, mais il est difficile de savoir ce qu'il y a dans les contrôleurs de SDRAM modernes. : Le fait de laisser ouverte une ligne ou au contraire de la fermer systématiquement, se fait pour chaque banque. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les mémoires RAM dynamiques (DRAM) | prevText=Les mémoires RAM dynamiques (DRAM) | next=Les mémoires associatives | nextText=Les mémoires associatives }} </noinclude> he2m594ylxtbiat61wk3jitel7ohyuy 764686 764685 2026-04-23T19:16:52Z Mewtow 31375 /* Le séquenceurs mémoire pour les SDRAM/DDR */ 764686 wikitext text/x-wiki Les mémoires ROM ou SRAM ont généralement une interface simple, à laquelle le processeur peut s'interfacer directement. Mais pour les DRAM, ce n'est pas le cas. Les DRAM utilisent un bus d'adresse multiplexé, où l'adresse est envoyée en deux fois. Connecter le processeur directement sur une DRAM n'est pas pratique : le bus d'adresse du processeur et celui de la mémoire ne collent pas. Les DRAM doivent aussi être rafraichies régulièrement. Le rafraichissement mémoire peut être délégué au processeur, mais c'est loin d'être idéal. Et il y a bien d'autres raisons qui font que le processeur ne peut pas s'interfacer facilement avec les mémoires DRAM. Pour gérer ces problèmes, les mémoires DRAM ne sont pas connectées directement au processeur. À la place, on ajoute un intermédiaire entre le processeur et la mémoire : le '''contrôleur mémoire externe'''. Son but est de montrer au processeur une interface simple, semblable à celle d'une SRAM classique, alors qu'il commande une mémoire DRAM à l'interface plus complexe. Il est placé sur la carte mère ou dans le processeur, et ne doit pas être confondu avec le contrôleur mémoire intégré dans la mémoire. Ce chapitre va expliquer quels sont les rôles du contrôleur mémoire, son interface et ce qu'il y a à l'intérieur. Dans ce chapitre, quand nous parlerons de ''contrôleur mémoire'', cela fera systématiquement référence au contrôleur mémoire externe. Et avant de poursuivre, sachez qu'il est difficile de faire des généralités sur les contrôleurs mémoire, car les mémoires DRAM elles-mêmes sont assez différentes les unes des autres. Entre une mémoire EDO, une mémoire SDR, une mémoire DDR et une DRAM asynchrone, les contrôleurs mémoires seront fortement différents. Aussi, il y a aura une différence entre un contrôleur pour une DRAM asynchrone et un contrôleur pour une mémoire EDO, une mémoire SDRAM, etc. J'ai choisit de vous séparer les contrôleurs mémoire pour les DRAM asynchrones de ceux pour les SDRAM/DRR. ==Le contrôleur d'une DRAM asynchrone== Les premières DRAM asynchrones avaient des contrôleurs mémoires dédiés, qui étaient séparés du processeur et du ''chipset'' de la carte mère. Par exemple, les composants Intel 8202, Intel 8203 et Intel 8207 étaient des contrôleurs mémoire pour DRAM asynchrones qui étaient vendus dans des boitiers DIP et étaient soudés sur la carte mère. Par la suite, ils ont été intégrés au ''chipset'' de la carte mère pendant les décennies 90-2000. Après les années 2000, ils ont été intégrés dans les processeurs. Leurs fonctions étaient le multiplexage de l'adresse et le rafraichissement mémoire. Ils recevaient une adresse mémoire complète, qu'ils découpaient une adresse de ligne et une adresse de colonne, envoyées l'une après l'autre sur le bus mémoire. Pour le rafraichissement mémoire, ils rafraichissaient la DRAM régulièrement, de manière automatique, entre deux accès mémoire normaux. Le processeur n'avait ainsi plus à rafraichir la mémoire lui-même, cette fonction était déléguée au contrôleur de DRAM. Ils étaient connectés au bus d'adresse et de commande, avec éventuellement des relations indirectes avec le bus de données. ===L'interface d'un contrôleur de DRAM asynchrone=== L'interface du contrôleur mémoire décrit ses broches d'entrées/sorties et leur signification. Elle est généralement très simple et contient deux ports : un connecté au processeur, un autre connecté à la DRAM. Cela trahit d'ailleurs son rôle principal, qui est de transformer les requêtes de lecture/écriture provenant du processeur en une suite de commandes acceptée par la mémoire. Le port connecté à la DRAM est connecté ua bus d'adresse et au bus de commande. Le bus de données est lui relié au processeur et/ou au bus système. Un accès mémoire provenant du processeur contient une adresse à lire/écrire, le bit R/W qui indique s'il faut faire une lecture ou une écriture, et éventuellement une donnée à écrire. Mais, nous avons vu que les accès mémoires sur une DRAM sont multiplexés : on envoie l'adresse en deux fois : la ligne d'abord, puis la colonne. De plus, il faut générer les signaux RAS, CAS et bien d'autres. Le tout est illustré ci-dessous. [[File:Contrôleur mémoire.png|centre|vignette|upright=2|Contrôleur mémoire externe.]] Un point important est que les DRAM asynchrones n'ont pas d'entrée ''Chip Select'' ou d'entrée ''Output Enable''. Les signaux RAS et CAS remplacent en quelque sorte ces deux signaux. Le bit RAS fait office de ''Chip Select'', le bit CAS fait office d'''Output Enable''. Pour certains contrôleurs de DRAM, il faut ajouter l''''interface électrique''', qui traduit les signaux du processeur en signaux compatibles avec la mémoire. Il est en effet très fréquent que la mémoire et le processeur n'utilisent pas les mêmes tensions pour coder un bit, ce qui fait qu'elles ne sont pas compatibles. Dans ce cas, le contrôleur mémoire fait la conversion. Le contrôleur DRAM peut fonctionner en mode synchrone ou asynchrone, du point de vue du processeur. Quand il fonctionne en mode synchrone, il permet d'interfacer un processeur synchrone avec une mémoire DRAM asynchrone. Un point important est que le contrôleur DRAM sert d'intermédiaire entre une mémoire DRAM et "le reste du monde". Il ne fait pas forcément office d'intermédiaire entre DRAM et processeur, mais peut aussi faire l'intermédiaire entre la DRAM et un bus système, entre une DRAM et le ''Video Display Controler'' d'une carte graphique, ou n'importe quel autre composant électronique qui utilise cette DRAM. ===Le générateur de ''timings'' et la traduction d'adresse=== Le contrôleur mémoire doit traduire les adresses du processeur en adresses compatibles avec la mémoire. Et la traduction est assez variable, suivant que le bus mémoire est un bus normal, un bus multiplexé, ou partiellement multiplexé. Nous avons vu ces trois types de bus mémoire dans le chapitre sur l'interface des mémoires, mais nous ferons quelques rappels rapides. Avec un ''bus totalement multiplexé'', le bus d'adresse et le bus de données sont fusionnés. Dans ce cas, on peut envoyer soit une adresse, soit lire/écrire une donnée sur le bus, mais on ne peut pas faire les deux en même temps. Un bit ALE indique si le bus est utilisé en tant que bus d'adresse ou bus de données. Le contrôleur mémoire gère cette situation, en fixant le bit ALE et en envoyant séparément adresse et donnée pour les écritures. [[File:Bus multiplexé avec bit ALE.png|centre|vignette|upright=2|Bus multiplexé avec bit ALE.]] Avec un ''bus d'adresse multiplexé'', l'adresse est découpée en une adresse de ligne et une adresse de colonne, envoyées l'une après l'autre. Le contrôleur mémoire prend en entrée une adresse mémoire complète, la découpe en deux, et envoie chaque morceau au bon moment. Pour cela, il suffit d'un registre pour mémoriser l'adresse et d'un multiplexeur. Le multiplexeur choisit soit les bits de poids fort de l'adresse, soit ceux de poids faible. Les premiers correspondent à l'adresse de ligne, les autres à l'adresse de colonne. La commande du multiplexeur est le fait d'un petit circuit séquentiel, qui génère aussi les signaux CAS et RAS. Au premier cycle, il met le signal RAS à 1, met le CAS à 0, et configure le MUX pour sélectionner les bits de poids fort. Au second cycle, il génère un signal CAS à 1, met le RAS à 0 et configure le MUX pour sélectionner les bits de poids faible. Le circuit en question est appelé le générateur de ''timings''. [[File:Controleur de DRAM simple, sans rafraichissement mémoire.png|centre|vignette|upright=2|Contrôleur de DRAM simple, sans rafraichissement mémoire.]] Le générateur de ''timings'' est un circuit séquentiel qui implémente une petite machine à état. Il est très simple sur une mémoire DRAM asynchrone basique, mais il est plus complexe sur les mémoires FPM, EDO, quartet, et autres. Le regroupement des multiplexeurs d'adresse et du générateur de ''timings'' est appelé le '''séquenceur mémoire'''. C'est le séquenceur mémoire qui traduit la requête processeur en commande DRAM, le reste du contrôleur est dédié au rafraichissement mémoire ou à d'autres fonctions facultatives. ===Le rafraichissement mémoire=== La gestion du rafraichissement mémoire est la fonction principale du contrôleur DRAM. Pour gérer le rafraichissement mémoire, le contrôleur mémoire intègre deux compteurs, un pour gérer l'adresse à rafraichir, l'autre pour gérer l'intervalle de temps entre deux rafraichissements. Le rafraichissement se fait à intervalle régulier, tous les x microsecondes. Pour déclencher le rafraichissement au bon moment, le contrôleur mémoire contient un ''Refresh Timer'', aussi appelé le '''compteur de rafraichissement'''. Il est initialisé avec le temps entre deux rafraichissements, une adresse est rafraichie quand ce compteur atteint 0. Le rafraichissement mémoire balaye la mémoire adresse par adresse. Pour savoir à quelle adresse il en est rendu, le contrôleur mémoire utilise un '''compteur d'adresse'''. Il contient la prochaine adresse à rafraichir, aussi appelée l'adresse de rafraichissement. Régulièrement, l'adresse dans ce compteur est envoyée à la RAM, pour une lecture. Mais la donnée lue n'est pas envoyée sur le bus de donnée, soit parce que la RAM est prévue pour, soit parce que le contrôleur désactive son bit ''output enable''. Dans le second cas, la RAM fait la lecture en interne, mais se déconnecte du bus de donnée, perdant la donnée lue dans le néant. Pour envoyer l'adresse de rafraichissement sur le bus d'adresse, il faut rajouter un multiplexeur, qui choisit entre l'adresse normale et l'adresse de rafraichissement. [[File:Controleur de DRAM avec rafraichissement mémoire.png|centre|vignette|upright=2|Controleur de DRAM avec rafraichissement mémoire.]] Le multiplexeur ne doit cependant pas être configuré si une adresse est déjà en cours de transfert. Pour cela, un circuit d'arbitrage se débrouille pour éviter qu'un accès mémoire soit interrompu par une demande de rafraichissement et inversement. Il peut être inclus dans le séquenceur mémoire ou séparé de celui-ci. [[File:Controleur mémoire, intérieur simplifié.png|centre|vignette|upright=2.5|Contrôleur mémoire, intérieur simplifié.]] Il faut noter que le rafraichissement mémoire peut être délégué non pas au contrôleur mémoire, mais au processeur où à la DRAM elle-même. Quand elle est le fait du processeur, celui-ci incorpore un ''refresh timer'' et un compteur d'adresse. Un exemple est celui du processeur Zilog Z80, qui implémentait des compteurs internes pour gérer le rafraichissement mémoire. On peut considérer que le processeur incorpore alors le contrôleur mémoire, au moins partiellement. Il est aussi possible de déléguer le rafraichissement au logiciel (certains jeux vidéos Amiga ou Commodore faisaient cela pour la mémoire vidéo). Quand la DRAM elle-même s'occupe de son propre rafraichissement, c'est elle qui intègre un ''refresh timer'' et le compteur d'adresse. ===Le décodage d'adresse=== Le contrôleur mémoire gère aussi le '''décodage d'adresse'''. pour rappel, celui-ci est utilisé quand on combine plusieurs chips mémoire ensemble, pour combiner leurs capacités mémoire. Par exemple, on peut combiner 4 chips mémoires de 1 mébioctet chacun, pour que le processeur voit comme 4 mébioctets de RAM unique. Le premier mébioctet est placé dans le premier chip mémoire, le second mébioctet dans le second chip, etc. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Pour cela, on active le chip mémoire adéquat, en fonction de l'adresse à consulter. Les autres chips mémoire sont désactivés pendant l'accès mémoire. En théorie, activer ou désactiver un chip mémoire se fait en utilisant son entrée ''Chip Select''. Il faut noter que si les SDRAM disposent bien d'un signal ''Chip Select'', ce n'est pas le cas des mémoires RAM asynchrones. A la place, ce sont les signaux RAS qui font office de ''Chip Select''. Une RAM asynchrone est activée quand son signal RAS lui demande de lire une ligne, elle est désactivée sinon. Mais c'est un détail. Toujours est-il que les signaux ''Chip Select'', ou leurs équivalents, sont générés par le contrôleur de DRAM, à partir des bits de poids fort de l'adresse. Par exemple, avec 4 chips mémoire, les deux bits de poids fort de l'adresse sont utilisés pour sélectionner le chip mémoire adéquat. Le contrôleur mémoire doit avoir plusieurs sorties ''Chip Select'', une par chip mémoire. Et le nombre de ces sorties limite le nombre de chips mémoire qu'on peut combiner. Par exemple, s'il y a seulement 4 sorties ''Chip Select'', on ne pourra brancher que 4 chips mémoire dessus. Sauf à ruser, avec un arrangement horizontal, mais cela n'est pas le ressort du contrôleur mémoire. [[File:Td6bfig3.png|centre|vignette|upright=2|Comparaison entre arrangement horizontal (à gauche) et arrangement vertical (à droite).]] ===Exemple : l'Intel 8202-8203=== L'Intel 8202 et le 8203 étaient des contrôleurs de mémoire DRAM, parmi les plus simples qui soient. Ils avaient une entrée d'adresse de 12 bits, ce qui permettait d'adresser 4 kibioctets de RAM. Ils fournissaient en sortie une adresse multiplexée sur 6 bits, envoyée en deux fois. Ils avaient donc 12 entrées d'adresse, 6 sorties d'adresse, un signal RAS, un signal CAS. Les adresses présentées en entrées n'étaient pas mémorisées dans des registres, ce qui fait qu'elles devaient être maintenues durant toute la durée de l'accès mémoire. Le processeur ne pouvait donc pas se déconnecter du bus d'adresse pendant l'accès mémoire, peu importe sa durée. Le 8202 contenait aussi un compteur de rafraichissement. Rappelons que sur les DRAM asynchrones, le rafraichissement se fait ligne par ligne. Le contrôleur mémoire a juste à présenter l'adresse de ligne, il n'a pas à envoyer l'adresse de colonne. La commande de rafraichissement se fait en mettant le signal RAS à 0, mais en laissant le CAS à 1 (je rappelle que les signaux sont actifs à 0). Le compteur d'adresse de rafraichissement a donc juste à mémoriser l'adresse de ligne. Le séquenceur mémoire était précédé par un circuit d'arbitrage, non-représenté dans le schéma ci-dessous. La microarchitecture de l'Intel 8202 est la suivante : [[File:Microarchitecture de l'Intel 8202.png|centre|vignette|upright=2|Microarchitecture de l'Intel 8202.]] Le 8202 avait une entrée pour un signal d'horloge, ainsi qu'un ''Chip Select'' un peu particulier. Si le signal CS passait à 0 lors d'un accès mémoire, le 8202/8203 ne se désactivait qu'une fois l'accès mémoire terminé. On ne pouvait pas l'interrompre pendant un accès mémoire, même en changeant le bit CS. Le signal d'horloge était utilisé pour commander le ''refresh timer''. Pour commander les lectures et écriture, il recevait en entrée un bit ''Write Request'' et un bit ''Read Request'', qui demandent respectivement une écriture et une lecture. En sortie, on trouvait un unique bit R/W qui valait 0 pour une lecture et 1 pour une écriture. Il avait aussi un bit d'entrée pour forcer le rafraichissement mémoire. S'il est à 1, la mémoire rafraichie l'adresse envoyée par le processeur. Le 8202 pouvait être connecté sur 1 à 4 chips mémoire, ce qui permettait d'adresser au maximum 16 kibioctets de RAM. Les 4 chips ne sont pas accédés en parallèle, un seul l'est à chaque fois. Pour le décodage d'adresse, le 8202 dispose de deux bits BO et B1 pour sélectionner le chip adéquat, ainsi que 4 sorties RAS pour activer la banque adéquate. On rappelle que le signal RAS remplace le signal ''Chip Select''. C'est le séquenceur mémoire qui se charge de générer ces signaux RAS, à partir des deux bits B0 et B1 (qui sont techniquement des bits d'adresse). Pour communiquer avec le processeur, l'Intel 8202 disposait de deux bits XACK et SACK. SACK indiquait au processeur que le 8202/8203 est en train de faire un accès mémoire et qu'il est indisponible pour un second accès mémoire. Cela permet de bloquer le processeur tant que le 8202 est indisponible. Le signal XACK indique que l'accès mémoire précédent est terminé et que : soit la donnée lue est présente sur le bus de données, soit que l'écriture s'est terminée. : Le signal SACK est très utile sur les configurations multiprocesseurs. Un processeur peut démarrer un accès mémoire, le signal SACK indiquera au second processeur qu'il doit attendre que l'accès soit terminé pour que ce soit son tour. ==Les contrôleurs de DRAM asynchrones évolués== L'Intel 8202 était un contrôleur mémoire basique, comme beaucoup d'autres à cette époque. Mais Intel a vendu des contrôleurs mémoires plus complexes. Par exemple, l'Intel 8207 était un contrôleur mémoire bien plus avancé que les deux précédents. Passons sur certains détails, comme le fait qu'il pouvait gérer jusqu'à 256 kibioctets de DRAM. Au-delà de ça, il y avait des fonctionnalités bien plus intéressantes, à savoir : un support de l'ECC, il était double port, il permettait de simuler une DRAM synchrone à partir d'une DRAM asynchrone. Il n'était pas le seul dans ce cas et des contrôleurs de DRAM très évolués étaient capables de faire des merveilles. Voyons comment cela était possible. ===Les contrôleurs mémoire synchrone=== Il est parfaitement possible d'utiliser un contrôleur mémoire synchrone avec une DRAM asynchrone. A vrai dire, le contrôleur DRAM peut complétement simuler une mémoire synchrone alors que la DRAM associée est asynchrone. La traduction asynchrone vers synchrone se fait en ajoutant des registres sur le bus mémoire, notamment sur le bus de données et le bus d'adresse/commande. Nous avions détaillé cela dans le chapitre sur les SRAM, c'est la même chose avec une mémoire DRAM. Sauf que cette fois-ci, le contrôleur mémoire doit aussi être prévu pour. [[File:Controleur mémoire synchrone utilisé avec une DRAM asynchrone.png|centre|vignette|upright=2|Contrôleur mémoire synchrone utilisé avec une DRAM asynchrone]] Les deux-trois registres illustrés plus haut peuvent être intégrés directement dans le contrôleur mémoire, que ce soit totalement ou en partie. Le strict minimum pour avoir un contrôleur mémoire synchrone est que celui-ci doit mémoriser adresse et commandes dans un registre. Par exemple, le 8207 d'Intel était capable de mémoriser les requêtes processeurs dans un registre interne, mais il fallait utiliser deux registres séparés pour le bus de données. Les deux registres étaient alors commandés par le contrôleur mémoire. Il est cependant possible d'aller plus loin et d'intégrer les registres du bus de données dans le contrôleur mémoire. [[File:Controleur mémoire DRAM synchrone.png|centre|vignette|upright=2|Contrôleur mémoire DRAM synchrone.]] : Il faut noter que cette fonctionnalité est parfois disponible sur les SRAM. En clair, on peut associer une SRAM asynchrone avec un contrôleur de SRAM synchrone. Le contrôleur de SRAM se charge alors de simuler une SRAM synchrone à partir de la SRAM asynchrone. Utiliser un contrôleur mémoire synchrone a de nombreux avantages, l'un d'entre eux étant lié aux ''wait state''. Quand le processeur envoie une requête de lecture/écriture à la mémoire RAM, celle-ci met plusieurs cycles d'horloge à répondre. Et pendant ce temps, le processeur... attend. Et pendant ce temps d'attente, il doit maintenir l'adresse mémoire sur le bus d'adresse. Les cycles d'horloge perdus à attendre la mémoire RAM étaient appelés des '''''Wait states'''''. Utiliser un contrôleur mémoire synchrone d'éliminer les ''wait state'', dans une certaine mesure. Avec un contrôleur mémoire synchrone, le processeur envoie l'adresse, mais c'est le contrôleur mémoire qui la maintient sur le bus d'adresse. Le processeur peut envoyer l'adresse et la donnée à écrire, elles sont recopiées dans les registres, et le controleur mémoire y a accès sans que le processeur doive les maintenir. Le processeur peut se déconnecter du bus mémoire et faire du travail dans son coin pendant que le contrôleur mémoire accède à la DRAM. Les ''wait state'' disparaissent alors, du moins du point de vue du processeur. ===La gestion de l'ECC=== L''''ECC''' peut être géré dans le contrôleur mémoire. Pour cela, on couple les registres mentionnés dans la section précédente, avec un circuit de détection et de correction d'erreur. Le circuit d'ECC peut, comme les registres synchrones, être intégré dans le contrôleur mémoire, ou au contraire être situé dans un circuit séparé. Si le circuit d'ECC est séparé du contrôleur mémoire, il communique avec lui, histoire que le contrôleur mémoire puisse signaler toute erreur de parité ou d'ECC au processeur. [[File:Controleur mémoire synchrone avec ECC intégré.png|centre|vignette|upright=2|Controleur mémoire synchrone avec ECC intégré]] Reprenons l'exemple du 8207 d'Intel. Le contrôleur mémoire 8207 gère le bus d'adresse et de commande, mais n'a pas de connexions directes avec le bus de données. Il ne peut donc pas prendre en charge l'ECC. Il avait besoin d'être couplé avec un circuit d'ECC séparé, relié au bus de données : l'Intel 8206. Le 8026 prenait en entrée : 16 bits de données et 8 bits d'ECC. Il fournissait en sortie 16 bits de données après correction d'erreur, les 8 bits d'ECC pour indiquer qu'une erreur a été détectée mais pas corrigée, ainsi que des bits de parité. Le 8206 détectait/corrigeait les erreurs et générait les bits d'ECC, mais il communiquait avec le contrôleur mémoire pour cela. [[File:8207 avec ECC.png|centre|vignette|upright=2|8207 avec ECC]] La détection/correction d'erreur était appliquée à la fois pour les accès mémoire et pour les rafraichissements mémoire. Lors d'un rafraichissement mémoire, la donnée rafraichie est lue et réécrite. Avec l'ECC activé et configuré correctement, le rafraichissement passe par le bus de données. Au lieu d'avoir un cycle de lecture-écriture interne à la DRAM, on a un cycle de lecture-correction-écriture qui utilise le 8206. La donnée lue est envoyée sur le bus de données, puis le 8206 corrige une éventuelle erreur, et la donnée corrigée est alors réécrite en mémoire. Au passage, si une erreur non-correctible est détectée, le 8206 ne fait rien, l'erreur est ignorée. La gestion de l'erreur sera retardée jusqu'à une lecture ultérieure. Et encore : si lecture ultérieure il y a. Si la donnée est écrasée par une écriture, la donnée corrompue sera simplement écrasée et disparaitra sans avoir pu faire le moindre dégât. Mais pour cela, le 8206 doit communiquer avec le contrôleur mémoire, pour savoir s'il est dans un cycle de rafraichissement ou un accès mémoire normal. Il prévient le 8207 lors d'une erreur, et c'est ce dernier qui décide si l'erreur doit être prise en compte ou ignorée. C'est seulement lors d'un accès mémoire normal que le processeur est prévenu qu'une erreur de parité/autre a eu lieu. ===Les contrôleurs mémoires multiports=== Les '''contrôleur mémoire multiport''' disposent de plusieurs ports, chacun permettant de traiter un accès mémoire. Ils peuvent simuler une mémoire multiport à partir d'une DRAM monoport. Évidemment, la simulation n'est pas parfaite. Des accès mémoire simultanés, envoyés en même temps sur différents ports, sont en réalité exécutés un par un, pas en même temps. Il y a donc une petite pénalité en termes de performances, mais elle est mineure. Encore une fois, nous allons reprendre l'exemple du 8207. Il avait deux ports séparés, et était prévu pour fonctionner dans un système à deux processeurs. L'usage de deux ports séparés permettait de partager une unique mémoire DRAM entre deux processeurs. Le partage se faisait en interfaçant deux processeurs sur le contrôleur mémoire, chacun étant connecté à un port. Lors d'une lecture, il redirigeait la donnée lue vers le bon processeur, en configurant le bus de données correctement. Le contrôleur mémoire recevait des requêtes mémoire de deux processeurs, mais il les exécutait une à la fois. S'il recevait deux requêtes en même temps, l'une d'entre elles était mise en attente. Le contrôleur mémoire doit arbitrer les accès à la mémoire, et faire en sorte que les deux processeurs aient accès à la mémoire à tour de rôle. Et non seulement il doit arbitrer les deux ports, mais il y a aussi un troisième port interne au contrôleur mémoire : le rafraichissement mémoire ! Pour cela, le circuit d'arbitrage qui choisissait entre rafraichissement mémoire et accès mémoire, est amélioré de manière à gérer un second port. Le circuit d'arbitrage donne l'accès au séquenceur mémoire à un port sélectionné. L'arbitrage était configurable, avec deux options : soit le port A est privilégié sur le port B, soit le port le plus récemment accédé à la priorité. Les deux ports pouvaient être configurés pour fonctionner soit de manière asynchrone, soit de manière synchrone. Il était aussi possible de configurer l'ECC, des options liées à la fréquence du processeur et de la RAM, ainsi que de nombreuses options liées au rafraichissement. Pour cela, le 8207 contenait un registre de configuration interne, programmable en fournissant les entrées adéquates. Tout ce qui vient d'être dit se généralise avec plus de deux processeurs. Le 8207 ne permettait pas ça, mais les contrôleurs mémoire des PC modernes en sont capables. Ils peuvent gérer plusieurs dizaines de processeurs facilement. ==Le contrôleur mémoire d'une DRAM ''Fast Page Mode''== Les mémoires DRAM classiques sont des mémoires à tampon de ligne, mais qui sont assez mal utilisées. Notamment, tout accès mémoire se fait en deux phases : un accès pour sélectionner la ligne, un autre pour sélectionner la colonne. Les mémoires ''Fast Page Mode'' permettent d'optimiser le tout. Elles permettent de faire plusieurs accès successifs à la même ligne, à des colonnes différentes. Et le contrôleur mémoire doit être adapté pour cela. [[File:Sélection d'une ligne sur une mémoire FPM ou EDO.png|centre|vignette|upright=2|Sélection d'une ligne sur une mémoire FPM ou EDO.]] Le contrôleur mémoire doit détecter que deux accès mémoire consécutifs se font dans la même ligne. Si deux accès consécutifs accèdent à la même ligne, on doit juste changer de colonne et altérer le signal CAS. C'est un ''succès de tampon de ligne'', aussi appelé un '''succès de page'''. Si deux accès consécutifs accèdent à une ligne différente, c'est un ''défaut de tampon de ligne'', aussi appelé un '''défaut de page'''. Il faut alors changer de ligne, en altérant les signaux RAS et en envoyant une adresse de ligne. Pour détecter les succès ou défauts de page, il faut ajouter un circuit spécialisé dans le contrôleur mémoire. Il mémorise la ligne ouverte, et plus précisément son adresse de ligne (numéro de banque inclut). A chaque requête processeur, il compare l'adresse de ligne recue avec celle déjà ouverte. C'est un succès si les deux sont égales, un défaut si elles sont différentes. Le circuit qui fait cette comparaison est appelé le '''décodeur de commande'''. Il prévient le séquenceur mémoire en cas de succès de page, grâce à un signal de un bit, qui vaut 0 en cas de défaut de page et 1 en cas de succès. Le séquenceur mémoire décide alors comment gérer les signaux RAS et CAS, ainsi que l'envoi des adresses de ligne/colonne. [[File:Controleur mémoire d'une FPM-DRAM.png|centre|vignette|upright=2|Controleur mémoire d'une FPM-DRAM]] ==Le contrôleur mémoire d'une SDRAM ou d'une DDR== l'intérieur d'un contrôleur de SDRAM ne change pas significativement d'un controleur de RAM asynchrone. Il regroupe toujours un séquenceur mémoire et une interface physique, un circuit pour le rafraichissement mémoire et un circuit d'arbitrage. Par contre, ses sorties changent pas mal. Contrairement aux mémoires DRAM basiques, les mémoires SDRAM sont cadencées par un signal d'horloge. Et ce signal d'horloge vient bien de quelque part. Pour cela, deux solutions : soit le contrôleur mémoire génère la fréquence qui commande la mémoire, soit il prend en entrée une fréquence de base qu'il multiplie pour obtenir la fréquence désirée. Les deux solutions sont équivalentes, si ce n'est que les circuits impliqués ne sont pas les mêmes. Dans le premier cas, le contrôleur doit embarquer un circuit oscillateur, qui génère la fréquence demandée. Dans l'autre cas, un simple multiplieur/diviseur de fréquence suffit et c'est généralement une PLL qui est utilisée pour cela. : Notez qu'il ne faut pas confondre la fréquence de la SDRAM et celle du contrôleur mémoire. Le contrôleur mémoire fonctionne à une vitesse assez élevée, en interne. Le port relié au processeur fonctionne à haute fréquence, généralement la même que celle du processeur. A vrai dire, de nos jours, il est intégré dans le processeur. Pour le décodage d'adresse, tout est plus simple sur les SDRAM, DDR inclues. Les chips de mémoire SDRAM et DDR disposent d'une entrée ''Chip Select'', ce qui facilite grandement le décodage d'adresse. Les bits de ''Chip Select'' sont générés par le contrôleur mémoire, et sont transmis sur le bus de commande. Le contrôleur de DRAM peut adresser un certain nombre de rangées, dispersés sur une ou plusieurs barrettes. La limite maximale dépend du contrôleur de DRAM, elle est souvent proche de 8 ou 16 rangées. Si on combine plusieurs barrettes de mémoire, il est possible de dépasser cette limite. Par exemple, pour un contrôleur de DRAM supportant maximum 8 rangées, 4 barrettes de 4 rangées chacune dépassent la limite. ===Le séquenceurs mémoire pour les SDRAM/DDR=== Le séquenceur mémoire existe toujours pour les mémoires SDRAM, c'est toujours un circuit séquentiel qui implémente une machine à état. Il traduit toujours une requête processeur en une séquence de commandes envoyées à des timings bien précis. Les commandes mémoires peuvent provenir de l'extérieur, mais aussi d'un circuit de rafraichissement intégré dans le contrôleur mémoire, comme pour les autres DRAM. La seule différence est que la machine à état est plus complexe. Pour rappel, une requête de lecture/écriture se fait en trois étapes maximum : une commande PRECHARGE pour précharger le tampon de ligne, une commande ACT qui fixe l'adresse de ligne, et enfin une commande READ/WRITE avec l'adresse de colonne. Et ces commandes sont séparées par des '''délais mémoire''' bien précis. Par exemple, je prends des chiffres arbitraires : il faut attendre 2 cycles entre une commande ACT et une commande READ, 6 cycles avant deux commandes WRITE consécutives, etc. La gestion des délais mémoire rend la conception du séquenceur plus complexe. Il faut aussi tenir compte des commandes SDRAM anticipées, à savoir que l'on peut envoyer des commandes avant que la précédente soit terminée. Les commandes anticipées sont idéales dans le cas où des accès successifs se font dans des banques différentes. Pour les exploiter au mieux, le contrôleur mémoire doit donc détecter si des accès successifs se font dans des banques différentes, ou dans la même banque, pour décider d'envoyer des commandes anticipées ou non. Cette '''détection des conflits de banque''' complexifie le séquenceur. ===La détection des succès/défauts de page=== Un point important est que dans certaines conditions, certaines commandes peuvent être omises. Par exemple, en cas de succès de page, les commandes PRECHARGE et ACT ne doivent pas être envoyées, seule les commandes READ/WRITE le sont. Le contrôleur doit toujours '''détecter les succès et défauts de page''' et agir en fonction. La solution utilisée est la même que pour les mémoires FPM : il faut mémoriser quelle ligne est ouverte ou fermée. La différence avec les FPM est qu'il faut faire cela pour chaque banque mémoire ! En effet, chaque banque a son propre tampon de ligne, ce qui fait que la gestion des lignes se fait indépendamment dans chaque banque. Le séquenceur mémoire doit donc se souvenir des lignes actives dans chaque banque. Pour cela, il mémorise ces lignes dans une petite mémoire : la '''table des banques''', aussi appelée ''bank status memory''. Pour détecter un succès ou un défaut, le contrôleur doit extraire la ligne de l'adresse, mais aussi le numéro de banque. Il envoie alors le numéro de banque à la table des banques, sur son entrée d'adresse. Il récupère alors le numéro de la ligne active sur les sorties de données. Il compare alors ce numéro de ligne avec le numéro de ligne de l'adresse envoyée par le processeur. C'est un succès si les deux sont égales, un défaut sinon. [[File:Controleur mémoire FPM avec plusieurs banques.jpg|centre|vignette|upright=2|Contrôleur mémoire FPM avec plusieurs banques.]] ===La politique de gestion du tampon de ligne=== Plus haut, nous avons parlé des mémoires FPM, qui ont introduit quelques optimisations pour utiliser au mieux le tampon de ligne. Il se trouve que les SDRAM conservent ces optimisations. Il y a toujours cette notion de succès de page et de défaut de page, suivant que deux accès se font dans la même ligne ou dans deux lignes différentes. Du moins, c'est le cas si le séquenceur mémoire est coopératif. En effet, il peut fonctionner de plusieurs manières, dont les plus extrêmes sont appelés la politique de la page fermée et la politique de la page ouverte. Voyons à quoi elles correspondent. Avec la '''politique de la page fermée''', chaque accès mémoire est suivi d'une commande PRECHARGE, qui ferme la ligne courante et précharge les lignes de bits. Même si deux accès consécutifs se font dans la même ligne, la ligne est fermée et ré-ouverte entre deux accès mémoire. En clair : l'optimisation introduite par les mémoires FPM est désactivée, le contrôleur mémoire fait exprès de ne pas en profiter. On appelle cette méthode la close ''page autoprecharge''. Cette méthode réduit grandement les performances pour les accès à des adresses consécutives, mais fonctionne à merveille si les accès sont "aléatoires", à savoir qu'ils se font sans régularités évidentes. Son seul avantage est que l'implémentation du séquenceur mémoire est très simple. En effet, le séquenceur mémoire se passe complétement de la table des banques, du comparateur de ligne, et de tous les circuits nécessaires pour vérifier les succès ou défauts de page. De plus, le séquenceur mémoire profite grandement des commandes READA et WRITEA, qui fusionnent une commande READ/WRITE avec une commande PRECHARGE. Le séquenceur mémoire a juste à envoyer des commandes ACT, READA, WRITEA et PREFETCH à la mémoire, pas besoin des commandes PRECHARGE, READ ou WRITE. A l'opposé, la '''politique de la page ouverte''' ne ferme pas automatiquement la ligne. Elle la laisse ouverte, en espérant que le prochain accès mémoire se fasse dans cette ligne. Lorsqu'un nouvel accès mémoire arrive, elle doit détecter les succès ou défauts de page et agir en fonction. En cas de défaut de page, la ligne est fermée, le séquenceur mémoire envoie une commande PRECHARGE, puis l'accès suivant effectue les deux commandes ACT + READ ou WRITE. En cas de succès de page, les commandes PRECHARGE et ACT ne sont pas envoyées, seules la commande READ ou WRITE l'est. Un désavantage est que le contrôleur mémoire doit inclure une table des banques et un comparateur, comme vu plus haut dans la section sur les mémoires FPM. un autre défaut est que garder une ligne ouverte consomme beaucoup d'énergie, comparé à un simple état de PRECHARGE. En conséquence, il est préférable de fermer les lignes dès que possible. Par contre, les performances sont d'autant meilleures que les accès mémoire consécutifs à une même ligne soient assez fréquents. Si les accès mémoire sont aléatoires, les performances sont moins bonnes. La politique de la page fermée fermait les lignes en avance, avec des commandes READA ou WRITEA, avant même que l'accès suivant démarre. Avec la politique de la page ouverte, on doit attendre pour détecter un défaut de page, puis fermer la ligne avec une commande PRECHARGE séparée. La ligne est donc fermée avec un peu temps de retard, et envoyer deux commandes au lieu d'une prend plus de temps. Les contrôleurs mémoires basiques utilisent une des deux solutions précédentes. Soit la page est toujours fermée, soit elle est toujours laissée ouverte jusqu'à ce qu'un accès mémoire la referme. Mais les contrôleurs plus évolués tentent de prédire s'il faut fermer ou laisser ouvertes les pages ouvertes. La méthode la plus simple attend un temps prédéterminé avant de fermer la ligne. Une autre solution regarde le tout dernier accès. On peut très bien décider de laisser la ligne ouverte si l'accès mémoire précédent était une rafale, et fermer sinon. Une solution plus complexe mémorise les N derniers accès et en déduit s'il faut fermer ou non la prochaine ligne. On peut mémoriser si l'accès en question a causé la fermeture d'une ligne avec un bit. Mémoriser les N derniers accès demande d'utiliser un simple registre à décalage. Pour chaque valeur de ce registre, il faut prédire si le prochain accès demandera une ouverture ou une fermeture. Une solution simple fait la moyenne des bits à 1 dans ce registre : si plus de la moitié des bits est à 1, on laisse la ligne ouverte et on ferme sinon. Pour améliorer un petit peu l'algorithme, on peut faire en sorte que les bits des accès mémoires les plus récents aient plus de poids dans le calcul de la moyenne. Il existe sans doute d'autres solutions plus évoluées, mais il est difficile de savoir ce qu'il y a dans les contrôleurs de SDRAM modernes. : Le fait de laisser ouverte une ligne ou au contraire de la fermer systématiquement, se fait pour chaque banque. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les mémoires RAM dynamiques (DRAM) | prevText=Les mémoires RAM dynamiques (DRAM) | next=Les mémoires associatives | nextText=Les mémoires associatives }} </noinclude> 54cs8xpcxvj1997nob5vp1mngzjon0p 764687 764686 2026-04-23T19:17:31Z Mewtow 31375 /* La politique de gestion du tampon de ligne */ 764687 wikitext text/x-wiki Les mémoires ROM ou SRAM ont généralement une interface simple, à laquelle le processeur peut s'interfacer directement. Mais pour les DRAM, ce n'est pas le cas. Les DRAM utilisent un bus d'adresse multiplexé, où l'adresse est envoyée en deux fois. Connecter le processeur directement sur une DRAM n'est pas pratique : le bus d'adresse du processeur et celui de la mémoire ne collent pas. Les DRAM doivent aussi être rafraichies régulièrement. Le rafraichissement mémoire peut être délégué au processeur, mais c'est loin d'être idéal. Et il y a bien d'autres raisons qui font que le processeur ne peut pas s'interfacer facilement avec les mémoires DRAM. Pour gérer ces problèmes, les mémoires DRAM ne sont pas connectées directement au processeur. À la place, on ajoute un intermédiaire entre le processeur et la mémoire : le '''contrôleur mémoire externe'''. Son but est de montrer au processeur une interface simple, semblable à celle d'une SRAM classique, alors qu'il commande une mémoire DRAM à l'interface plus complexe. Il est placé sur la carte mère ou dans le processeur, et ne doit pas être confondu avec le contrôleur mémoire intégré dans la mémoire. Ce chapitre va expliquer quels sont les rôles du contrôleur mémoire, son interface et ce qu'il y a à l'intérieur. Dans ce chapitre, quand nous parlerons de ''contrôleur mémoire'', cela fera systématiquement référence au contrôleur mémoire externe. Et avant de poursuivre, sachez qu'il est difficile de faire des généralités sur les contrôleurs mémoire, car les mémoires DRAM elles-mêmes sont assez différentes les unes des autres. Entre une mémoire EDO, une mémoire SDR, une mémoire DDR et une DRAM asynchrone, les contrôleurs mémoires seront fortement différents. Aussi, il y a aura une différence entre un contrôleur pour une DRAM asynchrone et un contrôleur pour une mémoire EDO, une mémoire SDRAM, etc. J'ai choisit de vous séparer les contrôleurs mémoire pour les DRAM asynchrones de ceux pour les SDRAM/DRR. ==Le contrôleur d'une DRAM asynchrone== Les premières DRAM asynchrones avaient des contrôleurs mémoires dédiés, qui étaient séparés du processeur et du ''chipset'' de la carte mère. Par exemple, les composants Intel 8202, Intel 8203 et Intel 8207 étaient des contrôleurs mémoire pour DRAM asynchrones qui étaient vendus dans des boitiers DIP et étaient soudés sur la carte mère. Par la suite, ils ont été intégrés au ''chipset'' de la carte mère pendant les décennies 90-2000. Après les années 2000, ils ont été intégrés dans les processeurs. Leurs fonctions étaient le multiplexage de l'adresse et le rafraichissement mémoire. Ils recevaient une adresse mémoire complète, qu'ils découpaient une adresse de ligne et une adresse de colonne, envoyées l'une après l'autre sur le bus mémoire. Pour le rafraichissement mémoire, ils rafraichissaient la DRAM régulièrement, de manière automatique, entre deux accès mémoire normaux. Le processeur n'avait ainsi plus à rafraichir la mémoire lui-même, cette fonction était déléguée au contrôleur de DRAM. Ils étaient connectés au bus d'adresse et de commande, avec éventuellement des relations indirectes avec le bus de données. ===L'interface d'un contrôleur de DRAM asynchrone=== L'interface du contrôleur mémoire décrit ses broches d'entrées/sorties et leur signification. Elle est généralement très simple et contient deux ports : un connecté au processeur, un autre connecté à la DRAM. Cela trahit d'ailleurs son rôle principal, qui est de transformer les requêtes de lecture/écriture provenant du processeur en une suite de commandes acceptée par la mémoire. Le port connecté à la DRAM est connecté ua bus d'adresse et au bus de commande. Le bus de données est lui relié au processeur et/ou au bus système. Un accès mémoire provenant du processeur contient une adresse à lire/écrire, le bit R/W qui indique s'il faut faire une lecture ou une écriture, et éventuellement une donnée à écrire. Mais, nous avons vu que les accès mémoires sur une DRAM sont multiplexés : on envoie l'adresse en deux fois : la ligne d'abord, puis la colonne. De plus, il faut générer les signaux RAS, CAS et bien d'autres. Le tout est illustré ci-dessous. [[File:Contrôleur mémoire.png|centre|vignette|upright=2|Contrôleur mémoire externe.]] Un point important est que les DRAM asynchrones n'ont pas d'entrée ''Chip Select'' ou d'entrée ''Output Enable''. Les signaux RAS et CAS remplacent en quelque sorte ces deux signaux. Le bit RAS fait office de ''Chip Select'', le bit CAS fait office d'''Output Enable''. Pour certains contrôleurs de DRAM, il faut ajouter l''''interface électrique''', qui traduit les signaux du processeur en signaux compatibles avec la mémoire. Il est en effet très fréquent que la mémoire et le processeur n'utilisent pas les mêmes tensions pour coder un bit, ce qui fait qu'elles ne sont pas compatibles. Dans ce cas, le contrôleur mémoire fait la conversion. Le contrôleur DRAM peut fonctionner en mode synchrone ou asynchrone, du point de vue du processeur. Quand il fonctionne en mode synchrone, il permet d'interfacer un processeur synchrone avec une mémoire DRAM asynchrone. Un point important est que le contrôleur DRAM sert d'intermédiaire entre une mémoire DRAM et "le reste du monde". Il ne fait pas forcément office d'intermédiaire entre DRAM et processeur, mais peut aussi faire l'intermédiaire entre la DRAM et un bus système, entre une DRAM et le ''Video Display Controler'' d'une carte graphique, ou n'importe quel autre composant électronique qui utilise cette DRAM. ===Le générateur de ''timings'' et la traduction d'adresse=== Le contrôleur mémoire doit traduire les adresses du processeur en adresses compatibles avec la mémoire. Et la traduction est assez variable, suivant que le bus mémoire est un bus normal, un bus multiplexé, ou partiellement multiplexé. Nous avons vu ces trois types de bus mémoire dans le chapitre sur l'interface des mémoires, mais nous ferons quelques rappels rapides. Avec un ''bus totalement multiplexé'', le bus d'adresse et le bus de données sont fusionnés. Dans ce cas, on peut envoyer soit une adresse, soit lire/écrire une donnée sur le bus, mais on ne peut pas faire les deux en même temps. Un bit ALE indique si le bus est utilisé en tant que bus d'adresse ou bus de données. Le contrôleur mémoire gère cette situation, en fixant le bit ALE et en envoyant séparément adresse et donnée pour les écritures. [[File:Bus multiplexé avec bit ALE.png|centre|vignette|upright=2|Bus multiplexé avec bit ALE.]] Avec un ''bus d'adresse multiplexé'', l'adresse est découpée en une adresse de ligne et une adresse de colonne, envoyées l'une après l'autre. Le contrôleur mémoire prend en entrée une adresse mémoire complète, la découpe en deux, et envoie chaque morceau au bon moment. Pour cela, il suffit d'un registre pour mémoriser l'adresse et d'un multiplexeur. Le multiplexeur choisit soit les bits de poids fort de l'adresse, soit ceux de poids faible. Les premiers correspondent à l'adresse de ligne, les autres à l'adresse de colonne. La commande du multiplexeur est le fait d'un petit circuit séquentiel, qui génère aussi les signaux CAS et RAS. Au premier cycle, il met le signal RAS à 1, met le CAS à 0, et configure le MUX pour sélectionner les bits de poids fort. Au second cycle, il génère un signal CAS à 1, met le RAS à 0 et configure le MUX pour sélectionner les bits de poids faible. Le circuit en question est appelé le générateur de ''timings''. [[File:Controleur de DRAM simple, sans rafraichissement mémoire.png|centre|vignette|upright=2|Contrôleur de DRAM simple, sans rafraichissement mémoire.]] Le générateur de ''timings'' est un circuit séquentiel qui implémente une petite machine à état. Il est très simple sur une mémoire DRAM asynchrone basique, mais il est plus complexe sur les mémoires FPM, EDO, quartet, et autres. Le regroupement des multiplexeurs d'adresse et du générateur de ''timings'' est appelé le '''séquenceur mémoire'''. C'est le séquenceur mémoire qui traduit la requête processeur en commande DRAM, le reste du contrôleur est dédié au rafraichissement mémoire ou à d'autres fonctions facultatives. ===Le rafraichissement mémoire=== La gestion du rafraichissement mémoire est la fonction principale du contrôleur DRAM. Pour gérer le rafraichissement mémoire, le contrôleur mémoire intègre deux compteurs, un pour gérer l'adresse à rafraichir, l'autre pour gérer l'intervalle de temps entre deux rafraichissements. Le rafraichissement se fait à intervalle régulier, tous les x microsecondes. Pour déclencher le rafraichissement au bon moment, le contrôleur mémoire contient un ''Refresh Timer'', aussi appelé le '''compteur de rafraichissement'''. Il est initialisé avec le temps entre deux rafraichissements, une adresse est rafraichie quand ce compteur atteint 0. Le rafraichissement mémoire balaye la mémoire adresse par adresse. Pour savoir à quelle adresse il en est rendu, le contrôleur mémoire utilise un '''compteur d'adresse'''. Il contient la prochaine adresse à rafraichir, aussi appelée l'adresse de rafraichissement. Régulièrement, l'adresse dans ce compteur est envoyée à la RAM, pour une lecture. Mais la donnée lue n'est pas envoyée sur le bus de donnée, soit parce que la RAM est prévue pour, soit parce que le contrôleur désactive son bit ''output enable''. Dans le second cas, la RAM fait la lecture en interne, mais se déconnecte du bus de donnée, perdant la donnée lue dans le néant. Pour envoyer l'adresse de rafraichissement sur le bus d'adresse, il faut rajouter un multiplexeur, qui choisit entre l'adresse normale et l'adresse de rafraichissement. [[File:Controleur de DRAM avec rafraichissement mémoire.png|centre|vignette|upright=2|Controleur de DRAM avec rafraichissement mémoire.]] Le multiplexeur ne doit cependant pas être configuré si une adresse est déjà en cours de transfert. Pour cela, un circuit d'arbitrage se débrouille pour éviter qu'un accès mémoire soit interrompu par une demande de rafraichissement et inversement. Il peut être inclus dans le séquenceur mémoire ou séparé de celui-ci. [[File:Controleur mémoire, intérieur simplifié.png|centre|vignette|upright=2.5|Contrôleur mémoire, intérieur simplifié.]] Il faut noter que le rafraichissement mémoire peut être délégué non pas au contrôleur mémoire, mais au processeur où à la DRAM elle-même. Quand elle est le fait du processeur, celui-ci incorpore un ''refresh timer'' et un compteur d'adresse. Un exemple est celui du processeur Zilog Z80, qui implémentait des compteurs internes pour gérer le rafraichissement mémoire. On peut considérer que le processeur incorpore alors le contrôleur mémoire, au moins partiellement. Il est aussi possible de déléguer le rafraichissement au logiciel (certains jeux vidéos Amiga ou Commodore faisaient cela pour la mémoire vidéo). Quand la DRAM elle-même s'occupe de son propre rafraichissement, c'est elle qui intègre un ''refresh timer'' et le compteur d'adresse. ===Le décodage d'adresse=== Le contrôleur mémoire gère aussi le '''décodage d'adresse'''. pour rappel, celui-ci est utilisé quand on combine plusieurs chips mémoire ensemble, pour combiner leurs capacités mémoire. Par exemple, on peut combiner 4 chips mémoires de 1 mébioctet chacun, pour que le processeur voit comme 4 mébioctets de RAM unique. Le premier mébioctet est placé dans le premier chip mémoire, le second mébioctet dans le second chip, etc. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Pour cela, on active le chip mémoire adéquat, en fonction de l'adresse à consulter. Les autres chips mémoire sont désactivés pendant l'accès mémoire. En théorie, activer ou désactiver un chip mémoire se fait en utilisant son entrée ''Chip Select''. Il faut noter que si les SDRAM disposent bien d'un signal ''Chip Select'', ce n'est pas le cas des mémoires RAM asynchrones. A la place, ce sont les signaux RAS qui font office de ''Chip Select''. Une RAM asynchrone est activée quand son signal RAS lui demande de lire une ligne, elle est désactivée sinon. Mais c'est un détail. Toujours est-il que les signaux ''Chip Select'', ou leurs équivalents, sont générés par le contrôleur de DRAM, à partir des bits de poids fort de l'adresse. Par exemple, avec 4 chips mémoire, les deux bits de poids fort de l'adresse sont utilisés pour sélectionner le chip mémoire adéquat. Le contrôleur mémoire doit avoir plusieurs sorties ''Chip Select'', une par chip mémoire. Et le nombre de ces sorties limite le nombre de chips mémoire qu'on peut combiner. Par exemple, s'il y a seulement 4 sorties ''Chip Select'', on ne pourra brancher que 4 chips mémoire dessus. Sauf à ruser, avec un arrangement horizontal, mais cela n'est pas le ressort du contrôleur mémoire. [[File:Td6bfig3.png|centre|vignette|upright=2|Comparaison entre arrangement horizontal (à gauche) et arrangement vertical (à droite).]] ===Exemple : l'Intel 8202-8203=== L'Intel 8202 et le 8203 étaient des contrôleurs de mémoire DRAM, parmi les plus simples qui soient. Ils avaient une entrée d'adresse de 12 bits, ce qui permettait d'adresser 4 kibioctets de RAM. Ils fournissaient en sortie une adresse multiplexée sur 6 bits, envoyée en deux fois. Ils avaient donc 12 entrées d'adresse, 6 sorties d'adresse, un signal RAS, un signal CAS. Les adresses présentées en entrées n'étaient pas mémorisées dans des registres, ce qui fait qu'elles devaient être maintenues durant toute la durée de l'accès mémoire. Le processeur ne pouvait donc pas se déconnecter du bus d'adresse pendant l'accès mémoire, peu importe sa durée. Le 8202 contenait aussi un compteur de rafraichissement. Rappelons que sur les DRAM asynchrones, le rafraichissement se fait ligne par ligne. Le contrôleur mémoire a juste à présenter l'adresse de ligne, il n'a pas à envoyer l'adresse de colonne. La commande de rafraichissement se fait en mettant le signal RAS à 0, mais en laissant le CAS à 1 (je rappelle que les signaux sont actifs à 0). Le compteur d'adresse de rafraichissement a donc juste à mémoriser l'adresse de ligne. Le séquenceur mémoire était précédé par un circuit d'arbitrage, non-représenté dans le schéma ci-dessous. La microarchitecture de l'Intel 8202 est la suivante : [[File:Microarchitecture de l'Intel 8202.png|centre|vignette|upright=2|Microarchitecture de l'Intel 8202.]] Le 8202 avait une entrée pour un signal d'horloge, ainsi qu'un ''Chip Select'' un peu particulier. Si le signal CS passait à 0 lors d'un accès mémoire, le 8202/8203 ne se désactivait qu'une fois l'accès mémoire terminé. On ne pouvait pas l'interrompre pendant un accès mémoire, même en changeant le bit CS. Le signal d'horloge était utilisé pour commander le ''refresh timer''. Pour commander les lectures et écriture, il recevait en entrée un bit ''Write Request'' et un bit ''Read Request'', qui demandent respectivement une écriture et une lecture. En sortie, on trouvait un unique bit R/W qui valait 0 pour une lecture et 1 pour une écriture. Il avait aussi un bit d'entrée pour forcer le rafraichissement mémoire. S'il est à 1, la mémoire rafraichie l'adresse envoyée par le processeur. Le 8202 pouvait être connecté sur 1 à 4 chips mémoire, ce qui permettait d'adresser au maximum 16 kibioctets de RAM. Les 4 chips ne sont pas accédés en parallèle, un seul l'est à chaque fois. Pour le décodage d'adresse, le 8202 dispose de deux bits BO et B1 pour sélectionner le chip adéquat, ainsi que 4 sorties RAS pour activer la banque adéquate. On rappelle que le signal RAS remplace le signal ''Chip Select''. C'est le séquenceur mémoire qui se charge de générer ces signaux RAS, à partir des deux bits B0 et B1 (qui sont techniquement des bits d'adresse). Pour communiquer avec le processeur, l'Intel 8202 disposait de deux bits XACK et SACK. SACK indiquait au processeur que le 8202/8203 est en train de faire un accès mémoire et qu'il est indisponible pour un second accès mémoire. Cela permet de bloquer le processeur tant que le 8202 est indisponible. Le signal XACK indique que l'accès mémoire précédent est terminé et que : soit la donnée lue est présente sur le bus de données, soit que l'écriture s'est terminée. : Le signal SACK est très utile sur les configurations multiprocesseurs. Un processeur peut démarrer un accès mémoire, le signal SACK indiquera au second processeur qu'il doit attendre que l'accès soit terminé pour que ce soit son tour. ==Les contrôleurs de DRAM asynchrones évolués== L'Intel 8202 était un contrôleur mémoire basique, comme beaucoup d'autres à cette époque. Mais Intel a vendu des contrôleurs mémoires plus complexes. Par exemple, l'Intel 8207 était un contrôleur mémoire bien plus avancé que les deux précédents. Passons sur certains détails, comme le fait qu'il pouvait gérer jusqu'à 256 kibioctets de DRAM. Au-delà de ça, il y avait des fonctionnalités bien plus intéressantes, à savoir : un support de l'ECC, il était double port, il permettait de simuler une DRAM synchrone à partir d'une DRAM asynchrone. Il n'était pas le seul dans ce cas et des contrôleurs de DRAM très évolués étaient capables de faire des merveilles. Voyons comment cela était possible. ===Les contrôleurs mémoire synchrone=== Il est parfaitement possible d'utiliser un contrôleur mémoire synchrone avec une DRAM asynchrone. A vrai dire, le contrôleur DRAM peut complétement simuler une mémoire synchrone alors que la DRAM associée est asynchrone. La traduction asynchrone vers synchrone se fait en ajoutant des registres sur le bus mémoire, notamment sur le bus de données et le bus d'adresse/commande. Nous avions détaillé cela dans le chapitre sur les SRAM, c'est la même chose avec une mémoire DRAM. Sauf que cette fois-ci, le contrôleur mémoire doit aussi être prévu pour. [[File:Controleur mémoire synchrone utilisé avec une DRAM asynchrone.png|centre|vignette|upright=2|Contrôleur mémoire synchrone utilisé avec une DRAM asynchrone]] Les deux-trois registres illustrés plus haut peuvent être intégrés directement dans le contrôleur mémoire, que ce soit totalement ou en partie. Le strict minimum pour avoir un contrôleur mémoire synchrone est que celui-ci doit mémoriser adresse et commandes dans un registre. Par exemple, le 8207 d'Intel était capable de mémoriser les requêtes processeurs dans un registre interne, mais il fallait utiliser deux registres séparés pour le bus de données. Les deux registres étaient alors commandés par le contrôleur mémoire. Il est cependant possible d'aller plus loin et d'intégrer les registres du bus de données dans le contrôleur mémoire. [[File:Controleur mémoire DRAM synchrone.png|centre|vignette|upright=2|Contrôleur mémoire DRAM synchrone.]] : Il faut noter que cette fonctionnalité est parfois disponible sur les SRAM. En clair, on peut associer une SRAM asynchrone avec un contrôleur de SRAM synchrone. Le contrôleur de SRAM se charge alors de simuler une SRAM synchrone à partir de la SRAM asynchrone. Utiliser un contrôleur mémoire synchrone a de nombreux avantages, l'un d'entre eux étant lié aux ''wait state''. Quand le processeur envoie une requête de lecture/écriture à la mémoire RAM, celle-ci met plusieurs cycles d'horloge à répondre. Et pendant ce temps, le processeur... attend. Et pendant ce temps d'attente, il doit maintenir l'adresse mémoire sur le bus d'adresse. Les cycles d'horloge perdus à attendre la mémoire RAM étaient appelés des '''''Wait states'''''. Utiliser un contrôleur mémoire synchrone d'éliminer les ''wait state'', dans une certaine mesure. Avec un contrôleur mémoire synchrone, le processeur envoie l'adresse, mais c'est le contrôleur mémoire qui la maintient sur le bus d'adresse. Le processeur peut envoyer l'adresse et la donnée à écrire, elles sont recopiées dans les registres, et le controleur mémoire y a accès sans que le processeur doive les maintenir. Le processeur peut se déconnecter du bus mémoire et faire du travail dans son coin pendant que le contrôleur mémoire accède à la DRAM. Les ''wait state'' disparaissent alors, du moins du point de vue du processeur. ===La gestion de l'ECC=== L''''ECC''' peut être géré dans le contrôleur mémoire. Pour cela, on couple les registres mentionnés dans la section précédente, avec un circuit de détection et de correction d'erreur. Le circuit d'ECC peut, comme les registres synchrones, être intégré dans le contrôleur mémoire, ou au contraire être situé dans un circuit séparé. Si le circuit d'ECC est séparé du contrôleur mémoire, il communique avec lui, histoire que le contrôleur mémoire puisse signaler toute erreur de parité ou d'ECC au processeur. [[File:Controleur mémoire synchrone avec ECC intégré.png|centre|vignette|upright=2|Controleur mémoire synchrone avec ECC intégré]] Reprenons l'exemple du 8207 d'Intel. Le contrôleur mémoire 8207 gère le bus d'adresse et de commande, mais n'a pas de connexions directes avec le bus de données. Il ne peut donc pas prendre en charge l'ECC. Il avait besoin d'être couplé avec un circuit d'ECC séparé, relié au bus de données : l'Intel 8206. Le 8026 prenait en entrée : 16 bits de données et 8 bits d'ECC. Il fournissait en sortie 16 bits de données après correction d'erreur, les 8 bits d'ECC pour indiquer qu'une erreur a été détectée mais pas corrigée, ainsi que des bits de parité. Le 8206 détectait/corrigeait les erreurs et générait les bits d'ECC, mais il communiquait avec le contrôleur mémoire pour cela. [[File:8207 avec ECC.png|centre|vignette|upright=2|8207 avec ECC]] La détection/correction d'erreur était appliquée à la fois pour les accès mémoire et pour les rafraichissements mémoire. Lors d'un rafraichissement mémoire, la donnée rafraichie est lue et réécrite. Avec l'ECC activé et configuré correctement, le rafraichissement passe par le bus de données. Au lieu d'avoir un cycle de lecture-écriture interne à la DRAM, on a un cycle de lecture-correction-écriture qui utilise le 8206. La donnée lue est envoyée sur le bus de données, puis le 8206 corrige une éventuelle erreur, et la donnée corrigée est alors réécrite en mémoire. Au passage, si une erreur non-correctible est détectée, le 8206 ne fait rien, l'erreur est ignorée. La gestion de l'erreur sera retardée jusqu'à une lecture ultérieure. Et encore : si lecture ultérieure il y a. Si la donnée est écrasée par une écriture, la donnée corrompue sera simplement écrasée et disparaitra sans avoir pu faire le moindre dégât. Mais pour cela, le 8206 doit communiquer avec le contrôleur mémoire, pour savoir s'il est dans un cycle de rafraichissement ou un accès mémoire normal. Il prévient le 8207 lors d'une erreur, et c'est ce dernier qui décide si l'erreur doit être prise en compte ou ignorée. C'est seulement lors d'un accès mémoire normal que le processeur est prévenu qu'une erreur de parité/autre a eu lieu. ===Les contrôleurs mémoires multiports=== Les '''contrôleur mémoire multiport''' disposent de plusieurs ports, chacun permettant de traiter un accès mémoire. Ils peuvent simuler une mémoire multiport à partir d'une DRAM monoport. Évidemment, la simulation n'est pas parfaite. Des accès mémoire simultanés, envoyés en même temps sur différents ports, sont en réalité exécutés un par un, pas en même temps. Il y a donc une petite pénalité en termes de performances, mais elle est mineure. Encore une fois, nous allons reprendre l'exemple du 8207. Il avait deux ports séparés, et était prévu pour fonctionner dans un système à deux processeurs. L'usage de deux ports séparés permettait de partager une unique mémoire DRAM entre deux processeurs. Le partage se faisait en interfaçant deux processeurs sur le contrôleur mémoire, chacun étant connecté à un port. Lors d'une lecture, il redirigeait la donnée lue vers le bon processeur, en configurant le bus de données correctement. Le contrôleur mémoire recevait des requêtes mémoire de deux processeurs, mais il les exécutait une à la fois. S'il recevait deux requêtes en même temps, l'une d'entre elles était mise en attente. Le contrôleur mémoire doit arbitrer les accès à la mémoire, et faire en sorte que les deux processeurs aient accès à la mémoire à tour de rôle. Et non seulement il doit arbitrer les deux ports, mais il y a aussi un troisième port interne au contrôleur mémoire : le rafraichissement mémoire ! Pour cela, le circuit d'arbitrage qui choisissait entre rafraichissement mémoire et accès mémoire, est amélioré de manière à gérer un second port. Le circuit d'arbitrage donne l'accès au séquenceur mémoire à un port sélectionné. L'arbitrage était configurable, avec deux options : soit le port A est privilégié sur le port B, soit le port le plus récemment accédé à la priorité. Les deux ports pouvaient être configurés pour fonctionner soit de manière asynchrone, soit de manière synchrone. Il était aussi possible de configurer l'ECC, des options liées à la fréquence du processeur et de la RAM, ainsi que de nombreuses options liées au rafraichissement. Pour cela, le 8207 contenait un registre de configuration interne, programmable en fournissant les entrées adéquates. Tout ce qui vient d'être dit se généralise avec plus de deux processeurs. Le 8207 ne permettait pas ça, mais les contrôleurs mémoire des PC modernes en sont capables. Ils peuvent gérer plusieurs dizaines de processeurs facilement. ==Le contrôleur mémoire d'une DRAM ''Fast Page Mode''== Les mémoires DRAM classiques sont des mémoires à tampon de ligne, mais qui sont assez mal utilisées. Notamment, tout accès mémoire se fait en deux phases : un accès pour sélectionner la ligne, un autre pour sélectionner la colonne. Les mémoires ''Fast Page Mode'' permettent d'optimiser le tout. Elles permettent de faire plusieurs accès successifs à la même ligne, à des colonnes différentes. Et le contrôleur mémoire doit être adapté pour cela. [[File:Sélection d'une ligne sur une mémoire FPM ou EDO.png|centre|vignette|upright=2|Sélection d'une ligne sur une mémoire FPM ou EDO.]] Le contrôleur mémoire doit détecter que deux accès mémoire consécutifs se font dans la même ligne. Si deux accès consécutifs accèdent à la même ligne, on doit juste changer de colonne et altérer le signal CAS. C'est un ''succès de tampon de ligne'', aussi appelé un '''succès de page'''. Si deux accès consécutifs accèdent à une ligne différente, c'est un ''défaut de tampon de ligne'', aussi appelé un '''défaut de page'''. Il faut alors changer de ligne, en altérant les signaux RAS et en envoyant une adresse de ligne. Pour détecter les succès ou défauts de page, il faut ajouter un circuit spécialisé dans le contrôleur mémoire. Il mémorise la ligne ouverte, et plus précisément son adresse de ligne (numéro de banque inclut). A chaque requête processeur, il compare l'adresse de ligne recue avec celle déjà ouverte. C'est un succès si les deux sont égales, un défaut si elles sont différentes. Le circuit qui fait cette comparaison est appelé le '''décodeur de commande'''. Il prévient le séquenceur mémoire en cas de succès de page, grâce à un signal de un bit, qui vaut 0 en cas de défaut de page et 1 en cas de succès. Le séquenceur mémoire décide alors comment gérer les signaux RAS et CAS, ainsi que l'envoi des adresses de ligne/colonne. [[File:Controleur mémoire d'une FPM-DRAM.png|centre|vignette|upright=2|Controleur mémoire d'une FPM-DRAM]] ==Le contrôleur mémoire d'une SDRAM ou d'une DDR== l'intérieur d'un contrôleur de SDRAM ne change pas significativement d'un controleur de RAM asynchrone. Il regroupe toujours un séquenceur mémoire et une interface physique, un circuit pour le rafraichissement mémoire et un circuit d'arbitrage. Par contre, ses sorties changent pas mal. Contrairement aux mémoires DRAM basiques, les mémoires SDRAM sont cadencées par un signal d'horloge. Et ce signal d'horloge vient bien de quelque part. Pour cela, deux solutions : soit le contrôleur mémoire génère la fréquence qui commande la mémoire, soit il prend en entrée une fréquence de base qu'il multiplie pour obtenir la fréquence désirée. Les deux solutions sont équivalentes, si ce n'est que les circuits impliqués ne sont pas les mêmes. Dans le premier cas, le contrôleur doit embarquer un circuit oscillateur, qui génère la fréquence demandée. Dans l'autre cas, un simple multiplieur/diviseur de fréquence suffit et c'est généralement une PLL qui est utilisée pour cela. : Notez qu'il ne faut pas confondre la fréquence de la SDRAM et celle du contrôleur mémoire. Le contrôleur mémoire fonctionne à une vitesse assez élevée, en interne. Le port relié au processeur fonctionne à haute fréquence, généralement la même que celle du processeur. A vrai dire, de nos jours, il est intégré dans le processeur. Pour le décodage d'adresse, tout est plus simple sur les SDRAM, DDR inclues. Les chips de mémoire SDRAM et DDR disposent d'une entrée ''Chip Select'', ce qui facilite grandement le décodage d'adresse. Les bits de ''Chip Select'' sont générés par le contrôleur mémoire, et sont transmis sur le bus de commande. Le contrôleur de DRAM peut adresser un certain nombre de rangées, dispersés sur une ou plusieurs barrettes. La limite maximale dépend du contrôleur de DRAM, elle est souvent proche de 8 ou 16 rangées. Si on combine plusieurs barrettes de mémoire, il est possible de dépasser cette limite. Par exemple, pour un contrôleur de DRAM supportant maximum 8 rangées, 4 barrettes de 4 rangées chacune dépassent la limite. ===Le séquenceurs mémoire pour les SDRAM/DDR=== Le séquenceur mémoire existe toujours pour les mémoires SDRAM, c'est toujours un circuit séquentiel qui implémente une machine à état. Il traduit toujours une requête processeur en une séquence de commandes envoyées à des timings bien précis. Les commandes mémoires peuvent provenir de l'extérieur, mais aussi d'un circuit de rafraichissement intégré dans le contrôleur mémoire, comme pour les autres DRAM. La seule différence est que la machine à état est plus complexe. Pour rappel, une requête de lecture/écriture se fait en trois étapes maximum : une commande PRECHARGE pour précharger le tampon de ligne, une commande ACT qui fixe l'adresse de ligne, et enfin une commande READ/WRITE avec l'adresse de colonne. Et ces commandes sont séparées par des '''délais mémoire''' bien précis. Par exemple, je prends des chiffres arbitraires : il faut attendre 2 cycles entre une commande ACT et une commande READ, 6 cycles avant deux commandes WRITE consécutives, etc. La gestion des délais mémoire rend la conception du séquenceur plus complexe. Il faut aussi tenir compte des commandes SDRAM anticipées, à savoir que l'on peut envoyer des commandes avant que la précédente soit terminée. Les commandes anticipées sont idéales dans le cas où des accès successifs se font dans des banques différentes. Pour les exploiter au mieux, le contrôleur mémoire doit donc détecter si des accès successifs se font dans des banques différentes, ou dans la même banque, pour décider d'envoyer des commandes anticipées ou non. Cette '''détection des conflits de banque''' complexifie le séquenceur. ===La détection des succès/défauts de page=== Un point important est que dans certaines conditions, certaines commandes peuvent être omises. Par exemple, en cas de succès de page, les commandes PRECHARGE et ACT ne doivent pas être envoyées, seule les commandes READ/WRITE le sont. Le contrôleur doit toujours '''détecter les succès et défauts de page''' et agir en fonction. La solution utilisée est la même que pour les mémoires FPM : il faut mémoriser quelle ligne est ouverte ou fermée. La différence avec les FPM est qu'il faut faire cela pour chaque banque mémoire ! En effet, chaque banque a son propre tampon de ligne, ce qui fait que la gestion des lignes se fait indépendamment dans chaque banque. Le séquenceur mémoire doit donc se souvenir des lignes actives dans chaque banque. Pour cela, il mémorise ces lignes dans une petite mémoire : la '''table des banques''', aussi appelée ''bank status memory''. Pour détecter un succès ou un défaut, le contrôleur doit extraire la ligne de l'adresse, mais aussi le numéro de banque. Il envoie alors le numéro de banque à la table des banques, sur son entrée d'adresse. Il récupère alors le numéro de la ligne active sur les sorties de données. Il compare alors ce numéro de ligne avec le numéro de ligne de l'adresse envoyée par le processeur. C'est un succès si les deux sont égales, un défaut sinon. [[File:Controleur mémoire FPM avec plusieurs banques.jpg|centre|vignette|upright=2|Contrôleur mémoire FPM avec plusieurs banques.]] ===La politique de gestion du tampon de ligne=== Pour ce qui est des succès/défauts de page, le séquenceur mémoire peut fonctionner de plusieurs manières, dont les plus extrêmes sont appelés la politique de la page fermée et la politique de la page ouverte. Voyons à quoi elles correspondent. Avec la '''politique de la page fermée''', chaque accès mémoire est suivi d'une commande PRECHARGE, qui ferme la ligne courante et précharge les lignes de bits. Même si deux accès consécutifs se font dans la même ligne, la ligne est fermée et ré-ouverte entre deux accès mémoire. En clair : l'optimisation introduite par les mémoires FPM est désactivée, le contrôleur mémoire fait exprès de ne pas en profiter. On appelle cette méthode la close ''page autoprecharge''. Cette méthode réduit grandement les performances pour les accès à des adresses consécutives, mais fonctionne à merveille si les accès sont "aléatoires", à savoir qu'ils se font sans régularités évidentes. Son seul avantage est que l'implémentation du séquenceur mémoire est très simple. En effet, le séquenceur mémoire se passe complétement de la table des banques, du comparateur de ligne, et de tous les circuits nécessaires pour vérifier les succès ou défauts de page. De plus, le séquenceur mémoire profite grandement des commandes READA et WRITEA, qui fusionnent une commande READ/WRITE avec une commande PRECHARGE. Le séquenceur mémoire a juste à envoyer des commandes ACT, READA, WRITEA et PREFETCH à la mémoire, pas besoin des commandes PRECHARGE, READ ou WRITE. A l'opposé, la '''politique de la page ouverte''' ne ferme pas automatiquement la ligne. Elle la laisse ouverte, en espérant que le prochain accès mémoire se fasse dans cette ligne. Lorsqu'un nouvel accès mémoire arrive, elle doit détecter les succès ou défauts de page et agir en fonction. En cas de défaut de page, la ligne est fermée, le séquenceur mémoire envoie une commande PRECHARGE, puis l'accès suivant effectue les deux commandes ACT + READ ou WRITE. En cas de succès de page, les commandes PRECHARGE et ACT ne sont pas envoyées, seules la commande READ ou WRITE l'est. Un désavantage est que le contrôleur mémoire doit inclure une table des banques et un comparateur, comme vu plus haut dans la section sur les mémoires FPM. un autre défaut est que garder une ligne ouverte consomme beaucoup d'énergie, comparé à un simple état de PRECHARGE. En conséquence, il est préférable de fermer les lignes dès que possible. Par contre, les performances sont d'autant meilleures que les accès mémoire consécutifs à une même ligne soient assez fréquents. Si les accès mémoire sont aléatoires, les performances sont moins bonnes. La politique de la page fermée fermait les lignes en avance, avec des commandes READA ou WRITEA, avant même que l'accès suivant démarre. Avec la politique de la page ouverte, on doit attendre pour détecter un défaut de page, puis fermer la ligne avec une commande PRECHARGE séparée. La ligne est donc fermée avec un peu temps de retard, et envoyer deux commandes au lieu d'une prend plus de temps. Les contrôleurs mémoires basiques utilisent une des deux solutions précédentes. Soit la page est toujours fermée, soit elle est toujours laissée ouverte jusqu'à ce qu'un accès mémoire la referme. Mais les contrôleurs plus évolués tentent de prédire s'il faut fermer ou laisser ouvertes les pages ouvertes. La méthode la plus simple attend un temps prédéterminé avant de fermer la ligne. Une autre solution regarde le tout dernier accès. On peut très bien décider de laisser la ligne ouverte si l'accès mémoire précédent était une rafale, et fermer sinon. Une solution plus complexe mémorise les N derniers accès et en déduit s'il faut fermer ou non la prochaine ligne. On peut mémoriser si l'accès en question a causé la fermeture d'une ligne avec un bit. Mémoriser les N derniers accès demande d'utiliser un simple registre à décalage. Pour chaque valeur de ce registre, il faut prédire si le prochain accès demandera une ouverture ou une fermeture. Une solution simple fait la moyenne des bits à 1 dans ce registre : si plus de la moitié des bits est à 1, on laisse la ligne ouverte et on ferme sinon. Pour améliorer un petit peu l'algorithme, on peut faire en sorte que les bits des accès mémoires les plus récents aient plus de poids dans le calcul de la moyenne. Il existe sans doute d'autres solutions plus évoluées, mais il est difficile de savoir ce qu'il y a dans les contrôleurs de SDRAM modernes. : Le fait de laisser ouverte une ligne ou au contraire de la fermer systématiquement, se fait pour chaque banque. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les mémoires RAM dynamiques (DRAM) | prevText=Les mémoires RAM dynamiques (DRAM) | next=Les mémoires associatives | nextText=Les mémoires associatives }} </noinclude> jmnv6mr90zw2p3f94ia7fvn5snpw1sy 764689 764687 2026-04-23T19:18:23Z Mewtow 31375 /* La politique de gestion du tampon de ligne */ 764689 wikitext text/x-wiki Les mémoires ROM ou SRAM ont généralement une interface simple, à laquelle le processeur peut s'interfacer directement. Mais pour les DRAM, ce n'est pas le cas. Les DRAM utilisent un bus d'adresse multiplexé, où l'adresse est envoyée en deux fois. Connecter le processeur directement sur une DRAM n'est pas pratique : le bus d'adresse du processeur et celui de la mémoire ne collent pas. Les DRAM doivent aussi être rafraichies régulièrement. Le rafraichissement mémoire peut être délégué au processeur, mais c'est loin d'être idéal. Et il y a bien d'autres raisons qui font que le processeur ne peut pas s'interfacer facilement avec les mémoires DRAM. Pour gérer ces problèmes, les mémoires DRAM ne sont pas connectées directement au processeur. À la place, on ajoute un intermédiaire entre le processeur et la mémoire : le '''contrôleur mémoire externe'''. Son but est de montrer au processeur une interface simple, semblable à celle d'une SRAM classique, alors qu'il commande une mémoire DRAM à l'interface plus complexe. Il est placé sur la carte mère ou dans le processeur, et ne doit pas être confondu avec le contrôleur mémoire intégré dans la mémoire. Ce chapitre va expliquer quels sont les rôles du contrôleur mémoire, son interface et ce qu'il y a à l'intérieur. Dans ce chapitre, quand nous parlerons de ''contrôleur mémoire'', cela fera systématiquement référence au contrôleur mémoire externe. Et avant de poursuivre, sachez qu'il est difficile de faire des généralités sur les contrôleurs mémoire, car les mémoires DRAM elles-mêmes sont assez différentes les unes des autres. Entre une mémoire EDO, une mémoire SDR, une mémoire DDR et une DRAM asynchrone, les contrôleurs mémoires seront fortement différents. Aussi, il y a aura une différence entre un contrôleur pour une DRAM asynchrone et un contrôleur pour une mémoire EDO, une mémoire SDRAM, etc. J'ai choisit de vous séparer les contrôleurs mémoire pour les DRAM asynchrones de ceux pour les SDRAM/DRR. ==Le contrôleur d'une DRAM asynchrone== Les premières DRAM asynchrones avaient des contrôleurs mémoires dédiés, qui étaient séparés du processeur et du ''chipset'' de la carte mère. Par exemple, les composants Intel 8202, Intel 8203 et Intel 8207 étaient des contrôleurs mémoire pour DRAM asynchrones qui étaient vendus dans des boitiers DIP et étaient soudés sur la carte mère. Par la suite, ils ont été intégrés au ''chipset'' de la carte mère pendant les décennies 90-2000. Après les années 2000, ils ont été intégrés dans les processeurs. Leurs fonctions étaient le multiplexage de l'adresse et le rafraichissement mémoire. Ils recevaient une adresse mémoire complète, qu'ils découpaient une adresse de ligne et une adresse de colonne, envoyées l'une après l'autre sur le bus mémoire. Pour le rafraichissement mémoire, ils rafraichissaient la DRAM régulièrement, de manière automatique, entre deux accès mémoire normaux. Le processeur n'avait ainsi plus à rafraichir la mémoire lui-même, cette fonction était déléguée au contrôleur de DRAM. Ils étaient connectés au bus d'adresse et de commande, avec éventuellement des relations indirectes avec le bus de données. ===L'interface d'un contrôleur de DRAM asynchrone=== L'interface du contrôleur mémoire décrit ses broches d'entrées/sorties et leur signification. Elle est généralement très simple et contient deux ports : un connecté au processeur, un autre connecté à la DRAM. Cela trahit d'ailleurs son rôle principal, qui est de transformer les requêtes de lecture/écriture provenant du processeur en une suite de commandes acceptée par la mémoire. Le port connecté à la DRAM est connecté ua bus d'adresse et au bus de commande. Le bus de données est lui relié au processeur et/ou au bus système. Un accès mémoire provenant du processeur contient une adresse à lire/écrire, le bit R/W qui indique s'il faut faire une lecture ou une écriture, et éventuellement une donnée à écrire. Mais, nous avons vu que les accès mémoires sur une DRAM sont multiplexés : on envoie l'adresse en deux fois : la ligne d'abord, puis la colonne. De plus, il faut générer les signaux RAS, CAS et bien d'autres. Le tout est illustré ci-dessous. [[File:Contrôleur mémoire.png|centre|vignette|upright=2|Contrôleur mémoire externe.]] Un point important est que les DRAM asynchrones n'ont pas d'entrée ''Chip Select'' ou d'entrée ''Output Enable''. Les signaux RAS et CAS remplacent en quelque sorte ces deux signaux. Le bit RAS fait office de ''Chip Select'', le bit CAS fait office d'''Output Enable''. Pour certains contrôleurs de DRAM, il faut ajouter l''''interface électrique''', qui traduit les signaux du processeur en signaux compatibles avec la mémoire. Il est en effet très fréquent que la mémoire et le processeur n'utilisent pas les mêmes tensions pour coder un bit, ce qui fait qu'elles ne sont pas compatibles. Dans ce cas, le contrôleur mémoire fait la conversion. Le contrôleur DRAM peut fonctionner en mode synchrone ou asynchrone, du point de vue du processeur. Quand il fonctionne en mode synchrone, il permet d'interfacer un processeur synchrone avec une mémoire DRAM asynchrone. Un point important est que le contrôleur DRAM sert d'intermédiaire entre une mémoire DRAM et "le reste du monde". Il ne fait pas forcément office d'intermédiaire entre DRAM et processeur, mais peut aussi faire l'intermédiaire entre la DRAM et un bus système, entre une DRAM et le ''Video Display Controler'' d'une carte graphique, ou n'importe quel autre composant électronique qui utilise cette DRAM. ===Le générateur de ''timings'' et la traduction d'adresse=== Le contrôleur mémoire doit traduire les adresses du processeur en adresses compatibles avec la mémoire. Et la traduction est assez variable, suivant que le bus mémoire est un bus normal, un bus multiplexé, ou partiellement multiplexé. Nous avons vu ces trois types de bus mémoire dans le chapitre sur l'interface des mémoires, mais nous ferons quelques rappels rapides. Avec un ''bus totalement multiplexé'', le bus d'adresse et le bus de données sont fusionnés. Dans ce cas, on peut envoyer soit une adresse, soit lire/écrire une donnée sur le bus, mais on ne peut pas faire les deux en même temps. Un bit ALE indique si le bus est utilisé en tant que bus d'adresse ou bus de données. Le contrôleur mémoire gère cette situation, en fixant le bit ALE et en envoyant séparément adresse et donnée pour les écritures. [[File:Bus multiplexé avec bit ALE.png|centre|vignette|upright=2|Bus multiplexé avec bit ALE.]] Avec un ''bus d'adresse multiplexé'', l'adresse est découpée en une adresse de ligne et une adresse de colonne, envoyées l'une après l'autre. Le contrôleur mémoire prend en entrée une adresse mémoire complète, la découpe en deux, et envoie chaque morceau au bon moment. Pour cela, il suffit d'un registre pour mémoriser l'adresse et d'un multiplexeur. Le multiplexeur choisit soit les bits de poids fort de l'adresse, soit ceux de poids faible. Les premiers correspondent à l'adresse de ligne, les autres à l'adresse de colonne. La commande du multiplexeur est le fait d'un petit circuit séquentiel, qui génère aussi les signaux CAS et RAS. Au premier cycle, il met le signal RAS à 1, met le CAS à 0, et configure le MUX pour sélectionner les bits de poids fort. Au second cycle, il génère un signal CAS à 1, met le RAS à 0 et configure le MUX pour sélectionner les bits de poids faible. Le circuit en question est appelé le générateur de ''timings''. [[File:Controleur de DRAM simple, sans rafraichissement mémoire.png|centre|vignette|upright=2|Contrôleur de DRAM simple, sans rafraichissement mémoire.]] Le générateur de ''timings'' est un circuit séquentiel qui implémente une petite machine à état. Il est très simple sur une mémoire DRAM asynchrone basique, mais il est plus complexe sur les mémoires FPM, EDO, quartet, et autres. Le regroupement des multiplexeurs d'adresse et du générateur de ''timings'' est appelé le '''séquenceur mémoire'''. C'est le séquenceur mémoire qui traduit la requête processeur en commande DRAM, le reste du contrôleur est dédié au rafraichissement mémoire ou à d'autres fonctions facultatives. ===Le rafraichissement mémoire=== La gestion du rafraichissement mémoire est la fonction principale du contrôleur DRAM. Pour gérer le rafraichissement mémoire, le contrôleur mémoire intègre deux compteurs, un pour gérer l'adresse à rafraichir, l'autre pour gérer l'intervalle de temps entre deux rafraichissements. Le rafraichissement se fait à intervalle régulier, tous les x microsecondes. Pour déclencher le rafraichissement au bon moment, le contrôleur mémoire contient un ''Refresh Timer'', aussi appelé le '''compteur de rafraichissement'''. Il est initialisé avec le temps entre deux rafraichissements, une adresse est rafraichie quand ce compteur atteint 0. Le rafraichissement mémoire balaye la mémoire adresse par adresse. Pour savoir à quelle adresse il en est rendu, le contrôleur mémoire utilise un '''compteur d'adresse'''. Il contient la prochaine adresse à rafraichir, aussi appelée l'adresse de rafraichissement. Régulièrement, l'adresse dans ce compteur est envoyée à la RAM, pour une lecture. Mais la donnée lue n'est pas envoyée sur le bus de donnée, soit parce que la RAM est prévue pour, soit parce que le contrôleur désactive son bit ''output enable''. Dans le second cas, la RAM fait la lecture en interne, mais se déconnecte du bus de donnée, perdant la donnée lue dans le néant. Pour envoyer l'adresse de rafraichissement sur le bus d'adresse, il faut rajouter un multiplexeur, qui choisit entre l'adresse normale et l'adresse de rafraichissement. [[File:Controleur de DRAM avec rafraichissement mémoire.png|centre|vignette|upright=2|Controleur de DRAM avec rafraichissement mémoire.]] Le multiplexeur ne doit cependant pas être configuré si une adresse est déjà en cours de transfert. Pour cela, un circuit d'arbitrage se débrouille pour éviter qu'un accès mémoire soit interrompu par une demande de rafraichissement et inversement. Il peut être inclus dans le séquenceur mémoire ou séparé de celui-ci. [[File:Controleur mémoire, intérieur simplifié.png|centre|vignette|upright=2.5|Contrôleur mémoire, intérieur simplifié.]] Il faut noter que le rafraichissement mémoire peut être délégué non pas au contrôleur mémoire, mais au processeur où à la DRAM elle-même. Quand elle est le fait du processeur, celui-ci incorpore un ''refresh timer'' et un compteur d'adresse. Un exemple est celui du processeur Zilog Z80, qui implémentait des compteurs internes pour gérer le rafraichissement mémoire. On peut considérer que le processeur incorpore alors le contrôleur mémoire, au moins partiellement. Il est aussi possible de déléguer le rafraichissement au logiciel (certains jeux vidéos Amiga ou Commodore faisaient cela pour la mémoire vidéo). Quand la DRAM elle-même s'occupe de son propre rafraichissement, c'est elle qui intègre un ''refresh timer'' et le compteur d'adresse. ===Le décodage d'adresse=== Le contrôleur mémoire gère aussi le '''décodage d'adresse'''. pour rappel, celui-ci est utilisé quand on combine plusieurs chips mémoire ensemble, pour combiner leurs capacités mémoire. Par exemple, on peut combiner 4 chips mémoires de 1 mébioctet chacun, pour que le processeur voit comme 4 mébioctets de RAM unique. Le premier mébioctet est placé dans le premier chip mémoire, le second mébioctet dans le second chip, etc. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Pour cela, on active le chip mémoire adéquat, en fonction de l'adresse à consulter. Les autres chips mémoire sont désactivés pendant l'accès mémoire. En théorie, activer ou désactiver un chip mémoire se fait en utilisant son entrée ''Chip Select''. Il faut noter que si les SDRAM disposent bien d'un signal ''Chip Select'', ce n'est pas le cas des mémoires RAM asynchrones. A la place, ce sont les signaux RAS qui font office de ''Chip Select''. Une RAM asynchrone est activée quand son signal RAS lui demande de lire une ligne, elle est désactivée sinon. Mais c'est un détail. Toujours est-il que les signaux ''Chip Select'', ou leurs équivalents, sont générés par le contrôleur de DRAM, à partir des bits de poids fort de l'adresse. Par exemple, avec 4 chips mémoire, les deux bits de poids fort de l'adresse sont utilisés pour sélectionner le chip mémoire adéquat. Le contrôleur mémoire doit avoir plusieurs sorties ''Chip Select'', une par chip mémoire. Et le nombre de ces sorties limite le nombre de chips mémoire qu'on peut combiner. Par exemple, s'il y a seulement 4 sorties ''Chip Select'', on ne pourra brancher que 4 chips mémoire dessus. Sauf à ruser, avec un arrangement horizontal, mais cela n'est pas le ressort du contrôleur mémoire. [[File:Td6bfig3.png|centre|vignette|upright=2|Comparaison entre arrangement horizontal (à gauche) et arrangement vertical (à droite).]] ===Exemple : l'Intel 8202-8203=== L'Intel 8202 et le 8203 étaient des contrôleurs de mémoire DRAM, parmi les plus simples qui soient. Ils avaient une entrée d'adresse de 12 bits, ce qui permettait d'adresser 4 kibioctets de RAM. Ils fournissaient en sortie une adresse multiplexée sur 6 bits, envoyée en deux fois. Ils avaient donc 12 entrées d'adresse, 6 sorties d'adresse, un signal RAS, un signal CAS. Les adresses présentées en entrées n'étaient pas mémorisées dans des registres, ce qui fait qu'elles devaient être maintenues durant toute la durée de l'accès mémoire. Le processeur ne pouvait donc pas se déconnecter du bus d'adresse pendant l'accès mémoire, peu importe sa durée. Le 8202 contenait aussi un compteur de rafraichissement. Rappelons que sur les DRAM asynchrones, le rafraichissement se fait ligne par ligne. Le contrôleur mémoire a juste à présenter l'adresse de ligne, il n'a pas à envoyer l'adresse de colonne. La commande de rafraichissement se fait en mettant le signal RAS à 0, mais en laissant le CAS à 1 (je rappelle que les signaux sont actifs à 0). Le compteur d'adresse de rafraichissement a donc juste à mémoriser l'adresse de ligne. Le séquenceur mémoire était précédé par un circuit d'arbitrage, non-représenté dans le schéma ci-dessous. La microarchitecture de l'Intel 8202 est la suivante : [[File:Microarchitecture de l'Intel 8202.png|centre|vignette|upright=2|Microarchitecture de l'Intel 8202.]] Le 8202 avait une entrée pour un signal d'horloge, ainsi qu'un ''Chip Select'' un peu particulier. Si le signal CS passait à 0 lors d'un accès mémoire, le 8202/8203 ne se désactivait qu'une fois l'accès mémoire terminé. On ne pouvait pas l'interrompre pendant un accès mémoire, même en changeant le bit CS. Le signal d'horloge était utilisé pour commander le ''refresh timer''. Pour commander les lectures et écriture, il recevait en entrée un bit ''Write Request'' et un bit ''Read Request'', qui demandent respectivement une écriture et une lecture. En sortie, on trouvait un unique bit R/W qui valait 0 pour une lecture et 1 pour une écriture. Il avait aussi un bit d'entrée pour forcer le rafraichissement mémoire. S'il est à 1, la mémoire rafraichie l'adresse envoyée par le processeur. Le 8202 pouvait être connecté sur 1 à 4 chips mémoire, ce qui permettait d'adresser au maximum 16 kibioctets de RAM. Les 4 chips ne sont pas accédés en parallèle, un seul l'est à chaque fois. Pour le décodage d'adresse, le 8202 dispose de deux bits BO et B1 pour sélectionner le chip adéquat, ainsi que 4 sorties RAS pour activer la banque adéquate. On rappelle que le signal RAS remplace le signal ''Chip Select''. C'est le séquenceur mémoire qui se charge de générer ces signaux RAS, à partir des deux bits B0 et B1 (qui sont techniquement des bits d'adresse). Pour communiquer avec le processeur, l'Intel 8202 disposait de deux bits XACK et SACK. SACK indiquait au processeur que le 8202/8203 est en train de faire un accès mémoire et qu'il est indisponible pour un second accès mémoire. Cela permet de bloquer le processeur tant que le 8202 est indisponible. Le signal XACK indique que l'accès mémoire précédent est terminé et que : soit la donnée lue est présente sur le bus de données, soit que l'écriture s'est terminée. : Le signal SACK est très utile sur les configurations multiprocesseurs. Un processeur peut démarrer un accès mémoire, le signal SACK indiquera au second processeur qu'il doit attendre que l'accès soit terminé pour que ce soit son tour. ==Les contrôleurs de DRAM asynchrones évolués== L'Intel 8202 était un contrôleur mémoire basique, comme beaucoup d'autres à cette époque. Mais Intel a vendu des contrôleurs mémoires plus complexes. Par exemple, l'Intel 8207 était un contrôleur mémoire bien plus avancé que les deux précédents. Passons sur certains détails, comme le fait qu'il pouvait gérer jusqu'à 256 kibioctets de DRAM. Au-delà de ça, il y avait des fonctionnalités bien plus intéressantes, à savoir : un support de l'ECC, il était double port, il permettait de simuler une DRAM synchrone à partir d'une DRAM asynchrone. Il n'était pas le seul dans ce cas et des contrôleurs de DRAM très évolués étaient capables de faire des merveilles. Voyons comment cela était possible. ===Les contrôleurs mémoire synchrone=== Il est parfaitement possible d'utiliser un contrôleur mémoire synchrone avec une DRAM asynchrone. A vrai dire, le contrôleur DRAM peut complétement simuler une mémoire synchrone alors que la DRAM associée est asynchrone. La traduction asynchrone vers synchrone se fait en ajoutant des registres sur le bus mémoire, notamment sur le bus de données et le bus d'adresse/commande. Nous avions détaillé cela dans le chapitre sur les SRAM, c'est la même chose avec une mémoire DRAM. Sauf que cette fois-ci, le contrôleur mémoire doit aussi être prévu pour. [[File:Controleur mémoire synchrone utilisé avec une DRAM asynchrone.png|centre|vignette|upright=2|Contrôleur mémoire synchrone utilisé avec une DRAM asynchrone]] Les deux-trois registres illustrés plus haut peuvent être intégrés directement dans le contrôleur mémoire, que ce soit totalement ou en partie. Le strict minimum pour avoir un contrôleur mémoire synchrone est que celui-ci doit mémoriser adresse et commandes dans un registre. Par exemple, le 8207 d'Intel était capable de mémoriser les requêtes processeurs dans un registre interne, mais il fallait utiliser deux registres séparés pour le bus de données. Les deux registres étaient alors commandés par le contrôleur mémoire. Il est cependant possible d'aller plus loin et d'intégrer les registres du bus de données dans le contrôleur mémoire. [[File:Controleur mémoire DRAM synchrone.png|centre|vignette|upright=2|Contrôleur mémoire DRAM synchrone.]] : Il faut noter que cette fonctionnalité est parfois disponible sur les SRAM. En clair, on peut associer une SRAM asynchrone avec un contrôleur de SRAM synchrone. Le contrôleur de SRAM se charge alors de simuler une SRAM synchrone à partir de la SRAM asynchrone. Utiliser un contrôleur mémoire synchrone a de nombreux avantages, l'un d'entre eux étant lié aux ''wait state''. Quand le processeur envoie une requête de lecture/écriture à la mémoire RAM, celle-ci met plusieurs cycles d'horloge à répondre. Et pendant ce temps, le processeur... attend. Et pendant ce temps d'attente, il doit maintenir l'adresse mémoire sur le bus d'adresse. Les cycles d'horloge perdus à attendre la mémoire RAM étaient appelés des '''''Wait states'''''. Utiliser un contrôleur mémoire synchrone d'éliminer les ''wait state'', dans une certaine mesure. Avec un contrôleur mémoire synchrone, le processeur envoie l'adresse, mais c'est le contrôleur mémoire qui la maintient sur le bus d'adresse. Le processeur peut envoyer l'adresse et la donnée à écrire, elles sont recopiées dans les registres, et le controleur mémoire y a accès sans que le processeur doive les maintenir. Le processeur peut se déconnecter du bus mémoire et faire du travail dans son coin pendant que le contrôleur mémoire accède à la DRAM. Les ''wait state'' disparaissent alors, du moins du point de vue du processeur. ===La gestion de l'ECC=== L''''ECC''' peut être géré dans le contrôleur mémoire. Pour cela, on couple les registres mentionnés dans la section précédente, avec un circuit de détection et de correction d'erreur. Le circuit d'ECC peut, comme les registres synchrones, être intégré dans le contrôleur mémoire, ou au contraire être situé dans un circuit séparé. Si le circuit d'ECC est séparé du contrôleur mémoire, il communique avec lui, histoire que le contrôleur mémoire puisse signaler toute erreur de parité ou d'ECC au processeur. [[File:Controleur mémoire synchrone avec ECC intégré.png|centre|vignette|upright=2|Controleur mémoire synchrone avec ECC intégré]] Reprenons l'exemple du 8207 d'Intel. Le contrôleur mémoire 8207 gère le bus d'adresse et de commande, mais n'a pas de connexions directes avec le bus de données. Il ne peut donc pas prendre en charge l'ECC. Il avait besoin d'être couplé avec un circuit d'ECC séparé, relié au bus de données : l'Intel 8206. Le 8026 prenait en entrée : 16 bits de données et 8 bits d'ECC. Il fournissait en sortie 16 bits de données après correction d'erreur, les 8 bits d'ECC pour indiquer qu'une erreur a été détectée mais pas corrigée, ainsi que des bits de parité. Le 8206 détectait/corrigeait les erreurs et générait les bits d'ECC, mais il communiquait avec le contrôleur mémoire pour cela. [[File:8207 avec ECC.png|centre|vignette|upright=2|8207 avec ECC]] La détection/correction d'erreur était appliquée à la fois pour les accès mémoire et pour les rafraichissements mémoire. Lors d'un rafraichissement mémoire, la donnée rafraichie est lue et réécrite. Avec l'ECC activé et configuré correctement, le rafraichissement passe par le bus de données. Au lieu d'avoir un cycle de lecture-écriture interne à la DRAM, on a un cycle de lecture-correction-écriture qui utilise le 8206. La donnée lue est envoyée sur le bus de données, puis le 8206 corrige une éventuelle erreur, et la donnée corrigée est alors réécrite en mémoire. Au passage, si une erreur non-correctible est détectée, le 8206 ne fait rien, l'erreur est ignorée. La gestion de l'erreur sera retardée jusqu'à une lecture ultérieure. Et encore : si lecture ultérieure il y a. Si la donnée est écrasée par une écriture, la donnée corrompue sera simplement écrasée et disparaitra sans avoir pu faire le moindre dégât. Mais pour cela, le 8206 doit communiquer avec le contrôleur mémoire, pour savoir s'il est dans un cycle de rafraichissement ou un accès mémoire normal. Il prévient le 8207 lors d'une erreur, et c'est ce dernier qui décide si l'erreur doit être prise en compte ou ignorée. C'est seulement lors d'un accès mémoire normal que le processeur est prévenu qu'une erreur de parité/autre a eu lieu. ===Les contrôleurs mémoires multiports=== Les '''contrôleur mémoire multiport''' disposent de plusieurs ports, chacun permettant de traiter un accès mémoire. Ils peuvent simuler une mémoire multiport à partir d'une DRAM monoport. Évidemment, la simulation n'est pas parfaite. Des accès mémoire simultanés, envoyés en même temps sur différents ports, sont en réalité exécutés un par un, pas en même temps. Il y a donc une petite pénalité en termes de performances, mais elle est mineure. Encore une fois, nous allons reprendre l'exemple du 8207. Il avait deux ports séparés, et était prévu pour fonctionner dans un système à deux processeurs. L'usage de deux ports séparés permettait de partager une unique mémoire DRAM entre deux processeurs. Le partage se faisait en interfaçant deux processeurs sur le contrôleur mémoire, chacun étant connecté à un port. Lors d'une lecture, il redirigeait la donnée lue vers le bon processeur, en configurant le bus de données correctement. Le contrôleur mémoire recevait des requêtes mémoire de deux processeurs, mais il les exécutait une à la fois. S'il recevait deux requêtes en même temps, l'une d'entre elles était mise en attente. Le contrôleur mémoire doit arbitrer les accès à la mémoire, et faire en sorte que les deux processeurs aient accès à la mémoire à tour de rôle. Et non seulement il doit arbitrer les deux ports, mais il y a aussi un troisième port interne au contrôleur mémoire : le rafraichissement mémoire ! Pour cela, le circuit d'arbitrage qui choisissait entre rafraichissement mémoire et accès mémoire, est amélioré de manière à gérer un second port. Le circuit d'arbitrage donne l'accès au séquenceur mémoire à un port sélectionné. L'arbitrage était configurable, avec deux options : soit le port A est privilégié sur le port B, soit le port le plus récemment accédé à la priorité. Les deux ports pouvaient être configurés pour fonctionner soit de manière asynchrone, soit de manière synchrone. Il était aussi possible de configurer l'ECC, des options liées à la fréquence du processeur et de la RAM, ainsi que de nombreuses options liées au rafraichissement. Pour cela, le 8207 contenait un registre de configuration interne, programmable en fournissant les entrées adéquates. Tout ce qui vient d'être dit se généralise avec plus de deux processeurs. Le 8207 ne permettait pas ça, mais les contrôleurs mémoire des PC modernes en sont capables. Ils peuvent gérer plusieurs dizaines de processeurs facilement. ==Le contrôleur mémoire d'une DRAM ''Fast Page Mode''== Les mémoires DRAM classiques sont des mémoires à tampon de ligne, mais qui sont assez mal utilisées. Notamment, tout accès mémoire se fait en deux phases : un accès pour sélectionner la ligne, un autre pour sélectionner la colonne. Les mémoires ''Fast Page Mode'' permettent d'optimiser le tout. Elles permettent de faire plusieurs accès successifs à la même ligne, à des colonnes différentes. Et le contrôleur mémoire doit être adapté pour cela. [[File:Sélection d'une ligne sur une mémoire FPM ou EDO.png|centre|vignette|upright=2|Sélection d'une ligne sur une mémoire FPM ou EDO.]] Le contrôleur mémoire doit détecter que deux accès mémoire consécutifs se font dans la même ligne. Si deux accès consécutifs accèdent à la même ligne, on doit juste changer de colonne et altérer le signal CAS. C'est un ''succès de tampon de ligne'', aussi appelé un '''succès de page'''. Si deux accès consécutifs accèdent à une ligne différente, c'est un ''défaut de tampon de ligne'', aussi appelé un '''défaut de page'''. Il faut alors changer de ligne, en altérant les signaux RAS et en envoyant une adresse de ligne. Pour détecter les succès ou défauts de page, il faut ajouter un circuit spécialisé dans le contrôleur mémoire. Il mémorise la ligne ouverte, et plus précisément son adresse de ligne (numéro de banque inclut). A chaque requête processeur, il compare l'adresse de ligne recue avec celle déjà ouverte. C'est un succès si les deux sont égales, un défaut si elles sont différentes. Le circuit qui fait cette comparaison est appelé le '''décodeur de commande'''. Il prévient le séquenceur mémoire en cas de succès de page, grâce à un signal de un bit, qui vaut 0 en cas de défaut de page et 1 en cas de succès. Le séquenceur mémoire décide alors comment gérer les signaux RAS et CAS, ainsi que l'envoi des adresses de ligne/colonne. [[File:Controleur mémoire d'une FPM-DRAM.png|centre|vignette|upright=2|Controleur mémoire d'une FPM-DRAM]] ==Le contrôleur mémoire d'une SDRAM ou d'une DDR== l'intérieur d'un contrôleur de SDRAM ne change pas significativement d'un controleur de RAM asynchrone. Il regroupe toujours un séquenceur mémoire et une interface physique, un circuit pour le rafraichissement mémoire et un circuit d'arbitrage. Par contre, ses sorties changent pas mal. Contrairement aux mémoires DRAM basiques, les mémoires SDRAM sont cadencées par un signal d'horloge. Et ce signal d'horloge vient bien de quelque part. Pour cela, deux solutions : soit le contrôleur mémoire génère la fréquence qui commande la mémoire, soit il prend en entrée une fréquence de base qu'il multiplie pour obtenir la fréquence désirée. Les deux solutions sont équivalentes, si ce n'est que les circuits impliqués ne sont pas les mêmes. Dans le premier cas, le contrôleur doit embarquer un circuit oscillateur, qui génère la fréquence demandée. Dans l'autre cas, un simple multiplieur/diviseur de fréquence suffit et c'est généralement une PLL qui est utilisée pour cela. : Notez qu'il ne faut pas confondre la fréquence de la SDRAM et celle du contrôleur mémoire. Le contrôleur mémoire fonctionne à une vitesse assez élevée, en interne. Le port relié au processeur fonctionne à haute fréquence, généralement la même que celle du processeur. A vrai dire, de nos jours, il est intégré dans le processeur. Pour le décodage d'adresse, tout est plus simple sur les SDRAM, DDR inclues. Les chips de mémoire SDRAM et DDR disposent d'une entrée ''Chip Select'', ce qui facilite grandement le décodage d'adresse. Les bits de ''Chip Select'' sont générés par le contrôleur mémoire, et sont transmis sur le bus de commande. Le contrôleur de DRAM peut adresser un certain nombre de rangées, dispersés sur une ou plusieurs barrettes. La limite maximale dépend du contrôleur de DRAM, elle est souvent proche de 8 ou 16 rangées. Si on combine plusieurs barrettes de mémoire, il est possible de dépasser cette limite. Par exemple, pour un contrôleur de DRAM supportant maximum 8 rangées, 4 barrettes de 4 rangées chacune dépassent la limite. ===Le séquenceurs mémoire pour les SDRAM/DDR=== Le séquenceur mémoire existe toujours pour les mémoires SDRAM, c'est toujours un circuit séquentiel qui implémente une machine à état. Il traduit toujours une requête processeur en une séquence de commandes envoyées à des timings bien précis. Les commandes mémoires peuvent provenir de l'extérieur, mais aussi d'un circuit de rafraichissement intégré dans le contrôleur mémoire, comme pour les autres DRAM. La seule différence est que la machine à état est plus complexe. Pour rappel, une requête de lecture/écriture se fait en trois étapes maximum : une commande PRECHARGE pour précharger le tampon de ligne, une commande ACT qui fixe l'adresse de ligne, et enfin une commande READ/WRITE avec l'adresse de colonne. Et ces commandes sont séparées par des '''délais mémoire''' bien précis. Par exemple, je prends des chiffres arbitraires : il faut attendre 2 cycles entre une commande ACT et une commande READ, 6 cycles avant deux commandes WRITE consécutives, etc. La gestion des délais mémoire rend la conception du séquenceur plus complexe. Il faut aussi tenir compte des commandes SDRAM anticipées, à savoir que l'on peut envoyer des commandes avant que la précédente soit terminée. Les commandes anticipées sont idéales dans le cas où des accès successifs se font dans des banques différentes. Pour les exploiter au mieux, le contrôleur mémoire doit donc détecter si des accès successifs se font dans des banques différentes, ou dans la même banque, pour décider d'envoyer des commandes anticipées ou non. Cette '''détection des conflits de banque''' complexifie le séquenceur. ===La détection des succès/défauts de page=== Un point important est que dans certaines conditions, certaines commandes peuvent être omises. Par exemple, en cas de succès de page, les commandes PRECHARGE et ACT ne doivent pas être envoyées, seule les commandes READ/WRITE le sont. Le contrôleur doit toujours '''détecter les succès et défauts de page''' et agir en fonction. La solution utilisée est la même que pour les mémoires FPM : il faut mémoriser quelle ligne est ouverte ou fermée. La différence avec les FPM est qu'il faut faire cela pour chaque banque mémoire ! En effet, chaque banque a son propre tampon de ligne, ce qui fait que la gestion des lignes se fait indépendamment dans chaque banque. Le séquenceur mémoire doit donc se souvenir des lignes actives dans chaque banque. Pour cela, il mémorise ces lignes dans une petite mémoire : la '''table des banques''', aussi appelée ''bank status memory''. Pour détecter un succès ou un défaut, le contrôleur doit extraire la ligne de l'adresse, mais aussi le numéro de banque. Il envoie alors le numéro de banque à la table des banques, sur son entrée d'adresse. Il récupère alors le numéro de la ligne active sur les sorties de données. Il compare alors ce numéro de ligne avec le numéro de ligne de l'adresse envoyée par le processeur. C'est un succès si les deux sont égales, un défaut sinon. [[File:Controleur mémoire FPM avec plusieurs banques.jpg|centre|vignette|upright=2|Contrôleur mémoire FPM avec plusieurs banques.]] ===La politique de gestion du tampon de ligne=== Pour ce qui est des succès/défauts de page, le séquenceur mémoire peut fonctionner de plusieurs manières, dont les plus extrêmes sont appelés la politique de la page fermée et la politique de la page ouverte. Voyons à quoi elles correspondent. Avec la '''politique de la page fermée''', chaque accès mémoire est suivi d'une commande PRECHARGE, qui ferme la ligne courante et précharge les lignes de bits. Même si deux accès consécutifs se font dans la même ligne, la ligne est fermée et ré-ouverte entre deux accès mémoire. En clair : l'optimisation introduite par les mémoires FPM est désactivée, le contrôleur mémoire fait exprès de ne pas en profiter. On appelle cette méthode la close ''page autoprecharge''. Cette méthode réduit grandement les performances pour les accès à des adresses consécutives, mais fonctionne à merveille si les accès sont "aléatoires", à savoir qu'ils se font sans régularités évidentes. De tels accèssont dispersés en mémoire, ce qui fait qu'il est rare qu'ils accédent deux fois de suite à la même ligne. Un autre avantage est que l'implémentation du séquenceur mémoire est très simple. En effet, le séquenceur mémoire se passe complétement de la table des banques, du comparateur de ligne, et de tous les circuits nécessaires pour vérifier les succès ou défauts de page. De plus, le séquenceur mémoire profite grandement des commandes READA et WRITEA, qui fusionnent une commande READ/WRITE avec une commande PRECHARGE. Le séquenceur mémoire a juste à envoyer des commandes ACT, READA, WRITEA et PREFETCH à la mémoire, pas besoin des commandes PRECHARGE, READ ou WRITE. A l'opposé, la '''politique de la page ouverte''' ne ferme pas automatiquement la ligne. Elle la laisse ouverte, en espérant que le prochain accès mémoire se fasse dans cette ligne. Lorsqu'un nouvel accès mémoire arrive, elle doit détecter les succès ou défauts de page et agir en fonction. En cas de défaut de page, la ligne est fermée, le séquenceur mémoire envoie une commande PRECHARGE, puis l'accès suivant effectue les deux commandes ACT + READ ou WRITE. En cas de succès de page, les commandes PRECHARGE et ACT ne sont pas envoyées, seules la commande READ ou WRITE l'est. Un désavantage est que le contrôleur mémoire doit inclure une table des banques et un comparateur, comme vu plus haut dans la section sur les mémoires FPM. un autre défaut est que garder une ligne ouverte consomme beaucoup d'énergie, comparé à un simple état de PRECHARGE. En conséquence, il est préférable de fermer les lignes dès que possible. Par contre, les performances sont d'autant meilleures que les accès mémoire consécutifs à une même ligne soient assez fréquents. Si les accès mémoire sont aléatoires, les performances sont moins bonnes. La politique de la page fermée fermait les lignes en avance, avec des commandes READA ou WRITEA, avant même que l'accès suivant démarre. Avec la politique de la page ouverte, on doit attendre pour détecter un défaut de page, puis fermer la ligne avec une commande PRECHARGE séparée. La ligne est donc fermée avec un peu temps de retard, et envoyer deux commandes au lieu d'une prend plus de temps. Les contrôleurs mémoires basiques utilisent une des deux solutions précédentes. Soit la page est toujours fermée, soit elle est toujours laissée ouverte jusqu'à ce qu'un accès mémoire la referme. Mais les contrôleurs plus évolués tentent de prédire s'il faut fermer ou laisser ouvertes les pages ouvertes. La méthode la plus simple attend un temps prédéterminé avant de fermer la ligne. Une autre solution regarde le tout dernier accès. On peut très bien décider de laisser la ligne ouverte si l'accès mémoire précédent était une rafale, et fermer sinon. Une solution plus complexe mémorise les N derniers accès et en déduit s'il faut fermer ou non la prochaine ligne. On peut mémoriser si l'accès en question a causé la fermeture d'une ligne avec un bit. Mémoriser les N derniers accès demande d'utiliser un simple registre à décalage. Pour chaque valeur de ce registre, il faut prédire si le prochain accès demandera une ouverture ou une fermeture. Une solution simple fait la moyenne des bits à 1 dans ce registre : si plus de la moitié des bits est à 1, on laisse la ligne ouverte et on ferme sinon. Pour améliorer un petit peu l'algorithme, on peut faire en sorte que les bits des accès mémoires les plus récents aient plus de poids dans le calcul de la moyenne. Il existe sans doute d'autres solutions plus évoluées, mais il est difficile de savoir ce qu'il y a dans les contrôleurs de SDRAM modernes. : Le fait de laisser ouverte une ligne ou au contraire de la fermer systématiquement, se fait pour chaque banque. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les mémoires RAM dynamiques (DRAM) | prevText=Les mémoires RAM dynamiques (DRAM) | next=Les mémoires associatives | nextText=Les mémoires associatives }} </noinclude> 411qisamcko7m2xybhvz9x0njtzkg07 764693 764689 2026-04-23T19:29:13Z Mewtow 31375 /* Le contrôleur mémoire d'une SDRAM ou d'une DDR */ 764693 wikitext text/x-wiki Les mémoires ROM ou SRAM ont généralement une interface simple, à laquelle le processeur peut s'interfacer directement. Mais pour les DRAM, ce n'est pas le cas. Les DRAM utilisent un bus d'adresse multiplexé, où l'adresse est envoyée en deux fois. Connecter le processeur directement sur une DRAM n'est pas pratique : le bus d'adresse du processeur et celui de la mémoire ne collent pas. Les DRAM doivent aussi être rafraichies régulièrement. Le rafraichissement mémoire peut être délégué au processeur, mais c'est loin d'être idéal. Et il y a bien d'autres raisons qui font que le processeur ne peut pas s'interfacer facilement avec les mémoires DRAM. Pour gérer ces problèmes, les mémoires DRAM ne sont pas connectées directement au processeur. À la place, on ajoute un intermédiaire entre le processeur et la mémoire : le '''contrôleur mémoire externe'''. Son but est de montrer au processeur une interface simple, semblable à celle d'une SRAM classique, alors qu'il commande une mémoire DRAM à l'interface plus complexe. Il est placé sur la carte mère ou dans le processeur, et ne doit pas être confondu avec le contrôleur mémoire intégré dans la mémoire. Ce chapitre va expliquer quels sont les rôles du contrôleur mémoire, son interface et ce qu'il y a à l'intérieur. Dans ce chapitre, quand nous parlerons de ''contrôleur mémoire'', cela fera systématiquement référence au contrôleur mémoire externe. Et avant de poursuivre, sachez qu'il est difficile de faire des généralités sur les contrôleurs mémoire, car les mémoires DRAM elles-mêmes sont assez différentes les unes des autres. Entre une mémoire EDO, une mémoire SDR, une mémoire DDR et une DRAM asynchrone, les contrôleurs mémoires seront fortement différents. Aussi, il y a aura une différence entre un contrôleur pour une DRAM asynchrone et un contrôleur pour une mémoire EDO, une mémoire SDRAM, etc. J'ai choisit de vous séparer les contrôleurs mémoire pour les DRAM asynchrones de ceux pour les SDRAM/DRR. ==Le contrôleur d'une DRAM asynchrone== Les premières DRAM asynchrones avaient des contrôleurs mémoires dédiés, qui étaient séparés du processeur et du ''chipset'' de la carte mère. Par exemple, les composants Intel 8202, Intel 8203 et Intel 8207 étaient des contrôleurs mémoire pour DRAM asynchrones qui étaient vendus dans des boitiers DIP et étaient soudés sur la carte mère. Par la suite, ils ont été intégrés au ''chipset'' de la carte mère pendant les décennies 90-2000. Après les années 2000, ils ont été intégrés dans les processeurs. Leurs fonctions étaient le multiplexage de l'adresse et le rafraichissement mémoire. Ils recevaient une adresse mémoire complète, qu'ils découpaient une adresse de ligne et une adresse de colonne, envoyées l'une après l'autre sur le bus mémoire. Pour le rafraichissement mémoire, ils rafraichissaient la DRAM régulièrement, de manière automatique, entre deux accès mémoire normaux. Le processeur n'avait ainsi plus à rafraichir la mémoire lui-même, cette fonction était déléguée au contrôleur de DRAM. Ils étaient connectés au bus d'adresse et de commande, avec éventuellement des relations indirectes avec le bus de données. ===L'interface d'un contrôleur de DRAM asynchrone=== L'interface du contrôleur mémoire décrit ses broches d'entrées/sorties et leur signification. Elle est généralement très simple et contient deux ports : un connecté au processeur, un autre connecté à la DRAM. Cela trahit d'ailleurs son rôle principal, qui est de transformer les requêtes de lecture/écriture provenant du processeur en une suite de commandes acceptée par la mémoire. Le port connecté à la DRAM est connecté ua bus d'adresse et au bus de commande. Le bus de données est lui relié au processeur et/ou au bus système. Un accès mémoire provenant du processeur contient une adresse à lire/écrire, le bit R/W qui indique s'il faut faire une lecture ou une écriture, et éventuellement une donnée à écrire. Mais, nous avons vu que les accès mémoires sur une DRAM sont multiplexés : on envoie l'adresse en deux fois : la ligne d'abord, puis la colonne. De plus, il faut générer les signaux RAS, CAS et bien d'autres. Le tout est illustré ci-dessous. [[File:Contrôleur mémoire.png|centre|vignette|upright=2|Contrôleur mémoire externe.]] Un point important est que les DRAM asynchrones n'ont pas d'entrée ''Chip Select'' ou d'entrée ''Output Enable''. Les signaux RAS et CAS remplacent en quelque sorte ces deux signaux. Le bit RAS fait office de ''Chip Select'', le bit CAS fait office d'''Output Enable''. Pour certains contrôleurs de DRAM, il faut ajouter l''''interface électrique''', qui traduit les signaux du processeur en signaux compatibles avec la mémoire. Il est en effet très fréquent que la mémoire et le processeur n'utilisent pas les mêmes tensions pour coder un bit, ce qui fait qu'elles ne sont pas compatibles. Dans ce cas, le contrôleur mémoire fait la conversion. Le contrôleur DRAM peut fonctionner en mode synchrone ou asynchrone, du point de vue du processeur. Quand il fonctionne en mode synchrone, il permet d'interfacer un processeur synchrone avec une mémoire DRAM asynchrone. Un point important est que le contrôleur DRAM sert d'intermédiaire entre une mémoire DRAM et "le reste du monde". Il ne fait pas forcément office d'intermédiaire entre DRAM et processeur, mais peut aussi faire l'intermédiaire entre la DRAM et un bus système, entre une DRAM et le ''Video Display Controler'' d'une carte graphique, ou n'importe quel autre composant électronique qui utilise cette DRAM. ===Le générateur de ''timings'' et la traduction d'adresse=== Le contrôleur mémoire doit traduire les adresses du processeur en adresses compatibles avec la mémoire. Et la traduction est assez variable, suivant que le bus mémoire est un bus normal, un bus multiplexé, ou partiellement multiplexé. Nous avons vu ces trois types de bus mémoire dans le chapitre sur l'interface des mémoires, mais nous ferons quelques rappels rapides. Avec un ''bus totalement multiplexé'', le bus d'adresse et le bus de données sont fusionnés. Dans ce cas, on peut envoyer soit une adresse, soit lire/écrire une donnée sur le bus, mais on ne peut pas faire les deux en même temps. Un bit ALE indique si le bus est utilisé en tant que bus d'adresse ou bus de données. Le contrôleur mémoire gère cette situation, en fixant le bit ALE et en envoyant séparément adresse et donnée pour les écritures. [[File:Bus multiplexé avec bit ALE.png|centre|vignette|upright=2|Bus multiplexé avec bit ALE.]] Avec un ''bus d'adresse multiplexé'', l'adresse est découpée en une adresse de ligne et une adresse de colonne, envoyées l'une après l'autre. Le contrôleur mémoire prend en entrée une adresse mémoire complète, la découpe en deux, et envoie chaque morceau au bon moment. Pour cela, il suffit d'un registre pour mémoriser l'adresse et d'un multiplexeur. Le multiplexeur choisit soit les bits de poids fort de l'adresse, soit ceux de poids faible. Les premiers correspondent à l'adresse de ligne, les autres à l'adresse de colonne. La commande du multiplexeur est le fait d'un petit circuit séquentiel, qui génère aussi les signaux CAS et RAS. Au premier cycle, il met le signal RAS à 1, met le CAS à 0, et configure le MUX pour sélectionner les bits de poids fort. Au second cycle, il génère un signal CAS à 1, met le RAS à 0 et configure le MUX pour sélectionner les bits de poids faible. Le circuit en question est appelé le générateur de ''timings''. [[File:Controleur de DRAM simple, sans rafraichissement mémoire.png|centre|vignette|upright=2|Contrôleur de DRAM simple, sans rafraichissement mémoire.]] Le générateur de ''timings'' est un circuit séquentiel qui implémente une petite machine à état. Il est très simple sur une mémoire DRAM asynchrone basique, mais il est plus complexe sur les mémoires FPM, EDO, quartet, et autres. Le regroupement des multiplexeurs d'adresse et du générateur de ''timings'' est appelé le '''séquenceur mémoire'''. C'est le séquenceur mémoire qui traduit la requête processeur en commande DRAM, le reste du contrôleur est dédié au rafraichissement mémoire ou à d'autres fonctions facultatives. ===Le rafraichissement mémoire=== La gestion du rafraichissement mémoire est la fonction principale du contrôleur DRAM. Pour gérer le rafraichissement mémoire, le contrôleur mémoire intègre deux compteurs, un pour gérer l'adresse à rafraichir, l'autre pour gérer l'intervalle de temps entre deux rafraichissements. Le rafraichissement se fait à intervalle régulier, tous les x microsecondes. Pour déclencher le rafraichissement au bon moment, le contrôleur mémoire contient un ''Refresh Timer'', aussi appelé le '''compteur de rafraichissement'''. Il est initialisé avec le temps entre deux rafraichissements, une adresse est rafraichie quand ce compteur atteint 0. Le rafraichissement mémoire balaye la mémoire adresse par adresse. Pour savoir à quelle adresse il en est rendu, le contrôleur mémoire utilise un '''compteur d'adresse'''. Il contient la prochaine adresse à rafraichir, aussi appelée l'adresse de rafraichissement. Régulièrement, l'adresse dans ce compteur est envoyée à la RAM, pour une lecture. Mais la donnée lue n'est pas envoyée sur le bus de donnée, soit parce que la RAM est prévue pour, soit parce que le contrôleur désactive son bit ''output enable''. Dans le second cas, la RAM fait la lecture en interne, mais se déconnecte du bus de donnée, perdant la donnée lue dans le néant. Pour envoyer l'adresse de rafraichissement sur le bus d'adresse, il faut rajouter un multiplexeur, qui choisit entre l'adresse normale et l'adresse de rafraichissement. [[File:Controleur de DRAM avec rafraichissement mémoire.png|centre|vignette|upright=2|Controleur de DRAM avec rafraichissement mémoire.]] Le multiplexeur ne doit cependant pas être configuré si une adresse est déjà en cours de transfert. Pour cela, un circuit d'arbitrage se débrouille pour éviter qu'un accès mémoire soit interrompu par une demande de rafraichissement et inversement. Il peut être inclus dans le séquenceur mémoire ou séparé de celui-ci. [[File:Controleur mémoire, intérieur simplifié.png|centre|vignette|upright=2.5|Contrôleur mémoire, intérieur simplifié.]] Il faut noter que le rafraichissement mémoire peut être délégué non pas au contrôleur mémoire, mais au processeur où à la DRAM elle-même. Quand elle est le fait du processeur, celui-ci incorpore un ''refresh timer'' et un compteur d'adresse. Un exemple est celui du processeur Zilog Z80, qui implémentait des compteurs internes pour gérer le rafraichissement mémoire. On peut considérer que le processeur incorpore alors le contrôleur mémoire, au moins partiellement. Il est aussi possible de déléguer le rafraichissement au logiciel (certains jeux vidéos Amiga ou Commodore faisaient cela pour la mémoire vidéo). Quand la DRAM elle-même s'occupe de son propre rafraichissement, c'est elle qui intègre un ''refresh timer'' et le compteur d'adresse. ===Le décodage d'adresse=== Le contrôleur mémoire gère aussi le '''décodage d'adresse'''. pour rappel, celui-ci est utilisé quand on combine plusieurs chips mémoire ensemble, pour combiner leurs capacités mémoire. Par exemple, on peut combiner 4 chips mémoires de 1 mébioctet chacun, pour que le processeur voit comme 4 mébioctets de RAM unique. Le premier mébioctet est placé dans le premier chip mémoire, le second mébioctet dans le second chip, etc. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Pour cela, on active le chip mémoire adéquat, en fonction de l'adresse à consulter. Les autres chips mémoire sont désactivés pendant l'accès mémoire. En théorie, activer ou désactiver un chip mémoire se fait en utilisant son entrée ''Chip Select''. Il faut noter que si les SDRAM disposent bien d'un signal ''Chip Select'', ce n'est pas le cas des mémoires RAM asynchrones. A la place, ce sont les signaux RAS qui font office de ''Chip Select''. Une RAM asynchrone est activée quand son signal RAS lui demande de lire une ligne, elle est désactivée sinon. Mais c'est un détail. Toujours est-il que les signaux ''Chip Select'', ou leurs équivalents, sont générés par le contrôleur de DRAM, à partir des bits de poids fort de l'adresse. Par exemple, avec 4 chips mémoire, les deux bits de poids fort de l'adresse sont utilisés pour sélectionner le chip mémoire adéquat. Le contrôleur mémoire doit avoir plusieurs sorties ''Chip Select'', une par chip mémoire. Et le nombre de ces sorties limite le nombre de chips mémoire qu'on peut combiner. Par exemple, s'il y a seulement 4 sorties ''Chip Select'', on ne pourra brancher que 4 chips mémoire dessus. Sauf à ruser, avec un arrangement horizontal, mais cela n'est pas le ressort du contrôleur mémoire. [[File:Td6bfig3.png|centre|vignette|upright=2|Comparaison entre arrangement horizontal (à gauche) et arrangement vertical (à droite).]] ===Exemple : l'Intel 8202-8203=== L'Intel 8202 et le 8203 étaient des contrôleurs de mémoire DRAM, parmi les plus simples qui soient. Ils avaient une entrée d'adresse de 12 bits, ce qui permettait d'adresser 4 kibioctets de RAM. Ils fournissaient en sortie une adresse multiplexée sur 6 bits, envoyée en deux fois. Ils avaient donc 12 entrées d'adresse, 6 sorties d'adresse, un signal RAS, un signal CAS. Les adresses présentées en entrées n'étaient pas mémorisées dans des registres, ce qui fait qu'elles devaient être maintenues durant toute la durée de l'accès mémoire. Le processeur ne pouvait donc pas se déconnecter du bus d'adresse pendant l'accès mémoire, peu importe sa durée. Le 8202 contenait aussi un compteur de rafraichissement. Rappelons que sur les DRAM asynchrones, le rafraichissement se fait ligne par ligne. Le contrôleur mémoire a juste à présenter l'adresse de ligne, il n'a pas à envoyer l'adresse de colonne. La commande de rafraichissement se fait en mettant le signal RAS à 0, mais en laissant le CAS à 1 (je rappelle que les signaux sont actifs à 0). Le compteur d'adresse de rafraichissement a donc juste à mémoriser l'adresse de ligne. Le séquenceur mémoire était précédé par un circuit d'arbitrage, non-représenté dans le schéma ci-dessous. La microarchitecture de l'Intel 8202 est la suivante : [[File:Microarchitecture de l'Intel 8202.png|centre|vignette|upright=2|Microarchitecture de l'Intel 8202.]] Le 8202 avait une entrée pour un signal d'horloge, ainsi qu'un ''Chip Select'' un peu particulier. Si le signal CS passait à 0 lors d'un accès mémoire, le 8202/8203 ne se désactivait qu'une fois l'accès mémoire terminé. On ne pouvait pas l'interrompre pendant un accès mémoire, même en changeant le bit CS. Le signal d'horloge était utilisé pour commander le ''refresh timer''. Pour commander les lectures et écriture, il recevait en entrée un bit ''Write Request'' et un bit ''Read Request'', qui demandent respectivement une écriture et une lecture. En sortie, on trouvait un unique bit R/W qui valait 0 pour une lecture et 1 pour une écriture. Il avait aussi un bit d'entrée pour forcer le rafraichissement mémoire. S'il est à 1, la mémoire rafraichie l'adresse envoyée par le processeur. Le 8202 pouvait être connecté sur 1 à 4 chips mémoire, ce qui permettait d'adresser au maximum 16 kibioctets de RAM. Les 4 chips ne sont pas accédés en parallèle, un seul l'est à chaque fois. Pour le décodage d'adresse, le 8202 dispose de deux bits BO et B1 pour sélectionner le chip adéquat, ainsi que 4 sorties RAS pour activer la banque adéquate. On rappelle que le signal RAS remplace le signal ''Chip Select''. C'est le séquenceur mémoire qui se charge de générer ces signaux RAS, à partir des deux bits B0 et B1 (qui sont techniquement des bits d'adresse). Pour communiquer avec le processeur, l'Intel 8202 disposait de deux bits XACK et SACK. SACK indiquait au processeur que le 8202/8203 est en train de faire un accès mémoire et qu'il est indisponible pour un second accès mémoire. Cela permet de bloquer le processeur tant que le 8202 est indisponible. Le signal XACK indique que l'accès mémoire précédent est terminé et que : soit la donnée lue est présente sur le bus de données, soit que l'écriture s'est terminée. : Le signal SACK est très utile sur les configurations multiprocesseurs. Un processeur peut démarrer un accès mémoire, le signal SACK indiquera au second processeur qu'il doit attendre que l'accès soit terminé pour que ce soit son tour. ==Les contrôleurs de DRAM asynchrones évolués== L'Intel 8202 était un contrôleur mémoire basique, comme beaucoup d'autres à cette époque. Mais Intel a vendu des contrôleurs mémoires plus complexes. Par exemple, l'Intel 8207 était un contrôleur mémoire bien plus avancé que les deux précédents. Passons sur certains détails, comme le fait qu'il pouvait gérer jusqu'à 256 kibioctets de DRAM. Au-delà de ça, il y avait des fonctionnalités bien plus intéressantes, à savoir : un support de l'ECC, il était double port, il permettait de simuler une DRAM synchrone à partir d'une DRAM asynchrone. Il n'était pas le seul dans ce cas et des contrôleurs de DRAM très évolués étaient capables de faire des merveilles. Voyons comment cela était possible. ===Les contrôleurs mémoire synchrone=== Il est parfaitement possible d'utiliser un contrôleur mémoire synchrone avec une DRAM asynchrone. A vrai dire, le contrôleur DRAM peut complétement simuler une mémoire synchrone alors que la DRAM associée est asynchrone. La traduction asynchrone vers synchrone se fait en ajoutant des registres sur le bus mémoire, notamment sur le bus de données et le bus d'adresse/commande. Nous avions détaillé cela dans le chapitre sur les SRAM, c'est la même chose avec une mémoire DRAM. Sauf que cette fois-ci, le contrôleur mémoire doit aussi être prévu pour. [[File:Controleur mémoire synchrone utilisé avec une DRAM asynchrone.png|centre|vignette|upright=2|Contrôleur mémoire synchrone utilisé avec une DRAM asynchrone]] Les deux-trois registres illustrés plus haut peuvent être intégrés directement dans le contrôleur mémoire, que ce soit totalement ou en partie. Le strict minimum pour avoir un contrôleur mémoire synchrone est que celui-ci doit mémoriser adresse et commandes dans un registre. Par exemple, le 8207 d'Intel était capable de mémoriser les requêtes processeurs dans un registre interne, mais il fallait utiliser deux registres séparés pour le bus de données. Les deux registres étaient alors commandés par le contrôleur mémoire. Il est cependant possible d'aller plus loin et d'intégrer les registres du bus de données dans le contrôleur mémoire. [[File:Controleur mémoire DRAM synchrone.png|centre|vignette|upright=2|Contrôleur mémoire DRAM synchrone.]] : Il faut noter que cette fonctionnalité est parfois disponible sur les SRAM. En clair, on peut associer une SRAM asynchrone avec un contrôleur de SRAM synchrone. Le contrôleur de SRAM se charge alors de simuler une SRAM synchrone à partir de la SRAM asynchrone. Utiliser un contrôleur mémoire synchrone a de nombreux avantages, l'un d'entre eux étant lié aux ''wait state''. Quand le processeur envoie une requête de lecture/écriture à la mémoire RAM, celle-ci met plusieurs cycles d'horloge à répondre. Et pendant ce temps, le processeur... attend. Et pendant ce temps d'attente, il doit maintenir l'adresse mémoire sur le bus d'adresse. Les cycles d'horloge perdus à attendre la mémoire RAM étaient appelés des '''''Wait states'''''. Utiliser un contrôleur mémoire synchrone d'éliminer les ''wait state'', dans une certaine mesure. Avec un contrôleur mémoire synchrone, le processeur envoie l'adresse, mais c'est le contrôleur mémoire qui la maintient sur le bus d'adresse. Le processeur peut envoyer l'adresse et la donnée à écrire, elles sont recopiées dans les registres, et le controleur mémoire y a accès sans que le processeur doive les maintenir. Le processeur peut se déconnecter du bus mémoire et faire du travail dans son coin pendant que le contrôleur mémoire accède à la DRAM. Les ''wait state'' disparaissent alors, du moins du point de vue du processeur. ===La gestion de l'ECC=== L''''ECC''' peut être géré dans le contrôleur mémoire. Pour cela, on couple les registres mentionnés dans la section précédente, avec un circuit de détection et de correction d'erreur. Le circuit d'ECC peut, comme les registres synchrones, être intégré dans le contrôleur mémoire, ou au contraire être situé dans un circuit séparé. Si le circuit d'ECC est séparé du contrôleur mémoire, il communique avec lui, histoire que le contrôleur mémoire puisse signaler toute erreur de parité ou d'ECC au processeur. [[File:Controleur mémoire synchrone avec ECC intégré.png|centre|vignette|upright=2|Controleur mémoire synchrone avec ECC intégré]] Reprenons l'exemple du 8207 d'Intel. Le contrôleur mémoire 8207 gère le bus d'adresse et de commande, mais n'a pas de connexions directes avec le bus de données. Il ne peut donc pas prendre en charge l'ECC. Il avait besoin d'être couplé avec un circuit d'ECC séparé, relié au bus de données : l'Intel 8206. Le 8026 prenait en entrée : 16 bits de données et 8 bits d'ECC. Il fournissait en sortie 16 bits de données après correction d'erreur, les 8 bits d'ECC pour indiquer qu'une erreur a été détectée mais pas corrigée, ainsi que des bits de parité. Le 8206 détectait/corrigeait les erreurs et générait les bits d'ECC, mais il communiquait avec le contrôleur mémoire pour cela. [[File:8207 avec ECC.png|centre|vignette|upright=2|8207 avec ECC]] La détection/correction d'erreur était appliquée à la fois pour les accès mémoire et pour les rafraichissements mémoire. Lors d'un rafraichissement mémoire, la donnée rafraichie est lue et réécrite. Avec l'ECC activé et configuré correctement, le rafraichissement passe par le bus de données. Au lieu d'avoir un cycle de lecture-écriture interne à la DRAM, on a un cycle de lecture-correction-écriture qui utilise le 8206. La donnée lue est envoyée sur le bus de données, puis le 8206 corrige une éventuelle erreur, et la donnée corrigée est alors réécrite en mémoire. Au passage, si une erreur non-correctible est détectée, le 8206 ne fait rien, l'erreur est ignorée. La gestion de l'erreur sera retardée jusqu'à une lecture ultérieure. Et encore : si lecture ultérieure il y a. Si la donnée est écrasée par une écriture, la donnée corrompue sera simplement écrasée et disparaitra sans avoir pu faire le moindre dégât. Mais pour cela, le 8206 doit communiquer avec le contrôleur mémoire, pour savoir s'il est dans un cycle de rafraichissement ou un accès mémoire normal. Il prévient le 8207 lors d'une erreur, et c'est ce dernier qui décide si l'erreur doit être prise en compte ou ignorée. C'est seulement lors d'un accès mémoire normal que le processeur est prévenu qu'une erreur de parité/autre a eu lieu. ===Les contrôleurs mémoires multiports=== Les '''contrôleur mémoire multiport''' disposent de plusieurs ports, chacun permettant de traiter un accès mémoire. Ils peuvent simuler une mémoire multiport à partir d'une DRAM monoport. Évidemment, la simulation n'est pas parfaite. Des accès mémoire simultanés, envoyés en même temps sur différents ports, sont en réalité exécutés un par un, pas en même temps. Il y a donc une petite pénalité en termes de performances, mais elle est mineure. Encore une fois, nous allons reprendre l'exemple du 8207. Il avait deux ports séparés, et était prévu pour fonctionner dans un système à deux processeurs. L'usage de deux ports séparés permettait de partager une unique mémoire DRAM entre deux processeurs. Le partage se faisait en interfaçant deux processeurs sur le contrôleur mémoire, chacun étant connecté à un port. Lors d'une lecture, il redirigeait la donnée lue vers le bon processeur, en configurant le bus de données correctement. Le contrôleur mémoire recevait des requêtes mémoire de deux processeurs, mais il les exécutait une à la fois. S'il recevait deux requêtes en même temps, l'une d'entre elles était mise en attente. Le contrôleur mémoire doit arbitrer les accès à la mémoire, et faire en sorte que les deux processeurs aient accès à la mémoire à tour de rôle. Et non seulement il doit arbitrer les deux ports, mais il y a aussi un troisième port interne au contrôleur mémoire : le rafraichissement mémoire ! Pour cela, le circuit d'arbitrage qui choisissait entre rafraichissement mémoire et accès mémoire, est amélioré de manière à gérer un second port. Le circuit d'arbitrage donne l'accès au séquenceur mémoire à un port sélectionné. L'arbitrage était configurable, avec deux options : soit le port A est privilégié sur le port B, soit le port le plus récemment accédé à la priorité. Les deux ports pouvaient être configurés pour fonctionner soit de manière asynchrone, soit de manière synchrone. Il était aussi possible de configurer l'ECC, des options liées à la fréquence du processeur et de la RAM, ainsi que de nombreuses options liées au rafraichissement. Pour cela, le 8207 contenait un registre de configuration interne, programmable en fournissant les entrées adéquates. Tout ce qui vient d'être dit se généralise avec plus de deux processeurs. Le 8207 ne permettait pas ça, mais les contrôleurs mémoire des PC modernes en sont capables. Ils peuvent gérer plusieurs dizaines de processeurs facilement. ==Le contrôleur mémoire d'une DRAM ''Fast Page Mode''== Les mémoires DRAM classiques sont des mémoires à tampon de ligne, mais qui sont assez mal utilisées. Notamment, tout accès mémoire se fait en deux phases : un accès pour sélectionner la ligne, un autre pour sélectionner la colonne. Les mémoires ''Fast Page Mode'' permettent d'optimiser le tout. Elles permettent de faire plusieurs accès successifs à la même ligne, à des colonnes différentes. Et le contrôleur mémoire doit être adapté pour cela. [[File:Sélection d'une ligne sur une mémoire FPM ou EDO.png|centre|vignette|upright=2|Sélection d'une ligne sur une mémoire FPM ou EDO.]] Le contrôleur mémoire doit détecter que deux accès mémoire consécutifs se font dans la même ligne. Si deux accès consécutifs accèdent à la même ligne, on doit juste changer de colonne et altérer le signal CAS. C'est un ''succès de tampon de ligne'', aussi appelé un '''succès de page'''. Si deux accès consécutifs accèdent à une ligne différente, c'est un ''défaut de tampon de ligne'', aussi appelé un '''défaut de page'''. Il faut alors changer de ligne, en altérant les signaux RAS et en envoyant une adresse de ligne. Pour détecter les succès ou défauts de page, il faut ajouter un circuit spécialisé dans le contrôleur mémoire. Il mémorise la ligne ouverte, et plus précisément son adresse de ligne (numéro de banque inclut). A chaque requête processeur, il compare l'adresse de ligne recue avec celle déjà ouverte. C'est un succès si les deux sont égales, un défaut si elles sont différentes. Le circuit qui fait cette comparaison est appelé le '''décodeur de commande'''. Il prévient le séquenceur mémoire en cas de succès de page, grâce à un signal de un bit, qui vaut 0 en cas de défaut de page et 1 en cas de succès. Le séquenceur mémoire décide alors comment gérer les signaux RAS et CAS, ainsi que l'envoi des adresses de ligne/colonne. [[File:Controleur mémoire d'une FPM-DRAM.png|centre|vignette|upright=2|Controleur mémoire d'une FPM-DRAM]] ==Le contrôleur mémoire d'une SDRAM ou d'une DDR== L'intérieur d'un contrôleur de SDRAM ne change pas significativement d'un contrôleur de RAM asynchrone. Il regroupe toujours un séquenceur mémoire et une interface physique, un circuit pour le rafraichissement mémoire et un circuit d'arbitrage. Par contre, ses sorties changent pas mal. Contrairement aux mémoires DRAM basiques, les mémoires SDRAM sont cadencées par un signal d'horloge. Et ce signal d'horloge vient bien de quelque part. Pour cela, deux solutions : soit le contrôleur mémoire génère la fréquence qui commande la mémoire, soit il prend en entrée une fréquence de base qu'il multiplie pour obtenir la fréquence désirée. Les deux solutions sont équivalentes, si ce n'est que les circuits impliqués ne sont pas les mêmes. Dans le premier cas, le contrôleur doit embarquer un circuit oscillateur, qui génère la fréquence demandée. Dans l'autre cas, un simple multiplieur/diviseur de fréquence suffit et c'est généralement une PLL qui est utilisée pour cela. : Notez qu'il ne faut pas confondre la fréquence de la SDRAM et celle du contrôleur mémoire. Le contrôleur mémoire fonctionne à une vitesse assez élevée, en interne. Le port relié au processeur fonctionne à haute fréquence, généralement la même que celle du processeur. A vrai dire, de nos jours, il est intégré dans le processeur. Pour le décodage d'adresse, tout est plus simple sur les SDRAM/DDR. Les chips de mémoire SDRAM et DDR disposent d'une entrée ''Chip Select'', ce qui facilite grandement le décodage d'adresse. Les bits de ''Chip Select'' sont générés par le contrôleur mémoire, et sont transmis sur le bus de commande. Le contrôleur de DRAM peut adresser un certain nombre de rangées, dispersés sur une ou plusieurs barrettes. La limite maximale dépend du contrôleur de DRAM, elle est souvent proche de 8 ou 16 rangées. Si on combine plusieurs barrettes de mémoire, il est possible de dépasser cette limite. Par exemple, pour un contrôleur de DRAM supportant maximum 8 rangées, 4 barrettes de 4 rangées chacune dépassent la limite. ===Le séquenceurs mémoire pour les SDRAM/DDR=== Le séquenceur mémoire existe toujours pour les mémoires SDRAM, c'est toujours un circuit séquentiel qui implémente une machine à état. Il traduit toujours une requête processeur en une séquence de commandes envoyées à des timings bien précis. Les commandes mémoires peuvent provenir de l'extérieur, mais aussi d'un circuit de rafraichissement intégré dans le contrôleur mémoire, comme pour les autres DRAM. La seule différence est que la machine à état est plus complexe. Pour rappel, une requête de lecture/écriture se fait en trois étapes maximum : une commande PRECHARGE pour précharger le tampon de ligne, une commande ACT qui fixe l'adresse de ligne, et enfin une commande READ/WRITE avec l'adresse de colonne. Et ces commandes sont séparées par des '''délais mémoire''' bien précis. Par exemple, je prends des chiffres arbitraires : il faut attendre 2 cycles entre une commande ACT et une commande READ, 6 cycles avant deux commandes WRITE consécutives, etc. La gestion des délais mémoire rend la conception du séquenceur plus complexe. Il faut aussi tenir compte des commandes SDRAM anticipées, à savoir que l'on peut envoyer des commandes avant que la précédente soit terminée. Les commandes anticipées sont idéales dans le cas où des accès successifs se font dans des banques différentes. Pour les exploiter au mieux, le contrôleur mémoire doit donc détecter si des accès successifs se font dans des banques différentes, ou dans la même banque, pour décider d'envoyer des commandes anticipées ou non. Cette '''détection des conflits de banque''' complexifie le séquenceur. ===La détection des succès/défauts de page=== Un point important est que dans certaines conditions, certaines commandes peuvent être omises. Par exemple, en cas de succès de page, les commandes PRECHARGE et ACT ne doivent pas être envoyées, seules les commandes READ/WRITE le sont. Le contrôleur doit toujours '''détecter les succès et défauts de page''' et agir en fonction. La solution utilisée est la même que pour les mémoires FPM : il faut mémoriser quelle ligne est ouverte ou fermée. La différence avec les FPM est qu'il faut faire cela pour chaque banque mémoire ! En effet, chaque banque a son propre tampon de ligne, ce qui fait que la gestion des lignes se fait indépendamment dans chaque banque. Le séquenceur mémoire doit donc se souvenir des lignes actives dans chaque banque. Pour cela, il mémorise ces lignes dans une petite mémoire : la '''table des banques''', aussi appelée ''bank status memory''. Pour détecter un succès ou un défaut, le contrôleur doit extraire la ligne de l'adresse, mais aussi le numéro de banque. Il envoie alors le numéro de banque à la table des banques, sur son entrée d'adresse. Il récupère alors le numéro de la ligne active sur les sorties de données. Il compare alors ce numéro de ligne avec le numéro de ligne de l'adresse envoyée par le processeur. C'est un succès si les deux sont égales, un défaut sinon. [[File:Controleur mémoire FPM avec plusieurs banques.jpg|centre|vignette|upright=2|Contrôleur mémoire FPM avec plusieurs banques.]] ===La politique de gestion du tampon de ligne=== Pour ce qui est des succès/défauts de page, le séquenceur mémoire peut fonctionner de plusieurs manières, dont les plus extrêmes sont appelés la politique de la page fermée et la politique de la page ouverte. Voyons à quoi elles correspondent. Avec la '''politique de la page fermée''', chaque accès mémoire est suivi d'une commande PRECHARGE, qui ferme la ligne courante et précharge les lignes de bits. Même si deux accès consécutifs se font dans la même ligne, la ligne est fermée et ré-ouverte entre deux accès mémoire. En clair : l'optimisation introduite par les mémoires FPM est désactivée, le contrôleur mémoire fait exprès de ne pas en profiter. On appelle cette méthode la close ''page autoprecharge''. Cette méthode réduit grandement les performances pour les accès à des adresses consécutives, mais fonctionne à merveille si les accès sont "aléatoires", à savoir qu'ils se font sans régularités évidentes. De tels accès sont dispersés en mémoire, ce qui fait qu'il est rare qu'ils accèdent deux fois de suite à la même ligne. Un autre avantage est que l'implémentation du séquenceur mémoire est très simple. En effet, le séquenceur mémoire se passe complétement de la table des banques, du comparateur de ligne, et de tous les circuits nécessaires pour vérifier les succès ou défauts de page. De plus, le séquenceur mémoire profite grandement des commandes READA et WRITEA, qui fusionnent une commande READ/WRITE avec une commande PRECHARGE. Le séquenceur mémoire a juste à envoyer des commandes ACT, READA, WRITEA et PREFETCH à la mémoire, pas besoin des commandes PRECHARGE, READ ou WRITE. À l'opposé, la '''politique de la page ouverte''' ne ferme pas automatiquement la ligne. Elle la laisse ouverte, en espérant que le prochain accès mémoire se fasse dans cette ligne. Lorsqu'un nouvel accès mémoire arrive, elle doit détecter les succès ou défauts de page et agir en fonction. En cas de défaut de page, la ligne est fermée, le séquenceur mémoire envoie une commande PRECHARGE, puis l'accès suivant effectue les deux commandes ACT + READ ou WRITE. En cas de succès de page, les commandes PRECHARGE et ACT ne sont pas envoyées, seules la commande READ ou WRITE l'est. Un désavantage est que le contrôleur mémoire doit inclure une table des banques et un comparateur, comme vu plus haut dans la section sur les mémoires FPM. un autre défaut est que garder une ligne ouverte consomme beaucoup d'énergie, comparé à un simple état de PRECHARGE. En conséquence, il est préférable de fermer les lignes dès que possible. Par contre, les performances sont d'autant meilleures que les accès mémoire consécutifs à une même ligne soient assez fréquents. Si les accès mémoire sont aléatoires, les performances sont moins bonnes. La politique de la page fermée fermait les lignes en avance, avec des commandes READA ou WRITEA, avant même que l'accès suivant démarre. Avec la politique de la page ouverte, on doit attendre pour détecter un défaut de page, puis fermer la ligne avec une commande PRECHARGE séparée. La ligne est donc fermée avec un peu temps de retard, et envoyer deux commandes au lieu d'une prend plus de temps. : Les lignes ne restent pas ouvertes indéfiniment, elles sont automatiquement fermées lorsqu'elles sont rafraichies. Vu que c'est le contrôleur mémoire qui décide du rafraichissement mémoire, il sait quelles lignes sont rafraichies à quel moment. Et il sait donc quand elles sont fermées. Les contrôleurs mémoires basiques utilisent une des deux solutions précédentes. Soit la page est toujours fermée, soit elle est toujours laissée ouverte jusqu'à ce qu'un accès mémoire la referme. Mais les contrôleurs plus évolués utilisent des '''politiques hybrides''', capables de switcher entre les deux suivant la situation. La méthode la plus simple laisse une ligne ouverte un temps prédéterminé avant de fermer la ligne. Il existe aussi des '''politiques prédictives'''. En clair, il, qui tentent de prédire s'il faut fermer ou laisser ouvertes les pages ouvertes. Elles regardent, pour les N derniers accès, s'ils ont fait un succès ou un défaut de page. Mémoriser les N derniers accès demande d'utiliser un simple registre à décalage de N bits, chaque bit indiquant si le énième accès précédent a été un succès de page ou non. Pour chaque valeur de ce registre, il faut prédire si le prochain accès demandera une ouverture ou une fermeture. Une prédiction simple fait la moyenne des bits à 1 dans ce registre et ferme la page si elle est inférieure à 1/2. Pour améliorer un petit peu l'algorithme, on peut faire en sorte que les bits des accès mémoires les plus récents aient plus de poids dans le calcul de la moyenne. Il existe sans doute d'autres solutions plus évoluées, mais il est difficile de savoir ce qu'il y a dans les contrôleurs de SDRAM modernes. : Le fait de laisser ouverte une ligne ou au contraire de la fermer systématiquement, se fait pour chaque banque. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les mémoires RAM dynamiques (DRAM) | prevText=Les mémoires RAM dynamiques (DRAM) | next=Les mémoires associatives | nextText=Les mémoires associatives }} </noinclude> 7sfb6rto7gxrrnvqsm6we5r1kkyg9x3 764696 764693 2026-04-23T19:29:53Z Mewtow 31375 /* La politique de gestion du tampon de ligne */ 764696 wikitext text/x-wiki Les mémoires ROM ou SRAM ont généralement une interface simple, à laquelle le processeur peut s'interfacer directement. Mais pour les DRAM, ce n'est pas le cas. Les DRAM utilisent un bus d'adresse multiplexé, où l'adresse est envoyée en deux fois. Connecter le processeur directement sur une DRAM n'est pas pratique : le bus d'adresse du processeur et celui de la mémoire ne collent pas. Les DRAM doivent aussi être rafraichies régulièrement. Le rafraichissement mémoire peut être délégué au processeur, mais c'est loin d'être idéal. Et il y a bien d'autres raisons qui font que le processeur ne peut pas s'interfacer facilement avec les mémoires DRAM. Pour gérer ces problèmes, les mémoires DRAM ne sont pas connectées directement au processeur. À la place, on ajoute un intermédiaire entre le processeur et la mémoire : le '''contrôleur mémoire externe'''. Son but est de montrer au processeur une interface simple, semblable à celle d'une SRAM classique, alors qu'il commande une mémoire DRAM à l'interface plus complexe. Il est placé sur la carte mère ou dans le processeur, et ne doit pas être confondu avec le contrôleur mémoire intégré dans la mémoire. Ce chapitre va expliquer quels sont les rôles du contrôleur mémoire, son interface et ce qu'il y a à l'intérieur. Dans ce chapitre, quand nous parlerons de ''contrôleur mémoire'', cela fera systématiquement référence au contrôleur mémoire externe. Et avant de poursuivre, sachez qu'il est difficile de faire des généralités sur les contrôleurs mémoire, car les mémoires DRAM elles-mêmes sont assez différentes les unes des autres. Entre une mémoire EDO, une mémoire SDR, une mémoire DDR et une DRAM asynchrone, les contrôleurs mémoires seront fortement différents. Aussi, il y a aura une différence entre un contrôleur pour une DRAM asynchrone et un contrôleur pour une mémoire EDO, une mémoire SDRAM, etc. J'ai choisit de vous séparer les contrôleurs mémoire pour les DRAM asynchrones de ceux pour les SDRAM/DRR. ==Le contrôleur d'une DRAM asynchrone== Les premières DRAM asynchrones avaient des contrôleurs mémoires dédiés, qui étaient séparés du processeur et du ''chipset'' de la carte mère. Par exemple, les composants Intel 8202, Intel 8203 et Intel 8207 étaient des contrôleurs mémoire pour DRAM asynchrones qui étaient vendus dans des boitiers DIP et étaient soudés sur la carte mère. Par la suite, ils ont été intégrés au ''chipset'' de la carte mère pendant les décennies 90-2000. Après les années 2000, ils ont été intégrés dans les processeurs. Leurs fonctions étaient le multiplexage de l'adresse et le rafraichissement mémoire. Ils recevaient une adresse mémoire complète, qu'ils découpaient une adresse de ligne et une adresse de colonne, envoyées l'une après l'autre sur le bus mémoire. Pour le rafraichissement mémoire, ils rafraichissaient la DRAM régulièrement, de manière automatique, entre deux accès mémoire normaux. Le processeur n'avait ainsi plus à rafraichir la mémoire lui-même, cette fonction était déléguée au contrôleur de DRAM. Ils étaient connectés au bus d'adresse et de commande, avec éventuellement des relations indirectes avec le bus de données. ===L'interface d'un contrôleur de DRAM asynchrone=== L'interface du contrôleur mémoire décrit ses broches d'entrées/sorties et leur signification. Elle est généralement très simple et contient deux ports : un connecté au processeur, un autre connecté à la DRAM. Cela trahit d'ailleurs son rôle principal, qui est de transformer les requêtes de lecture/écriture provenant du processeur en une suite de commandes acceptée par la mémoire. Le port connecté à la DRAM est connecté ua bus d'adresse et au bus de commande. Le bus de données est lui relié au processeur et/ou au bus système. Un accès mémoire provenant du processeur contient une adresse à lire/écrire, le bit R/W qui indique s'il faut faire une lecture ou une écriture, et éventuellement une donnée à écrire. Mais, nous avons vu que les accès mémoires sur une DRAM sont multiplexés : on envoie l'adresse en deux fois : la ligne d'abord, puis la colonne. De plus, il faut générer les signaux RAS, CAS et bien d'autres. Le tout est illustré ci-dessous. [[File:Contrôleur mémoire.png|centre|vignette|upright=2|Contrôleur mémoire externe.]] Un point important est que les DRAM asynchrones n'ont pas d'entrée ''Chip Select'' ou d'entrée ''Output Enable''. Les signaux RAS et CAS remplacent en quelque sorte ces deux signaux. Le bit RAS fait office de ''Chip Select'', le bit CAS fait office d'''Output Enable''. Pour certains contrôleurs de DRAM, il faut ajouter l''''interface électrique''', qui traduit les signaux du processeur en signaux compatibles avec la mémoire. Il est en effet très fréquent que la mémoire et le processeur n'utilisent pas les mêmes tensions pour coder un bit, ce qui fait qu'elles ne sont pas compatibles. Dans ce cas, le contrôleur mémoire fait la conversion. Le contrôleur DRAM peut fonctionner en mode synchrone ou asynchrone, du point de vue du processeur. Quand il fonctionne en mode synchrone, il permet d'interfacer un processeur synchrone avec une mémoire DRAM asynchrone. Un point important est que le contrôleur DRAM sert d'intermédiaire entre une mémoire DRAM et "le reste du monde". Il ne fait pas forcément office d'intermédiaire entre DRAM et processeur, mais peut aussi faire l'intermédiaire entre la DRAM et un bus système, entre une DRAM et le ''Video Display Controler'' d'une carte graphique, ou n'importe quel autre composant électronique qui utilise cette DRAM. ===Le générateur de ''timings'' et la traduction d'adresse=== Le contrôleur mémoire doit traduire les adresses du processeur en adresses compatibles avec la mémoire. Et la traduction est assez variable, suivant que le bus mémoire est un bus normal, un bus multiplexé, ou partiellement multiplexé. Nous avons vu ces trois types de bus mémoire dans le chapitre sur l'interface des mémoires, mais nous ferons quelques rappels rapides. Avec un ''bus totalement multiplexé'', le bus d'adresse et le bus de données sont fusionnés. Dans ce cas, on peut envoyer soit une adresse, soit lire/écrire une donnée sur le bus, mais on ne peut pas faire les deux en même temps. Un bit ALE indique si le bus est utilisé en tant que bus d'adresse ou bus de données. Le contrôleur mémoire gère cette situation, en fixant le bit ALE et en envoyant séparément adresse et donnée pour les écritures. [[File:Bus multiplexé avec bit ALE.png|centre|vignette|upright=2|Bus multiplexé avec bit ALE.]] Avec un ''bus d'adresse multiplexé'', l'adresse est découpée en une adresse de ligne et une adresse de colonne, envoyées l'une après l'autre. Le contrôleur mémoire prend en entrée une adresse mémoire complète, la découpe en deux, et envoie chaque morceau au bon moment. Pour cela, il suffit d'un registre pour mémoriser l'adresse et d'un multiplexeur. Le multiplexeur choisit soit les bits de poids fort de l'adresse, soit ceux de poids faible. Les premiers correspondent à l'adresse de ligne, les autres à l'adresse de colonne. La commande du multiplexeur est le fait d'un petit circuit séquentiel, qui génère aussi les signaux CAS et RAS. Au premier cycle, il met le signal RAS à 1, met le CAS à 0, et configure le MUX pour sélectionner les bits de poids fort. Au second cycle, il génère un signal CAS à 1, met le RAS à 0 et configure le MUX pour sélectionner les bits de poids faible. Le circuit en question est appelé le générateur de ''timings''. [[File:Controleur de DRAM simple, sans rafraichissement mémoire.png|centre|vignette|upright=2|Contrôleur de DRAM simple, sans rafraichissement mémoire.]] Le générateur de ''timings'' est un circuit séquentiel qui implémente une petite machine à état. Il est très simple sur une mémoire DRAM asynchrone basique, mais il est plus complexe sur les mémoires FPM, EDO, quartet, et autres. Le regroupement des multiplexeurs d'adresse et du générateur de ''timings'' est appelé le '''séquenceur mémoire'''. C'est le séquenceur mémoire qui traduit la requête processeur en commande DRAM, le reste du contrôleur est dédié au rafraichissement mémoire ou à d'autres fonctions facultatives. ===Le rafraichissement mémoire=== La gestion du rafraichissement mémoire est la fonction principale du contrôleur DRAM. Pour gérer le rafraichissement mémoire, le contrôleur mémoire intègre deux compteurs, un pour gérer l'adresse à rafraichir, l'autre pour gérer l'intervalle de temps entre deux rafraichissements. Le rafraichissement se fait à intervalle régulier, tous les x microsecondes. Pour déclencher le rafraichissement au bon moment, le contrôleur mémoire contient un ''Refresh Timer'', aussi appelé le '''compteur de rafraichissement'''. Il est initialisé avec le temps entre deux rafraichissements, une adresse est rafraichie quand ce compteur atteint 0. Le rafraichissement mémoire balaye la mémoire adresse par adresse. Pour savoir à quelle adresse il en est rendu, le contrôleur mémoire utilise un '''compteur d'adresse'''. Il contient la prochaine adresse à rafraichir, aussi appelée l'adresse de rafraichissement. Régulièrement, l'adresse dans ce compteur est envoyée à la RAM, pour une lecture. Mais la donnée lue n'est pas envoyée sur le bus de donnée, soit parce que la RAM est prévue pour, soit parce que le contrôleur désactive son bit ''output enable''. Dans le second cas, la RAM fait la lecture en interne, mais se déconnecte du bus de donnée, perdant la donnée lue dans le néant. Pour envoyer l'adresse de rafraichissement sur le bus d'adresse, il faut rajouter un multiplexeur, qui choisit entre l'adresse normale et l'adresse de rafraichissement. [[File:Controleur de DRAM avec rafraichissement mémoire.png|centre|vignette|upright=2|Controleur de DRAM avec rafraichissement mémoire.]] Le multiplexeur ne doit cependant pas être configuré si une adresse est déjà en cours de transfert. Pour cela, un circuit d'arbitrage se débrouille pour éviter qu'un accès mémoire soit interrompu par une demande de rafraichissement et inversement. Il peut être inclus dans le séquenceur mémoire ou séparé de celui-ci. [[File:Controleur mémoire, intérieur simplifié.png|centre|vignette|upright=2.5|Contrôleur mémoire, intérieur simplifié.]] Il faut noter que le rafraichissement mémoire peut être délégué non pas au contrôleur mémoire, mais au processeur où à la DRAM elle-même. Quand elle est le fait du processeur, celui-ci incorpore un ''refresh timer'' et un compteur d'adresse. Un exemple est celui du processeur Zilog Z80, qui implémentait des compteurs internes pour gérer le rafraichissement mémoire. On peut considérer que le processeur incorpore alors le contrôleur mémoire, au moins partiellement. Il est aussi possible de déléguer le rafraichissement au logiciel (certains jeux vidéos Amiga ou Commodore faisaient cela pour la mémoire vidéo). Quand la DRAM elle-même s'occupe de son propre rafraichissement, c'est elle qui intègre un ''refresh timer'' et le compteur d'adresse. ===Le décodage d'adresse=== Le contrôleur mémoire gère aussi le '''décodage d'adresse'''. pour rappel, celui-ci est utilisé quand on combine plusieurs chips mémoire ensemble, pour combiner leurs capacités mémoire. Par exemple, on peut combiner 4 chips mémoires de 1 mébioctet chacun, pour que le processeur voit comme 4 mébioctets de RAM unique. Le premier mébioctet est placé dans le premier chip mémoire, le second mébioctet dans le second chip, etc. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Pour cela, on active le chip mémoire adéquat, en fonction de l'adresse à consulter. Les autres chips mémoire sont désactivés pendant l'accès mémoire. En théorie, activer ou désactiver un chip mémoire se fait en utilisant son entrée ''Chip Select''. Il faut noter que si les SDRAM disposent bien d'un signal ''Chip Select'', ce n'est pas le cas des mémoires RAM asynchrones. A la place, ce sont les signaux RAS qui font office de ''Chip Select''. Une RAM asynchrone est activée quand son signal RAS lui demande de lire une ligne, elle est désactivée sinon. Mais c'est un détail. Toujours est-il que les signaux ''Chip Select'', ou leurs équivalents, sont générés par le contrôleur de DRAM, à partir des bits de poids fort de l'adresse. Par exemple, avec 4 chips mémoire, les deux bits de poids fort de l'adresse sont utilisés pour sélectionner le chip mémoire adéquat. Le contrôleur mémoire doit avoir plusieurs sorties ''Chip Select'', une par chip mémoire. Et le nombre de ces sorties limite le nombre de chips mémoire qu'on peut combiner. Par exemple, s'il y a seulement 4 sorties ''Chip Select'', on ne pourra brancher que 4 chips mémoire dessus. Sauf à ruser, avec un arrangement horizontal, mais cela n'est pas le ressort du contrôleur mémoire. [[File:Td6bfig3.png|centre|vignette|upright=2|Comparaison entre arrangement horizontal (à gauche) et arrangement vertical (à droite).]] ===Exemple : l'Intel 8202-8203=== L'Intel 8202 et le 8203 étaient des contrôleurs de mémoire DRAM, parmi les plus simples qui soient. Ils avaient une entrée d'adresse de 12 bits, ce qui permettait d'adresser 4 kibioctets de RAM. Ils fournissaient en sortie une adresse multiplexée sur 6 bits, envoyée en deux fois. Ils avaient donc 12 entrées d'adresse, 6 sorties d'adresse, un signal RAS, un signal CAS. Les adresses présentées en entrées n'étaient pas mémorisées dans des registres, ce qui fait qu'elles devaient être maintenues durant toute la durée de l'accès mémoire. Le processeur ne pouvait donc pas se déconnecter du bus d'adresse pendant l'accès mémoire, peu importe sa durée. Le 8202 contenait aussi un compteur de rafraichissement. Rappelons que sur les DRAM asynchrones, le rafraichissement se fait ligne par ligne. Le contrôleur mémoire a juste à présenter l'adresse de ligne, il n'a pas à envoyer l'adresse de colonne. La commande de rafraichissement se fait en mettant le signal RAS à 0, mais en laissant le CAS à 1 (je rappelle que les signaux sont actifs à 0). Le compteur d'adresse de rafraichissement a donc juste à mémoriser l'adresse de ligne. Le séquenceur mémoire était précédé par un circuit d'arbitrage, non-représenté dans le schéma ci-dessous. La microarchitecture de l'Intel 8202 est la suivante : [[File:Microarchitecture de l'Intel 8202.png|centre|vignette|upright=2|Microarchitecture de l'Intel 8202.]] Le 8202 avait une entrée pour un signal d'horloge, ainsi qu'un ''Chip Select'' un peu particulier. Si le signal CS passait à 0 lors d'un accès mémoire, le 8202/8203 ne se désactivait qu'une fois l'accès mémoire terminé. On ne pouvait pas l'interrompre pendant un accès mémoire, même en changeant le bit CS. Le signal d'horloge était utilisé pour commander le ''refresh timer''. Pour commander les lectures et écriture, il recevait en entrée un bit ''Write Request'' et un bit ''Read Request'', qui demandent respectivement une écriture et une lecture. En sortie, on trouvait un unique bit R/W qui valait 0 pour une lecture et 1 pour une écriture. Il avait aussi un bit d'entrée pour forcer le rafraichissement mémoire. S'il est à 1, la mémoire rafraichie l'adresse envoyée par le processeur. Le 8202 pouvait être connecté sur 1 à 4 chips mémoire, ce qui permettait d'adresser au maximum 16 kibioctets de RAM. Les 4 chips ne sont pas accédés en parallèle, un seul l'est à chaque fois. Pour le décodage d'adresse, le 8202 dispose de deux bits BO et B1 pour sélectionner le chip adéquat, ainsi que 4 sorties RAS pour activer la banque adéquate. On rappelle que le signal RAS remplace le signal ''Chip Select''. C'est le séquenceur mémoire qui se charge de générer ces signaux RAS, à partir des deux bits B0 et B1 (qui sont techniquement des bits d'adresse). Pour communiquer avec le processeur, l'Intel 8202 disposait de deux bits XACK et SACK. SACK indiquait au processeur que le 8202/8203 est en train de faire un accès mémoire et qu'il est indisponible pour un second accès mémoire. Cela permet de bloquer le processeur tant que le 8202 est indisponible. Le signal XACK indique que l'accès mémoire précédent est terminé et que : soit la donnée lue est présente sur le bus de données, soit que l'écriture s'est terminée. : Le signal SACK est très utile sur les configurations multiprocesseurs. Un processeur peut démarrer un accès mémoire, le signal SACK indiquera au second processeur qu'il doit attendre que l'accès soit terminé pour que ce soit son tour. ==Les contrôleurs de DRAM asynchrones évolués== L'Intel 8202 était un contrôleur mémoire basique, comme beaucoup d'autres à cette époque. Mais Intel a vendu des contrôleurs mémoires plus complexes. Par exemple, l'Intel 8207 était un contrôleur mémoire bien plus avancé que les deux précédents. Passons sur certains détails, comme le fait qu'il pouvait gérer jusqu'à 256 kibioctets de DRAM. Au-delà de ça, il y avait des fonctionnalités bien plus intéressantes, à savoir : un support de l'ECC, il était double port, il permettait de simuler une DRAM synchrone à partir d'une DRAM asynchrone. Il n'était pas le seul dans ce cas et des contrôleurs de DRAM très évolués étaient capables de faire des merveilles. Voyons comment cela était possible. ===Les contrôleurs mémoire synchrone=== Il est parfaitement possible d'utiliser un contrôleur mémoire synchrone avec une DRAM asynchrone. A vrai dire, le contrôleur DRAM peut complétement simuler une mémoire synchrone alors que la DRAM associée est asynchrone. La traduction asynchrone vers synchrone se fait en ajoutant des registres sur le bus mémoire, notamment sur le bus de données et le bus d'adresse/commande. Nous avions détaillé cela dans le chapitre sur les SRAM, c'est la même chose avec une mémoire DRAM. Sauf que cette fois-ci, le contrôleur mémoire doit aussi être prévu pour. [[File:Controleur mémoire synchrone utilisé avec une DRAM asynchrone.png|centre|vignette|upright=2|Contrôleur mémoire synchrone utilisé avec une DRAM asynchrone]] Les deux-trois registres illustrés plus haut peuvent être intégrés directement dans le contrôleur mémoire, que ce soit totalement ou en partie. Le strict minimum pour avoir un contrôleur mémoire synchrone est que celui-ci doit mémoriser adresse et commandes dans un registre. Par exemple, le 8207 d'Intel était capable de mémoriser les requêtes processeurs dans un registre interne, mais il fallait utiliser deux registres séparés pour le bus de données. Les deux registres étaient alors commandés par le contrôleur mémoire. Il est cependant possible d'aller plus loin et d'intégrer les registres du bus de données dans le contrôleur mémoire. [[File:Controleur mémoire DRAM synchrone.png|centre|vignette|upright=2|Contrôleur mémoire DRAM synchrone.]] : Il faut noter que cette fonctionnalité est parfois disponible sur les SRAM. En clair, on peut associer une SRAM asynchrone avec un contrôleur de SRAM synchrone. Le contrôleur de SRAM se charge alors de simuler une SRAM synchrone à partir de la SRAM asynchrone. Utiliser un contrôleur mémoire synchrone a de nombreux avantages, l'un d'entre eux étant lié aux ''wait state''. Quand le processeur envoie une requête de lecture/écriture à la mémoire RAM, celle-ci met plusieurs cycles d'horloge à répondre. Et pendant ce temps, le processeur... attend. Et pendant ce temps d'attente, il doit maintenir l'adresse mémoire sur le bus d'adresse. Les cycles d'horloge perdus à attendre la mémoire RAM étaient appelés des '''''Wait states'''''. Utiliser un contrôleur mémoire synchrone d'éliminer les ''wait state'', dans une certaine mesure. Avec un contrôleur mémoire synchrone, le processeur envoie l'adresse, mais c'est le contrôleur mémoire qui la maintient sur le bus d'adresse. Le processeur peut envoyer l'adresse et la donnée à écrire, elles sont recopiées dans les registres, et le controleur mémoire y a accès sans que le processeur doive les maintenir. Le processeur peut se déconnecter du bus mémoire et faire du travail dans son coin pendant que le contrôleur mémoire accède à la DRAM. Les ''wait state'' disparaissent alors, du moins du point de vue du processeur. ===La gestion de l'ECC=== L''''ECC''' peut être géré dans le contrôleur mémoire. Pour cela, on couple les registres mentionnés dans la section précédente, avec un circuit de détection et de correction d'erreur. Le circuit d'ECC peut, comme les registres synchrones, être intégré dans le contrôleur mémoire, ou au contraire être situé dans un circuit séparé. Si le circuit d'ECC est séparé du contrôleur mémoire, il communique avec lui, histoire que le contrôleur mémoire puisse signaler toute erreur de parité ou d'ECC au processeur. [[File:Controleur mémoire synchrone avec ECC intégré.png|centre|vignette|upright=2|Controleur mémoire synchrone avec ECC intégré]] Reprenons l'exemple du 8207 d'Intel. Le contrôleur mémoire 8207 gère le bus d'adresse et de commande, mais n'a pas de connexions directes avec le bus de données. Il ne peut donc pas prendre en charge l'ECC. Il avait besoin d'être couplé avec un circuit d'ECC séparé, relié au bus de données : l'Intel 8206. Le 8026 prenait en entrée : 16 bits de données et 8 bits d'ECC. Il fournissait en sortie 16 bits de données après correction d'erreur, les 8 bits d'ECC pour indiquer qu'une erreur a été détectée mais pas corrigée, ainsi que des bits de parité. Le 8206 détectait/corrigeait les erreurs et générait les bits d'ECC, mais il communiquait avec le contrôleur mémoire pour cela. [[File:8207 avec ECC.png|centre|vignette|upright=2|8207 avec ECC]] La détection/correction d'erreur était appliquée à la fois pour les accès mémoire et pour les rafraichissements mémoire. Lors d'un rafraichissement mémoire, la donnée rafraichie est lue et réécrite. Avec l'ECC activé et configuré correctement, le rafraichissement passe par le bus de données. Au lieu d'avoir un cycle de lecture-écriture interne à la DRAM, on a un cycle de lecture-correction-écriture qui utilise le 8206. La donnée lue est envoyée sur le bus de données, puis le 8206 corrige une éventuelle erreur, et la donnée corrigée est alors réécrite en mémoire. Au passage, si une erreur non-correctible est détectée, le 8206 ne fait rien, l'erreur est ignorée. La gestion de l'erreur sera retardée jusqu'à une lecture ultérieure. Et encore : si lecture ultérieure il y a. Si la donnée est écrasée par une écriture, la donnée corrompue sera simplement écrasée et disparaitra sans avoir pu faire le moindre dégât. Mais pour cela, le 8206 doit communiquer avec le contrôleur mémoire, pour savoir s'il est dans un cycle de rafraichissement ou un accès mémoire normal. Il prévient le 8207 lors d'une erreur, et c'est ce dernier qui décide si l'erreur doit être prise en compte ou ignorée. C'est seulement lors d'un accès mémoire normal que le processeur est prévenu qu'une erreur de parité/autre a eu lieu. ===Les contrôleurs mémoires multiports=== Les '''contrôleur mémoire multiport''' disposent de plusieurs ports, chacun permettant de traiter un accès mémoire. Ils peuvent simuler une mémoire multiport à partir d'une DRAM monoport. Évidemment, la simulation n'est pas parfaite. Des accès mémoire simultanés, envoyés en même temps sur différents ports, sont en réalité exécutés un par un, pas en même temps. Il y a donc une petite pénalité en termes de performances, mais elle est mineure. Encore une fois, nous allons reprendre l'exemple du 8207. Il avait deux ports séparés, et était prévu pour fonctionner dans un système à deux processeurs. L'usage de deux ports séparés permettait de partager une unique mémoire DRAM entre deux processeurs. Le partage se faisait en interfaçant deux processeurs sur le contrôleur mémoire, chacun étant connecté à un port. Lors d'une lecture, il redirigeait la donnée lue vers le bon processeur, en configurant le bus de données correctement. Le contrôleur mémoire recevait des requêtes mémoire de deux processeurs, mais il les exécutait une à la fois. S'il recevait deux requêtes en même temps, l'une d'entre elles était mise en attente. Le contrôleur mémoire doit arbitrer les accès à la mémoire, et faire en sorte que les deux processeurs aient accès à la mémoire à tour de rôle. Et non seulement il doit arbitrer les deux ports, mais il y a aussi un troisième port interne au contrôleur mémoire : le rafraichissement mémoire ! Pour cela, le circuit d'arbitrage qui choisissait entre rafraichissement mémoire et accès mémoire, est amélioré de manière à gérer un second port. Le circuit d'arbitrage donne l'accès au séquenceur mémoire à un port sélectionné. L'arbitrage était configurable, avec deux options : soit le port A est privilégié sur le port B, soit le port le plus récemment accédé à la priorité. Les deux ports pouvaient être configurés pour fonctionner soit de manière asynchrone, soit de manière synchrone. Il était aussi possible de configurer l'ECC, des options liées à la fréquence du processeur et de la RAM, ainsi que de nombreuses options liées au rafraichissement. Pour cela, le 8207 contenait un registre de configuration interne, programmable en fournissant les entrées adéquates. Tout ce qui vient d'être dit se généralise avec plus de deux processeurs. Le 8207 ne permettait pas ça, mais les contrôleurs mémoire des PC modernes en sont capables. Ils peuvent gérer plusieurs dizaines de processeurs facilement. ==Le contrôleur mémoire d'une DRAM ''Fast Page Mode''== Les mémoires DRAM classiques sont des mémoires à tampon de ligne, mais qui sont assez mal utilisées. Notamment, tout accès mémoire se fait en deux phases : un accès pour sélectionner la ligne, un autre pour sélectionner la colonne. Les mémoires ''Fast Page Mode'' permettent d'optimiser le tout. Elles permettent de faire plusieurs accès successifs à la même ligne, à des colonnes différentes. Et le contrôleur mémoire doit être adapté pour cela. [[File:Sélection d'une ligne sur une mémoire FPM ou EDO.png|centre|vignette|upright=2|Sélection d'une ligne sur une mémoire FPM ou EDO.]] Le contrôleur mémoire doit détecter que deux accès mémoire consécutifs se font dans la même ligne. Si deux accès consécutifs accèdent à la même ligne, on doit juste changer de colonne et altérer le signal CAS. C'est un ''succès de tampon de ligne'', aussi appelé un '''succès de page'''. Si deux accès consécutifs accèdent à une ligne différente, c'est un ''défaut de tampon de ligne'', aussi appelé un '''défaut de page'''. Il faut alors changer de ligne, en altérant les signaux RAS et en envoyant une adresse de ligne. Pour détecter les succès ou défauts de page, il faut ajouter un circuit spécialisé dans le contrôleur mémoire. Il mémorise la ligne ouverte, et plus précisément son adresse de ligne (numéro de banque inclut). A chaque requête processeur, il compare l'adresse de ligne recue avec celle déjà ouverte. C'est un succès si les deux sont égales, un défaut si elles sont différentes. Le circuit qui fait cette comparaison est appelé le '''décodeur de commande'''. Il prévient le séquenceur mémoire en cas de succès de page, grâce à un signal de un bit, qui vaut 0 en cas de défaut de page et 1 en cas de succès. Le séquenceur mémoire décide alors comment gérer les signaux RAS et CAS, ainsi que l'envoi des adresses de ligne/colonne. [[File:Controleur mémoire d'une FPM-DRAM.png|centre|vignette|upright=2|Controleur mémoire d'une FPM-DRAM]] ==Le contrôleur mémoire d'une SDRAM ou d'une DDR== L'intérieur d'un contrôleur de SDRAM ne change pas significativement d'un contrôleur de RAM asynchrone. Il regroupe toujours un séquenceur mémoire et une interface physique, un circuit pour le rafraichissement mémoire et un circuit d'arbitrage. Par contre, ses sorties changent pas mal. Contrairement aux mémoires DRAM basiques, les mémoires SDRAM sont cadencées par un signal d'horloge. Et ce signal d'horloge vient bien de quelque part. Pour cela, deux solutions : soit le contrôleur mémoire génère la fréquence qui commande la mémoire, soit il prend en entrée une fréquence de base qu'il multiplie pour obtenir la fréquence désirée. Les deux solutions sont équivalentes, si ce n'est que les circuits impliqués ne sont pas les mêmes. Dans le premier cas, le contrôleur doit embarquer un circuit oscillateur, qui génère la fréquence demandée. Dans l'autre cas, un simple multiplieur/diviseur de fréquence suffit et c'est généralement une PLL qui est utilisée pour cela. : Notez qu'il ne faut pas confondre la fréquence de la SDRAM et celle du contrôleur mémoire. Le contrôleur mémoire fonctionne à une vitesse assez élevée, en interne. Le port relié au processeur fonctionne à haute fréquence, généralement la même que celle du processeur. A vrai dire, de nos jours, il est intégré dans le processeur. Pour le décodage d'adresse, tout est plus simple sur les SDRAM/DDR. Les chips de mémoire SDRAM et DDR disposent d'une entrée ''Chip Select'', ce qui facilite grandement le décodage d'adresse. Les bits de ''Chip Select'' sont générés par le contrôleur mémoire, et sont transmis sur le bus de commande. Le contrôleur de DRAM peut adresser un certain nombre de rangées, dispersés sur une ou plusieurs barrettes. La limite maximale dépend du contrôleur de DRAM, elle est souvent proche de 8 ou 16 rangées. Si on combine plusieurs barrettes de mémoire, il est possible de dépasser cette limite. Par exemple, pour un contrôleur de DRAM supportant maximum 8 rangées, 4 barrettes de 4 rangées chacune dépassent la limite. ===Le séquenceurs mémoire pour les SDRAM/DDR=== Le séquenceur mémoire existe toujours pour les mémoires SDRAM, c'est toujours un circuit séquentiel qui implémente une machine à état. Il traduit toujours une requête processeur en une séquence de commandes envoyées à des timings bien précis. Les commandes mémoires peuvent provenir de l'extérieur, mais aussi d'un circuit de rafraichissement intégré dans le contrôleur mémoire, comme pour les autres DRAM. La seule différence est que la machine à état est plus complexe. Pour rappel, une requête de lecture/écriture se fait en trois étapes maximum : une commande PRECHARGE pour précharger le tampon de ligne, une commande ACT qui fixe l'adresse de ligne, et enfin une commande READ/WRITE avec l'adresse de colonne. Et ces commandes sont séparées par des '''délais mémoire''' bien précis. Par exemple, je prends des chiffres arbitraires : il faut attendre 2 cycles entre une commande ACT et une commande READ, 6 cycles avant deux commandes WRITE consécutives, etc. La gestion des délais mémoire rend la conception du séquenceur plus complexe. Il faut aussi tenir compte des commandes SDRAM anticipées, à savoir que l'on peut envoyer des commandes avant que la précédente soit terminée. Les commandes anticipées sont idéales dans le cas où des accès successifs se font dans des banques différentes. Pour les exploiter au mieux, le contrôleur mémoire doit donc détecter si des accès successifs se font dans des banques différentes, ou dans la même banque, pour décider d'envoyer des commandes anticipées ou non. Cette '''détection des conflits de banque''' complexifie le séquenceur. ===La détection des succès/défauts de page=== Un point important est que dans certaines conditions, certaines commandes peuvent être omises. Par exemple, en cas de succès de page, les commandes PRECHARGE et ACT ne doivent pas être envoyées, seules les commandes READ/WRITE le sont. Le contrôleur doit toujours '''détecter les succès et défauts de page''' et agir en fonction. La solution utilisée est la même que pour les mémoires FPM : il faut mémoriser quelle ligne est ouverte ou fermée. La différence avec les FPM est qu'il faut faire cela pour chaque banque mémoire ! En effet, chaque banque a son propre tampon de ligne, ce qui fait que la gestion des lignes se fait indépendamment dans chaque banque. Le séquenceur mémoire doit donc se souvenir des lignes actives dans chaque banque. Pour cela, il mémorise ces lignes dans une petite mémoire : la '''table des banques''', aussi appelée ''bank status memory''. Pour détecter un succès ou un défaut, le contrôleur doit extraire la ligne de l'adresse, mais aussi le numéro de banque. Il envoie alors le numéro de banque à la table des banques, sur son entrée d'adresse. Il récupère alors le numéro de la ligne active sur les sorties de données. Il compare alors ce numéro de ligne avec le numéro de ligne de l'adresse envoyée par le processeur. C'est un succès si les deux sont égales, un défaut sinon. [[File:Controleur mémoire FPM avec plusieurs banques.jpg|centre|vignette|upright=2|Contrôleur mémoire FPM avec plusieurs banques.]] ===La politique de gestion du tampon de ligne=== Pour ce qui est des succès/défauts de page, le séquenceur mémoire peut fonctionner de plusieurs manières, dont les plus extrêmes sont appelés la politique de la page fermée et la politique de la page ouverte. Voyons à quoi elles correspondent. Avec la '''politique de la page fermée''', chaque accès mémoire est suivi d'une commande PRECHARGE, qui ferme la ligne courante et précharge les lignes de bits. Même si deux accès consécutifs se font dans la même ligne, la ligne est fermée et ré-ouverte entre deux accès mémoire. En clair : l'optimisation introduite par les mémoires FPM est désactivée, le contrôleur mémoire fait exprès de ne pas en profiter. On appelle cette méthode la close ''page autoprecharge''. Cette méthode réduit grandement les performances pour les accès à des adresses consécutives, mais fonctionne à merveille si les accès sont "aléatoires", à savoir qu'ils se font sans régularités évidentes. De tels accès sont dispersés en mémoire, ce qui fait qu'il est rare qu'ils accèdent deux fois de suite à la même ligne. Un autre avantage est que l'implémentation du séquenceur mémoire est très simple. En effet, le séquenceur mémoire se passe complétement de la table des banques, du comparateur de ligne, et de tous les circuits nécessaires pour vérifier les succès ou défauts de page. De plus, le séquenceur mémoire profite grandement des commandes READA et WRITEA, qui fusionnent une commande READ/WRITE avec une commande PRECHARGE. Le séquenceur mémoire a juste à envoyer des commandes ACT, READA, WRITEA et PREFETCH à la mémoire, pas besoin des commandes PRECHARGE, READ ou WRITE. À l'opposé, la '''politique de la page ouverte''' ne ferme pas automatiquement la ligne. Elle la laisse ouverte, en espérant que le prochain accès mémoire se fasse dans cette ligne. Lorsqu'un nouvel accès mémoire arrive, elle doit détecter les succès ou défauts de page et agir en fonction. En cas de défaut de page, la ligne est fermée, le séquenceur mémoire envoie une commande PRECHARGE, puis l'accès suivant effectue les deux commandes ACT + READ ou WRITE. En cas de succès de page, les commandes PRECHARGE et ACT ne sont pas envoyées, seules la commande READ ou WRITE l'est. Un désavantage est que le contrôleur mémoire doit inclure une table des banques et un comparateur, comme vu plus haut dans la section sur les mémoires FPM. Un autre défaut est que garder une ligne ouverte consomme beaucoup d'énergie, comparé à un simple état de PRECHARGE. En conséquence, il est préférable de fermer les lignes dès que possible. Par contre, les performances sont d'autant meilleures que les accès mémoire consécutifs à une même ligne soient assez fréquents. Si les accès mémoire sont aléatoires, les performances sont moins bonnes. La politique de la page fermée fermait les lignes en avance, avec des commandes READA ou WRITEA, avant même que l'accès suivant démarre. Avec la politique de la page ouverte, on doit attendre pour détecter un défaut de page, puis fermer la ligne avec une commande PRECHARGE séparée. La ligne est donc fermée avec un peu temps de retard, et envoyer deux commandes au lieu d'une prend plus de temps. : Les lignes ne restent pas ouvertes indéfiniment, elles sont automatiquement fermées lorsqu'elles sont rafraichies. Vu que c'est le contrôleur mémoire qui décide du rafraichissement mémoire, il sait quelles lignes sont rafraichies à quel moment. Et il sait donc quand elles sont fermées. Les contrôleurs mémoires basiques utilisent une des deux solutions précédentes. Soit la page est toujours fermée, soit elle est toujours laissée ouverte jusqu'à ce qu'un accès mémoire la referme. Mais les contrôleurs plus évolués utilisent des '''politiques hybrides''', capables de switcher entre les deux suivant la situation. La méthode la plus simple laisse une ligne ouverte un temps prédéterminé avant de fermer la ligne. Il existe aussi des '''politiques prédictives'''. En clair, il, qui tentent de prédire s'il faut fermer ou laisser ouvertes les pages ouvertes. Elles regardent, pour les N derniers accès, s'ils ont fait un succès ou un défaut de page. Mémoriser les N derniers accès demande d'utiliser un simple registre à décalage de N bits, chaque bit indiquant si le énième accès précédent a été un succès de page ou non. Pour chaque valeur de ce registre, il faut prédire si le prochain accès demandera une ouverture ou une fermeture. Une prédiction simple fait la moyenne des bits à 1 dans ce registre et ferme la page si elle est inférieure à 1/2. Pour améliorer un petit peu l'algorithme, on peut faire en sorte que les bits des accès mémoires les plus récents aient plus de poids dans le calcul de la moyenne. Il existe sans doute d'autres solutions plus évoluées, mais il est difficile de savoir ce qu'il y a dans les contrôleurs de SDRAM modernes. : Le fait de laisser ouverte une ligne ou au contraire de la fermer systématiquement, se fait pour chaque banque. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les mémoires RAM dynamiques (DRAM) | prevText=Les mémoires RAM dynamiques (DRAM) | next=Les mémoires associatives | nextText=Les mémoires associatives }} </noinclude> b7bseya1kwug1m3lsgbuonh868fto4m 764700 764696 2026-04-23T19:34:27Z Mewtow 31375 /* Le séquenceurs mémoire pour les SDRAM/DDR */ 764700 wikitext text/x-wiki Les mémoires ROM ou SRAM ont généralement une interface simple, à laquelle le processeur peut s'interfacer directement. Mais pour les DRAM, ce n'est pas le cas. Les DRAM utilisent un bus d'adresse multiplexé, où l'adresse est envoyée en deux fois. Connecter le processeur directement sur une DRAM n'est pas pratique : le bus d'adresse du processeur et celui de la mémoire ne collent pas. Les DRAM doivent aussi être rafraichies régulièrement. Le rafraichissement mémoire peut être délégué au processeur, mais c'est loin d'être idéal. Et il y a bien d'autres raisons qui font que le processeur ne peut pas s'interfacer facilement avec les mémoires DRAM. Pour gérer ces problèmes, les mémoires DRAM ne sont pas connectées directement au processeur. À la place, on ajoute un intermédiaire entre le processeur et la mémoire : le '''contrôleur mémoire externe'''. Son but est de montrer au processeur une interface simple, semblable à celle d'une SRAM classique, alors qu'il commande une mémoire DRAM à l'interface plus complexe. Il est placé sur la carte mère ou dans le processeur, et ne doit pas être confondu avec le contrôleur mémoire intégré dans la mémoire. Ce chapitre va expliquer quels sont les rôles du contrôleur mémoire, son interface et ce qu'il y a à l'intérieur. Dans ce chapitre, quand nous parlerons de ''contrôleur mémoire'', cela fera systématiquement référence au contrôleur mémoire externe. Et avant de poursuivre, sachez qu'il est difficile de faire des généralités sur les contrôleurs mémoire, car les mémoires DRAM elles-mêmes sont assez différentes les unes des autres. Entre une mémoire EDO, une mémoire SDR, une mémoire DDR et une DRAM asynchrone, les contrôleurs mémoires seront fortement différents. Aussi, il y a aura une différence entre un contrôleur pour une DRAM asynchrone et un contrôleur pour une mémoire EDO, une mémoire SDRAM, etc. J'ai choisit de vous séparer les contrôleurs mémoire pour les DRAM asynchrones de ceux pour les SDRAM/DRR. ==Le contrôleur d'une DRAM asynchrone== Les premières DRAM asynchrones avaient des contrôleurs mémoires dédiés, qui étaient séparés du processeur et du ''chipset'' de la carte mère. Par exemple, les composants Intel 8202, Intel 8203 et Intel 8207 étaient des contrôleurs mémoire pour DRAM asynchrones qui étaient vendus dans des boitiers DIP et étaient soudés sur la carte mère. Par la suite, ils ont été intégrés au ''chipset'' de la carte mère pendant les décennies 90-2000. Après les années 2000, ils ont été intégrés dans les processeurs. Leurs fonctions étaient le multiplexage de l'adresse et le rafraichissement mémoire. Ils recevaient une adresse mémoire complète, qu'ils découpaient une adresse de ligne et une adresse de colonne, envoyées l'une après l'autre sur le bus mémoire. Pour le rafraichissement mémoire, ils rafraichissaient la DRAM régulièrement, de manière automatique, entre deux accès mémoire normaux. Le processeur n'avait ainsi plus à rafraichir la mémoire lui-même, cette fonction était déléguée au contrôleur de DRAM. Ils étaient connectés au bus d'adresse et de commande, avec éventuellement des relations indirectes avec le bus de données. ===L'interface d'un contrôleur de DRAM asynchrone=== L'interface du contrôleur mémoire décrit ses broches d'entrées/sorties et leur signification. Elle est généralement très simple et contient deux ports : un connecté au processeur, un autre connecté à la DRAM. Cela trahit d'ailleurs son rôle principal, qui est de transformer les requêtes de lecture/écriture provenant du processeur en une suite de commandes acceptée par la mémoire. Le port connecté à la DRAM est connecté ua bus d'adresse et au bus de commande. Le bus de données est lui relié au processeur et/ou au bus système. Un accès mémoire provenant du processeur contient une adresse à lire/écrire, le bit R/W qui indique s'il faut faire une lecture ou une écriture, et éventuellement une donnée à écrire. Mais, nous avons vu que les accès mémoires sur une DRAM sont multiplexés : on envoie l'adresse en deux fois : la ligne d'abord, puis la colonne. De plus, il faut générer les signaux RAS, CAS et bien d'autres. Le tout est illustré ci-dessous. [[File:Contrôleur mémoire.png|centre|vignette|upright=2|Contrôleur mémoire externe.]] Un point important est que les DRAM asynchrones n'ont pas d'entrée ''Chip Select'' ou d'entrée ''Output Enable''. Les signaux RAS et CAS remplacent en quelque sorte ces deux signaux. Le bit RAS fait office de ''Chip Select'', le bit CAS fait office d'''Output Enable''. Pour certains contrôleurs de DRAM, il faut ajouter l''''interface électrique''', qui traduit les signaux du processeur en signaux compatibles avec la mémoire. Il est en effet très fréquent que la mémoire et le processeur n'utilisent pas les mêmes tensions pour coder un bit, ce qui fait qu'elles ne sont pas compatibles. Dans ce cas, le contrôleur mémoire fait la conversion. Le contrôleur DRAM peut fonctionner en mode synchrone ou asynchrone, du point de vue du processeur. Quand il fonctionne en mode synchrone, il permet d'interfacer un processeur synchrone avec une mémoire DRAM asynchrone. Un point important est que le contrôleur DRAM sert d'intermédiaire entre une mémoire DRAM et "le reste du monde". Il ne fait pas forcément office d'intermédiaire entre DRAM et processeur, mais peut aussi faire l'intermédiaire entre la DRAM et un bus système, entre une DRAM et le ''Video Display Controler'' d'une carte graphique, ou n'importe quel autre composant électronique qui utilise cette DRAM. ===Le générateur de ''timings'' et la traduction d'adresse=== Le contrôleur mémoire doit traduire les adresses du processeur en adresses compatibles avec la mémoire. Et la traduction est assez variable, suivant que le bus mémoire est un bus normal, un bus multiplexé, ou partiellement multiplexé. Nous avons vu ces trois types de bus mémoire dans le chapitre sur l'interface des mémoires, mais nous ferons quelques rappels rapides. Avec un ''bus totalement multiplexé'', le bus d'adresse et le bus de données sont fusionnés. Dans ce cas, on peut envoyer soit une adresse, soit lire/écrire une donnée sur le bus, mais on ne peut pas faire les deux en même temps. Un bit ALE indique si le bus est utilisé en tant que bus d'adresse ou bus de données. Le contrôleur mémoire gère cette situation, en fixant le bit ALE et en envoyant séparément adresse et donnée pour les écritures. [[File:Bus multiplexé avec bit ALE.png|centre|vignette|upright=2|Bus multiplexé avec bit ALE.]] Avec un ''bus d'adresse multiplexé'', l'adresse est découpée en une adresse de ligne et une adresse de colonne, envoyées l'une après l'autre. Le contrôleur mémoire prend en entrée une adresse mémoire complète, la découpe en deux, et envoie chaque morceau au bon moment. Pour cela, il suffit d'un registre pour mémoriser l'adresse et d'un multiplexeur. Le multiplexeur choisit soit les bits de poids fort de l'adresse, soit ceux de poids faible. Les premiers correspondent à l'adresse de ligne, les autres à l'adresse de colonne. La commande du multiplexeur est le fait d'un petit circuit séquentiel, qui génère aussi les signaux CAS et RAS. Au premier cycle, il met le signal RAS à 1, met le CAS à 0, et configure le MUX pour sélectionner les bits de poids fort. Au second cycle, il génère un signal CAS à 1, met le RAS à 0 et configure le MUX pour sélectionner les bits de poids faible. Le circuit en question est appelé le générateur de ''timings''. [[File:Controleur de DRAM simple, sans rafraichissement mémoire.png|centre|vignette|upright=2|Contrôleur de DRAM simple, sans rafraichissement mémoire.]] Le générateur de ''timings'' est un circuit séquentiel qui implémente une petite machine à état. Il est très simple sur une mémoire DRAM asynchrone basique, mais il est plus complexe sur les mémoires FPM, EDO, quartet, et autres. Le regroupement des multiplexeurs d'adresse et du générateur de ''timings'' est appelé le '''séquenceur mémoire'''. C'est le séquenceur mémoire qui traduit la requête processeur en commande DRAM, le reste du contrôleur est dédié au rafraichissement mémoire ou à d'autres fonctions facultatives. ===Le rafraichissement mémoire=== La gestion du rafraichissement mémoire est la fonction principale du contrôleur DRAM. Pour gérer le rafraichissement mémoire, le contrôleur mémoire intègre deux compteurs, un pour gérer l'adresse à rafraichir, l'autre pour gérer l'intervalle de temps entre deux rafraichissements. Le rafraichissement se fait à intervalle régulier, tous les x microsecondes. Pour déclencher le rafraichissement au bon moment, le contrôleur mémoire contient un ''Refresh Timer'', aussi appelé le '''compteur de rafraichissement'''. Il est initialisé avec le temps entre deux rafraichissements, une adresse est rafraichie quand ce compteur atteint 0. Le rafraichissement mémoire balaye la mémoire adresse par adresse. Pour savoir à quelle adresse il en est rendu, le contrôleur mémoire utilise un '''compteur d'adresse'''. Il contient la prochaine adresse à rafraichir, aussi appelée l'adresse de rafraichissement. Régulièrement, l'adresse dans ce compteur est envoyée à la RAM, pour une lecture. Mais la donnée lue n'est pas envoyée sur le bus de donnée, soit parce que la RAM est prévue pour, soit parce que le contrôleur désactive son bit ''output enable''. Dans le second cas, la RAM fait la lecture en interne, mais se déconnecte du bus de donnée, perdant la donnée lue dans le néant. Pour envoyer l'adresse de rafraichissement sur le bus d'adresse, il faut rajouter un multiplexeur, qui choisit entre l'adresse normale et l'adresse de rafraichissement. [[File:Controleur de DRAM avec rafraichissement mémoire.png|centre|vignette|upright=2|Controleur de DRAM avec rafraichissement mémoire.]] Le multiplexeur ne doit cependant pas être configuré si une adresse est déjà en cours de transfert. Pour cela, un circuit d'arbitrage se débrouille pour éviter qu'un accès mémoire soit interrompu par une demande de rafraichissement et inversement. Il peut être inclus dans le séquenceur mémoire ou séparé de celui-ci. [[File:Controleur mémoire, intérieur simplifié.png|centre|vignette|upright=2.5|Contrôleur mémoire, intérieur simplifié.]] Il faut noter que le rafraichissement mémoire peut être délégué non pas au contrôleur mémoire, mais au processeur où à la DRAM elle-même. Quand elle est le fait du processeur, celui-ci incorpore un ''refresh timer'' et un compteur d'adresse. Un exemple est celui du processeur Zilog Z80, qui implémentait des compteurs internes pour gérer le rafraichissement mémoire. On peut considérer que le processeur incorpore alors le contrôleur mémoire, au moins partiellement. Il est aussi possible de déléguer le rafraichissement au logiciel (certains jeux vidéos Amiga ou Commodore faisaient cela pour la mémoire vidéo). Quand la DRAM elle-même s'occupe de son propre rafraichissement, c'est elle qui intègre un ''refresh timer'' et le compteur d'adresse. ===Le décodage d'adresse=== Le contrôleur mémoire gère aussi le '''décodage d'adresse'''. pour rappel, celui-ci est utilisé quand on combine plusieurs chips mémoire ensemble, pour combiner leurs capacités mémoire. Par exemple, on peut combiner 4 chips mémoires de 1 mébioctet chacun, pour que le processeur voit comme 4 mébioctets de RAM unique. Le premier mébioctet est placé dans le premier chip mémoire, le second mébioctet dans le second chip, etc. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Pour cela, on active le chip mémoire adéquat, en fonction de l'adresse à consulter. Les autres chips mémoire sont désactivés pendant l'accès mémoire. En théorie, activer ou désactiver un chip mémoire se fait en utilisant son entrée ''Chip Select''. Il faut noter que si les SDRAM disposent bien d'un signal ''Chip Select'', ce n'est pas le cas des mémoires RAM asynchrones. A la place, ce sont les signaux RAS qui font office de ''Chip Select''. Une RAM asynchrone est activée quand son signal RAS lui demande de lire une ligne, elle est désactivée sinon. Mais c'est un détail. Toujours est-il que les signaux ''Chip Select'', ou leurs équivalents, sont générés par le contrôleur de DRAM, à partir des bits de poids fort de l'adresse. Par exemple, avec 4 chips mémoire, les deux bits de poids fort de l'adresse sont utilisés pour sélectionner le chip mémoire adéquat. Le contrôleur mémoire doit avoir plusieurs sorties ''Chip Select'', une par chip mémoire. Et le nombre de ces sorties limite le nombre de chips mémoire qu'on peut combiner. Par exemple, s'il y a seulement 4 sorties ''Chip Select'', on ne pourra brancher que 4 chips mémoire dessus. Sauf à ruser, avec un arrangement horizontal, mais cela n'est pas le ressort du contrôleur mémoire. [[File:Td6bfig3.png|centre|vignette|upright=2|Comparaison entre arrangement horizontal (à gauche) et arrangement vertical (à droite).]] ===Exemple : l'Intel 8202-8203=== L'Intel 8202 et le 8203 étaient des contrôleurs de mémoire DRAM, parmi les plus simples qui soient. Ils avaient une entrée d'adresse de 12 bits, ce qui permettait d'adresser 4 kibioctets de RAM. Ils fournissaient en sortie une adresse multiplexée sur 6 bits, envoyée en deux fois. Ils avaient donc 12 entrées d'adresse, 6 sorties d'adresse, un signal RAS, un signal CAS. Les adresses présentées en entrées n'étaient pas mémorisées dans des registres, ce qui fait qu'elles devaient être maintenues durant toute la durée de l'accès mémoire. Le processeur ne pouvait donc pas se déconnecter du bus d'adresse pendant l'accès mémoire, peu importe sa durée. Le 8202 contenait aussi un compteur de rafraichissement. Rappelons que sur les DRAM asynchrones, le rafraichissement se fait ligne par ligne. Le contrôleur mémoire a juste à présenter l'adresse de ligne, il n'a pas à envoyer l'adresse de colonne. La commande de rafraichissement se fait en mettant le signal RAS à 0, mais en laissant le CAS à 1 (je rappelle que les signaux sont actifs à 0). Le compteur d'adresse de rafraichissement a donc juste à mémoriser l'adresse de ligne. Le séquenceur mémoire était précédé par un circuit d'arbitrage, non-représenté dans le schéma ci-dessous. La microarchitecture de l'Intel 8202 est la suivante : [[File:Microarchitecture de l'Intel 8202.png|centre|vignette|upright=2|Microarchitecture de l'Intel 8202.]] Le 8202 avait une entrée pour un signal d'horloge, ainsi qu'un ''Chip Select'' un peu particulier. Si le signal CS passait à 0 lors d'un accès mémoire, le 8202/8203 ne se désactivait qu'une fois l'accès mémoire terminé. On ne pouvait pas l'interrompre pendant un accès mémoire, même en changeant le bit CS. Le signal d'horloge était utilisé pour commander le ''refresh timer''. Pour commander les lectures et écriture, il recevait en entrée un bit ''Write Request'' et un bit ''Read Request'', qui demandent respectivement une écriture et une lecture. En sortie, on trouvait un unique bit R/W qui valait 0 pour une lecture et 1 pour une écriture. Il avait aussi un bit d'entrée pour forcer le rafraichissement mémoire. S'il est à 1, la mémoire rafraichie l'adresse envoyée par le processeur. Le 8202 pouvait être connecté sur 1 à 4 chips mémoire, ce qui permettait d'adresser au maximum 16 kibioctets de RAM. Les 4 chips ne sont pas accédés en parallèle, un seul l'est à chaque fois. Pour le décodage d'adresse, le 8202 dispose de deux bits BO et B1 pour sélectionner le chip adéquat, ainsi que 4 sorties RAS pour activer la banque adéquate. On rappelle que le signal RAS remplace le signal ''Chip Select''. C'est le séquenceur mémoire qui se charge de générer ces signaux RAS, à partir des deux bits B0 et B1 (qui sont techniquement des bits d'adresse). Pour communiquer avec le processeur, l'Intel 8202 disposait de deux bits XACK et SACK. SACK indiquait au processeur que le 8202/8203 est en train de faire un accès mémoire et qu'il est indisponible pour un second accès mémoire. Cela permet de bloquer le processeur tant que le 8202 est indisponible. Le signal XACK indique que l'accès mémoire précédent est terminé et que : soit la donnée lue est présente sur le bus de données, soit que l'écriture s'est terminée. : Le signal SACK est très utile sur les configurations multiprocesseurs. Un processeur peut démarrer un accès mémoire, le signal SACK indiquera au second processeur qu'il doit attendre que l'accès soit terminé pour que ce soit son tour. ==Les contrôleurs de DRAM asynchrones évolués== L'Intel 8202 était un contrôleur mémoire basique, comme beaucoup d'autres à cette époque. Mais Intel a vendu des contrôleurs mémoires plus complexes. Par exemple, l'Intel 8207 était un contrôleur mémoire bien plus avancé que les deux précédents. Passons sur certains détails, comme le fait qu'il pouvait gérer jusqu'à 256 kibioctets de DRAM. Au-delà de ça, il y avait des fonctionnalités bien plus intéressantes, à savoir : un support de l'ECC, il était double port, il permettait de simuler une DRAM synchrone à partir d'une DRAM asynchrone. Il n'était pas le seul dans ce cas et des contrôleurs de DRAM très évolués étaient capables de faire des merveilles. Voyons comment cela était possible. ===Les contrôleurs mémoire synchrone=== Il est parfaitement possible d'utiliser un contrôleur mémoire synchrone avec une DRAM asynchrone. A vrai dire, le contrôleur DRAM peut complétement simuler une mémoire synchrone alors que la DRAM associée est asynchrone. La traduction asynchrone vers synchrone se fait en ajoutant des registres sur le bus mémoire, notamment sur le bus de données et le bus d'adresse/commande. Nous avions détaillé cela dans le chapitre sur les SRAM, c'est la même chose avec une mémoire DRAM. Sauf que cette fois-ci, le contrôleur mémoire doit aussi être prévu pour. [[File:Controleur mémoire synchrone utilisé avec une DRAM asynchrone.png|centre|vignette|upright=2|Contrôleur mémoire synchrone utilisé avec une DRAM asynchrone]] Les deux-trois registres illustrés plus haut peuvent être intégrés directement dans le contrôleur mémoire, que ce soit totalement ou en partie. Le strict minimum pour avoir un contrôleur mémoire synchrone est que celui-ci doit mémoriser adresse et commandes dans un registre. Par exemple, le 8207 d'Intel était capable de mémoriser les requêtes processeurs dans un registre interne, mais il fallait utiliser deux registres séparés pour le bus de données. Les deux registres étaient alors commandés par le contrôleur mémoire. Il est cependant possible d'aller plus loin et d'intégrer les registres du bus de données dans le contrôleur mémoire. [[File:Controleur mémoire DRAM synchrone.png|centre|vignette|upright=2|Contrôleur mémoire DRAM synchrone.]] : Il faut noter que cette fonctionnalité est parfois disponible sur les SRAM. En clair, on peut associer une SRAM asynchrone avec un contrôleur de SRAM synchrone. Le contrôleur de SRAM se charge alors de simuler une SRAM synchrone à partir de la SRAM asynchrone. Utiliser un contrôleur mémoire synchrone a de nombreux avantages, l'un d'entre eux étant lié aux ''wait state''. Quand le processeur envoie une requête de lecture/écriture à la mémoire RAM, celle-ci met plusieurs cycles d'horloge à répondre. Et pendant ce temps, le processeur... attend. Et pendant ce temps d'attente, il doit maintenir l'adresse mémoire sur le bus d'adresse. Les cycles d'horloge perdus à attendre la mémoire RAM étaient appelés des '''''Wait states'''''. Utiliser un contrôleur mémoire synchrone d'éliminer les ''wait state'', dans une certaine mesure. Avec un contrôleur mémoire synchrone, le processeur envoie l'adresse, mais c'est le contrôleur mémoire qui la maintient sur le bus d'adresse. Le processeur peut envoyer l'adresse et la donnée à écrire, elles sont recopiées dans les registres, et le controleur mémoire y a accès sans que le processeur doive les maintenir. Le processeur peut se déconnecter du bus mémoire et faire du travail dans son coin pendant que le contrôleur mémoire accède à la DRAM. Les ''wait state'' disparaissent alors, du moins du point de vue du processeur. ===La gestion de l'ECC=== L''''ECC''' peut être géré dans le contrôleur mémoire. Pour cela, on couple les registres mentionnés dans la section précédente, avec un circuit de détection et de correction d'erreur. Le circuit d'ECC peut, comme les registres synchrones, être intégré dans le contrôleur mémoire, ou au contraire être situé dans un circuit séparé. Si le circuit d'ECC est séparé du contrôleur mémoire, il communique avec lui, histoire que le contrôleur mémoire puisse signaler toute erreur de parité ou d'ECC au processeur. [[File:Controleur mémoire synchrone avec ECC intégré.png|centre|vignette|upright=2|Controleur mémoire synchrone avec ECC intégré]] Reprenons l'exemple du 8207 d'Intel. Le contrôleur mémoire 8207 gère le bus d'adresse et de commande, mais n'a pas de connexions directes avec le bus de données. Il ne peut donc pas prendre en charge l'ECC. Il avait besoin d'être couplé avec un circuit d'ECC séparé, relié au bus de données : l'Intel 8206. Le 8026 prenait en entrée : 16 bits de données et 8 bits d'ECC. Il fournissait en sortie 16 bits de données après correction d'erreur, les 8 bits d'ECC pour indiquer qu'une erreur a été détectée mais pas corrigée, ainsi que des bits de parité. Le 8206 détectait/corrigeait les erreurs et générait les bits d'ECC, mais il communiquait avec le contrôleur mémoire pour cela. [[File:8207 avec ECC.png|centre|vignette|upright=2|8207 avec ECC]] La détection/correction d'erreur était appliquée à la fois pour les accès mémoire et pour les rafraichissements mémoire. Lors d'un rafraichissement mémoire, la donnée rafraichie est lue et réécrite. Avec l'ECC activé et configuré correctement, le rafraichissement passe par le bus de données. Au lieu d'avoir un cycle de lecture-écriture interne à la DRAM, on a un cycle de lecture-correction-écriture qui utilise le 8206. La donnée lue est envoyée sur le bus de données, puis le 8206 corrige une éventuelle erreur, et la donnée corrigée est alors réécrite en mémoire. Au passage, si une erreur non-correctible est détectée, le 8206 ne fait rien, l'erreur est ignorée. La gestion de l'erreur sera retardée jusqu'à une lecture ultérieure. Et encore : si lecture ultérieure il y a. Si la donnée est écrasée par une écriture, la donnée corrompue sera simplement écrasée et disparaitra sans avoir pu faire le moindre dégât. Mais pour cela, le 8206 doit communiquer avec le contrôleur mémoire, pour savoir s'il est dans un cycle de rafraichissement ou un accès mémoire normal. Il prévient le 8207 lors d'une erreur, et c'est ce dernier qui décide si l'erreur doit être prise en compte ou ignorée. C'est seulement lors d'un accès mémoire normal que le processeur est prévenu qu'une erreur de parité/autre a eu lieu. ===Les contrôleurs mémoires multiports=== Les '''contrôleur mémoire multiport''' disposent de plusieurs ports, chacun permettant de traiter un accès mémoire. Ils peuvent simuler une mémoire multiport à partir d'une DRAM monoport. Évidemment, la simulation n'est pas parfaite. Des accès mémoire simultanés, envoyés en même temps sur différents ports, sont en réalité exécutés un par un, pas en même temps. Il y a donc une petite pénalité en termes de performances, mais elle est mineure. Encore une fois, nous allons reprendre l'exemple du 8207. Il avait deux ports séparés, et était prévu pour fonctionner dans un système à deux processeurs. L'usage de deux ports séparés permettait de partager une unique mémoire DRAM entre deux processeurs. Le partage se faisait en interfaçant deux processeurs sur le contrôleur mémoire, chacun étant connecté à un port. Lors d'une lecture, il redirigeait la donnée lue vers le bon processeur, en configurant le bus de données correctement. Le contrôleur mémoire recevait des requêtes mémoire de deux processeurs, mais il les exécutait une à la fois. S'il recevait deux requêtes en même temps, l'une d'entre elles était mise en attente. Le contrôleur mémoire doit arbitrer les accès à la mémoire, et faire en sorte que les deux processeurs aient accès à la mémoire à tour de rôle. Et non seulement il doit arbitrer les deux ports, mais il y a aussi un troisième port interne au contrôleur mémoire : le rafraichissement mémoire ! Pour cela, le circuit d'arbitrage qui choisissait entre rafraichissement mémoire et accès mémoire, est amélioré de manière à gérer un second port. Le circuit d'arbitrage donne l'accès au séquenceur mémoire à un port sélectionné. L'arbitrage était configurable, avec deux options : soit le port A est privilégié sur le port B, soit le port le plus récemment accédé à la priorité. Les deux ports pouvaient être configurés pour fonctionner soit de manière asynchrone, soit de manière synchrone. Il était aussi possible de configurer l'ECC, des options liées à la fréquence du processeur et de la RAM, ainsi que de nombreuses options liées au rafraichissement. Pour cela, le 8207 contenait un registre de configuration interne, programmable en fournissant les entrées adéquates. Tout ce qui vient d'être dit se généralise avec plus de deux processeurs. Le 8207 ne permettait pas ça, mais les contrôleurs mémoire des PC modernes en sont capables. Ils peuvent gérer plusieurs dizaines de processeurs facilement. ==Le contrôleur mémoire d'une DRAM ''Fast Page Mode''== Les mémoires DRAM classiques sont des mémoires à tampon de ligne, mais qui sont assez mal utilisées. Notamment, tout accès mémoire se fait en deux phases : un accès pour sélectionner la ligne, un autre pour sélectionner la colonne. Les mémoires ''Fast Page Mode'' permettent d'optimiser le tout. Elles permettent de faire plusieurs accès successifs à la même ligne, à des colonnes différentes. Et le contrôleur mémoire doit être adapté pour cela. [[File:Sélection d'une ligne sur une mémoire FPM ou EDO.png|centre|vignette|upright=2|Sélection d'une ligne sur une mémoire FPM ou EDO.]] Le contrôleur mémoire doit détecter que deux accès mémoire consécutifs se font dans la même ligne. Si deux accès consécutifs accèdent à la même ligne, on doit juste changer de colonne et altérer le signal CAS. C'est un ''succès de tampon de ligne'', aussi appelé un '''succès de page'''. Si deux accès consécutifs accèdent à une ligne différente, c'est un ''défaut de tampon de ligne'', aussi appelé un '''défaut de page'''. Il faut alors changer de ligne, en altérant les signaux RAS et en envoyant une adresse de ligne. Pour détecter les succès ou défauts de page, il faut ajouter un circuit spécialisé dans le contrôleur mémoire. Il mémorise la ligne ouverte, et plus précisément son adresse de ligne (numéro de banque inclut). A chaque requête processeur, il compare l'adresse de ligne recue avec celle déjà ouverte. C'est un succès si les deux sont égales, un défaut si elles sont différentes. Le circuit qui fait cette comparaison est appelé le '''décodeur de commande'''. Il prévient le séquenceur mémoire en cas de succès de page, grâce à un signal de un bit, qui vaut 0 en cas de défaut de page et 1 en cas de succès. Le séquenceur mémoire décide alors comment gérer les signaux RAS et CAS, ainsi que l'envoi des adresses de ligne/colonne. [[File:Controleur mémoire d'une FPM-DRAM.png|centre|vignette|upright=2|Controleur mémoire d'une FPM-DRAM]] ==Le contrôleur mémoire d'une SDRAM ou d'une DDR== L'intérieur d'un contrôleur de SDRAM ne change pas significativement d'un contrôleur de RAM asynchrone. Il regroupe toujours un séquenceur mémoire et une interface physique, un circuit pour le rafraichissement mémoire et un circuit d'arbitrage. Par contre, ses sorties changent pas mal. Contrairement aux mémoires DRAM basiques, les mémoires SDRAM sont cadencées par un signal d'horloge. Et ce signal d'horloge vient bien de quelque part. Pour cela, deux solutions : soit le contrôleur mémoire génère la fréquence qui commande la mémoire, soit il prend en entrée une fréquence de base qu'il multiplie pour obtenir la fréquence désirée. Les deux solutions sont équivalentes, si ce n'est que les circuits impliqués ne sont pas les mêmes. Dans le premier cas, le contrôleur doit embarquer un circuit oscillateur, qui génère la fréquence demandée. Dans l'autre cas, un simple multiplieur/diviseur de fréquence suffit et c'est généralement une PLL qui est utilisée pour cela. : Notez qu'il ne faut pas confondre la fréquence de la SDRAM et celle du contrôleur mémoire. Le contrôleur mémoire fonctionne à une vitesse assez élevée, en interne. Le port relié au processeur fonctionne à haute fréquence, généralement la même que celle du processeur. A vrai dire, de nos jours, il est intégré dans le processeur. Pour le décodage d'adresse, tout est plus simple sur les SDRAM/DDR. Les chips de mémoire SDRAM et DDR disposent d'une entrée ''Chip Select'', ce qui facilite grandement le décodage d'adresse. Les bits de ''Chip Select'' sont générés par le contrôleur mémoire, et sont transmis sur le bus de commande. Le contrôleur de DRAM peut adresser un certain nombre de rangées, dispersés sur une ou plusieurs barrettes. La limite maximale dépend du contrôleur de DRAM, elle est souvent proche de 8 ou 16 rangées. Si on combine plusieurs barrettes de mémoire, il est possible de dépasser cette limite. Par exemple, pour un contrôleur de DRAM supportant maximum 8 rangées, 4 barrettes de 4 rangées chacune dépassent la limite. ===Le séquenceurs mémoire pour les SDRAM/DDR=== Le séquenceur mémoire existe toujours pour les mémoires SDRAM, c'est toujours un circuit séquentiel qui implémente une machine à état. Il traduit toujours une requête processeur en une séquence de commandes envoyées à des timings bien précis. Les commandes mémoires peuvent provenir de l'extérieur, mais aussi d'un circuit de rafraichissement intégré dans le contrôleur mémoire, comme pour les autres DRAM. La seule différence est que la machine à état est plus complexe. Pour rappel, une requête de lecture/écriture se fait en trois étapes maximum : une commande PRECHARGE pour précharger le tampon de ligne, une commande ACT qui fixe l'adresse de ligne, et enfin une commande READ/WRITE avec l'adresse de colonne. Et ces commandes sont séparées par des '''délais mémoire''' bien précis. Par exemple, je prends des chiffres arbitraires : il faut attendre 2 cycles entre une commande ACT et une commande READ, 6 cycles avant deux commandes WRITE consécutives, etc. La gestion des délais mémoire rend la conception du séquenceur plus complexe. Il faut aussi tenir compte des commandes SDRAM anticipées, à savoir que l'on peut envoyer des commandes avant que la précédente soit terminée. Les commandes anticipées sont idéales dans le cas où des accès successifs se font dans des banques différentes. Pour les exploiter au mieux, le contrôleur mémoire doit donc détecter si des accès successifs se font dans des banques différentes, ou dans la même banque, pour décider d'envoyer des commandes anticipées ou non, mais aussi pour gérer les succès/défauts de cache. Cette '''détection des conflits de banque''' complexifie le séquenceur. ===La détection des succès/défauts de page=== Un point important est que dans certaines conditions, certaines commandes peuvent être omises. Par exemple, en cas de succès de page, les commandes PRECHARGE et ACT ne doivent pas être envoyées, seules les commandes READ/WRITE le sont. Le contrôleur doit toujours '''détecter les succès et défauts de page''' et agir en fonction. La solution utilisée est la même que pour les mémoires FPM : il faut mémoriser quelle ligne est ouverte ou fermée. La différence avec les FPM est qu'il faut faire cela pour chaque banque mémoire ! En effet, chaque banque a son propre tampon de ligne, ce qui fait que la gestion des lignes se fait indépendamment dans chaque banque. Le séquenceur mémoire doit donc se souvenir des lignes actives dans chaque banque. Pour cela, il mémorise ces lignes dans une petite mémoire : la '''table des banques''', aussi appelée ''bank status memory''. Pour détecter un succès ou un défaut, le contrôleur doit extraire la ligne de l'adresse, mais aussi le numéro de banque. Il envoie alors le numéro de banque à la table des banques, sur son entrée d'adresse. Il récupère alors le numéro de la ligne active sur les sorties de données. Il compare alors ce numéro de ligne avec le numéro de ligne de l'adresse envoyée par le processeur. C'est un succès si les deux sont égales, un défaut sinon. [[File:Controleur mémoire FPM avec plusieurs banques.jpg|centre|vignette|upright=2|Contrôleur mémoire FPM avec plusieurs banques.]] ===La politique de gestion du tampon de ligne=== Pour ce qui est des succès/défauts de page, le séquenceur mémoire peut fonctionner de plusieurs manières, dont les plus extrêmes sont appelés la politique de la page fermée et la politique de la page ouverte. Voyons à quoi elles correspondent. Avec la '''politique de la page fermée''', chaque accès mémoire est suivi d'une commande PRECHARGE, qui ferme la ligne courante et précharge les lignes de bits. Même si deux accès consécutifs se font dans la même ligne, la ligne est fermée et ré-ouverte entre deux accès mémoire. En clair : l'optimisation introduite par les mémoires FPM est désactivée, le contrôleur mémoire fait exprès de ne pas en profiter. On appelle cette méthode la close ''page autoprecharge''. Cette méthode réduit grandement les performances pour les accès à des adresses consécutives, mais fonctionne à merveille si les accès sont "aléatoires", à savoir qu'ils se font sans régularités évidentes. De tels accès sont dispersés en mémoire, ce qui fait qu'il est rare qu'ils accèdent deux fois de suite à la même ligne. Un autre avantage est que l'implémentation du séquenceur mémoire est très simple. En effet, le séquenceur mémoire se passe complétement de la table des banques, du comparateur de ligne, et de tous les circuits nécessaires pour vérifier les succès ou défauts de page. De plus, le séquenceur mémoire profite grandement des commandes READA et WRITEA, qui fusionnent une commande READ/WRITE avec une commande PRECHARGE. Le séquenceur mémoire a juste à envoyer des commandes ACT, READA, WRITEA et PREFETCH à la mémoire, pas besoin des commandes PRECHARGE, READ ou WRITE. À l'opposé, la '''politique de la page ouverte''' ne ferme pas automatiquement la ligne. Elle la laisse ouverte, en espérant que le prochain accès mémoire se fasse dans cette ligne. Lorsqu'un nouvel accès mémoire arrive, elle doit détecter les succès ou défauts de page et agir en fonction. En cas de défaut de page, la ligne est fermée, le séquenceur mémoire envoie une commande PRECHARGE, puis l'accès suivant effectue les deux commandes ACT + READ ou WRITE. En cas de succès de page, les commandes PRECHARGE et ACT ne sont pas envoyées, seules la commande READ ou WRITE l'est. Un désavantage est que le contrôleur mémoire doit inclure une table des banques et un comparateur, comme vu plus haut dans la section sur les mémoires FPM. Un autre défaut est que garder une ligne ouverte consomme beaucoup d'énergie, comparé à un simple état de PRECHARGE. En conséquence, il est préférable de fermer les lignes dès que possible. Par contre, les performances sont d'autant meilleures que les accès mémoire consécutifs à une même ligne soient assez fréquents. Si les accès mémoire sont aléatoires, les performances sont moins bonnes. La politique de la page fermée fermait les lignes en avance, avec des commandes READA ou WRITEA, avant même que l'accès suivant démarre. Avec la politique de la page ouverte, on doit attendre pour détecter un défaut de page, puis fermer la ligne avec une commande PRECHARGE séparée. La ligne est donc fermée avec un peu temps de retard, et envoyer deux commandes au lieu d'une prend plus de temps. : Les lignes ne restent pas ouvertes indéfiniment, elles sont automatiquement fermées lorsqu'elles sont rafraichies. Vu que c'est le contrôleur mémoire qui décide du rafraichissement mémoire, il sait quelles lignes sont rafraichies à quel moment. Et il sait donc quand elles sont fermées. Les contrôleurs mémoires basiques utilisent une des deux solutions précédentes. Soit la page est toujours fermée, soit elle est toujours laissée ouverte jusqu'à ce qu'un accès mémoire la referme. Mais les contrôleurs plus évolués utilisent des '''politiques hybrides''', capables de switcher entre les deux suivant la situation. La méthode la plus simple laisse une ligne ouverte un temps prédéterminé avant de fermer la ligne. Il existe aussi des '''politiques prédictives'''. En clair, il, qui tentent de prédire s'il faut fermer ou laisser ouvertes les pages ouvertes. Elles regardent, pour les N derniers accès, s'ils ont fait un succès ou un défaut de page. Mémoriser les N derniers accès demande d'utiliser un simple registre à décalage de N bits, chaque bit indiquant si le énième accès précédent a été un succès de page ou non. Pour chaque valeur de ce registre, il faut prédire si le prochain accès demandera une ouverture ou une fermeture. Une prédiction simple fait la moyenne des bits à 1 dans ce registre et ferme la page si elle est inférieure à 1/2. Pour améliorer un petit peu l'algorithme, on peut faire en sorte que les bits des accès mémoires les plus récents aient plus de poids dans le calcul de la moyenne. Il existe sans doute d'autres solutions plus évoluées, mais il est difficile de savoir ce qu'il y a dans les contrôleurs de SDRAM modernes. : Le fait de laisser ouverte une ligne ou au contraire de la fermer systématiquement, se fait pour chaque banque. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les mémoires RAM dynamiques (DRAM) | prevText=Les mémoires RAM dynamiques (DRAM) | next=Les mémoires associatives | nextText=Les mémoires associatives }} </noinclude> 3hg2rj090sa14izotdg88s7t558yhwm 764703 764700 2026-04-23T20:45:31Z Mewtow 31375 /* Le contrôleur mémoire d'une SDRAM ou d'une DDR */ 764703 wikitext text/x-wiki Les mémoires ROM ou SRAM ont généralement une interface simple, à laquelle le processeur peut s'interfacer directement. Mais pour les DRAM, ce n'est pas le cas. Les DRAM utilisent un bus d'adresse multiplexé, où l'adresse est envoyée en deux fois. Connecter le processeur directement sur une DRAM n'est pas pratique : le bus d'adresse du processeur et celui de la mémoire ne collent pas. Les DRAM doivent aussi être rafraichies régulièrement. Le rafraichissement mémoire peut être délégué au processeur, mais c'est loin d'être idéal. Et il y a bien d'autres raisons qui font que le processeur ne peut pas s'interfacer facilement avec les mémoires DRAM. Pour gérer ces problèmes, les mémoires DRAM ne sont pas connectées directement au processeur. À la place, on ajoute un intermédiaire entre le processeur et la mémoire : le '''contrôleur mémoire externe'''. Son but est de montrer au processeur une interface simple, semblable à celle d'une SRAM classique, alors qu'il commande une mémoire DRAM à l'interface plus complexe. Il est placé sur la carte mère ou dans le processeur, et ne doit pas être confondu avec le contrôleur mémoire intégré dans la mémoire. Ce chapitre va expliquer quels sont les rôles du contrôleur mémoire, son interface et ce qu'il y a à l'intérieur. Dans ce chapitre, quand nous parlerons de ''contrôleur mémoire'', cela fera systématiquement référence au contrôleur mémoire externe. Et avant de poursuivre, sachez qu'il est difficile de faire des généralités sur les contrôleurs mémoire, car les mémoires DRAM elles-mêmes sont assez différentes les unes des autres. Entre une mémoire EDO, une mémoire SDR, une mémoire DDR et une DRAM asynchrone, les contrôleurs mémoires seront fortement différents. Aussi, il y a aura une différence entre un contrôleur pour une DRAM asynchrone et un contrôleur pour une mémoire EDO, une mémoire SDRAM, etc. J'ai choisit de vous séparer les contrôleurs mémoire pour les DRAM asynchrones de ceux pour les SDRAM/DRR. ==Le contrôleur d'une DRAM asynchrone== Les premières DRAM asynchrones avaient des contrôleurs mémoires dédiés, qui étaient séparés du processeur et du ''chipset'' de la carte mère. Par exemple, les composants Intel 8202, Intel 8203 et Intel 8207 étaient des contrôleurs mémoire pour DRAM asynchrones qui étaient vendus dans des boitiers DIP et étaient soudés sur la carte mère. Par la suite, ils ont été intégrés au ''chipset'' de la carte mère pendant les décennies 90-2000. Après les années 2000, ils ont été intégrés dans les processeurs. Leurs fonctions étaient le multiplexage de l'adresse et le rafraichissement mémoire. Ils recevaient une adresse mémoire complète, qu'ils découpaient une adresse de ligne et une adresse de colonne, envoyées l'une après l'autre sur le bus mémoire. Pour le rafraichissement mémoire, ils rafraichissaient la DRAM régulièrement, de manière automatique, entre deux accès mémoire normaux. Le processeur n'avait ainsi plus à rafraichir la mémoire lui-même, cette fonction était déléguée au contrôleur de DRAM. Ils étaient connectés au bus d'adresse et de commande, avec éventuellement des relations indirectes avec le bus de données. ===L'interface d'un contrôleur de DRAM asynchrone=== L'interface du contrôleur mémoire décrit ses broches d'entrées/sorties et leur signification. Elle est généralement très simple et contient deux ports : un connecté au processeur, un autre connecté à la DRAM. Cela trahit d'ailleurs son rôle principal, qui est de transformer les requêtes de lecture/écriture provenant du processeur en une suite de commandes acceptée par la mémoire. Le port connecté à la DRAM est connecté ua bus d'adresse et au bus de commande. Le bus de données est lui relié au processeur et/ou au bus système. Un accès mémoire provenant du processeur contient une adresse à lire/écrire, le bit R/W qui indique s'il faut faire une lecture ou une écriture, et éventuellement une donnée à écrire. Mais, nous avons vu que les accès mémoires sur une DRAM sont multiplexés : on envoie l'adresse en deux fois : la ligne d'abord, puis la colonne. De plus, il faut générer les signaux RAS, CAS et bien d'autres. Le tout est illustré ci-dessous. [[File:Contrôleur mémoire.png|centre|vignette|upright=2|Contrôleur mémoire externe.]] Un point important est que les DRAM asynchrones n'ont pas d'entrée ''Chip Select'' ou d'entrée ''Output Enable''. Les signaux RAS et CAS remplacent en quelque sorte ces deux signaux. Le bit RAS fait office de ''Chip Select'', le bit CAS fait office d'''Output Enable''. Pour certains contrôleurs de DRAM, il faut ajouter l''''interface électrique''', qui traduit les signaux du processeur en signaux compatibles avec la mémoire. Il est en effet très fréquent que la mémoire et le processeur n'utilisent pas les mêmes tensions pour coder un bit, ce qui fait qu'elles ne sont pas compatibles. Dans ce cas, le contrôleur mémoire fait la conversion. Le contrôleur DRAM peut fonctionner en mode synchrone ou asynchrone, du point de vue du processeur. Quand il fonctionne en mode synchrone, il permet d'interfacer un processeur synchrone avec une mémoire DRAM asynchrone. Un point important est que le contrôleur DRAM sert d'intermédiaire entre une mémoire DRAM et "le reste du monde". Il ne fait pas forcément office d'intermédiaire entre DRAM et processeur, mais peut aussi faire l'intermédiaire entre la DRAM et un bus système, entre une DRAM et le ''Video Display Controler'' d'une carte graphique, ou n'importe quel autre composant électronique qui utilise cette DRAM. ===Le générateur de ''timings'' et la traduction d'adresse=== Le contrôleur mémoire doit traduire les adresses du processeur en adresses compatibles avec la mémoire. Et la traduction est assez variable, suivant que le bus mémoire est un bus normal, un bus multiplexé, ou partiellement multiplexé. Nous avons vu ces trois types de bus mémoire dans le chapitre sur l'interface des mémoires, mais nous ferons quelques rappels rapides. Avec un ''bus totalement multiplexé'', le bus d'adresse et le bus de données sont fusionnés. Dans ce cas, on peut envoyer soit une adresse, soit lire/écrire une donnée sur le bus, mais on ne peut pas faire les deux en même temps. Un bit ALE indique si le bus est utilisé en tant que bus d'adresse ou bus de données. Le contrôleur mémoire gère cette situation, en fixant le bit ALE et en envoyant séparément adresse et donnée pour les écritures. [[File:Bus multiplexé avec bit ALE.png|centre|vignette|upright=2|Bus multiplexé avec bit ALE.]] Avec un ''bus d'adresse multiplexé'', l'adresse est découpée en une adresse de ligne et une adresse de colonne, envoyées l'une après l'autre. Le contrôleur mémoire prend en entrée une adresse mémoire complète, la découpe en deux, et envoie chaque morceau au bon moment. Pour cela, il suffit d'un registre pour mémoriser l'adresse et d'un multiplexeur. Le multiplexeur choisit soit les bits de poids fort de l'adresse, soit ceux de poids faible. Les premiers correspondent à l'adresse de ligne, les autres à l'adresse de colonne. La commande du multiplexeur est le fait d'un petit circuit séquentiel, qui génère aussi les signaux CAS et RAS. Au premier cycle, il met le signal RAS à 1, met le CAS à 0, et configure le MUX pour sélectionner les bits de poids fort. Au second cycle, il génère un signal CAS à 1, met le RAS à 0 et configure le MUX pour sélectionner les bits de poids faible. Le circuit en question est appelé le générateur de ''timings''. [[File:Controleur de DRAM simple, sans rafraichissement mémoire.png|centre|vignette|upright=2|Contrôleur de DRAM simple, sans rafraichissement mémoire.]] Le générateur de ''timings'' est un circuit séquentiel qui implémente une petite machine à état. Il est très simple sur une mémoire DRAM asynchrone basique, mais il est plus complexe sur les mémoires FPM, EDO, quartet, et autres. Le regroupement des multiplexeurs d'adresse et du générateur de ''timings'' est appelé le '''séquenceur mémoire'''. C'est le séquenceur mémoire qui traduit la requête processeur en commande DRAM, le reste du contrôleur est dédié au rafraichissement mémoire ou à d'autres fonctions facultatives. ===Le rafraichissement mémoire=== La gestion du rafraichissement mémoire est la fonction principale du contrôleur DRAM. Pour gérer le rafraichissement mémoire, le contrôleur mémoire intègre deux compteurs, un pour gérer l'adresse à rafraichir, l'autre pour gérer l'intervalle de temps entre deux rafraichissements. Le rafraichissement se fait à intervalle régulier, tous les x microsecondes. Pour déclencher le rafraichissement au bon moment, le contrôleur mémoire contient un ''Refresh Timer'', aussi appelé le '''compteur de rafraichissement'''. Il est initialisé avec le temps entre deux rafraichissements, une adresse est rafraichie quand ce compteur atteint 0. Le rafraichissement mémoire balaye la mémoire adresse par adresse. Pour savoir à quelle adresse il en est rendu, le contrôleur mémoire utilise un '''compteur d'adresse'''. Il contient la prochaine adresse à rafraichir, aussi appelée l'adresse de rafraichissement. Régulièrement, l'adresse dans ce compteur est envoyée à la RAM, pour une lecture. Mais la donnée lue n'est pas envoyée sur le bus de donnée, soit parce que la RAM est prévue pour, soit parce que le contrôleur désactive son bit ''output enable''. Dans le second cas, la RAM fait la lecture en interne, mais se déconnecte du bus de donnée, perdant la donnée lue dans le néant. Pour envoyer l'adresse de rafraichissement sur le bus d'adresse, il faut rajouter un multiplexeur, qui choisit entre l'adresse normale et l'adresse de rafraichissement. [[File:Controleur de DRAM avec rafraichissement mémoire.png|centre|vignette|upright=2|Controleur de DRAM avec rafraichissement mémoire.]] Le multiplexeur ne doit cependant pas être configuré si une adresse est déjà en cours de transfert. Pour cela, un circuit d'arbitrage se débrouille pour éviter qu'un accès mémoire soit interrompu par une demande de rafraichissement et inversement. Il peut être inclus dans le séquenceur mémoire ou séparé de celui-ci. [[File:Controleur mémoire, intérieur simplifié.png|centre|vignette|upright=2.5|Contrôleur mémoire, intérieur simplifié.]] Il faut noter que le rafraichissement mémoire peut être délégué non pas au contrôleur mémoire, mais au processeur où à la DRAM elle-même. Quand elle est le fait du processeur, celui-ci incorpore un ''refresh timer'' et un compteur d'adresse. Un exemple est celui du processeur Zilog Z80, qui implémentait des compteurs internes pour gérer le rafraichissement mémoire. On peut considérer que le processeur incorpore alors le contrôleur mémoire, au moins partiellement. Il est aussi possible de déléguer le rafraichissement au logiciel (certains jeux vidéos Amiga ou Commodore faisaient cela pour la mémoire vidéo). Quand la DRAM elle-même s'occupe de son propre rafraichissement, c'est elle qui intègre un ''refresh timer'' et le compteur d'adresse. ===Le décodage d'adresse=== Le contrôleur mémoire gère aussi le '''décodage d'adresse'''. pour rappel, celui-ci est utilisé quand on combine plusieurs chips mémoire ensemble, pour combiner leurs capacités mémoire. Par exemple, on peut combiner 4 chips mémoires de 1 mébioctet chacun, pour que le processeur voit comme 4 mébioctets de RAM unique. Le premier mébioctet est placé dans le premier chip mémoire, le second mébioctet dans le second chip, etc. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Pour cela, on active le chip mémoire adéquat, en fonction de l'adresse à consulter. Les autres chips mémoire sont désactivés pendant l'accès mémoire. En théorie, activer ou désactiver un chip mémoire se fait en utilisant son entrée ''Chip Select''. Il faut noter que si les SDRAM disposent bien d'un signal ''Chip Select'', ce n'est pas le cas des mémoires RAM asynchrones. A la place, ce sont les signaux RAS qui font office de ''Chip Select''. Une RAM asynchrone est activée quand son signal RAS lui demande de lire une ligne, elle est désactivée sinon. Mais c'est un détail. Toujours est-il que les signaux ''Chip Select'', ou leurs équivalents, sont générés par le contrôleur de DRAM, à partir des bits de poids fort de l'adresse. Par exemple, avec 4 chips mémoire, les deux bits de poids fort de l'adresse sont utilisés pour sélectionner le chip mémoire adéquat. Le contrôleur mémoire doit avoir plusieurs sorties ''Chip Select'', une par chip mémoire. Et le nombre de ces sorties limite le nombre de chips mémoire qu'on peut combiner. Par exemple, s'il y a seulement 4 sorties ''Chip Select'', on ne pourra brancher que 4 chips mémoire dessus. Sauf à ruser, avec un arrangement horizontal, mais cela n'est pas le ressort du contrôleur mémoire. [[File:Td6bfig3.png|centre|vignette|upright=2|Comparaison entre arrangement horizontal (à gauche) et arrangement vertical (à droite).]] ===Exemple : l'Intel 8202-8203=== L'Intel 8202 et le 8203 étaient des contrôleurs de mémoire DRAM, parmi les plus simples qui soient. Ils avaient une entrée d'adresse de 12 bits, ce qui permettait d'adresser 4 kibioctets de RAM. Ils fournissaient en sortie une adresse multiplexée sur 6 bits, envoyée en deux fois. Ils avaient donc 12 entrées d'adresse, 6 sorties d'adresse, un signal RAS, un signal CAS. Les adresses présentées en entrées n'étaient pas mémorisées dans des registres, ce qui fait qu'elles devaient être maintenues durant toute la durée de l'accès mémoire. Le processeur ne pouvait donc pas se déconnecter du bus d'adresse pendant l'accès mémoire, peu importe sa durée. Le 8202 contenait aussi un compteur de rafraichissement. Rappelons que sur les DRAM asynchrones, le rafraichissement se fait ligne par ligne. Le contrôleur mémoire a juste à présenter l'adresse de ligne, il n'a pas à envoyer l'adresse de colonne. La commande de rafraichissement se fait en mettant le signal RAS à 0, mais en laissant le CAS à 1 (je rappelle que les signaux sont actifs à 0). Le compteur d'adresse de rafraichissement a donc juste à mémoriser l'adresse de ligne. Le séquenceur mémoire était précédé par un circuit d'arbitrage, non-représenté dans le schéma ci-dessous. La microarchitecture de l'Intel 8202 est la suivante : [[File:Microarchitecture de l'Intel 8202.png|centre|vignette|upright=2|Microarchitecture de l'Intel 8202.]] Le 8202 avait une entrée pour un signal d'horloge, ainsi qu'un ''Chip Select'' un peu particulier. Si le signal CS passait à 0 lors d'un accès mémoire, le 8202/8203 ne se désactivait qu'une fois l'accès mémoire terminé. On ne pouvait pas l'interrompre pendant un accès mémoire, même en changeant le bit CS. Le signal d'horloge était utilisé pour commander le ''refresh timer''. Pour commander les lectures et écriture, il recevait en entrée un bit ''Write Request'' et un bit ''Read Request'', qui demandent respectivement une écriture et une lecture. En sortie, on trouvait un unique bit R/W qui valait 0 pour une lecture et 1 pour une écriture. Il avait aussi un bit d'entrée pour forcer le rafraichissement mémoire. S'il est à 1, la mémoire rafraichie l'adresse envoyée par le processeur. Le 8202 pouvait être connecté sur 1 à 4 chips mémoire, ce qui permettait d'adresser au maximum 16 kibioctets de RAM. Les 4 chips ne sont pas accédés en parallèle, un seul l'est à chaque fois. Pour le décodage d'adresse, le 8202 dispose de deux bits BO et B1 pour sélectionner le chip adéquat, ainsi que 4 sorties RAS pour activer la banque adéquate. On rappelle que le signal RAS remplace le signal ''Chip Select''. C'est le séquenceur mémoire qui se charge de générer ces signaux RAS, à partir des deux bits B0 et B1 (qui sont techniquement des bits d'adresse). Pour communiquer avec le processeur, l'Intel 8202 disposait de deux bits XACK et SACK. SACK indiquait au processeur que le 8202/8203 est en train de faire un accès mémoire et qu'il est indisponible pour un second accès mémoire. Cela permet de bloquer le processeur tant que le 8202 est indisponible. Le signal XACK indique que l'accès mémoire précédent est terminé et que : soit la donnée lue est présente sur le bus de données, soit que l'écriture s'est terminée. : Le signal SACK est très utile sur les configurations multiprocesseurs. Un processeur peut démarrer un accès mémoire, le signal SACK indiquera au second processeur qu'il doit attendre que l'accès soit terminé pour que ce soit son tour. ==Les contrôleurs de DRAM asynchrones évolués== L'Intel 8202 était un contrôleur mémoire basique, comme beaucoup d'autres à cette époque. Mais Intel a vendu des contrôleurs mémoires plus complexes. Par exemple, l'Intel 8207 était un contrôleur mémoire bien plus avancé que les deux précédents. Passons sur certains détails, comme le fait qu'il pouvait gérer jusqu'à 256 kibioctets de DRAM. Au-delà de ça, il y avait des fonctionnalités bien plus intéressantes, à savoir : un support de l'ECC, il était double port, il permettait de simuler une DRAM synchrone à partir d'une DRAM asynchrone. Il n'était pas le seul dans ce cas et des contrôleurs de DRAM très évolués étaient capables de faire des merveilles. Voyons comment cela était possible. ===Les contrôleurs mémoire synchrone=== Il est parfaitement possible d'utiliser un contrôleur mémoire synchrone avec une DRAM asynchrone. A vrai dire, le contrôleur DRAM peut complétement simuler une mémoire synchrone alors que la DRAM associée est asynchrone. La traduction asynchrone vers synchrone se fait en ajoutant des registres sur le bus mémoire, notamment sur le bus de données et le bus d'adresse/commande. Nous avions détaillé cela dans le chapitre sur les SRAM, c'est la même chose avec une mémoire DRAM. Sauf que cette fois-ci, le contrôleur mémoire doit aussi être prévu pour. [[File:Controleur mémoire synchrone utilisé avec une DRAM asynchrone.png|centre|vignette|upright=2|Contrôleur mémoire synchrone utilisé avec une DRAM asynchrone]] Les deux-trois registres illustrés plus haut peuvent être intégrés directement dans le contrôleur mémoire, que ce soit totalement ou en partie. Le strict minimum pour avoir un contrôleur mémoire synchrone est que celui-ci doit mémoriser adresse et commandes dans un registre. Par exemple, le 8207 d'Intel était capable de mémoriser les requêtes processeurs dans un registre interne, mais il fallait utiliser deux registres séparés pour le bus de données. Les deux registres étaient alors commandés par le contrôleur mémoire. Il est cependant possible d'aller plus loin et d'intégrer les registres du bus de données dans le contrôleur mémoire. [[File:Controleur mémoire DRAM synchrone.png|centre|vignette|upright=2|Contrôleur mémoire DRAM synchrone.]] : Il faut noter que cette fonctionnalité est parfois disponible sur les SRAM. En clair, on peut associer une SRAM asynchrone avec un contrôleur de SRAM synchrone. Le contrôleur de SRAM se charge alors de simuler une SRAM synchrone à partir de la SRAM asynchrone. Utiliser un contrôleur mémoire synchrone a de nombreux avantages, l'un d'entre eux étant lié aux ''wait state''. Quand le processeur envoie une requête de lecture/écriture à la mémoire RAM, celle-ci met plusieurs cycles d'horloge à répondre. Et pendant ce temps, le processeur... attend. Et pendant ce temps d'attente, il doit maintenir l'adresse mémoire sur le bus d'adresse. Les cycles d'horloge perdus à attendre la mémoire RAM étaient appelés des '''''Wait states'''''. Utiliser un contrôleur mémoire synchrone d'éliminer les ''wait state'', dans une certaine mesure. Avec un contrôleur mémoire synchrone, le processeur envoie l'adresse, mais c'est le contrôleur mémoire qui la maintient sur le bus d'adresse. Le processeur peut envoyer l'adresse et la donnée à écrire, elles sont recopiées dans les registres, et le controleur mémoire y a accès sans que le processeur doive les maintenir. Le processeur peut se déconnecter du bus mémoire et faire du travail dans son coin pendant que le contrôleur mémoire accède à la DRAM. Les ''wait state'' disparaissent alors, du moins du point de vue du processeur. ===La gestion de l'ECC=== L''''ECC''' peut être géré dans le contrôleur mémoire. Pour cela, on couple les registres mentionnés dans la section précédente, avec un circuit de détection et de correction d'erreur. Le circuit d'ECC peut, comme les registres synchrones, être intégré dans le contrôleur mémoire, ou au contraire être situé dans un circuit séparé. Si le circuit d'ECC est séparé du contrôleur mémoire, il communique avec lui, histoire que le contrôleur mémoire puisse signaler toute erreur de parité ou d'ECC au processeur. [[File:Controleur mémoire synchrone avec ECC intégré.png|centre|vignette|upright=2|Controleur mémoire synchrone avec ECC intégré]] Reprenons l'exemple du 8207 d'Intel. Le contrôleur mémoire 8207 gère le bus d'adresse et de commande, mais n'a pas de connexions directes avec le bus de données. Il ne peut donc pas prendre en charge l'ECC. Il avait besoin d'être couplé avec un circuit d'ECC séparé, relié au bus de données : l'Intel 8206. Le 8026 prenait en entrée : 16 bits de données et 8 bits d'ECC. Il fournissait en sortie 16 bits de données après correction d'erreur, les 8 bits d'ECC pour indiquer qu'une erreur a été détectée mais pas corrigée, ainsi que des bits de parité. Le 8206 détectait/corrigeait les erreurs et générait les bits d'ECC, mais il communiquait avec le contrôleur mémoire pour cela. [[File:8207 avec ECC.png|centre|vignette|upright=2|8207 avec ECC]] La détection/correction d'erreur était appliquée à la fois pour les accès mémoire et pour les rafraichissements mémoire. Lors d'un rafraichissement mémoire, la donnée rafraichie est lue et réécrite. Avec l'ECC activé et configuré correctement, le rafraichissement passe par le bus de données. Au lieu d'avoir un cycle de lecture-écriture interne à la DRAM, on a un cycle de lecture-correction-écriture qui utilise le 8206. La donnée lue est envoyée sur le bus de données, puis le 8206 corrige une éventuelle erreur, et la donnée corrigée est alors réécrite en mémoire. Au passage, si une erreur non-correctible est détectée, le 8206 ne fait rien, l'erreur est ignorée. La gestion de l'erreur sera retardée jusqu'à une lecture ultérieure. Et encore : si lecture ultérieure il y a. Si la donnée est écrasée par une écriture, la donnée corrompue sera simplement écrasée et disparaitra sans avoir pu faire le moindre dégât. Mais pour cela, le 8206 doit communiquer avec le contrôleur mémoire, pour savoir s'il est dans un cycle de rafraichissement ou un accès mémoire normal. Il prévient le 8207 lors d'une erreur, et c'est ce dernier qui décide si l'erreur doit être prise en compte ou ignorée. C'est seulement lors d'un accès mémoire normal que le processeur est prévenu qu'une erreur de parité/autre a eu lieu. ===Les contrôleurs mémoires multiports=== Les '''contrôleur mémoire multiport''' disposent de plusieurs ports, chacun permettant de traiter un accès mémoire. Ils peuvent simuler une mémoire multiport à partir d'une DRAM monoport. Évidemment, la simulation n'est pas parfaite. Des accès mémoire simultanés, envoyés en même temps sur différents ports, sont en réalité exécutés un par un, pas en même temps. Il y a donc une petite pénalité en termes de performances, mais elle est mineure. Encore une fois, nous allons reprendre l'exemple du 8207. Il avait deux ports séparés, et était prévu pour fonctionner dans un système à deux processeurs. L'usage de deux ports séparés permettait de partager une unique mémoire DRAM entre deux processeurs. Le partage se faisait en interfaçant deux processeurs sur le contrôleur mémoire, chacun étant connecté à un port. Lors d'une lecture, il redirigeait la donnée lue vers le bon processeur, en configurant le bus de données correctement. Le contrôleur mémoire recevait des requêtes mémoire de deux processeurs, mais il les exécutait une à la fois. S'il recevait deux requêtes en même temps, l'une d'entre elles était mise en attente. Le contrôleur mémoire doit arbitrer les accès à la mémoire, et faire en sorte que les deux processeurs aient accès à la mémoire à tour de rôle. Et non seulement il doit arbitrer les deux ports, mais il y a aussi un troisième port interne au contrôleur mémoire : le rafraichissement mémoire ! Pour cela, le circuit d'arbitrage qui choisissait entre rafraichissement mémoire et accès mémoire, est amélioré de manière à gérer un second port. Le circuit d'arbitrage donne l'accès au séquenceur mémoire à un port sélectionné. L'arbitrage était configurable, avec deux options : soit le port A est privilégié sur le port B, soit le port le plus récemment accédé à la priorité. Les deux ports pouvaient être configurés pour fonctionner soit de manière asynchrone, soit de manière synchrone. Il était aussi possible de configurer l'ECC, des options liées à la fréquence du processeur et de la RAM, ainsi que de nombreuses options liées au rafraichissement. Pour cela, le 8207 contenait un registre de configuration interne, programmable en fournissant les entrées adéquates. Tout ce qui vient d'être dit se généralise avec plus de deux processeurs. Le 8207 ne permettait pas ça, mais les contrôleurs mémoire des PC modernes en sont capables. Ils peuvent gérer plusieurs dizaines de processeurs facilement. ==Le contrôleur mémoire d'une DRAM ''Fast Page Mode''== Les mémoires DRAM classiques sont des mémoires à tampon de ligne, mais qui sont assez mal utilisées. Notamment, tout accès mémoire se fait en deux phases : un accès pour sélectionner la ligne, un autre pour sélectionner la colonne. Les mémoires ''Fast Page Mode'' permettent d'optimiser le tout. Elles permettent de faire plusieurs accès successifs à la même ligne, à des colonnes différentes. Et le contrôleur mémoire doit être adapté pour cela. [[File:Sélection d'une ligne sur une mémoire FPM ou EDO.png|centre|vignette|upright=2|Sélection d'une ligne sur une mémoire FPM ou EDO.]] Le contrôleur mémoire doit détecter que deux accès mémoire consécutifs se font dans la même ligne. Si deux accès consécutifs accèdent à la même ligne, on doit juste changer de colonne et altérer le signal CAS. C'est un ''succès de tampon de ligne'', aussi appelé un '''succès de page'''. Si deux accès consécutifs accèdent à une ligne différente, c'est un ''défaut de tampon de ligne'', aussi appelé un '''défaut de page'''. Il faut alors changer de ligne, en altérant les signaux RAS et en envoyant une adresse de ligne. Pour détecter les succès ou défauts de page, il faut ajouter un circuit spécialisé dans le contrôleur mémoire. Il mémorise la ligne ouverte, et plus précisément son adresse de ligne (numéro de banque inclut). A chaque requête processeur, il compare l'adresse de ligne recue avec celle déjà ouverte. C'est un succès si les deux sont égales, un défaut si elles sont différentes. Le circuit qui fait cette comparaison est appelé le '''décodeur de commande'''. Il prévient le séquenceur mémoire en cas de succès de page, grâce à un signal de un bit, qui vaut 0 en cas de défaut de page et 1 en cas de succès. Le séquenceur mémoire décide alors comment gérer les signaux RAS et CAS, ainsi que l'envoi des adresses de ligne/colonne. [[File:Controleur mémoire d'une FPM-DRAM.png|centre|vignette|upright=2|Controleur mémoire d'une FPM-DRAM]] ==Le contrôleur mémoire d'une SDRAM ou d'une DDR== Peu de choses sont connues sur les contrôleurs de SDRAM/DDR modernes, les fabricants ne donnant que peu de détails dessus. Les rares simulateurs qui tentent de décrire leur fonctionnement, comme DRAM SIM I et II, sont particulièrement simples et ne vont pas dans le détail. Néanmoins, le peu qu'on sait est tout de même instructif. ===L'interface physique des SDRAM et son impact sur le contrôleur SDRAM=== Contrairement aux mémoires DRAM basiques, les mémoires SDRAM sont cadencées par un signal d'horloge. Et ce signal d'horloge vient bien de quelque part. Pour cela, deux solutions : soit le contrôleur mémoire génère la fréquence qui commande la mémoire, soit il prend en entrée une fréquence de base qu'il multiplie pour obtenir la fréquence désirée. Les deux solutions sont équivalentes, si ce n'est que les circuits impliqués ne sont pas les mêmes. Dans le premier cas, le contrôleur doit embarquer un circuit oscillateur, qui génère la fréquence demandée. Dans l'autre cas, un simple multiplieur/diviseur de fréquence suffit et c'est généralement une PLL qui est utilisée pour cela. : Notez qu'il ne faut pas confondre la fréquence de la SDRAM et celle du contrôleur mémoire. Le contrôleur mémoire fonctionne à une vitesse assez élevée, en interne. Le port relié au processeur fonctionne à haute fréquence, généralement la même que celle du processeur. A vrai dire, de nos jours, il est intégré dans le processeur. Pour le décodage d'adresse, tout est plus simple sur les SDRAM/DDR. Les chips de mémoire SDRAM et DDR disposent d'une entrée ''Chip Select'', ce qui facilite grandement le décodage d'adresse. Les bits de ''Chip Select'' sont générés par le contrôleur mémoire, et sont transmis sur le bus de commande. Le contrôleur de DRAM peut adresser un certain nombre de rangées, dispersés sur une ou plusieurs barrettes. La limite maximale dépend du contrôleur de DRAM, elle est souvent proche de 8 ou 16 rangées. Si on combine plusieurs barrettes de mémoire, il est possible de dépasser cette limite. Par exemple, pour un contrôleur de DRAM supportant maximum 8 rangées, 4 barrettes de 4 rangées chacune dépassent la limite. ===Le séquenceurs mémoire pour les SDRAM/DDR=== Le séquenceur mémoire existe toujours pour les mémoires SDRAM, c'est toujours un circuit séquentiel qui implémente une machine à état. Il traduit toujours une requête processeur en une séquence de commandes envoyées à des timings bien précis. Les commandes mémoires peuvent provenir de l'extérieur, mais aussi d'un circuit de rafraichissement intégré dans le contrôleur mémoire, comme pour les autres DRAM. La seule différence est que la machine à état est plus complexe. Pour rappel, une requête de lecture/écriture se fait en trois étapes maximum : une commande PRECHARGE pour précharger le tampon de ligne, une commande ACT qui fixe l'adresse de ligne, et enfin une commande READ/WRITE avec l'adresse de colonne. Et ces commandes sont séparées par des '''délais mémoire''' bien précis. Par exemple, je prends des chiffres arbitraires : il faut attendre 2 cycles entre une commande ACT et une commande READ, 6 cycles avant deux commandes WRITE consécutives, etc. La gestion des délais mémoire rend la conception du séquenceur plus complexe. Il faut aussi tenir compte des commandes SDRAM anticipées, à savoir que l'on peut envoyer des commandes avant que la précédente soit terminée. Les commandes anticipées sont idéales dans le cas où des accès successifs se font dans des banques différentes. Pour les exploiter au mieux, le contrôleur mémoire doit donc détecter si des accès successifs se font dans des banques différentes, ou dans la même banque, pour décider d'envoyer des commandes anticipées ou non, mais aussi pour gérer les succès/défauts de cache. Cette '''détection des conflits de banque''' complexifie le séquenceur. ===La détection des succès/défauts de page=== Un point important est que dans certaines conditions, certaines commandes peuvent être omises. Par exemple, en cas de succès de page, les commandes PRECHARGE et ACT ne doivent pas être envoyées, seules les commandes READ/WRITE le sont. Le contrôleur doit toujours '''détecter les succès et défauts de page''' et agir en fonction. La solution utilisée est la même que pour les mémoires FPM : il faut mémoriser quelle ligne est ouverte ou fermée. La différence avec les FPM est qu'il faut faire cela pour chaque banque mémoire ! En effet, chaque banque a son propre tampon de ligne, ce qui fait que la gestion des lignes se fait indépendamment dans chaque banque. Le séquenceur mémoire doit donc se souvenir des lignes actives dans chaque banque. Pour cela, il mémorise ces lignes dans une petite mémoire : la '''table des banques''', aussi appelée ''bank status memory''. Pour détecter un succès ou un défaut, le contrôleur doit extraire la ligne de l'adresse, mais aussi le numéro de banque. Il envoie alors le numéro de banque à la table des banques, sur son entrée d'adresse. Il récupère alors le numéro de la ligne active sur les sorties de données. Il compare alors ce numéro de ligne avec le numéro de ligne de l'adresse envoyée par le processeur. C'est un succès si les deux sont égales, un défaut sinon. [[File:Controleur mémoire FPM avec plusieurs banques.jpg|centre|vignette|upright=2|Contrôleur mémoire FPM avec plusieurs banques.]] ===La politique de gestion du tampon de ligne=== Pour ce qui est des succès/défauts de page, le séquenceur mémoire peut fonctionner de plusieurs manières, dont les plus extrêmes sont appelés la politique de la page fermée et la politique de la page ouverte. Voyons à quoi elles correspondent. Avec la '''politique de la page fermée''', chaque accès mémoire est suivi d'une commande PRECHARGE, qui ferme la ligne courante et précharge les lignes de bits. Même si deux accès consécutifs se font dans la même ligne, la ligne est fermée et ré-ouverte entre deux accès mémoire. En clair : l'optimisation introduite par les mémoires FPM est désactivée, le contrôleur mémoire fait exprès de ne pas en profiter. On appelle cette méthode la close ''page autoprecharge''. Cette méthode réduit grandement les performances pour les accès à des adresses consécutives, mais fonctionne à merveille si les accès sont "aléatoires", à savoir qu'ils se font sans régularités évidentes. De tels accès sont dispersés en mémoire, ce qui fait qu'il est rare qu'ils accèdent deux fois de suite à la même ligne. Un autre avantage est que l'implémentation du séquenceur mémoire est très simple. En effet, le séquenceur mémoire se passe complétement de la table des banques, du comparateur de ligne, et de tous les circuits nécessaires pour vérifier les succès ou défauts de page. De plus, le séquenceur mémoire profite grandement des commandes READA et WRITEA, qui fusionnent une commande READ/WRITE avec une commande PRECHARGE. Le séquenceur mémoire a juste à envoyer des commandes ACT, READA, WRITEA et PREFETCH à la mémoire, pas besoin des commandes PRECHARGE, READ ou WRITE. À l'opposé, la '''politique de la page ouverte''' ne ferme pas automatiquement la ligne. Elle la laisse ouverte, en espérant que le prochain accès mémoire se fasse dans cette ligne. Lorsqu'un nouvel accès mémoire arrive, elle doit détecter les succès ou défauts de page et agir en fonction. En cas de défaut de page, la ligne est fermée, le séquenceur mémoire envoie une commande PRECHARGE, puis l'accès suivant effectue les deux commandes ACT + READ ou WRITE. En cas de succès de page, les commandes PRECHARGE et ACT ne sont pas envoyées, seules la commande READ ou WRITE l'est. Un désavantage est que le contrôleur mémoire doit inclure une table des banques et un comparateur, comme vu plus haut dans la section sur les mémoires FPM. Un autre défaut est que garder une ligne ouverte consomme beaucoup d'énergie, comparé à un simple état de PRECHARGE. En conséquence, il est préférable de fermer les lignes dès que possible. Par contre, les performances sont d'autant meilleures que les accès mémoire consécutifs à une même ligne soient assez fréquents. Si les accès mémoire sont aléatoires, les performances sont moins bonnes. La politique de la page fermée fermait les lignes en avance, avec des commandes READA ou WRITEA, avant même que l'accès suivant démarre. Avec la politique de la page ouverte, on doit attendre pour détecter un défaut de page, puis fermer la ligne avec une commande PRECHARGE séparée. La ligne est donc fermée avec un peu temps de retard, et envoyer deux commandes au lieu d'une prend plus de temps. : Les lignes ne restent pas ouvertes indéfiniment, elles sont automatiquement fermées lorsqu'elles sont rafraichies. Vu que c'est le contrôleur mémoire qui décide du rafraichissement mémoire, il sait quelles lignes sont rafraichies à quel moment. Et il sait donc quand elles sont fermées. Les contrôleurs mémoires basiques utilisent une des deux solutions précédentes. Soit la page est toujours fermée, soit elle est toujours laissée ouverte jusqu'à ce qu'un accès mémoire la referme. Mais les contrôleurs plus évolués utilisent des '''politiques hybrides''', capables de switcher entre les deux suivant la situation. La méthode la plus simple laisse une ligne ouverte un temps prédéterminé avant de fermer la ligne. Il existe aussi des '''politiques prédictives'''. En clair, il, qui tentent de prédire s'il faut fermer ou laisser ouvertes les pages ouvertes. Elles regardent, pour les N derniers accès, s'ils ont fait un succès ou un défaut de page. Mémoriser les N derniers accès demande d'utiliser un simple registre à décalage de N bits, chaque bit indiquant si le énième accès précédent a été un succès de page ou non. Pour chaque valeur de ce registre, il faut prédire si le prochain accès demandera une ouverture ou une fermeture. Une prédiction simple fait la moyenne des bits à 1 dans ce registre et ferme la page si elle est inférieure à 1/2. Pour améliorer un petit peu l'algorithme, on peut faire en sorte que les bits des accès mémoires les plus récents aient plus de poids dans le calcul de la moyenne. Il existe sans doute d'autres solutions plus évoluées, mais il est difficile de savoir ce qu'il y a dans les contrôleurs de SDRAM modernes. : Le fait de laisser ouverte une ligne ou au contraire de la fermer systématiquement, se fait pour chaque banque. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les mémoires RAM dynamiques (DRAM) | prevText=Les mémoires RAM dynamiques (DRAM) | next=Les mémoires associatives | nextText=Les mémoires associatives }} </noinclude> 4gvhcoq7t880z557jrvabzk5pqu602m 764704 764703 2026-04-23T20:50:37Z Mewtow 31375 /* La politique de gestion du tampon de ligne */ 764704 wikitext text/x-wiki Les mémoires ROM ou SRAM ont généralement une interface simple, à laquelle le processeur peut s'interfacer directement. Mais pour les DRAM, ce n'est pas le cas. Les DRAM utilisent un bus d'adresse multiplexé, où l'adresse est envoyée en deux fois. Connecter le processeur directement sur une DRAM n'est pas pratique : le bus d'adresse du processeur et celui de la mémoire ne collent pas. Les DRAM doivent aussi être rafraichies régulièrement. Le rafraichissement mémoire peut être délégué au processeur, mais c'est loin d'être idéal. Et il y a bien d'autres raisons qui font que le processeur ne peut pas s'interfacer facilement avec les mémoires DRAM. Pour gérer ces problèmes, les mémoires DRAM ne sont pas connectées directement au processeur. À la place, on ajoute un intermédiaire entre le processeur et la mémoire : le '''contrôleur mémoire externe'''. Son but est de montrer au processeur une interface simple, semblable à celle d'une SRAM classique, alors qu'il commande une mémoire DRAM à l'interface plus complexe. Il est placé sur la carte mère ou dans le processeur, et ne doit pas être confondu avec le contrôleur mémoire intégré dans la mémoire. Ce chapitre va expliquer quels sont les rôles du contrôleur mémoire, son interface et ce qu'il y a à l'intérieur. Dans ce chapitre, quand nous parlerons de ''contrôleur mémoire'', cela fera systématiquement référence au contrôleur mémoire externe. Et avant de poursuivre, sachez qu'il est difficile de faire des généralités sur les contrôleurs mémoire, car les mémoires DRAM elles-mêmes sont assez différentes les unes des autres. Entre une mémoire EDO, une mémoire SDR, une mémoire DDR et une DRAM asynchrone, les contrôleurs mémoires seront fortement différents. Aussi, il y a aura une différence entre un contrôleur pour une DRAM asynchrone et un contrôleur pour une mémoire EDO, une mémoire SDRAM, etc. J'ai choisit de vous séparer les contrôleurs mémoire pour les DRAM asynchrones de ceux pour les SDRAM/DRR. ==Le contrôleur d'une DRAM asynchrone== Les premières DRAM asynchrones avaient des contrôleurs mémoires dédiés, qui étaient séparés du processeur et du ''chipset'' de la carte mère. Par exemple, les composants Intel 8202, Intel 8203 et Intel 8207 étaient des contrôleurs mémoire pour DRAM asynchrones qui étaient vendus dans des boitiers DIP et étaient soudés sur la carte mère. Par la suite, ils ont été intégrés au ''chipset'' de la carte mère pendant les décennies 90-2000. Après les années 2000, ils ont été intégrés dans les processeurs. Leurs fonctions étaient le multiplexage de l'adresse et le rafraichissement mémoire. Ils recevaient une adresse mémoire complète, qu'ils découpaient une adresse de ligne et une adresse de colonne, envoyées l'une après l'autre sur le bus mémoire. Pour le rafraichissement mémoire, ils rafraichissaient la DRAM régulièrement, de manière automatique, entre deux accès mémoire normaux. Le processeur n'avait ainsi plus à rafraichir la mémoire lui-même, cette fonction était déléguée au contrôleur de DRAM. Ils étaient connectés au bus d'adresse et de commande, avec éventuellement des relations indirectes avec le bus de données. ===L'interface d'un contrôleur de DRAM asynchrone=== L'interface du contrôleur mémoire décrit ses broches d'entrées/sorties et leur signification. Elle est généralement très simple et contient deux ports : un connecté au processeur, un autre connecté à la DRAM. Cela trahit d'ailleurs son rôle principal, qui est de transformer les requêtes de lecture/écriture provenant du processeur en une suite de commandes acceptée par la mémoire. Le port connecté à la DRAM est connecté ua bus d'adresse et au bus de commande. Le bus de données est lui relié au processeur et/ou au bus système. Un accès mémoire provenant du processeur contient une adresse à lire/écrire, le bit R/W qui indique s'il faut faire une lecture ou une écriture, et éventuellement une donnée à écrire. Mais, nous avons vu que les accès mémoires sur une DRAM sont multiplexés : on envoie l'adresse en deux fois : la ligne d'abord, puis la colonne. De plus, il faut générer les signaux RAS, CAS et bien d'autres. Le tout est illustré ci-dessous. [[File:Contrôleur mémoire.png|centre|vignette|upright=2|Contrôleur mémoire externe.]] Un point important est que les DRAM asynchrones n'ont pas d'entrée ''Chip Select'' ou d'entrée ''Output Enable''. Les signaux RAS et CAS remplacent en quelque sorte ces deux signaux. Le bit RAS fait office de ''Chip Select'', le bit CAS fait office d'''Output Enable''. Pour certains contrôleurs de DRAM, il faut ajouter l''''interface électrique''', qui traduit les signaux du processeur en signaux compatibles avec la mémoire. Il est en effet très fréquent que la mémoire et le processeur n'utilisent pas les mêmes tensions pour coder un bit, ce qui fait qu'elles ne sont pas compatibles. Dans ce cas, le contrôleur mémoire fait la conversion. Le contrôleur DRAM peut fonctionner en mode synchrone ou asynchrone, du point de vue du processeur. Quand il fonctionne en mode synchrone, il permet d'interfacer un processeur synchrone avec une mémoire DRAM asynchrone. Un point important est que le contrôleur DRAM sert d'intermédiaire entre une mémoire DRAM et "le reste du monde". Il ne fait pas forcément office d'intermédiaire entre DRAM et processeur, mais peut aussi faire l'intermédiaire entre la DRAM et un bus système, entre une DRAM et le ''Video Display Controler'' d'une carte graphique, ou n'importe quel autre composant électronique qui utilise cette DRAM. ===Le générateur de ''timings'' et la traduction d'adresse=== Le contrôleur mémoire doit traduire les adresses du processeur en adresses compatibles avec la mémoire. Et la traduction est assez variable, suivant que le bus mémoire est un bus normal, un bus multiplexé, ou partiellement multiplexé. Nous avons vu ces trois types de bus mémoire dans le chapitre sur l'interface des mémoires, mais nous ferons quelques rappels rapides. Avec un ''bus totalement multiplexé'', le bus d'adresse et le bus de données sont fusionnés. Dans ce cas, on peut envoyer soit une adresse, soit lire/écrire une donnée sur le bus, mais on ne peut pas faire les deux en même temps. Un bit ALE indique si le bus est utilisé en tant que bus d'adresse ou bus de données. Le contrôleur mémoire gère cette situation, en fixant le bit ALE et en envoyant séparément adresse et donnée pour les écritures. [[File:Bus multiplexé avec bit ALE.png|centre|vignette|upright=2|Bus multiplexé avec bit ALE.]] Avec un ''bus d'adresse multiplexé'', l'adresse est découpée en une adresse de ligne et une adresse de colonne, envoyées l'une après l'autre. Le contrôleur mémoire prend en entrée une adresse mémoire complète, la découpe en deux, et envoie chaque morceau au bon moment. Pour cela, il suffit d'un registre pour mémoriser l'adresse et d'un multiplexeur. Le multiplexeur choisit soit les bits de poids fort de l'adresse, soit ceux de poids faible. Les premiers correspondent à l'adresse de ligne, les autres à l'adresse de colonne. La commande du multiplexeur est le fait d'un petit circuit séquentiel, qui génère aussi les signaux CAS et RAS. Au premier cycle, il met le signal RAS à 1, met le CAS à 0, et configure le MUX pour sélectionner les bits de poids fort. Au second cycle, il génère un signal CAS à 1, met le RAS à 0 et configure le MUX pour sélectionner les bits de poids faible. Le circuit en question est appelé le générateur de ''timings''. [[File:Controleur de DRAM simple, sans rafraichissement mémoire.png|centre|vignette|upright=2|Contrôleur de DRAM simple, sans rafraichissement mémoire.]] Le générateur de ''timings'' est un circuit séquentiel qui implémente une petite machine à état. Il est très simple sur une mémoire DRAM asynchrone basique, mais il est plus complexe sur les mémoires FPM, EDO, quartet, et autres. Le regroupement des multiplexeurs d'adresse et du générateur de ''timings'' est appelé le '''séquenceur mémoire'''. C'est le séquenceur mémoire qui traduit la requête processeur en commande DRAM, le reste du contrôleur est dédié au rafraichissement mémoire ou à d'autres fonctions facultatives. ===Le rafraichissement mémoire=== La gestion du rafraichissement mémoire est la fonction principale du contrôleur DRAM. Pour gérer le rafraichissement mémoire, le contrôleur mémoire intègre deux compteurs, un pour gérer l'adresse à rafraichir, l'autre pour gérer l'intervalle de temps entre deux rafraichissements. Le rafraichissement se fait à intervalle régulier, tous les x microsecondes. Pour déclencher le rafraichissement au bon moment, le contrôleur mémoire contient un ''Refresh Timer'', aussi appelé le '''compteur de rafraichissement'''. Il est initialisé avec le temps entre deux rafraichissements, une adresse est rafraichie quand ce compteur atteint 0. Le rafraichissement mémoire balaye la mémoire adresse par adresse. Pour savoir à quelle adresse il en est rendu, le contrôleur mémoire utilise un '''compteur d'adresse'''. Il contient la prochaine adresse à rafraichir, aussi appelée l'adresse de rafraichissement. Régulièrement, l'adresse dans ce compteur est envoyée à la RAM, pour une lecture. Mais la donnée lue n'est pas envoyée sur le bus de donnée, soit parce que la RAM est prévue pour, soit parce que le contrôleur désactive son bit ''output enable''. Dans le second cas, la RAM fait la lecture en interne, mais se déconnecte du bus de donnée, perdant la donnée lue dans le néant. Pour envoyer l'adresse de rafraichissement sur le bus d'adresse, il faut rajouter un multiplexeur, qui choisit entre l'adresse normale et l'adresse de rafraichissement. [[File:Controleur de DRAM avec rafraichissement mémoire.png|centre|vignette|upright=2|Controleur de DRAM avec rafraichissement mémoire.]] Le multiplexeur ne doit cependant pas être configuré si une adresse est déjà en cours de transfert. Pour cela, un circuit d'arbitrage se débrouille pour éviter qu'un accès mémoire soit interrompu par une demande de rafraichissement et inversement. Il peut être inclus dans le séquenceur mémoire ou séparé de celui-ci. [[File:Controleur mémoire, intérieur simplifié.png|centre|vignette|upright=2.5|Contrôleur mémoire, intérieur simplifié.]] Il faut noter que le rafraichissement mémoire peut être délégué non pas au contrôleur mémoire, mais au processeur où à la DRAM elle-même. Quand elle est le fait du processeur, celui-ci incorpore un ''refresh timer'' et un compteur d'adresse. Un exemple est celui du processeur Zilog Z80, qui implémentait des compteurs internes pour gérer le rafraichissement mémoire. On peut considérer que le processeur incorpore alors le contrôleur mémoire, au moins partiellement. Il est aussi possible de déléguer le rafraichissement au logiciel (certains jeux vidéos Amiga ou Commodore faisaient cela pour la mémoire vidéo). Quand la DRAM elle-même s'occupe de son propre rafraichissement, c'est elle qui intègre un ''refresh timer'' et le compteur d'adresse. ===Le décodage d'adresse=== Le contrôleur mémoire gère aussi le '''décodage d'adresse'''. pour rappel, celui-ci est utilisé quand on combine plusieurs chips mémoire ensemble, pour combiner leurs capacités mémoire. Par exemple, on peut combiner 4 chips mémoires de 1 mébioctet chacun, pour que le processeur voit comme 4 mébioctets de RAM unique. Le premier mébioctet est placé dans le premier chip mémoire, le second mébioctet dans le second chip, etc. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Pour cela, on active le chip mémoire adéquat, en fonction de l'adresse à consulter. Les autres chips mémoire sont désactivés pendant l'accès mémoire. En théorie, activer ou désactiver un chip mémoire se fait en utilisant son entrée ''Chip Select''. Il faut noter que si les SDRAM disposent bien d'un signal ''Chip Select'', ce n'est pas le cas des mémoires RAM asynchrones. A la place, ce sont les signaux RAS qui font office de ''Chip Select''. Une RAM asynchrone est activée quand son signal RAS lui demande de lire une ligne, elle est désactivée sinon. Mais c'est un détail. Toujours est-il que les signaux ''Chip Select'', ou leurs équivalents, sont générés par le contrôleur de DRAM, à partir des bits de poids fort de l'adresse. Par exemple, avec 4 chips mémoire, les deux bits de poids fort de l'adresse sont utilisés pour sélectionner le chip mémoire adéquat. Le contrôleur mémoire doit avoir plusieurs sorties ''Chip Select'', une par chip mémoire. Et le nombre de ces sorties limite le nombre de chips mémoire qu'on peut combiner. Par exemple, s'il y a seulement 4 sorties ''Chip Select'', on ne pourra brancher que 4 chips mémoire dessus. Sauf à ruser, avec un arrangement horizontal, mais cela n'est pas le ressort du contrôleur mémoire. [[File:Td6bfig3.png|centre|vignette|upright=2|Comparaison entre arrangement horizontal (à gauche) et arrangement vertical (à droite).]] ===Exemple : l'Intel 8202-8203=== L'Intel 8202 et le 8203 étaient des contrôleurs de mémoire DRAM, parmi les plus simples qui soient. Ils avaient une entrée d'adresse de 12 bits, ce qui permettait d'adresser 4 kibioctets de RAM. Ils fournissaient en sortie une adresse multiplexée sur 6 bits, envoyée en deux fois. Ils avaient donc 12 entrées d'adresse, 6 sorties d'adresse, un signal RAS, un signal CAS. Les adresses présentées en entrées n'étaient pas mémorisées dans des registres, ce qui fait qu'elles devaient être maintenues durant toute la durée de l'accès mémoire. Le processeur ne pouvait donc pas se déconnecter du bus d'adresse pendant l'accès mémoire, peu importe sa durée. Le 8202 contenait aussi un compteur de rafraichissement. Rappelons que sur les DRAM asynchrones, le rafraichissement se fait ligne par ligne. Le contrôleur mémoire a juste à présenter l'adresse de ligne, il n'a pas à envoyer l'adresse de colonne. La commande de rafraichissement se fait en mettant le signal RAS à 0, mais en laissant le CAS à 1 (je rappelle que les signaux sont actifs à 0). Le compteur d'adresse de rafraichissement a donc juste à mémoriser l'adresse de ligne. Le séquenceur mémoire était précédé par un circuit d'arbitrage, non-représenté dans le schéma ci-dessous. La microarchitecture de l'Intel 8202 est la suivante : [[File:Microarchitecture de l'Intel 8202.png|centre|vignette|upright=2|Microarchitecture de l'Intel 8202.]] Le 8202 avait une entrée pour un signal d'horloge, ainsi qu'un ''Chip Select'' un peu particulier. Si le signal CS passait à 0 lors d'un accès mémoire, le 8202/8203 ne se désactivait qu'une fois l'accès mémoire terminé. On ne pouvait pas l'interrompre pendant un accès mémoire, même en changeant le bit CS. Le signal d'horloge était utilisé pour commander le ''refresh timer''. Pour commander les lectures et écriture, il recevait en entrée un bit ''Write Request'' et un bit ''Read Request'', qui demandent respectivement une écriture et une lecture. En sortie, on trouvait un unique bit R/W qui valait 0 pour une lecture et 1 pour une écriture. Il avait aussi un bit d'entrée pour forcer le rafraichissement mémoire. S'il est à 1, la mémoire rafraichie l'adresse envoyée par le processeur. Le 8202 pouvait être connecté sur 1 à 4 chips mémoire, ce qui permettait d'adresser au maximum 16 kibioctets de RAM. Les 4 chips ne sont pas accédés en parallèle, un seul l'est à chaque fois. Pour le décodage d'adresse, le 8202 dispose de deux bits BO et B1 pour sélectionner le chip adéquat, ainsi que 4 sorties RAS pour activer la banque adéquate. On rappelle que le signal RAS remplace le signal ''Chip Select''. C'est le séquenceur mémoire qui se charge de générer ces signaux RAS, à partir des deux bits B0 et B1 (qui sont techniquement des bits d'adresse). Pour communiquer avec le processeur, l'Intel 8202 disposait de deux bits XACK et SACK. SACK indiquait au processeur que le 8202/8203 est en train de faire un accès mémoire et qu'il est indisponible pour un second accès mémoire. Cela permet de bloquer le processeur tant que le 8202 est indisponible. Le signal XACK indique que l'accès mémoire précédent est terminé et que : soit la donnée lue est présente sur le bus de données, soit que l'écriture s'est terminée. : Le signal SACK est très utile sur les configurations multiprocesseurs. Un processeur peut démarrer un accès mémoire, le signal SACK indiquera au second processeur qu'il doit attendre que l'accès soit terminé pour que ce soit son tour. ==Les contrôleurs de DRAM asynchrones évolués== L'Intel 8202 était un contrôleur mémoire basique, comme beaucoup d'autres à cette époque. Mais Intel a vendu des contrôleurs mémoires plus complexes. Par exemple, l'Intel 8207 était un contrôleur mémoire bien plus avancé que les deux précédents. Passons sur certains détails, comme le fait qu'il pouvait gérer jusqu'à 256 kibioctets de DRAM. Au-delà de ça, il y avait des fonctionnalités bien plus intéressantes, à savoir : un support de l'ECC, il était double port, il permettait de simuler une DRAM synchrone à partir d'une DRAM asynchrone. Il n'était pas le seul dans ce cas et des contrôleurs de DRAM très évolués étaient capables de faire des merveilles. Voyons comment cela était possible. ===Les contrôleurs mémoire synchrone=== Il est parfaitement possible d'utiliser un contrôleur mémoire synchrone avec une DRAM asynchrone. A vrai dire, le contrôleur DRAM peut complétement simuler une mémoire synchrone alors que la DRAM associée est asynchrone. La traduction asynchrone vers synchrone se fait en ajoutant des registres sur le bus mémoire, notamment sur le bus de données et le bus d'adresse/commande. Nous avions détaillé cela dans le chapitre sur les SRAM, c'est la même chose avec une mémoire DRAM. Sauf que cette fois-ci, le contrôleur mémoire doit aussi être prévu pour. [[File:Controleur mémoire synchrone utilisé avec une DRAM asynchrone.png|centre|vignette|upright=2|Contrôleur mémoire synchrone utilisé avec une DRAM asynchrone]] Les deux-trois registres illustrés plus haut peuvent être intégrés directement dans le contrôleur mémoire, que ce soit totalement ou en partie. Le strict minimum pour avoir un contrôleur mémoire synchrone est que celui-ci doit mémoriser adresse et commandes dans un registre. Par exemple, le 8207 d'Intel était capable de mémoriser les requêtes processeurs dans un registre interne, mais il fallait utiliser deux registres séparés pour le bus de données. Les deux registres étaient alors commandés par le contrôleur mémoire. Il est cependant possible d'aller plus loin et d'intégrer les registres du bus de données dans le contrôleur mémoire. [[File:Controleur mémoire DRAM synchrone.png|centre|vignette|upright=2|Contrôleur mémoire DRAM synchrone.]] : Il faut noter que cette fonctionnalité est parfois disponible sur les SRAM. En clair, on peut associer une SRAM asynchrone avec un contrôleur de SRAM synchrone. Le contrôleur de SRAM se charge alors de simuler une SRAM synchrone à partir de la SRAM asynchrone. Utiliser un contrôleur mémoire synchrone a de nombreux avantages, l'un d'entre eux étant lié aux ''wait state''. Quand le processeur envoie une requête de lecture/écriture à la mémoire RAM, celle-ci met plusieurs cycles d'horloge à répondre. Et pendant ce temps, le processeur... attend. Et pendant ce temps d'attente, il doit maintenir l'adresse mémoire sur le bus d'adresse. Les cycles d'horloge perdus à attendre la mémoire RAM étaient appelés des '''''Wait states'''''. Utiliser un contrôleur mémoire synchrone d'éliminer les ''wait state'', dans une certaine mesure. Avec un contrôleur mémoire synchrone, le processeur envoie l'adresse, mais c'est le contrôleur mémoire qui la maintient sur le bus d'adresse. Le processeur peut envoyer l'adresse et la donnée à écrire, elles sont recopiées dans les registres, et le controleur mémoire y a accès sans que le processeur doive les maintenir. Le processeur peut se déconnecter du bus mémoire et faire du travail dans son coin pendant que le contrôleur mémoire accède à la DRAM. Les ''wait state'' disparaissent alors, du moins du point de vue du processeur. ===La gestion de l'ECC=== L''''ECC''' peut être géré dans le contrôleur mémoire. Pour cela, on couple les registres mentionnés dans la section précédente, avec un circuit de détection et de correction d'erreur. Le circuit d'ECC peut, comme les registres synchrones, être intégré dans le contrôleur mémoire, ou au contraire être situé dans un circuit séparé. Si le circuit d'ECC est séparé du contrôleur mémoire, il communique avec lui, histoire que le contrôleur mémoire puisse signaler toute erreur de parité ou d'ECC au processeur. [[File:Controleur mémoire synchrone avec ECC intégré.png|centre|vignette|upright=2|Controleur mémoire synchrone avec ECC intégré]] Reprenons l'exemple du 8207 d'Intel. Le contrôleur mémoire 8207 gère le bus d'adresse et de commande, mais n'a pas de connexions directes avec le bus de données. Il ne peut donc pas prendre en charge l'ECC. Il avait besoin d'être couplé avec un circuit d'ECC séparé, relié au bus de données : l'Intel 8206. Le 8026 prenait en entrée : 16 bits de données et 8 bits d'ECC. Il fournissait en sortie 16 bits de données après correction d'erreur, les 8 bits d'ECC pour indiquer qu'une erreur a été détectée mais pas corrigée, ainsi que des bits de parité. Le 8206 détectait/corrigeait les erreurs et générait les bits d'ECC, mais il communiquait avec le contrôleur mémoire pour cela. [[File:8207 avec ECC.png|centre|vignette|upright=2|8207 avec ECC]] La détection/correction d'erreur était appliquée à la fois pour les accès mémoire et pour les rafraichissements mémoire. Lors d'un rafraichissement mémoire, la donnée rafraichie est lue et réécrite. Avec l'ECC activé et configuré correctement, le rafraichissement passe par le bus de données. Au lieu d'avoir un cycle de lecture-écriture interne à la DRAM, on a un cycle de lecture-correction-écriture qui utilise le 8206. La donnée lue est envoyée sur le bus de données, puis le 8206 corrige une éventuelle erreur, et la donnée corrigée est alors réécrite en mémoire. Au passage, si une erreur non-correctible est détectée, le 8206 ne fait rien, l'erreur est ignorée. La gestion de l'erreur sera retardée jusqu'à une lecture ultérieure. Et encore : si lecture ultérieure il y a. Si la donnée est écrasée par une écriture, la donnée corrompue sera simplement écrasée et disparaitra sans avoir pu faire le moindre dégât. Mais pour cela, le 8206 doit communiquer avec le contrôleur mémoire, pour savoir s'il est dans un cycle de rafraichissement ou un accès mémoire normal. Il prévient le 8207 lors d'une erreur, et c'est ce dernier qui décide si l'erreur doit être prise en compte ou ignorée. C'est seulement lors d'un accès mémoire normal que le processeur est prévenu qu'une erreur de parité/autre a eu lieu. ===Les contrôleurs mémoires multiports=== Les '''contrôleur mémoire multiport''' disposent de plusieurs ports, chacun permettant de traiter un accès mémoire. Ils peuvent simuler une mémoire multiport à partir d'une DRAM monoport. Évidemment, la simulation n'est pas parfaite. Des accès mémoire simultanés, envoyés en même temps sur différents ports, sont en réalité exécutés un par un, pas en même temps. Il y a donc une petite pénalité en termes de performances, mais elle est mineure. Encore une fois, nous allons reprendre l'exemple du 8207. Il avait deux ports séparés, et était prévu pour fonctionner dans un système à deux processeurs. L'usage de deux ports séparés permettait de partager une unique mémoire DRAM entre deux processeurs. Le partage se faisait en interfaçant deux processeurs sur le contrôleur mémoire, chacun étant connecté à un port. Lors d'une lecture, il redirigeait la donnée lue vers le bon processeur, en configurant le bus de données correctement. Le contrôleur mémoire recevait des requêtes mémoire de deux processeurs, mais il les exécutait une à la fois. S'il recevait deux requêtes en même temps, l'une d'entre elles était mise en attente. Le contrôleur mémoire doit arbitrer les accès à la mémoire, et faire en sorte que les deux processeurs aient accès à la mémoire à tour de rôle. Et non seulement il doit arbitrer les deux ports, mais il y a aussi un troisième port interne au contrôleur mémoire : le rafraichissement mémoire ! Pour cela, le circuit d'arbitrage qui choisissait entre rafraichissement mémoire et accès mémoire, est amélioré de manière à gérer un second port. Le circuit d'arbitrage donne l'accès au séquenceur mémoire à un port sélectionné. L'arbitrage était configurable, avec deux options : soit le port A est privilégié sur le port B, soit le port le plus récemment accédé à la priorité. Les deux ports pouvaient être configurés pour fonctionner soit de manière asynchrone, soit de manière synchrone. Il était aussi possible de configurer l'ECC, des options liées à la fréquence du processeur et de la RAM, ainsi que de nombreuses options liées au rafraichissement. Pour cela, le 8207 contenait un registre de configuration interne, programmable en fournissant les entrées adéquates. Tout ce qui vient d'être dit se généralise avec plus de deux processeurs. Le 8207 ne permettait pas ça, mais les contrôleurs mémoire des PC modernes en sont capables. Ils peuvent gérer plusieurs dizaines de processeurs facilement. ==Le contrôleur mémoire d'une DRAM ''Fast Page Mode''== Les mémoires DRAM classiques sont des mémoires à tampon de ligne, mais qui sont assez mal utilisées. Notamment, tout accès mémoire se fait en deux phases : un accès pour sélectionner la ligne, un autre pour sélectionner la colonne. Les mémoires ''Fast Page Mode'' permettent d'optimiser le tout. Elles permettent de faire plusieurs accès successifs à la même ligne, à des colonnes différentes. Et le contrôleur mémoire doit être adapté pour cela. [[File:Sélection d'une ligne sur une mémoire FPM ou EDO.png|centre|vignette|upright=2|Sélection d'une ligne sur une mémoire FPM ou EDO.]] Le contrôleur mémoire doit détecter que deux accès mémoire consécutifs se font dans la même ligne. Si deux accès consécutifs accèdent à la même ligne, on doit juste changer de colonne et altérer le signal CAS. C'est un ''succès de tampon de ligne'', aussi appelé un '''succès de page'''. Si deux accès consécutifs accèdent à une ligne différente, c'est un ''défaut de tampon de ligne'', aussi appelé un '''défaut de page'''. Il faut alors changer de ligne, en altérant les signaux RAS et en envoyant une adresse de ligne. Pour détecter les succès ou défauts de page, il faut ajouter un circuit spécialisé dans le contrôleur mémoire. Il mémorise la ligne ouverte, et plus précisément son adresse de ligne (numéro de banque inclut). A chaque requête processeur, il compare l'adresse de ligne recue avec celle déjà ouverte. C'est un succès si les deux sont égales, un défaut si elles sont différentes. Le circuit qui fait cette comparaison est appelé le '''décodeur de commande'''. Il prévient le séquenceur mémoire en cas de succès de page, grâce à un signal de un bit, qui vaut 0 en cas de défaut de page et 1 en cas de succès. Le séquenceur mémoire décide alors comment gérer les signaux RAS et CAS, ainsi que l'envoi des adresses de ligne/colonne. [[File:Controleur mémoire d'une FPM-DRAM.png|centre|vignette|upright=2|Controleur mémoire d'une FPM-DRAM]] ==Le contrôleur mémoire d'une SDRAM ou d'une DDR== Peu de choses sont connues sur les contrôleurs de SDRAM/DDR modernes, les fabricants ne donnant que peu de détails dessus. Les rares simulateurs qui tentent de décrire leur fonctionnement, comme DRAM SIM I et II, sont particulièrement simples et ne vont pas dans le détail. Néanmoins, le peu qu'on sait est tout de même instructif. ===L'interface physique des SDRAM et son impact sur le contrôleur SDRAM=== Contrairement aux mémoires DRAM basiques, les mémoires SDRAM sont cadencées par un signal d'horloge. Et ce signal d'horloge vient bien de quelque part. Pour cela, deux solutions : soit le contrôleur mémoire génère la fréquence qui commande la mémoire, soit il prend en entrée une fréquence de base qu'il multiplie pour obtenir la fréquence désirée. Les deux solutions sont équivalentes, si ce n'est que les circuits impliqués ne sont pas les mêmes. Dans le premier cas, le contrôleur doit embarquer un circuit oscillateur, qui génère la fréquence demandée. Dans l'autre cas, un simple multiplieur/diviseur de fréquence suffit et c'est généralement une PLL qui est utilisée pour cela. : Notez qu'il ne faut pas confondre la fréquence de la SDRAM et celle du contrôleur mémoire. Le contrôleur mémoire fonctionne à une vitesse assez élevée, en interne. Le port relié au processeur fonctionne à haute fréquence, généralement la même que celle du processeur. A vrai dire, de nos jours, il est intégré dans le processeur. Pour le décodage d'adresse, tout est plus simple sur les SDRAM/DDR. Les chips de mémoire SDRAM et DDR disposent d'une entrée ''Chip Select'', ce qui facilite grandement le décodage d'adresse. Les bits de ''Chip Select'' sont générés par le contrôleur mémoire, et sont transmis sur le bus de commande. Le contrôleur de DRAM peut adresser un certain nombre de rangées, dispersés sur une ou plusieurs barrettes. La limite maximale dépend du contrôleur de DRAM, elle est souvent proche de 8 ou 16 rangées. Si on combine plusieurs barrettes de mémoire, il est possible de dépasser cette limite. Par exemple, pour un contrôleur de DRAM supportant maximum 8 rangées, 4 barrettes de 4 rangées chacune dépassent la limite. ===Le séquenceurs mémoire pour les SDRAM/DDR=== Le séquenceur mémoire existe toujours pour les mémoires SDRAM, c'est toujours un circuit séquentiel qui implémente une machine à état. Il traduit toujours une requête processeur en une séquence de commandes envoyées à des timings bien précis. Les commandes mémoires peuvent provenir de l'extérieur, mais aussi d'un circuit de rafraichissement intégré dans le contrôleur mémoire, comme pour les autres DRAM. La seule différence est que la machine à état est plus complexe. Pour rappel, une requête de lecture/écriture se fait en trois étapes maximum : une commande PRECHARGE pour précharger le tampon de ligne, une commande ACT qui fixe l'adresse de ligne, et enfin une commande READ/WRITE avec l'adresse de colonne. Et ces commandes sont séparées par des '''délais mémoire''' bien précis. Par exemple, je prends des chiffres arbitraires : il faut attendre 2 cycles entre une commande ACT et une commande READ, 6 cycles avant deux commandes WRITE consécutives, etc. La gestion des délais mémoire rend la conception du séquenceur plus complexe. Il faut aussi tenir compte des commandes SDRAM anticipées, à savoir que l'on peut envoyer des commandes avant que la précédente soit terminée. Les commandes anticipées sont idéales dans le cas où des accès successifs se font dans des banques différentes. Pour les exploiter au mieux, le contrôleur mémoire doit donc détecter si des accès successifs se font dans des banques différentes, ou dans la même banque, pour décider d'envoyer des commandes anticipées ou non, mais aussi pour gérer les succès/défauts de cache. Cette '''détection des conflits de banque''' complexifie le séquenceur. ===La détection des succès/défauts de page=== Un point important est que dans certaines conditions, certaines commandes peuvent être omises. Par exemple, en cas de succès de page, les commandes PRECHARGE et ACT ne doivent pas être envoyées, seules les commandes READ/WRITE le sont. Le contrôleur doit toujours '''détecter les succès et défauts de page''' et agir en fonction. La solution utilisée est la même que pour les mémoires FPM : il faut mémoriser quelle ligne est ouverte ou fermée. La différence avec les FPM est qu'il faut faire cela pour chaque banque mémoire ! En effet, chaque banque a son propre tampon de ligne, ce qui fait que la gestion des lignes se fait indépendamment dans chaque banque. Le séquenceur mémoire doit donc se souvenir des lignes actives dans chaque banque. Pour cela, il mémorise ces lignes dans une petite mémoire : la '''table des banques''', aussi appelée ''bank status memory''. Pour détecter un succès ou un défaut, le contrôleur doit extraire la ligne de l'adresse, mais aussi le numéro de banque. Il envoie alors le numéro de banque à la table des banques, sur son entrée d'adresse. Il récupère alors le numéro de la ligne active sur les sorties de données. Il compare alors ce numéro de ligne avec le numéro de ligne de l'adresse envoyée par le processeur. C'est un succès si les deux sont égales, un défaut sinon. [[File:Controleur mémoire FPM avec plusieurs banques.jpg|centre|vignette|upright=2|Contrôleur mémoire FPM avec plusieurs banques.]] ===La politique de gestion du tampon de ligne=== Pour ce qui est des succès/défauts de page, le séquenceur mémoire peut fonctionner de plusieurs manières, dont les plus extrêmes sont appelés la politique de la page fermée et la politique de la page ouverte. Voyons à quoi elles correspondent. Avec la '''politique de la page fermée''', chaque accès mémoire est suivi d'une commande PRECHARGE, qui ferme la ligne courante et précharge les lignes de bits. Même si deux accès consécutifs se font dans la même ligne, la ligne est fermée et ré-ouverte entre deux accès mémoire. En clair : l'optimisation introduite par les mémoires FPM est désactivée, le contrôleur mémoire fait exprès de ne pas en profiter. On appelle cette méthode la close ''page autoprecharge''. Cette méthode réduit grandement les performances pour les accès à des adresses consécutives, mais fonctionne à merveille si les accès sont "aléatoires", à savoir qu'ils se font sans régularités évidentes. Un exemple classique est celui où plusieurs processeurs accèdent à la même mémoire RAM, mais accèdent à des données très éloignées en mémoire (ce n'est pas toujours le cas, mais ça l'est dans cet exemple). Leurs accès sont dispersés en mémoire, ce qui fait qu'il est rare qu'ils accèdent deux fois de suite à la même ligne. Utiliser la politique de la page fermée améliore les performances, dans ce cas précis. Un autre avantage est que l'implémentation du séquenceur mémoire est très simple. En effet, le séquenceur mémoire se passe complétement de la table des banques, du comparateur de ligne, et de tous les circuits nécessaires pour vérifier les succès ou défauts de page. De plus, le séquenceur mémoire profite grandement des commandes READA et WRITEA, qui fusionnent une commande READ/WRITE avec une commande PRECHARGE. Le séquenceur mémoire a juste à envoyer des commandes ACT, READA, WRITEA et PREFETCH à la mémoire, pas besoin des commandes PRECHARGE, READ ou WRITE. Et ce détail simplifie grandement la conception du séquenceur mémoire. À l'opposé, la '''politique de la page ouverte''' ne ferme pas automatiquement la ligne. Elle la laisse ouverte, en espérant que le prochain accès mémoire se fasse dans cette ligne. Lorsqu'un nouvel accès mémoire arrive, elle doit détecter les succès ou défauts de page et agir en fonction. En cas de défaut de page, la ligne est fermée, le séquenceur mémoire envoie une commande PRECHARGE, puis l'accès suivant effectue les deux commandes ACT + READ ou WRITE. En cas de succès de page, les commandes PRECHARGE et ACT ne sont pas envoyées, seules la commande READ ou WRITE l'est. Un désavantage est que le contrôleur mémoire doit inclure une table des banques et un comparateur, comme vu plus haut dans la section sur les mémoires FPM. Un autre défaut est que garder une ligne ouverte consomme beaucoup d'énergie, comparé à un simple état de PRECHARGE. En conséquence, il est préférable de fermer les lignes dès que possible. Par contre, les performances sont d'autant meilleures que les accès mémoire consécutifs à une même ligne soient assez fréquents. Si les accès mémoire sont aléatoires, les performances sont moins bonnes. La politique de la page fermée fermait les lignes en avance, avec des commandes READA ou WRITEA, avant même que l'accès suivant démarre. Avec la politique de la page ouverte, on doit attendre pour détecter un défaut de page, puis fermer la ligne avec une commande PRECHARGE séparée. La ligne est donc fermée avec un peu temps de retard, et envoyer deux commandes au lieu d'une prend plus de temps. : Les lignes ne restent pas ouvertes indéfiniment, elles sont automatiquement fermées lorsqu'elles sont rafraichies. Vu que c'est le contrôleur mémoire qui décide du rafraichissement mémoire, il sait quelles lignes sont rafraichies à quel moment. Et il sait donc quand elles sont fermées. Les contrôleurs mémoires basiques utilisent une des deux solutions précédentes. Soit la page est toujours fermée, soit elle est toujours laissée ouverte jusqu'à ce qu'un accès mémoire la referme. Mais les contrôleurs plus évolués utilisent des '''politiques hybrides''', capables de switcher entre les deux suivant la situation. La méthode la plus simple laisse une ligne ouverte un temps prédéterminé avant de fermer la ligne. Il existe aussi des '''politiques prédictives'''. En clair, il, qui tentent de prédire s'il faut fermer ou laisser ouvertes les pages ouvertes. Elles regardent, pour les N derniers accès, s'ils ont fait un succès ou un défaut de page. Mémoriser les N derniers accès demande d'utiliser un simple registre à décalage de N bits, chaque bit indiquant si le énième accès précédent a été un succès de page ou non. Pour chaque valeur de ce registre, il faut prédire si le prochain accès demandera une ouverture ou une fermeture. Une prédiction simple fait la moyenne des bits à 1 dans ce registre et ferme la page si elle est inférieure à 1/2. Pour améliorer un petit peu l'algorithme, on peut faire en sorte que les bits des accès mémoires les plus récents aient plus de poids dans le calcul de la moyenne. Il existe sans doute d'autres solutions plus évoluées, mais il est difficile de savoir ce qu'il y a dans les contrôleurs de SDRAM modernes. : Le fait de laisser ouverte une ligne ou au contraire de la fermer systématiquement, se fait pour chaque banque. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les mémoires RAM dynamiques (DRAM) | prevText=Les mémoires RAM dynamiques (DRAM) | next=Les mémoires associatives | nextText=Les mémoires associatives }} </noinclude> 24x37sc4j9iu2d0nkl4l27rubau642v Fonctionnement d'un ordinateur/Le préchargement 0 65804 764647 761814 2026-04-23T14:03:15Z Mewtow 31375 /* Les accès par enjambées */ 764647 wikitext text/x-wiki En raison de la localité spatiale, il est avantageux de précharger dans le cache des données/instructions proches de celles chargées il y a peu. Ce '''préchargement''' (en anglais, ''prefetching''), peut être effectué par le programmeur, à la condition que le processeur possède une instruction de préchargement. Mais celui-ci peut aussi être pris en charge directement par le processeur, sans implication du programmeur, ce qui a de nombreux avantages. Pour ce faire, le processeur doit contenir un circuit nommé ''prefetcher'', associé au cache, qui charger l'avance des données/instructions susceptibles d’être utilisées dans un avenir proche. Qui plus est, on peut utiliser à la fois des instructions de préchargement et un ''prefetcher'' intégré au processeur : les deux solutions ne sont pas incompatibles. Il faut savoir que le préchargement ne fonctionne pas tout à fait de la même manière pour les données et les instructions. La raison est que l'organisation des données en mémoire diffère de celle des instructions, du moins pour les structures de données usuelles. Les processeurs modernes disposent de ''prefetchers'' spécialisés pour les instructions, séparés des ''prefetchers'' spécialisés dans les données. Nous allons ici nous concentrer sur les ''prefetchers'' spécialisés dans les données, avec seulement quelques parenthèses sur les ''prefetchers'' d'instructions. Il faut dire que parler du préchargement des instructions demande de parler des algorithmes de prédiction de branchement, ce qui fera l'objet d'un chapitre ultérieur. ==Les problèmes à résoudre pour précharger une donnée/instruction== Précharger des données/instructions demande de faire plusieurs choses. La première est de prédire à l'avance quelles données/instructions seront utilisées dans le futur. Et cette '''prédiction d'adresse''' ne se fait pas de la même manière entre instruction et données. Pour les instructions, cela revient à prédire quelles instructions seront exécutées dans le futur. Vous vous dites qu'il n'y a rien de plus simple : l'exécution des instruction est séquentielle, les instructions se suivent en mémoire, rien de bien compliqué. Mais il faut prendre en compte les branchements qui peuvent envoyer le programme n'importe où en mémoire. Et résoudre le problème des branchements demande de faire ce qui s'appelle de la prédiction de branchements, qu'on verra dans un chapitre dédié. Pour les données, tout dépend de la manière dont les structures de données sont parcourues et tout dépend de la structure de donnée. Les tableaux sont la structure de donnée idéale pour le préchargement, comme on le verra plus tard, mais d'autres techniques fonctionnent pour d'autres structures de données. Le second point est de décider ''quand précharger des données''. Il ne faut pas que le préchargement marche sur les pieds des accès mémoire normaux. Est-ce qu'on précharge les données dès que possible quitte à retarder un accès mémoire normal, où attend-t-on que la mémoire soit libre pour faire le préchargement. Dans le second cas, il faut décider si le préchargement en vaut la peine. Peut-être que la mémoire ne sera libre que trop tard, à un moment où précharger ne fera que gagner quelques cycles, ce qui ne vaut pas le coup comparé aux inconvénients du préchargement. Mais le cas inverse, à savoir un préchargement trop anticipé, peut aussi donner des performances inférieures. Dans le pire des cas, une donnée chargée trop en avance sera évincée du cache avant d'être utilisée. Tout cela fera l'objet d'une section dédiée dans le chapitre. Enfin, il faut prendre en compte que la donnée préchargée va prendre la place d'une autre donnée dans le cache. Il faut que ce remplacement en vaille la peine. Si la donnée préchargée l'est à tord, elle remplacera une donnée potentiellement plus utile. Il faut absolument éviter cette ''pollution de cache''. ==Les algorithmes de préchargement des données== Le préchargement est fortement influencé par la manière dont les données sont représentées en mémoire. Le point principal est si elles sont regroupées ensemble dans un tableau ou si elles sont dispersées, et comment elles sont dispersées. Il existe ainsi des ''prefetchers'' qui marchent assez bien pour certaines structures de données spécialisées et très mal sur d'autres. Les ''prefetchers'' pour les données sont surtout adaptés à l'utilisation de tableaux (des ensembles de données consécutives de même taille et de même type). Ils profitent du fait que ces tableaux sont souvent accédés case par case. Ils marchent un peu moins bien pour les autres structures de données, comme les listes chainées ou les arbres, mais il existe des ''prefetchers'' spécialisés pour ces dernières. ===Les ''Prefetchers'' séquentiels=== Les ''prefetchers'' séquentiels préchargent les données immédiatement consécutives de la donnée venant tout juste d'être lue ou écrite. Ils fonctionnent bien lorsqu'on accède à des données consécutives en mémoire, ce qui arrive souvent lors de parcours de tableaux. Dans le cas le plus simple, on précharge le bloc de mémoire qui suit immédiatement la dernière ligne chargée. L'adresse de ce bloc se calcule en additionnant la longueur d'une ligne de cache à la dernière adresse lue ou écrite. Ce qui peut être fait avec un seul bloc de mémoire peut aussi l'être avec plusieurs. Rien n’empêche de charger non pas un, mais deux ou trois blocs consécutifs dans notre mémoire cache. Mais attention : le nombre de blocs de mémoire chargés dans le cache est fixe. [[File:Préchargement séquentiel.png|centre|vignette|upright=2|Préchargement séquentiel.]] Le préchargement séquentiel ne fonctionne que pour des accès à des adresses consécutives, pour des données avec une bonne localité spatiale. Pour les autres types d'accès, l'utilisation d'un ''prefetcher'' séquentiel est généralement contreproductive. Pour limiter la casse, les ''prefetchers'' évolués sont capables de distinguer les accès séquentiels et les accès problématiques. Cette détection peut se faire de deux façons. Avec la première, le ''prefetcher'' calcule une moyenne du nombre de blocs préchargés qui ont été utiles, à partir des n derniers blocs préchargés. En clair, il calcule le rapport entre le nombre de blocs qu'il a préchargés dans le cache et le nombre de ces blocs qui ont été accédés. Si jamais ce rapport diminue trop, cela signifie que l'on n’a pas affaire à des accès séquentiels : le ''prefetcher'' arrête temporairement de précharger. Autre solution : garder un historique des derniers accès mémoires pour vérifier s'ils accèdent à des adresses consécutives. Le processeur peut décider de désactiver temporairement le préchargement si jamais le nombre de blocs préchargés utilement tombe trop près de zéro. ====Les ''stream buffers''==== L'implémentation du préchargement séquentiel peut se faire de nombreuses façons. Mais la plus simple et la plus commune est celle des '''''stream buffers'''''. Elle utilise une mémoire FIFO séparée du cache, appelée le ''stream buffer'', dans laquelle sont préchargés les blocs. Lors d'un défaut de cache, le bloc lu/écrit par le processeur est chargé depuis la mémoire et placé dans le cache. Les blocs suivants sont eux préchargés dans le ''stream buffer''. Le ''stream buffer'' permet de précharger un certain nombre de blocs, fixe : 2, 3, 4 blocs, rarement plus. Si le processeur accède à un bloc préchargé, celui-ci sera lu directement depuis le ''stream buffer'', pas depuis la mémoire ou le cache. L'usage de ''stream buffer'' est assez simple et n'entraine pas de modifications substantielles du cache, ce qui est un avantage certain. Pas besoin de modifier la politique de remplacement du cache, en cas de préchargement inutile, par exemple. De plus, le processeur peut avoir plusieurs ''stream buffer'', ce qui permet d'effectuer du préchargement pour plusieurs défauts de cache. Un défaut de cache peut remplir un ''stream buffer'', puis un autre défaut en remplir un autre, etc. Les performances sont alors d'autant plus grandes que le nombre et la taille des ''stream buffer'' sont élevés. La toute première technique de ce genre est illustrée ci-dessous. Elle était pensée pour précharger des données dans le cache L1 d'un processeur, que ce soit pour précharger des données de la RAM dans le cache, ou pour précharger des données du cache L2 vers le cache L1. Mais cette technique s'adapte à toute forme de préchargement et à toute hiérarchie mémoire. On peut l'utiliser à tous les niveaux de la hiérarchie mémoire. Une version améliorée précharge les données dans le ''stream buffer'' non pas lors d'un défaut de cache, mais lors de chaque accès mémoire : tout accès à une donnée charge la donnée suivante dans le ''stream buffer''. Mais nous en reparlerons plus bas, dans la section sur les évènements qui déclenchent le préchargement. [[File:CachePrefetching StreamBuffers.png|centre|vignette|upright=2|Technique des ''stream buffers''.]] ====L’utilisation du préchargement séquentiel pour les instructions==== Le préchargement séquentiel est assez adapté au préchargement des instructions d'un programme, vu que ses instructions sont placées les unes après les autres en mémoire. Notons que par préchargement des instructions, on veut parler du chargement des instructions dans le cache d'instruction L1, à partir du cache L2. Les niveaux de cache en-dessous n'utilisent pas vraiment de préchargement d'instruction. Le préchargement séquentiel des instructions n'est pas une technologie nouvelle. Dès les années 60, des utilisations commerciales existaient. Par exemple, l'ordinateur IBM System/360 Model 91 gérait le préchargement séquentiel des instructions. Le seul bémol est qu'un ''prefetcher'' purement séquentiel gère mal les branchements. Le branchement envoie le programme à une adresse qui peut être n'importe où dans le programme, pas forcément dans la ligne de cache suivante. Les techniques pour résoudre ce problème sont assez complexes, sans compter qu'elles demandent d'utiliser de la prédiction de branchement, chose que nous n'avons pas encore abordé dans ce cours. [[File:Branchements et préchargement séquentiel.png|centre|vignette|upright=2|Branchements et préchargement séquentiel.]] ===Les accès par enjambées=== Les '''accès par enjambées''' (ou ''stride access'') se font sur des données séparées par une distance constante k. Ils surviennent quand un programmeur utilise certaines structures de données, à savoir : des tableaux de structures/objets, des tableaux multidimensionnels. Un cas classique est celui d'une matrice, à savoir un tableau de nombres rectangulaire avec N lignes et M colonnes. Les matrices sont souvent stockées dans un tableau unique, où les nombres sont mémorisés ligne par ligne. Les accès qui se font ligne par ligne ne font que parcourir le tableau en passant d'une case à la suivante. Cependant, les parcours qui se font colonne par colonne, font de grandes enjabées dans la mémoire RAM. Avec ce genre d'accès, un ''prefetcher'' séquentiel charge des données inutiles, ce qui est synonyme de baisse de performances. Mais certains ''prefetchers'' gèrent de tels accès à la perfection. Cela ne rend pas ces accès aussi rapides que des accès à des blocs de mémoire consécutifs, vu qu'on gâche une ligne de cache pour n'en utiliser qu'une petite portion, mais cela aide tout de même beaucoup. [[File:Accès par enjambées.png|centre|vignette|upright=2|Accès par enjambées.]] Ces prefetchers conservent un historique des accès mémoires effectués récemment dans un cache : la '''table de prédiction de références''' (''reference prediction table''). Chaque ligne de cache associe à une instruction toutes les informations nécessaires pour prédire quelle sera la prochaine adresse utilisée par celle-ci. Elle stocke notamment la dernière adresse lue/écrite par l'instruction, l’enjambée, ainsi que des bits qui indiquent la validité de ces informations. L'adresse de l'instruction est dans le tag de la ligne de cache. Pour prédire la prochaine adresse, il suffit d'ajouter la longueur d’enjambée à l'adresse à lire ou écrire. Cette technique peut être adaptée pour précharger non seulement la prochaine adresse, mais aussi les n adresses suivantes, la énième adresse ayant pour valeur : adresse + n × enjambée. Évidemment, ces adresses à précharger ne peuvent pas être lues ou écrites simultanément depuis la mémoire. On doit donc les mettre en attente, le temps que la mémoire soit libre. Pour cela, on utilise un tampon de préchargement, qui stocke des requêtes de lecture ou d'écriture fournies par l'unité de préchargement. L'algorithme de gestion des enjambées doit détecter les enjambées, déterminer leur taille de l’enjambée et précharger un ou plusieurs blocs. Détecter les enjambées et déterminer leur taille peut se faire simultanément de différentes manières. La première considère que si une instruction effectue deux défauts de cache à la suite, elle effectue un accès par enjambées. Il s'agit là d'une approximation grossière, mais qui ne fonctionne pas trop mal. Avec cette méthode, une ligne de cache de la table de prédiction de référence peut avoir deux états : un état où l'instruction n'effectue pas d'accès par enjambées, ainsi qu'un second état pour les instructions qui effectuent des accès par enjambées. La première fois qu'une instruction effectue un défaut de cache, une entrée lui est allouée dans la table de prédiction de références. La ligne est initialisée avec une enjambée inconnue en état "préchargement désactivé". Lors du second accès, la ligne de cache est mise à jour en état "préchargement activé". Le ''prefetcher'' considère que la distance entre les deux adresses (celle du premier accès et celle du second) est l'enjambée, cette distance se calculant avec une simple soustraction. [[File:Calcul de l’enjambée.png|centre|vignette|upright=2|Calcul de l’enjambée.]] Néanmoins, cet algorithme voit souvent des accès par enjambées là où il n'y en a pas. Une solution à cela consiste à attendre un troisième accès avant de commencer le préchargement, afin de vérifier si l'enjambée calculée est la bonne. Lorsque l'instruction effectue son premier défaut de cache, l'entrée est initialisée dans l'état ''no prefetch''. Lors du défaut de cache suivant, l’enjambée est calculée, mais le préchargement ne commence pas : l'entrée est placée dans l'état ''init''. C'est lors d'un troisième défaut de cache que l’enjambée est recalculée, et comparée avec l’enjambée calculée lors des deux précédents défauts. Si les deux correspondent, un accès par enjambées est détecté, et le préchargement commence. Sinon, l'instruction n'effectue pas d'accès par enjambées : on place l'entrée en état ''no prefetch''. [[File:Calcul amélioré de l’enjambée, partie 2.png|centre|vignette|upright=2|Calcul amélioré de l’enjambée, partie 2.]] On peut améliorer l'algorithme précédent pour recalculer l’enjambée à chaque accès mémoire de l'instruction, et vérifier si celui-ci a changé. Si un changement est détecté, la prédiction avec enjambée est certainement fausse et on ne précharge rien. Pour que cet algorithme fonctionne, on doit ajouter un quatrième état aux entrées : « transitoire » (''transient''), qui stoppe le préchargement et recalcule l’enjambée. [[File:Recalcul du préchargement à chaque défaut de cache.png|centre|vignette|upright=2|Recalcul du préchargement à chaque défaut de cache.]] ===Le préchargement selon les dépendances=== Certaines applications ont besoin de structures de données qui permettent de supprimer ou d'ajouter un élément rapidement. On peut notamment citer les listes, les arbres et les graphes. Dans ces structures de données alternatives aux tableaux, les données sont souvent dispersées dans la mémoire. Pour faire le lien entre les données, chacune d'entre elles sera stockée avec les adresses des données suivantes ou précédentes. Les ''prefetechers'' précédents fonctionnent mal avec ces structures de données, où les données ne sont pas placées à intervalle régulier en mémoire. Cela dit, il n'existe des techniques de préchargement adaptées pour ce genre de structures de données. La première de ces techniques a reçu le nom de '''préchargement selon les dépendances''' (''dependence based prefetching''). Elle ne donne de bons résultats que sur des listes. Prenons un exemple : une liste simplement chainée, une structure où chaque donnée indique l'adresse de la suivante. Pour lire la donnée suivante, le processeur doit récupérer son adresse, qui est placée à côté de la donnée actuelle. Puis, il doit charger tout ou partie de la donnée suivante dans un registre. Pour résumer, on se retrouve avec deux lectures : la première récupère l'adresse et l'autre l'utilise. Dans ce qui va suivre, je vais identifier ces deux instructions en parlant d'instruction productrice (celle qui charge l'adresse) et consommatrice (celle qui utilise l'adresse chargée). {| |[[File:Instruction productrice.jpg|vignette|Instruction productrice.]] |[[File:Instruction comsommatrice.jpg|vignette|Instruction consommatrice.]] |} Avec le préchargement selon les dépendances, le processeur mémorise si deux instructions ont une dépendance producteur-consommateur dans un cache : la '''table de corrélations'''. Chaque ligne de celle-ci stocke les adresses du producteur et du consommateur. Reste que ces corrélations ne sortent pas de la cuisse de Jupiter. Elles sont détectées lors de l’exécution d'une instruction consommatrice. Pour toute lecture, le processeur vérifie si la donnée à lire a été chargée par une autre instruction : si c'est le cas, l'instruction est consommatrice. Pour cela, le processeur contient une table de correspondances entre la donnée lue et l'adresse de l'instruction (le ''program counter'') : la '''fenêtre de producteurs potentiels'''. Lors de l’exécution d'une instruction, il vérifie si l'adresse à lire est dans la fenêtre de producteurs potentiels : si c'est le cas, c'est qu'une instruction productrice a chargé l'adresse, et que l'instruction en cours est consommatrice. L'adresse des instructions productrice et consommatrice sont alors stockées dans la table de corrélations. À chaque lecture, le processeur vérifie si l'instruction est productrice en regardant le contenu de la table de corrélations. Dès qu'une instruction détectée comme productrice a chargé son adresse, le processeur précharge les données de l'instruction consommatrice associée. Lorsqu'elle s’exécutera quelques cycles plus tard, la donnée aura déjà été lue depuis la mémoire. ===Le préchargement de Markov=== Pour les structures de données plus évoluées, comme des arbres ou des graphes, la technique précédente ne marche pas très bien. Avec ces types de données, chaque donnée a plusieurs successeurs, ce qui fait qu'une instruction consommatrice ne va pas toujours consommer la même adresse. Pour gérer cette situation, on doit utiliser des ''prefetchers'' plus évolués, comme des '''''prefetchers'' de Markov'''. Ils fonctionnent comme les précédents, sauf que la table de corrélations permet de mémoriser plusieurs correspondances, plusieurs adresses de successeurs. Dans certains ''prefetchers'', toutes les adresses des successeurs sont préchargées. Mais sur d'autres, le ''prefetcher'' se débrouille pour prédire quelle sera la bonne adresse du successeurs. Pour cela, le ''prefetcher'' calcule, pour chaque adresse, la probabilité qu'elle soit accédée. À chaque lecture ou écriture, les probabilités sont mises à jour. Seule l'adresse de plus forte probabilité est préchargée. Vu que la mémoire ne peut précharger qu'une seule donnée à la fois, certaines adresses sont mises en attente dans une mémoire tampon de préchargement. Lors du préchargement, le ''program counter'' de l'instruction qui initiera le préchargement sera envoyé à la table de correspondance. Cette table fournira plusieurs adresses, qui seront mises en attente dans le tampon de préchargement avant leur préchargement. L'ordre d'envoi des requêtes de préchargement (le passage de la mémoire tampon au sous-système mémoire) est déterminé par les probabilités des différentes adresses : on précharge d'abord les adresses les plus probables. ===Le préchargement par distance=== Le gain apporté par les ''prefetchers'' vus auparavant est appréciable, mais ceux-ci fonctionnent mal sur des accès cycliques ou répétitifs, certes rares dans le cas général, mais présents à foison dans certaines applications. Ils apparaissent surtout quand on parcourt plusieurs tableaux à la fois. Pour gérer au mieux ces accès, on a inventé des ''prefetchers'' plus évolués, capables de ce genre de prouesses. [[File:Accès mémoire cyclique sans enjambée.jpg|centre|vignette|upright=2|Accès mémoire cyclique sans enjambée.]] Le '''préchargement par distance''' (''distance prefetching''), une adaptation du ''prefetcher'' du Markov, est un de ces ''prefetchers''. Celui-ci n'utilise pas les adresses, mais les différences entre adresses accédées de manière consécutive, qui sont appelées des deltas. Ces deltas se calculent tout simplement en soustrayant les deux adresses. Ainsi, si j'accède à une adresse A, suivie par une adresse B, le préchargement par distance calculera le delta B - A, et l'utilisera pour sélectionner une entrée dans la table de correspondances. La table de correspondances est toujours structurée autour d'entrées, qui stockent chacune plusieurs correspondances, sauf qu'elle stocke les deltas. Cette table permet de faire des prédictions du style : si le delta entre B et A est de 3, alors le delta entre la prochaine adresse sera soit 5, soit 6, soit 7. L'utilité du ''prefetcher'' de Markov, c'est que la même entrée peut servir pour des adresses différentes. ===Le Tampon d’historique global=== Les techniques vues plus haut utilisent toutes une sorte de table de correspondances. L'accès à la table s'effectue soit en envoyant le ''program counter'' de l'instruction en entrée (préchargement par enjambées), soit l'adresse lue, soit les différences entre adresses. Ce qui est envoyé en entrée sera appelé l''''index''' de la table, dans la suite de cette partie. Cette table stocke une quantité limitée de données, tirées de l'historique des défauts de cache précédents. En somme, la table stocke, pour chaque index, un historique des défauts de cache associés à l'index. Dans les techniques vues précédemment, chaque table stocke un nombre fixe de défauts de cache par index : le ''one block lookahead'' stocke une adresse par instruction, le ''stride'' stocke une enjambée et une adresse pour chaque instruction, le préchargement de Markov stocke une ou plusieurs adresses par instruction, etc. Dit autrement, l'historique a une taille fixe. Vu que cette quantité est fixe, elle est souvent sous-utilisée. Par exemple, le préchargement de Markov limite le nombre d'adresses pour chaque instruction à 4, 5, 6 suivant le ''prefetcher''. Certaines instructions n'utiliseront jamais plus de deux entrées, tandis que le nombre de ces entrées n'est pas suffisant pour d'autres instructions plus rares. La quantité d'informations mémorisée pour chaque instruction est toujours la même, alors que les instructions n'ont pas les mêmes besoins : c'est loin d'être optimal. De plus, le nombre de défauts de cache par index limite le nombre d'instructions ou d'adresses qui peuvent être prédites. De plus, il se peut que des données assez anciennes restent dans la table de prédiction, et mènent à de mauvaises prédictions : pour prédire l'avenir, il faut des données à jour. Pour éviter ce genre de défauts, les chercheurs ont inventé des ''prefetchers'' qui utilisent un tampon d’historique global (''global history buffer''). Celui-ci permet d’implémenter plusieurs techniques de préchargement. Les techniques précédentes peuvent s'implémenter facilement sur ces ''prefetchers'', mais sans les défauts cités au-dessus. Ces ''prefetchers'' sont composés de deux sous-composants. Premièrement, on trouve une mémoire tampon de type FIFO (''First In, First Out'') qui mémorise les défauts de cache les plus récents : l''''historique global'''. Pour chaque défaut de cache, la mémoire FIFO mémorise l'adresse lue ou écrite dans une entrée. Pour effectuer des prédictions crédibles, ces défauts de cache sont regroupés suivant divers critères : l'instruction à l'origine du défaut, par exemple. Pour cela, les entrées sont organisées en liste chainée : chaque entrée pointe sur l'entrée suivante qui appartient au même groupe. On peut voir chacune de ces listes comme un historique dédié à un index : cela peut être l'ensemble des défauts de cache associés à une instruction, l'ensemble des défauts de cache qui suivront l'accès à une adresse donnée, etc. Généralement, les instructions sont triées à l'intérieur de chaque groupe dans l'ordre d'arrivée : l'entrée la plus récente contient le défaut de cache le plus récent du groupe. Ainsi, la taille de l'historique s'adapte dynamiquement suivant les besoins, contrairement aux ''prefetchers'' précédents où celui-ci tait de taille fixe. [[File:Tampon d’historique global.jpg|centre|vignette|upright=1|Tampon d’historique global.]] Reste que le processeur doit savoir où est l'entrée qui correspond au début de chaque liste. Pour cela, on doit rajouter une '''table de correspondances d'historiques''', qui permet de dire où se trouve l'historique associé à chaque index. Cette table de correspondances (index → historique par index) a bien sûr une taille finie. En somme, le nombre d'entrées de cette table limite le nombre d'index (souvent des instructions) gérées en même temps. Mais par contre, pour chaque instruction, la taille de l'historique des défauts de cache est variable. [[File:Tampon d’historique global avec sa table d’index.jpg|centre|vignette|upright=2|Tampon d’historique global avec sa table d’index.]] La table de correspondances et l'historique global sont couplés avec un '''circuit de prédiction''', qui peut utiliser chaque historique pour faire ces prédictions. Celui-ci peut aussi bien utiliser la totalité de l'historique global, que les historiques dédiés à un index. Faire une prédiction est simple demande d’accéder à la table de correspondances avec l'index adéquat : l'adresse lue ou écrite, le ''program counter'' de l'instruction d'accès mémoire, la distance entre cette adresse et la précédente, etc. Cela va alors sélectionner une liste dans l'historique global, qui sera parcourue de proche en proche par le circuit de prédiction, qui déterminera l'adresse à précharger en fonction de l'historique stocké dans la liste. Dans certains cas, l'historique global est aussi parcouru par le circuit de prédiction, mais c'est plus rare. Ce tampon d’historique global permet d’implémenter un algorithme de Markov assez simplement : il suffit que la table d'index mémorise une correspondance adresse → début de liste. Ainsi, pour chaque adresse, on associera la liste d'adresses suivantes possibles, classées suivant leur probabilité. L'adresse au début de la liste sera la plus probable, tandis que celle de la fin sera la moins probable. Même chose pour le préchargement par distance : il suffit que l'index soit la distance entre adresse précédemment accédée et adresse couramment accédée. Dans ce cas, la liste des entrées mémorisera la suite de distances qui correspond. L'implémentation d'un préchargement par enjambées est aussi possible, mais assez complexe. Mais de nouveaux algorithmes sont aussi possibles. ===Les variantes du tampon d’historique global=== Des variantes du tampon d'historique global ont été inventées. On pourrait citer celle qui ajoute, en plus de l'historique global, des historiques locaux sous la forme de mémoires FIFO qui mémorisent les derniers accès effectués par une instruction. Plus précisément, si on dispose de n historiques locaux, chacun de ces n historiques mémorise l'historique des n dernières instructions d'accès mémoire les plus récentes. D'autres variantes, et notamment celle du '''cache d'accès aux données''', ont ajouté une seconde table d'index : * la première table d'index prend en entrée le ''program counter'' de l'instruction à l'origine du défaut de cache ou de l'accès mémoire ; * la seconde prend en entrée l'adresse à lire ou écrire. [[File:Data access cache.png|centre|vignette|upright=2|Cache d'accès aux données.]] ==La pollution du cache== Le ''prefetcher'' peut se tromper et précharger des données inutilement dans le cache. Et outre l'inutilité de charger des données qui ne servent à rien, cela éjecte aussi des données potentiellement utiles du cache. C'est le phénomène de '''pollution de cache'''. Il va de soi que limiter au maximum cette pollution du cache permet de tirer parti au maximum de la mémoire cache, reste à savoir comment. Diverses solutions existent. ===L'usage d'un ''Dirty bit''=== Avec la première solution, la donnée chargée inutilement sera sélectionnée pour remplacement lors du prochain défaut de cache. Si le cache utilise un algorithme de sélection des lignes de cache de type LRU (''Least Recently Used''), on peut la mettre directement dans l'état « utilisée la moins récemment », ou « très peu utilisée ». ===L'usage d'un cache spécialisé pour le préchargement=== L'autre solution est de précharger les données non pas dans le cache, mais dans une mémoire dédiée au préchargement : un '''tampon de préchargement''', aussi appelé ''prefetch buffer'' en anglais. C'est ce qui est utilisé dans le préchargement séquentiel avec les ''stream buffers'', par exemple. Lors d'un accès au cache, on vérifie en parallèle si le tampon de préchargement contient la donnée demandée. Si ce n'est pas le cas, c'est que le tampon de flux contient des données préchargées à tort : le tampon de flux est totalement vidé, et on va chercher la donnée en mémoire. Si la donnée est disponible dans le tampon de préchargement, deux solutions sont possibles : on y accède directement dans le ''stream buffer'', ou on la rapatrie dans le cache avant de relancer l'accès dans le cache. ===Le filtrage de cache=== Une autre solution consiste à détecter les requêtes de préchargement inutiles en sortie du ''prefetcher''. Entre les circuits d'adressage de la mémoire (ou les niveaux de cache inférieurs) et le ''prefetcher'', on ajoute un circuit de filtrage qui détecte les requêtes de préchargement visiblement inutiles et contreproductives. Les algorithmes utilisés par ce circuit de filtrage de cache varient considérablement suivant le processeur et les travaux de recherche sur le sujet sont légion. ===Le duel d’ensembles=== Des chercheurs ont inventé des techniques plus complexes, dont la plus connue est le duel d'ensembles (''set dueling''). Dans leurs travaux, ils utilisent un cache associatif à plusieurs voies. Les voies sont réparties en deux groupes : statiques ou dynamiques. Les voies statiques ont une politique de remplacement fixée une fois pour toutes : * dans certaines voies statiques, toute ligne chargée depuis la mémoire est considérée comme la plus récemment utilisée ; * dans les autres voies statiques, toute ligne chargée depuis la mémoire est considérée comme la moins récemment utilisée. Les voies restantes choisissent dynamiquement si la ligne chargée est considérée comme la moins récemment utilisée ou la plus récemment utilisée. La décision se fait selon les voies statiques qui ont le plus de défauts de cache : si les voies "moins récemment utilisée" ont plus de défauts de cache que les autres, on ne l'utilise pas et inversement. Il suffit d'utiliser un simple compteur incrémenté ou décrémenté lors d'un défaut de cache dans une voie utilisant ou non l’optimisation. ==Quand précharger ?== Une problématique importante est de savoir quand précharger des données. Si on précharge des données trop tard ou trop tôt, le résultat n'est pas optimal. Pour résoudre au mieux ce problème, il existe deux grandes solutions : le préchargement sur événement et le préchargement par prédiction. Le '''préchargement par prédiction''' essaie de prédire le moment adéquat pour précharger, quitte à se tromper où à donner une réponse inadéquate. Le ''prefetecher'' décide de précharger ou non, en se basant sur l'historique des accès précédents. Il en déduit des statistiques qui permettent de savoir quand précharger. Par exemple, ils peuvent calculer le temps d'accès moyen entre un accès mémoire et un préchargement, et armer des chronomètres pour initialiser le préchargement en temps voulu. Les algorithmes pour cela sont assez variés, mais aussi assez compliqués. Le '''préchargement sur événement''' consiste à précharger quand certains événements spéciaux ont lieu. Elle s'enclenche quand un évènement bloque le processeur, dans le sens où le programme est bloqué à un instant précis. Par exemple, on peut précharger à chaque défaut de cache, à chaque accès mémoire, lors de l’exécution d'un branchement (pour le préchargement des instructions), etc. Dans ce qui suit, nous allons surtout détailler le préchargement sur évènement. ===Le préchargement associé aux accès mémoire=== Le préchargement par évènement le plus simple est celui initié par les accès mémoire. Reste à voir quels accès mémoire prendre en compte. La technique la plus simple effectue un préchargement lors de chaque accès mémoire. L'idée est que lorsqu'on accède à un bloc de mémoire, on précharge le bloc suivant de manière systématique, lors de chaque accès mémoire. Et mine de rien, cela ne fonctionne pas trop mal. Par contre, le préchargement accède beaucoup à la RAM, parfois pour précharger des données inutiles. Une optimisation serait de filtrer certains accès mémoires, à savoir que certains ne déclencheront pas de préchargement. Le choix des accès mémoire à filtrer doit se faire de manière judicieuse, elle doit identifier les accès mémoires inutiles. Une solution pour cela est de ne précharger que lors d'un défaut de cache. Ainsi, si j'ai un défaut de cache qui me force à charger un bloc dans le cache, le ''prefetcher'' chargera les blocs consécutifs suivants avec. On filtre alors beaucoup d'accès mémoires, et le préchargement est généralement assez efficace. Une solution précharge non pas lors d'un défaut de cache, mais lors d'un succès de cache uniquement. A chaque accès d'une ligne de cache, l'idée est de précharger la suivante. Reste à expliquer ce qu'on veut dire avec la suivante. La ligne de cache accédée correspond à un bloc en mémoire RAM, appelons-le le bloc A. Par ligne de cache suivante, on veut dire le bloc suivant en mémoire RAM, celui situé après le bloc A. Pour cela, on mémorise quelle est la dernière ligne de cache qui a été accédée. Il suffit d'ajouter un bit par ligne de cache, qui indique si cette ligne a été accédée lors du dernier cycle d'horloge. Il est automatiquement mis à un lors d'un accès à une ligne de cache. Il est remis à zéro dans deux conditions : lors d'un accès à une autre ligne de cache, au bout d'un certain temps. Le ''prefetcher'' se contente de charger le bloc qui suit la ligne de cache dont le bit vaut 1. [[File:Préchargement anticipé.png|centre|vignette|upright=2|Préchargement anticipé.]] ===Le préchargement anticipé (''runahead prefetching'')=== La technique du '''préchargement anticipé''' (''runahead prefetching'') effectue du préchargement sur un défaut de cache, et seulement pour les lectures. Le défaut de cache est censé bloquer totalement le processeur, tant qu'il n'est pas résolu. Et le processeur est censé reprendre l'exécution du programme, une fois la donnée chargée dans les registres. L'idée du préchargement anticipé est que le processeur ne se bloque pas, mais continue l'exécution du programme alors qu'il ne devrait pas. Il rentre dans un mode ''runahead '' dans lequel il exécute le programme en avance. Les instructions qui suivent la lecture s'exécutent, y compris d'autres instructions de lecture. Les instructions vont alors générer des adresses, des lectures vont être déclenchées, etc. Et le principe est que les lectures seront exécutées en mode ''runahead'', donc en avance. Les lectures en question, exécutées en mode ''runahead'' sont appelées des '''lectures anticipées'''. Les lectures anticipées lisent des données dans le cache de données, ce qui précharge les données associées. Mais ce que fait le processeur en mode ''runahead'' est totalement annulé une fois que le défaut de cache est résolu, le processeur revient à la normale, à l'état antérieur. Une seule chose n'est pas effacée : les données et instructions préchargées dans le cache. Pour le reste, les registres sont remis à leur état antérieur. Pour cela, le processeur sauvegarde les registres en entrant en mode ''runahead'', puis les restaure une fois le défaut de cache terminée. De même, les écritures en mémoire RAM ne sont pas exécutées en mode ''runahead''. Pas question d'écrire des données potentiellement fausses en RAM. : Notons que cette technique n'a de sens que sur des processeurs dits à émission dans l'ordre. Nous verrons dans la suite du cours ce que sont les processeurs à exécution dans l'ordre et dans le désordre, mais sachez que dès qu'un processeur a de l'exécution dans le désordre, la technique devient totalement inutile. De même certains processeurs à exécution dans l'ordre n'ont pas besoin de cette technique, s'ils gèrent les lectures non-bloquantes. En pratique, seuls quelques processeurs dits VLIW incorporent cette technique, et ils sont rares. Un problème de cette technique est la gestion des dépendances avec les lectures. Généralement, une lecture est suivie d'instructions qui utilisent son résultat. Une lecture charge une donnée dans un ''registre de destination'', qui est lu par d'autres instructions dépendantes. En mode ''runahead'', ces instructions lisent le registre de destination, mais la valeur ne sera pas la bonne, car la lecture n'a pas encore eu lieu. En soi, cela ne pose pas de problèmes, vu que le processeur restaure son état normal en sortie du mode ''runahead''. Un problème survient cependant quand ces instructions sont elle-mêmes être des lectures. De telles lectures prendront une adresse/opérande erronée, ce qui fait qu'elles ne liront pas la donnée correcte, mais une donnée qui n'avait pas à être lue. En conséquence, elles vont précharger des données invalides, qui n'auraient pas dû l'être. Le préchargement perd alors en efficacité. Une solution possible serait d'interrompre le mode ''runahead'' quand le processeur détecte ce genre de situation, le processeur restaure son état antérieur et attend la fin du défaut de cache. Une solution alternative marque le registre de destination comme invalide. Lors d'un défaut de cache en lecture, le registre de destination est marqué comme invalide, et cette valeur invalide se propage d'instruction en instruction. Typiquement, dès qu'une instruction lit un registre invalide, elle marquera son registre de résultat comme invalide. Les adresses marquées comme invalides ne sont pas utilisées pour démarrer des lectures ou écritures, afin d'économiser des préchargements inutiles. Pour marquer les registres comme invalides, il suffit de leur rajouter un bit ''invalid'' qui indique si le résultat est valide ou non. Il faut aussi gérer la propagation du statut invalide d'une instruction à l'autre. Cela ne pose aucun problème pour les instructions dont les opérandes et la destination sont des registres : si une opérande a un statut invalide, la destination l'est aussi. Pour les autres instructions, les règles pour propager les valeurs invalides d'une instruction à l'autre sont assez complexes, surtout quand on prend en compte les écritures. Il faut aussi gérer les branchements et autres, mais passons. L'essentiel est que toutes les instructions qui dépendent de la lecture, directement ou indirectement, soient marquées invalides. Un autre problème survient dans un cas bien précis : une dépendance entre une lecture anticipée et une écriture anticipée. Le cas est celui où l'écriture anticipée écrit une donnée en mémoire, qui est lue par une lecture anticipée. Vu que l'écriture anticipée n'est pas exécutée, la lecture anticipée aura un registre de destination invalide, idem pour ses instructions dépendantes. Pourtant, c'est un cas où la lecture anticipée aurait pu s'exécuter en avance et précharger la bonne donnée. Mais les implémentations plus élaborées gèrent naturellement ce genre de cas. Pour cela, il faut marquer les lignes de cache comme valides ou invalides, selon les opérandes des écritures. Une lecture marquera son registre de destination comme valide si la ligne de cache lue est valide, comme invalide si elle l'est ou qu'elle déclenche un défaut de cache. Une autre solution est de détourner les écritures dans un cache séparé, spécifique au mode ''runahead'', dans lequel les écritures en mode ''runahead'' écrivent. : Notons que cette technique demande dans l'idéal d'utiliser des techniques de prédiction de branchement et les circuits associés, qui sont censés être vus dans les chapitres ultérieurs. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les mémoires cache | prevText=Les mémoires cache | next=Le Translation Lookaside Buffer | nextText=Le Translation Lookaside Buffer }} </noinclude> arf0t7rcnq0sr55zdjv7wrzmjoyj44z 764659 764647 2026-04-23T14:54:56Z Mewtow 31375 /* Les accès par enjambées */ 764659 wikitext text/x-wiki En raison de la localité spatiale, il est avantageux de précharger dans le cache des données/instructions proches de celles chargées il y a peu. Ce '''préchargement''' (en anglais, ''prefetching''), peut être effectué par le programmeur, à la condition que le processeur possède une instruction de préchargement. Mais celui-ci peut aussi être pris en charge directement par le processeur, sans implication du programmeur, ce qui a de nombreux avantages. Pour ce faire, le processeur doit contenir un circuit nommé ''prefetcher'', associé au cache, qui charger l'avance des données/instructions susceptibles d’être utilisées dans un avenir proche. Qui plus est, on peut utiliser à la fois des instructions de préchargement et un ''prefetcher'' intégré au processeur : les deux solutions ne sont pas incompatibles. Il faut savoir que le préchargement ne fonctionne pas tout à fait de la même manière pour les données et les instructions. La raison est que l'organisation des données en mémoire diffère de celle des instructions, du moins pour les structures de données usuelles. Les processeurs modernes disposent de ''prefetchers'' spécialisés pour les instructions, séparés des ''prefetchers'' spécialisés dans les données. Nous allons ici nous concentrer sur les ''prefetchers'' spécialisés dans les données, avec seulement quelques parenthèses sur les ''prefetchers'' d'instructions. Il faut dire que parler du préchargement des instructions demande de parler des algorithmes de prédiction de branchement, ce qui fera l'objet d'un chapitre ultérieur. ==Les problèmes à résoudre pour précharger une donnée/instruction== Précharger des données/instructions demande de faire plusieurs choses. La première est de prédire à l'avance quelles données/instructions seront utilisées dans le futur. Et cette '''prédiction d'adresse''' ne se fait pas de la même manière entre instruction et données. Pour les instructions, cela revient à prédire quelles instructions seront exécutées dans le futur. Vous vous dites qu'il n'y a rien de plus simple : l'exécution des instruction est séquentielle, les instructions se suivent en mémoire, rien de bien compliqué. Mais il faut prendre en compte les branchements qui peuvent envoyer le programme n'importe où en mémoire. Et résoudre le problème des branchements demande de faire ce qui s'appelle de la prédiction de branchements, qu'on verra dans un chapitre dédié. Pour les données, tout dépend de la manière dont les structures de données sont parcourues et tout dépend de la structure de donnée. Les tableaux sont la structure de donnée idéale pour le préchargement, comme on le verra plus tard, mais d'autres techniques fonctionnent pour d'autres structures de données. Le second point est de décider ''quand précharger des données''. Il ne faut pas que le préchargement marche sur les pieds des accès mémoire normaux. Est-ce qu'on précharge les données dès que possible quitte à retarder un accès mémoire normal, où attend-t-on que la mémoire soit libre pour faire le préchargement. Dans le second cas, il faut décider si le préchargement en vaut la peine. Peut-être que la mémoire ne sera libre que trop tard, à un moment où précharger ne fera que gagner quelques cycles, ce qui ne vaut pas le coup comparé aux inconvénients du préchargement. Mais le cas inverse, à savoir un préchargement trop anticipé, peut aussi donner des performances inférieures. Dans le pire des cas, une donnée chargée trop en avance sera évincée du cache avant d'être utilisée. Tout cela fera l'objet d'une section dédiée dans le chapitre. Enfin, il faut prendre en compte que la donnée préchargée va prendre la place d'une autre donnée dans le cache. Il faut que ce remplacement en vaille la peine. Si la donnée préchargée l'est à tord, elle remplacera une donnée potentiellement plus utile. Il faut absolument éviter cette ''pollution de cache''. ==Les algorithmes de préchargement des données== Le préchargement est fortement influencé par la manière dont les données sont représentées en mémoire. Le point principal est si elles sont regroupées ensemble dans un tableau ou si elles sont dispersées, et comment elles sont dispersées. Il existe ainsi des ''prefetchers'' qui marchent assez bien pour certaines structures de données spécialisées et très mal sur d'autres. Les ''prefetchers'' pour les données sont surtout adaptés à l'utilisation de tableaux (des ensembles de données consécutives de même taille et de même type). Ils profitent du fait que ces tableaux sont souvent accédés case par case. Ils marchent un peu moins bien pour les autres structures de données, comme les listes chainées ou les arbres, mais il existe des ''prefetchers'' spécialisés pour ces dernières. ===Les ''Prefetchers'' séquentiels=== Les ''prefetchers'' séquentiels préchargent les données immédiatement consécutives de la donnée venant tout juste d'être lue ou écrite. Ils fonctionnent bien lorsqu'on accède à des données consécutives en mémoire, ce qui arrive souvent lors de parcours de tableaux. Dans le cas le plus simple, on précharge le bloc de mémoire qui suit immédiatement la dernière ligne chargée. L'adresse de ce bloc se calcule en additionnant la longueur d'une ligne de cache à la dernière adresse lue ou écrite. Ce qui peut être fait avec un seul bloc de mémoire peut aussi l'être avec plusieurs. Rien n’empêche de charger non pas un, mais deux ou trois blocs consécutifs dans notre mémoire cache. Mais attention : le nombre de blocs de mémoire chargés dans le cache est fixe. [[File:Préchargement séquentiel.png|centre|vignette|upright=2|Préchargement séquentiel.]] Le préchargement séquentiel ne fonctionne que pour des accès à des adresses consécutives, pour des données avec une bonne localité spatiale. Pour les autres types d'accès, l'utilisation d'un ''prefetcher'' séquentiel est généralement contreproductive. Pour limiter la casse, les ''prefetchers'' évolués sont capables de distinguer les accès séquentiels et les accès problématiques. Cette détection peut se faire de deux façons. Avec la première, le ''prefetcher'' calcule une moyenne du nombre de blocs préchargés qui ont été utiles, à partir des n derniers blocs préchargés. En clair, il calcule le rapport entre le nombre de blocs qu'il a préchargés dans le cache et le nombre de ces blocs qui ont été accédés. Si jamais ce rapport diminue trop, cela signifie que l'on n’a pas affaire à des accès séquentiels : le ''prefetcher'' arrête temporairement de précharger. Autre solution : garder un historique des derniers accès mémoires pour vérifier s'ils accèdent à des adresses consécutives. Le processeur peut décider de désactiver temporairement le préchargement si jamais le nombre de blocs préchargés utilement tombe trop près de zéro. ====Les ''stream buffers''==== L'implémentation du préchargement séquentiel peut se faire de nombreuses façons. Mais la plus simple et la plus commune est celle des '''''stream buffers'''''. Elle utilise une mémoire FIFO séparée du cache, appelée le ''stream buffer'', dans laquelle sont préchargés les blocs. Lors d'un défaut de cache, le bloc lu/écrit par le processeur est chargé depuis la mémoire et placé dans le cache. Les blocs suivants sont eux préchargés dans le ''stream buffer''. Le ''stream buffer'' permet de précharger un certain nombre de blocs, fixe : 2, 3, 4 blocs, rarement plus. Si le processeur accède à un bloc préchargé, celui-ci sera lu directement depuis le ''stream buffer'', pas depuis la mémoire ou le cache. L'usage de ''stream buffer'' est assez simple et n'entraine pas de modifications substantielles du cache, ce qui est un avantage certain. Pas besoin de modifier la politique de remplacement du cache, en cas de préchargement inutile, par exemple. De plus, le processeur peut avoir plusieurs ''stream buffer'', ce qui permet d'effectuer du préchargement pour plusieurs défauts de cache. Un défaut de cache peut remplir un ''stream buffer'', puis un autre défaut en remplir un autre, etc. Les performances sont alors d'autant plus grandes que le nombre et la taille des ''stream buffer'' sont élevés. La toute première technique de ce genre est illustrée ci-dessous. Elle était pensée pour précharger des données dans le cache L1 d'un processeur, que ce soit pour précharger des données de la RAM dans le cache, ou pour précharger des données du cache L2 vers le cache L1. Mais cette technique s'adapte à toute forme de préchargement et à toute hiérarchie mémoire. On peut l'utiliser à tous les niveaux de la hiérarchie mémoire. Une version améliorée précharge les données dans le ''stream buffer'' non pas lors d'un défaut de cache, mais lors de chaque accès mémoire : tout accès à une donnée charge la donnée suivante dans le ''stream buffer''. Mais nous en reparlerons plus bas, dans la section sur les évènements qui déclenchent le préchargement. [[File:CachePrefetching StreamBuffers.png|centre|vignette|upright=2|Technique des ''stream buffers''.]] ====L’utilisation du préchargement séquentiel pour les instructions==== Le préchargement séquentiel est assez adapté au préchargement des instructions d'un programme, vu que ses instructions sont placées les unes après les autres en mémoire. Notons que par préchargement des instructions, on veut parler du chargement des instructions dans le cache d'instruction L1, à partir du cache L2. Les niveaux de cache en-dessous n'utilisent pas vraiment de préchargement d'instruction. Le préchargement séquentiel des instructions n'est pas une technologie nouvelle. Dès les années 60, des utilisations commerciales existaient. Par exemple, l'ordinateur IBM System/360 Model 91 gérait le préchargement séquentiel des instructions. Le seul bémol est qu'un ''prefetcher'' purement séquentiel gère mal les branchements. Le branchement envoie le programme à une adresse qui peut être n'importe où dans le programme, pas forcément dans la ligne de cache suivante. Les techniques pour résoudre ce problème sont assez complexes, sans compter qu'elles demandent d'utiliser de la prédiction de branchement, chose que nous n'avons pas encore abordé dans ce cours. [[File:Branchements et préchargement séquentiel.png|centre|vignette|upright=2|Branchements et préchargement séquentiel.]] ===Les accès par enjambées=== Les '''accès par enjambées''' (ou ''stride access'') se font sur des données séparées par une distance constante k. Ils surviennent quand un programmeur utilise certaines structures de données, à savoir : des tableaux de structures/objets, des tableaux multidimensionnels. Un cas classique est celui d'une matrice, à savoir un tableau de nombres rectangulaire avec N lignes et M colonnes. Les matrices sont souvent stockées dans un tableau unique, où les nombres sont mémorisés ligne par ligne. Les accès qui se font ligne par ligne ne font que parcourir le tableau en passant d'une case à la suivante. Cependant, les parcours qui se font colonne par colonne, font de grandes enjabées dans la mémoire RAM. [[File:Accès par enjambées.png|centre|vignette|upright=2|Accès par enjambées.]] Avec ce genre d'accès, un ''prefetcher'' séquentiel charge des données inutiles, ce qui est synonyme de baisse de performances. Mais certains ''prefetchers'' gèrent de tels accès à la perfection. Cela ne rend pas ces accès aussi rapides que des accès à des blocs de mémoire consécutifs, vu qu'on gâche une ligne de cache pour n'en utiliser qu'une petite portion, mais cela aide tout de même beaucoup. Ces prefetchers conservent un historique des accès mémoires effectués récemment dans un cache : la '''table de prédiction de références''' (''reference prediction table''). Chaque ligne de cache associe à une instruction toutes les informations nécessaires pour prédire quelle sera la prochaine adresse utilisée par celle-ci. Elle stocke notamment la dernière adresse lue/écrite par l'instruction, l’enjambée, ainsi que des bits qui indiquent la validité de ces informations. L'adresse de l'instruction est dans le tag de la ligne de cache. Pour prédire la prochaine adresse, il suffit d'ajouter la longueur d’enjambée à l'adresse à lire ou écrire. Cette technique peut être adaptée pour précharger non seulement la prochaine adresse, mais aussi les n adresses suivantes, la énième adresse ayant pour valeur : adresse + n × enjambée. Évidemment, ces adresses à précharger ne peuvent pas être lues ou écrites simultanément depuis la mémoire. On doit donc les mettre en attente, le temps que la mémoire soit libre. Pour cela, on utilise un tampon de préchargement, qui stocke des requêtes de lecture ou d'écriture fournies par l'unité de préchargement. L'algorithme de gestion des enjambées doit détecter les enjambées, déterminer leur taille de l’enjambée et précharger un ou plusieurs blocs. Détecter les enjambées et déterminer leur taille peut se faire simultanément de différentes manières. La première considère que si une instruction effectue deux défauts de cache à la suite, elle effectue un accès par enjambées. Il s'agit là d'une approximation grossière, mais qui ne fonctionne pas trop mal. Avec cette méthode, une ligne de cache de la table de prédiction de référence peut avoir deux états : un état où l'instruction n'effectue pas d'accès par enjambées, ainsi qu'un second état pour les instructions qui effectuent des accès par enjambées. La première fois qu'une instruction effectue un défaut de cache, une entrée lui est allouée dans la table de prédiction de références. La ligne est initialisée avec une enjambée inconnue en état "préchargement désactivé". Lors du second accès, la ligne de cache est mise à jour en état "préchargement activé". Le ''prefetcher'' considère que la distance entre les deux adresses (celle du premier accès et celle du second) est l'enjambée, cette distance se calculant avec une simple soustraction. [[File:Calcul de l’enjambée.png|centre|vignette|upright=2|Calcul de l’enjambée.]] Néanmoins, cet algorithme voit souvent des accès par enjambées là où il n'y en a pas. Une solution à cela consiste à attendre un troisième accès avant de commencer le préchargement, afin de vérifier si l'enjambée calculée est la bonne. Lorsque l'instruction effectue son premier défaut de cache, l'entrée est initialisée dans l'état ''no prefetch''. Lors du défaut de cache suivant, l’enjambée est calculée, mais le préchargement ne commence pas : l'entrée est placée dans l'état ''init''. C'est lors d'un troisième défaut de cache que l’enjambée est recalculée, et comparée avec l’enjambée calculée lors des deux précédents défauts. Si les deux correspondent, un accès par enjambées est détecté, et le préchargement commence. Sinon, l'instruction n'effectue pas d'accès par enjambées : on place l'entrée en état ''no prefetch''. [[File:Calcul amélioré de l’enjambée, partie 2.png|centre|vignette|upright=2|Calcul amélioré de l’enjambée, partie 2.]] On peut améliorer l'algorithme précédent pour recalculer l’enjambée à chaque accès mémoire de l'instruction, et vérifier si celui-ci a changé. Si un changement est détecté, la prédiction avec enjambée est certainement fausse et on ne précharge rien. Pour que cet algorithme fonctionne, on doit ajouter un quatrième état aux entrées : « transitoire » (''transient''), qui stoppe le préchargement et recalcule l’enjambée. [[File:Recalcul du préchargement à chaque défaut de cache.png|centre|vignette|upright=2|Recalcul du préchargement à chaque défaut de cache.]] ===Le préchargement selon les dépendances=== Certaines applications ont besoin de structures de données qui permettent de supprimer ou d'ajouter un élément rapidement. On peut notamment citer les listes, les arbres et les graphes. Dans ces structures de données alternatives aux tableaux, les données sont souvent dispersées dans la mémoire. Pour faire le lien entre les données, chacune d'entre elles sera stockée avec les adresses des données suivantes ou précédentes. Les ''prefetechers'' précédents fonctionnent mal avec ces structures de données, où les données ne sont pas placées à intervalle régulier en mémoire. Cela dit, il n'existe des techniques de préchargement adaptées pour ce genre de structures de données. La première de ces techniques a reçu le nom de '''préchargement selon les dépendances''' (''dependence based prefetching''). Elle ne donne de bons résultats que sur des listes. Prenons un exemple : une liste simplement chainée, une structure où chaque donnée indique l'adresse de la suivante. Pour lire la donnée suivante, le processeur doit récupérer son adresse, qui est placée à côté de la donnée actuelle. Puis, il doit charger tout ou partie de la donnée suivante dans un registre. Pour résumer, on se retrouve avec deux lectures : la première récupère l'adresse et l'autre l'utilise. Dans ce qui va suivre, je vais identifier ces deux instructions en parlant d'instruction productrice (celle qui charge l'adresse) et consommatrice (celle qui utilise l'adresse chargée). {| |[[File:Instruction productrice.jpg|vignette|Instruction productrice.]] |[[File:Instruction comsommatrice.jpg|vignette|Instruction consommatrice.]] |} Avec le préchargement selon les dépendances, le processeur mémorise si deux instructions ont une dépendance producteur-consommateur dans un cache : la '''table de corrélations'''. Chaque ligne de celle-ci stocke les adresses du producteur et du consommateur. Reste que ces corrélations ne sortent pas de la cuisse de Jupiter. Elles sont détectées lors de l’exécution d'une instruction consommatrice. Pour toute lecture, le processeur vérifie si la donnée à lire a été chargée par une autre instruction : si c'est le cas, l'instruction est consommatrice. Pour cela, le processeur contient une table de correspondances entre la donnée lue et l'adresse de l'instruction (le ''program counter'') : la '''fenêtre de producteurs potentiels'''. Lors de l’exécution d'une instruction, il vérifie si l'adresse à lire est dans la fenêtre de producteurs potentiels : si c'est le cas, c'est qu'une instruction productrice a chargé l'adresse, et que l'instruction en cours est consommatrice. L'adresse des instructions productrice et consommatrice sont alors stockées dans la table de corrélations. À chaque lecture, le processeur vérifie si l'instruction est productrice en regardant le contenu de la table de corrélations. Dès qu'une instruction détectée comme productrice a chargé son adresse, le processeur précharge les données de l'instruction consommatrice associée. Lorsqu'elle s’exécutera quelques cycles plus tard, la donnée aura déjà été lue depuis la mémoire. ===Le préchargement de Markov=== Pour les structures de données plus évoluées, comme des arbres ou des graphes, la technique précédente ne marche pas très bien. Avec ces types de données, chaque donnée a plusieurs successeurs, ce qui fait qu'une instruction consommatrice ne va pas toujours consommer la même adresse. Pour gérer cette situation, on doit utiliser des ''prefetchers'' plus évolués, comme des '''''prefetchers'' de Markov'''. Ils fonctionnent comme les précédents, sauf que la table de corrélations permet de mémoriser plusieurs correspondances, plusieurs adresses de successeurs. Dans certains ''prefetchers'', toutes les adresses des successeurs sont préchargées. Mais sur d'autres, le ''prefetcher'' se débrouille pour prédire quelle sera la bonne adresse du successeurs. Pour cela, le ''prefetcher'' calcule, pour chaque adresse, la probabilité qu'elle soit accédée. À chaque lecture ou écriture, les probabilités sont mises à jour. Seule l'adresse de plus forte probabilité est préchargée. Vu que la mémoire ne peut précharger qu'une seule donnée à la fois, certaines adresses sont mises en attente dans une mémoire tampon de préchargement. Lors du préchargement, le ''program counter'' de l'instruction qui initiera le préchargement sera envoyé à la table de correspondance. Cette table fournira plusieurs adresses, qui seront mises en attente dans le tampon de préchargement avant leur préchargement. L'ordre d'envoi des requêtes de préchargement (le passage de la mémoire tampon au sous-système mémoire) est déterminé par les probabilités des différentes adresses : on précharge d'abord les adresses les plus probables. ===Le préchargement par distance=== Le gain apporté par les ''prefetchers'' vus auparavant est appréciable, mais ceux-ci fonctionnent mal sur des accès cycliques ou répétitifs, certes rares dans le cas général, mais présents à foison dans certaines applications. Ils apparaissent surtout quand on parcourt plusieurs tableaux à la fois. Pour gérer au mieux ces accès, on a inventé des ''prefetchers'' plus évolués, capables de ce genre de prouesses. [[File:Accès mémoire cyclique sans enjambée.jpg|centre|vignette|upright=2|Accès mémoire cyclique sans enjambée.]] Le '''préchargement par distance''' (''distance prefetching''), une adaptation du ''prefetcher'' du Markov, est un de ces ''prefetchers''. Celui-ci n'utilise pas les adresses, mais les différences entre adresses accédées de manière consécutive, qui sont appelées des deltas. Ces deltas se calculent tout simplement en soustrayant les deux adresses. Ainsi, si j'accède à une adresse A, suivie par une adresse B, le préchargement par distance calculera le delta B - A, et l'utilisera pour sélectionner une entrée dans la table de correspondances. La table de correspondances est toujours structurée autour d'entrées, qui stockent chacune plusieurs correspondances, sauf qu'elle stocke les deltas. Cette table permet de faire des prédictions du style : si le delta entre B et A est de 3, alors le delta entre la prochaine adresse sera soit 5, soit 6, soit 7. L'utilité du ''prefetcher'' de Markov, c'est que la même entrée peut servir pour des adresses différentes. ===Le Tampon d’historique global=== Les techniques vues plus haut utilisent toutes une sorte de table de correspondances. L'accès à la table s'effectue soit en envoyant le ''program counter'' de l'instruction en entrée (préchargement par enjambées), soit l'adresse lue, soit les différences entre adresses. Ce qui est envoyé en entrée sera appelé l''''index''' de la table, dans la suite de cette partie. Cette table stocke une quantité limitée de données, tirées de l'historique des défauts de cache précédents. En somme, la table stocke, pour chaque index, un historique des défauts de cache associés à l'index. Dans les techniques vues précédemment, chaque table stocke un nombre fixe de défauts de cache par index : le ''one block lookahead'' stocke une adresse par instruction, le ''stride'' stocke une enjambée et une adresse pour chaque instruction, le préchargement de Markov stocke une ou plusieurs adresses par instruction, etc. Dit autrement, l'historique a une taille fixe. Vu que cette quantité est fixe, elle est souvent sous-utilisée. Par exemple, le préchargement de Markov limite le nombre d'adresses pour chaque instruction à 4, 5, 6 suivant le ''prefetcher''. Certaines instructions n'utiliseront jamais plus de deux entrées, tandis que le nombre de ces entrées n'est pas suffisant pour d'autres instructions plus rares. La quantité d'informations mémorisée pour chaque instruction est toujours la même, alors que les instructions n'ont pas les mêmes besoins : c'est loin d'être optimal. De plus, le nombre de défauts de cache par index limite le nombre d'instructions ou d'adresses qui peuvent être prédites. De plus, il se peut que des données assez anciennes restent dans la table de prédiction, et mènent à de mauvaises prédictions : pour prédire l'avenir, il faut des données à jour. Pour éviter ce genre de défauts, les chercheurs ont inventé des ''prefetchers'' qui utilisent un tampon d’historique global (''global history buffer''). Celui-ci permet d’implémenter plusieurs techniques de préchargement. Les techniques précédentes peuvent s'implémenter facilement sur ces ''prefetchers'', mais sans les défauts cités au-dessus. Ces ''prefetchers'' sont composés de deux sous-composants. Premièrement, on trouve une mémoire tampon de type FIFO (''First In, First Out'') qui mémorise les défauts de cache les plus récents : l''''historique global'''. Pour chaque défaut de cache, la mémoire FIFO mémorise l'adresse lue ou écrite dans une entrée. Pour effectuer des prédictions crédibles, ces défauts de cache sont regroupés suivant divers critères : l'instruction à l'origine du défaut, par exemple. Pour cela, les entrées sont organisées en liste chainée : chaque entrée pointe sur l'entrée suivante qui appartient au même groupe. On peut voir chacune de ces listes comme un historique dédié à un index : cela peut être l'ensemble des défauts de cache associés à une instruction, l'ensemble des défauts de cache qui suivront l'accès à une adresse donnée, etc. Généralement, les instructions sont triées à l'intérieur de chaque groupe dans l'ordre d'arrivée : l'entrée la plus récente contient le défaut de cache le plus récent du groupe. Ainsi, la taille de l'historique s'adapte dynamiquement suivant les besoins, contrairement aux ''prefetchers'' précédents où celui-ci tait de taille fixe. [[File:Tampon d’historique global.jpg|centre|vignette|upright=1|Tampon d’historique global.]] Reste que le processeur doit savoir où est l'entrée qui correspond au début de chaque liste. Pour cela, on doit rajouter une '''table de correspondances d'historiques''', qui permet de dire où se trouve l'historique associé à chaque index. Cette table de correspondances (index → historique par index) a bien sûr une taille finie. En somme, le nombre d'entrées de cette table limite le nombre d'index (souvent des instructions) gérées en même temps. Mais par contre, pour chaque instruction, la taille de l'historique des défauts de cache est variable. [[File:Tampon d’historique global avec sa table d’index.jpg|centre|vignette|upright=2|Tampon d’historique global avec sa table d’index.]] La table de correspondances et l'historique global sont couplés avec un '''circuit de prédiction''', qui peut utiliser chaque historique pour faire ces prédictions. Celui-ci peut aussi bien utiliser la totalité de l'historique global, que les historiques dédiés à un index. Faire une prédiction est simple demande d’accéder à la table de correspondances avec l'index adéquat : l'adresse lue ou écrite, le ''program counter'' de l'instruction d'accès mémoire, la distance entre cette adresse et la précédente, etc. Cela va alors sélectionner une liste dans l'historique global, qui sera parcourue de proche en proche par le circuit de prédiction, qui déterminera l'adresse à précharger en fonction de l'historique stocké dans la liste. Dans certains cas, l'historique global est aussi parcouru par le circuit de prédiction, mais c'est plus rare. Ce tampon d’historique global permet d’implémenter un algorithme de Markov assez simplement : il suffit que la table d'index mémorise une correspondance adresse → début de liste. Ainsi, pour chaque adresse, on associera la liste d'adresses suivantes possibles, classées suivant leur probabilité. L'adresse au début de la liste sera la plus probable, tandis que celle de la fin sera la moins probable. Même chose pour le préchargement par distance : il suffit que l'index soit la distance entre adresse précédemment accédée et adresse couramment accédée. Dans ce cas, la liste des entrées mémorisera la suite de distances qui correspond. L'implémentation d'un préchargement par enjambées est aussi possible, mais assez complexe. Mais de nouveaux algorithmes sont aussi possibles. ===Les variantes du tampon d’historique global=== Des variantes du tampon d'historique global ont été inventées. On pourrait citer celle qui ajoute, en plus de l'historique global, des historiques locaux sous la forme de mémoires FIFO qui mémorisent les derniers accès effectués par une instruction. Plus précisément, si on dispose de n historiques locaux, chacun de ces n historiques mémorise l'historique des n dernières instructions d'accès mémoire les plus récentes. D'autres variantes, et notamment celle du '''cache d'accès aux données''', ont ajouté une seconde table d'index : * la première table d'index prend en entrée le ''program counter'' de l'instruction à l'origine du défaut de cache ou de l'accès mémoire ; * la seconde prend en entrée l'adresse à lire ou écrire. [[File:Data access cache.png|centre|vignette|upright=2|Cache d'accès aux données.]] ==La pollution du cache== Le ''prefetcher'' peut se tromper et précharger des données inutilement dans le cache. Et outre l'inutilité de charger des données qui ne servent à rien, cela éjecte aussi des données potentiellement utiles du cache. C'est le phénomène de '''pollution de cache'''. Il va de soi que limiter au maximum cette pollution du cache permet de tirer parti au maximum de la mémoire cache, reste à savoir comment. Diverses solutions existent. ===L'usage d'un ''Dirty bit''=== Avec la première solution, la donnée chargée inutilement sera sélectionnée pour remplacement lors du prochain défaut de cache. Si le cache utilise un algorithme de sélection des lignes de cache de type LRU (''Least Recently Used''), on peut la mettre directement dans l'état « utilisée la moins récemment », ou « très peu utilisée ». ===L'usage d'un cache spécialisé pour le préchargement=== L'autre solution est de précharger les données non pas dans le cache, mais dans une mémoire dédiée au préchargement : un '''tampon de préchargement''', aussi appelé ''prefetch buffer'' en anglais. C'est ce qui est utilisé dans le préchargement séquentiel avec les ''stream buffers'', par exemple. Lors d'un accès au cache, on vérifie en parallèle si le tampon de préchargement contient la donnée demandée. Si ce n'est pas le cas, c'est que le tampon de flux contient des données préchargées à tort : le tampon de flux est totalement vidé, et on va chercher la donnée en mémoire. Si la donnée est disponible dans le tampon de préchargement, deux solutions sont possibles : on y accède directement dans le ''stream buffer'', ou on la rapatrie dans le cache avant de relancer l'accès dans le cache. ===Le filtrage de cache=== Une autre solution consiste à détecter les requêtes de préchargement inutiles en sortie du ''prefetcher''. Entre les circuits d'adressage de la mémoire (ou les niveaux de cache inférieurs) et le ''prefetcher'', on ajoute un circuit de filtrage qui détecte les requêtes de préchargement visiblement inutiles et contreproductives. Les algorithmes utilisés par ce circuit de filtrage de cache varient considérablement suivant le processeur et les travaux de recherche sur le sujet sont légion. ===Le duel d’ensembles=== Des chercheurs ont inventé des techniques plus complexes, dont la plus connue est le duel d'ensembles (''set dueling''). Dans leurs travaux, ils utilisent un cache associatif à plusieurs voies. Les voies sont réparties en deux groupes : statiques ou dynamiques. Les voies statiques ont une politique de remplacement fixée une fois pour toutes : * dans certaines voies statiques, toute ligne chargée depuis la mémoire est considérée comme la plus récemment utilisée ; * dans les autres voies statiques, toute ligne chargée depuis la mémoire est considérée comme la moins récemment utilisée. Les voies restantes choisissent dynamiquement si la ligne chargée est considérée comme la moins récemment utilisée ou la plus récemment utilisée. La décision se fait selon les voies statiques qui ont le plus de défauts de cache : si les voies "moins récemment utilisée" ont plus de défauts de cache que les autres, on ne l'utilise pas et inversement. Il suffit d'utiliser un simple compteur incrémenté ou décrémenté lors d'un défaut de cache dans une voie utilisant ou non l’optimisation. ==Quand précharger ?== Une problématique importante est de savoir quand précharger des données. Si on précharge des données trop tard ou trop tôt, le résultat n'est pas optimal. Pour résoudre au mieux ce problème, il existe deux grandes solutions : le préchargement sur événement et le préchargement par prédiction. Le '''préchargement par prédiction''' essaie de prédire le moment adéquat pour précharger, quitte à se tromper où à donner une réponse inadéquate. Le ''prefetecher'' décide de précharger ou non, en se basant sur l'historique des accès précédents. Il en déduit des statistiques qui permettent de savoir quand précharger. Par exemple, ils peuvent calculer le temps d'accès moyen entre un accès mémoire et un préchargement, et armer des chronomètres pour initialiser le préchargement en temps voulu. Les algorithmes pour cela sont assez variés, mais aussi assez compliqués. Le '''préchargement sur événement''' consiste à précharger quand certains événements spéciaux ont lieu. Elle s'enclenche quand un évènement bloque le processeur, dans le sens où le programme est bloqué à un instant précis. Par exemple, on peut précharger à chaque défaut de cache, à chaque accès mémoire, lors de l’exécution d'un branchement (pour le préchargement des instructions), etc. Dans ce qui suit, nous allons surtout détailler le préchargement sur évènement. ===Le préchargement associé aux accès mémoire=== Le préchargement par évènement le plus simple est celui initié par les accès mémoire. Reste à voir quels accès mémoire prendre en compte. La technique la plus simple effectue un préchargement lors de chaque accès mémoire. L'idée est que lorsqu'on accède à un bloc de mémoire, on précharge le bloc suivant de manière systématique, lors de chaque accès mémoire. Et mine de rien, cela ne fonctionne pas trop mal. Par contre, le préchargement accède beaucoup à la RAM, parfois pour précharger des données inutiles. Une optimisation serait de filtrer certains accès mémoires, à savoir que certains ne déclencheront pas de préchargement. Le choix des accès mémoire à filtrer doit se faire de manière judicieuse, elle doit identifier les accès mémoires inutiles. Une solution pour cela est de ne précharger que lors d'un défaut de cache. Ainsi, si j'ai un défaut de cache qui me force à charger un bloc dans le cache, le ''prefetcher'' chargera les blocs consécutifs suivants avec. On filtre alors beaucoup d'accès mémoires, et le préchargement est généralement assez efficace. Une solution précharge non pas lors d'un défaut de cache, mais lors d'un succès de cache uniquement. A chaque accès d'une ligne de cache, l'idée est de précharger la suivante. Reste à expliquer ce qu'on veut dire avec la suivante. La ligne de cache accédée correspond à un bloc en mémoire RAM, appelons-le le bloc A. Par ligne de cache suivante, on veut dire le bloc suivant en mémoire RAM, celui situé après le bloc A. Pour cela, on mémorise quelle est la dernière ligne de cache qui a été accédée. Il suffit d'ajouter un bit par ligne de cache, qui indique si cette ligne a été accédée lors du dernier cycle d'horloge. Il est automatiquement mis à un lors d'un accès à une ligne de cache. Il est remis à zéro dans deux conditions : lors d'un accès à une autre ligne de cache, au bout d'un certain temps. Le ''prefetcher'' se contente de charger le bloc qui suit la ligne de cache dont le bit vaut 1. [[File:Préchargement anticipé.png|centre|vignette|upright=2|Préchargement anticipé.]] ===Le préchargement anticipé (''runahead prefetching'')=== La technique du '''préchargement anticipé''' (''runahead prefetching'') effectue du préchargement sur un défaut de cache, et seulement pour les lectures. Le défaut de cache est censé bloquer totalement le processeur, tant qu'il n'est pas résolu. Et le processeur est censé reprendre l'exécution du programme, une fois la donnée chargée dans les registres. L'idée du préchargement anticipé est que le processeur ne se bloque pas, mais continue l'exécution du programme alors qu'il ne devrait pas. Il rentre dans un mode ''runahead '' dans lequel il exécute le programme en avance. Les instructions qui suivent la lecture s'exécutent, y compris d'autres instructions de lecture. Les instructions vont alors générer des adresses, des lectures vont être déclenchées, etc. Et le principe est que les lectures seront exécutées en mode ''runahead'', donc en avance. Les lectures en question, exécutées en mode ''runahead'' sont appelées des '''lectures anticipées'''. Les lectures anticipées lisent des données dans le cache de données, ce qui précharge les données associées. Mais ce que fait le processeur en mode ''runahead'' est totalement annulé une fois que le défaut de cache est résolu, le processeur revient à la normale, à l'état antérieur. Une seule chose n'est pas effacée : les données et instructions préchargées dans le cache. Pour le reste, les registres sont remis à leur état antérieur. Pour cela, le processeur sauvegarde les registres en entrant en mode ''runahead'', puis les restaure une fois le défaut de cache terminée. De même, les écritures en mémoire RAM ne sont pas exécutées en mode ''runahead''. Pas question d'écrire des données potentiellement fausses en RAM. : Notons que cette technique n'a de sens que sur des processeurs dits à émission dans l'ordre. Nous verrons dans la suite du cours ce que sont les processeurs à exécution dans l'ordre et dans le désordre, mais sachez que dès qu'un processeur a de l'exécution dans le désordre, la technique devient totalement inutile. De même certains processeurs à exécution dans l'ordre n'ont pas besoin de cette technique, s'ils gèrent les lectures non-bloquantes. En pratique, seuls quelques processeurs dits VLIW incorporent cette technique, et ils sont rares. Un problème de cette technique est la gestion des dépendances avec les lectures. Généralement, une lecture est suivie d'instructions qui utilisent son résultat. Une lecture charge une donnée dans un ''registre de destination'', qui est lu par d'autres instructions dépendantes. En mode ''runahead'', ces instructions lisent le registre de destination, mais la valeur ne sera pas la bonne, car la lecture n'a pas encore eu lieu. En soi, cela ne pose pas de problèmes, vu que le processeur restaure son état normal en sortie du mode ''runahead''. Un problème survient cependant quand ces instructions sont elle-mêmes être des lectures. De telles lectures prendront une adresse/opérande erronée, ce qui fait qu'elles ne liront pas la donnée correcte, mais une donnée qui n'avait pas à être lue. En conséquence, elles vont précharger des données invalides, qui n'auraient pas dû l'être. Le préchargement perd alors en efficacité. Une solution possible serait d'interrompre le mode ''runahead'' quand le processeur détecte ce genre de situation, le processeur restaure son état antérieur et attend la fin du défaut de cache. Une solution alternative marque le registre de destination comme invalide. Lors d'un défaut de cache en lecture, le registre de destination est marqué comme invalide, et cette valeur invalide se propage d'instruction en instruction. Typiquement, dès qu'une instruction lit un registre invalide, elle marquera son registre de résultat comme invalide. Les adresses marquées comme invalides ne sont pas utilisées pour démarrer des lectures ou écritures, afin d'économiser des préchargements inutiles. Pour marquer les registres comme invalides, il suffit de leur rajouter un bit ''invalid'' qui indique si le résultat est valide ou non. Il faut aussi gérer la propagation du statut invalide d'une instruction à l'autre. Cela ne pose aucun problème pour les instructions dont les opérandes et la destination sont des registres : si une opérande a un statut invalide, la destination l'est aussi. Pour les autres instructions, les règles pour propager les valeurs invalides d'une instruction à l'autre sont assez complexes, surtout quand on prend en compte les écritures. Il faut aussi gérer les branchements et autres, mais passons. L'essentiel est que toutes les instructions qui dépendent de la lecture, directement ou indirectement, soient marquées invalides. Un autre problème survient dans un cas bien précis : une dépendance entre une lecture anticipée et une écriture anticipée. Le cas est celui où l'écriture anticipée écrit une donnée en mémoire, qui est lue par une lecture anticipée. Vu que l'écriture anticipée n'est pas exécutée, la lecture anticipée aura un registre de destination invalide, idem pour ses instructions dépendantes. Pourtant, c'est un cas où la lecture anticipée aurait pu s'exécuter en avance et précharger la bonne donnée. Mais les implémentations plus élaborées gèrent naturellement ce genre de cas. Pour cela, il faut marquer les lignes de cache comme valides ou invalides, selon les opérandes des écritures. Une lecture marquera son registre de destination comme valide si la ligne de cache lue est valide, comme invalide si elle l'est ou qu'elle déclenche un défaut de cache. Une autre solution est de détourner les écritures dans un cache séparé, spécifique au mode ''runahead'', dans lequel les écritures en mode ''runahead'' écrivent. : Notons que cette technique demande dans l'idéal d'utiliser des techniques de prédiction de branchement et les circuits associés, qui sont censés être vus dans les chapitres ultérieurs. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les mémoires cache | prevText=Les mémoires cache | next=Le Translation Lookaside Buffer | nextText=Le Translation Lookaside Buffer }} </noinclude> 50fo2bnslcft15trxy41zqvcgebxqo1 Fonctionnement d'un ordinateur/La désambiguïsation mémoire 0 65845 764638 762198 2026-04-23T13:37:29Z Mewtow 31375 /* La modèle de consistance des processeurs x86 */ 764638 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 utilisee 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. A 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> cgu6b0ng78iua3jr35lfz7img6uv4ot Fonctionnement d'un ordinateur/Le parallélisme mémoire 0 65888 764635 764598 2026-04-23T13:37:03Z Mewtow 31375 Mewtow a déplacé la page [[Fonctionnement d'un ordinateur/Le parallélisme mémoire au niveau du cache]] vers [[Fonctionnement d'un ordinateur/Le parallélisme mémoire]] : Elargissement du sujet du chapitre 764598 wikitext text/x-wiki Dans ce chapitre, nous allons voir les techniques qui permettent de gérer plusieurs accès mémoire simultanés directement au niveau du cache ou de la mémoire RAM. Par plusieurs accès mémoire simultanés, vous pensez sans doute à l'usage de cache multiports, voire de mémoires RAM multiport. C'est en effet une solution, mais ce n'est clairement pas la seule. Par exemple, il est possible de pipeliner l'accès au cache, voire à la mémoire RAM. Il existe de nombreuses techniques de '''parallélisme mémoire''', et nous allons les voir dans ce chapitre. Le parallélisme mémoire est utile aussi bien sur des processeurs à émission dans l'ordre que dans le désordre. Même les processeurs à émission dans l'ordre peuvent lancer des accès mémoire à chaque cycle. Par exemple, un processeur avec des lectures non-bloquantes peut techniquement lancer une seconde lecture, après avoir rencontré une première lecture non-bloquante. En général, le nombre de lectures consécutives est cependant limité à moins d'une dizaine. Les processeurs sans lectures non-bloquantes sont eux beaucoup plus limités. A la rigueur, l'usage du préchargement peut profiter du parallélisme mémoire : pendant que le processeur exécute une lecture/écriture, il peut précharger une autre donnée en parallèle. Inutile de dire que sans pipeline processeur, le parallélisme mémoire ne sert pas à grand chose. ==Le ''line fill buffer'' et les techniques associées== Lors d'un défaut de cache, le processeur doit attendre que toute la ligne de cache soit chargée avant d'être utilisable. Or, la taille d'une ligne de cache est supérieure à la largeur du bus mémoire, ce qui fait qu'une ligne de cache est chargée en plusieurs fois, morceaux par morceaux, mot mémoire par mot mémoire. Le chargement peut se faire directement dans le cache, mais ce n'est pas une solution très pratique. À la place, beaucoup de processeurs ajoutent une mémoire tampon entre la RAM et le cache, appelée le '''tampon de remplissage de ligne''' (''line-fill buffer'') dans les processeurs modernes. Lors d'un défaut de cache, le processeur charge la donnée de la RAM dans le tampon de remplissage de ligne, mot mémoire par mot mémoire. Une fois plein, le tampon de remplissage de ligne est recopié dans la ligne de cache. Le tampon de remplissage de ligne contient une ligne de cache, si ce n'est que certains bits de contrôle ne sont pas présents. Le tampon de remplissage de ligne contient un bit de validité pour chaque mot mémoire de la ligne de cache, qui indiquent si le mot mémoire a été chargé. Par exemple, prenons un processeur 64 bits, qui gère donc des mots mémoire de 8 octets, avec des lignes de cache 256 octets/32 mots mémoire. Le tampon de remplissage de ligne contiendra 32 bits de validité. Si le processeur a chargé les 6 premiers mots mémoire, les 6 premiers bits de validité seront mis à 1, les autres seront encore à zéro. Il faut noter que la disponibilité de la ligne complète se détermine assez facilement en faisant un ET logique entre tous les bits de validité. Le processeur sait ainsi quand la ligne de cache est disponible entièrement et donc quand la transférer dans le cache. La même chose existe avec une hiérarchie de cache, sauf que l'on trouve une mémoire tampon entre chaque niveau de cache. Il y a un tampon de remplissage de ligne entre le cache L1 et le cache L2, entre le cache L2 et le L3, etc. Si le cache ne gère qu'un seul défaut de cache à la fois, le ''fill line buffer'' est une mémoire très simple, qui ne mémorise qu'une seule ligne de cache. Et cela vaut aussi bien pour un cache bloquant que non-bloquant. Mais sur les caches capables de gérer plusieurs défauts simultanés, le tampon de remplissage de ligne est une mémoire de type FIFO ou LIFO, capable de mémoriser plusieurs lignes de cache. Notons que le tampon de remplissage de ligne est très utile pour implémenter certaines techniques, par exemple le contournement du cache. Nous avions vu dans le chapitre sur le cache que certains accès mémoire doivent contourner le cache, pour des raisons de cohérence des caches. C'est notamment nécessaire pour accéder aux périphériques, mais c'est aussi utile pour des raisons de performances dans des cas très spécifiques. Les accès qui contournent le cache se font directement dans le tampon de remplissage de ligne : le processeur écrit ou lit les données depuis ce tampon de remplissage de ligne, sans accéder au cache. ===L'''early restart'' et le ''critical word load''=== La présence du ''line buffer'' permet une optimisation assez intéressante, qui permet de réduire la latence des défauts de cache. L'optimisation consiste à lire un mot mémoire dans le tampon de remplissage de ligne, même si la ligne de cache complète n'a pas encore été chargée. Il existe deux manières de faire cela, qui portent les noms d'''early restart'' et de ''critical word load''. La première est la version la plus simple, la seconde est plus complexe mais plus performante. Avec la technique d''''''early restart''''', la ligne de cache est chargée normalement, en partant de son premier mot mémoire. Dès que le mot mémoire lu/écrit par le processeur est copié dans le ''line fill buffer'', il est envoyé au processeur immédiatement. Illustrons le tout par un exemple, où une ligne de cache fait 16 mots mémoire. Le processeur effectue une lecture, qui lit le 5ème mot mémoire. Sans ''early restart'', le processeur doit charger les 16 mots mémoire avant de faire la lecture dans le cache. Avec ''early restart'', le processeur reçoit la donnée dès que le 5ème mot mémoire est disponible. Le processeur doit attendre que les 4 premiers mots mémoire soient chargés, puis le 5ème mot mémoire arrive et est envoyé directement au processeur, il est lu directement depuis le ''line fill buffer''. Les 11 mots mémoire suivants sont ensuite chargés dans le cache pendant que le processeur fait des calculs dans son coin. Le ''critical word load'' est une optimisation de la technique précédente où le chargement de la ligne de cache commence directement à la donnée demandée par le processeur. Pour reprendre l'exemple précédent, où le processeur demande le 5ème mot mémoire sur 16, le ''critical word load'' charge le 5ème mot mémoire en premier et l'envoie au processeur, ce qui fait qu'il est chargé très rapidement. Pas besoin d'attendre que le processeur charge les 4 mots mémoire précédents comme avec l'''early restart''. Dans le détail, le processeur charge le 5ème mot mémoire en premier, puis charge les 11 suivants, et termine par les 4 mots mémoire du début. En clair, le ''critical word load'' commence par charger le mot mémoire lu, puis les blocs suivants, avant de revenir au début du bloc pour charger les blocs restants. Ainsi, la donnée demandée par le processeur sera la première disponible. Pour cela, l'organisation du tampon de remplissage de ligne est modifiée de manière à rendre cela possible. Il a une taille égale à une ligne de cache complète, qui contient elle-même plusieurs mots mémoire. Dans le ''line-fill buffer'', chaque mot mémoire est stocké avec un tag, qui indique l'adresse du mot mémoire stocké dans le ''line-fill buffer''. Le ''line-fill buffer'' est donc un cache un peu particulier, qui fonctionne comme un cache du point de vue du processeur, comme une mémoire FIFO pour les transferts avec le cache. Ainsi, un processeur qui veut lire dans le cache après un défaut peut accéder à la donnée directement depuis le tampon de remplissage de ligne, alors que la ligne de cache n'a pas encore été totalement recopiée en mémoire. ==Les caches pipelinés== Il est possible de pipeliner l'accès mémoire sur plusieurs cycles, avec plusieurs étages MEM consécutifs. La méthode la plus simple pipeline l'accès au cache, l'autre pipeline l'accès à la mémoire en utilisant des mémoires entrelacées. Pipeliner la mémoire est une méthode assez ancienne, utilisée sur les anciens ordinateurs historiques, qui ne disposaient pas de cache, mais avaient bien un pipeline (et parfois, de l'exécution dans le désordre et du renommage de registres). Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, même ceux avec un pipeline dynamique. Beaucoup de processeurs des années 2000 avaient une fréquence peu élevée comparée aux standards d'aujourd'hui, ce qui fait que leur cache avait bien un temps d'accès d'un cycle d'horloge. Mais l'accès au cache se faisait en deux cycles d'horloge : un cycle pour calculer une adresse et un cycle pour l'accès mémoire proprement dit. Et ces deux étapes étaient pipelinées, à savoir que deux micro-opérations mémoire peuvent s'exécuter en même temps : une dans l'étage de calcul d'adresse, une autre dans le cache. L'unité mémoire était donc pipelinée, alors que l'accès au cache ne l'était pas. Les processeurs qui implémentaient cette technique regroupent les micro-architectures K5 et K6 d'AMD, les processeurs Intel de micro-architecture P6 (Pentium 2 et 3) et quelques autres. Il est aussi possible de pipeliner l'accès au cache lui-même. Avec un cache pipeliné, l'accès au cache ne se fait pas en un seul cycle, mais en plusieurs. Cependant, on peut lancer un nouvel accès au cache à chaque cycle d'horloge, comme un processeur pipeliné lance une nouvelle instruction à chaque cycle. L'implémentation est assez simple : il suffit d'ajouter des registres dans le cache. Pour cela, on profite que le cache est composé de plusieurs composants séparés, qui échangent des données dans un ordre bien précis, d'un composant à un autre. De plus, le trajet des informations dans un cache est linéaire, ce qui les rend parfois pour l'usage d'un pipeline. ===Le ''pipelining'' des caches ''direct-mapped''=== Voici ce qui se passe avec un cache directement adressé. Pour rappel ce genre de cache est conçu en combinant une mémoire RAM, généralement une SRAM, avec quelques circuits de comparaison et un MUX. L'accès au cache se fait globalement en deux étapes : la première lit la donnée et le ''tag'' dans la SRAM, et on utilise les deux dans une seconde étape. Nous avions vu il y a quelques chapitre comment pipeliner des mémoires, dans le chapitre sur les mémoires évoluées. Et bien ces méthodes peuvent s'utiliser pour la mémoire RAM interne au cache !L'idée est d'insérer un registre entre la sortie de la RAM et la suite du cache, pour en faire un cache pipéliné, à deux étages. Le premier étage lit dans la SRAM, le second fait le reste. L'implémentation sur les caches associatifs à plusieurs voies est globalement la même à quelques détails près. [[File:Cache directement adressé pipeliné - deux étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - deux étages]] Avec le circuit précédent, il est possible d'aller plus loin, cette fois en pipelinant l'accès à la mémoire RAM interne au cache. Pour comprendre comment, rappelons qu'une mémoire SRAM est composée d'un plan mémoire et d'un décodeur. L'accès à la mémoire demande d'abord que le décodeur fasse son travail pour sélectionner la case mémoire adéquate, puis ensuite la lecture ou écriture a lieu dans cette case mémoire. L'accès se fait donc en deux étapes successives séparées, on a juste à mettre un registre entre les deux. Il suffit donc de mettre un gros registre entre le décodeur et le plan mémoire. [[File:Cache directement adressé pipeliné - trois étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - trois étages]] Et on peut aller encore plus loin en découpant le décodeur en deux circuits séparés. En effet, rappelez-vous le chapitre sur les circuits de sélection : nous avions vu qu'il est possible de créer des décodeurs en assemblant des décodeurs plus petits, contrôlés par un circuit de prédécodage. Et bien on peut encore une ajouter un registre entre ce circuit de prédécodage et les petits décodeurs. : Théoriquement, toute l'adresse est fournie d'un seul coup au cache, la quasi-totalité des processeurs présentent l'adresse complète à un cache pipeliné. Mais le Pentium 4 fait autrement. Il faut noter que les premiers étages manipulent l'indice dans la SRAM, qui est dans les bits de poids faible, alors que les étages ultérieurs manipulent le ''tag'' qui est dans les bits de poids fort. Les concepteurs du Pentium 4 ont alors décidé de présenter les bits de poids faible lors du premier cycle d'accès au cache, puis ceux de poids fort au second cycle. Pour cela, l'ALU fonctionnait à une fréquence double de celle du processeur, tout comme le cache L1. Il n'y avait pas de pipeline proprement dit, mais cela réduisait grandement la latence d'accès au cache. ===Le ''pipelining'' des caches splittés/sériels=== Il est aussi possible de pipeliner un cache dits splittés, aussi appelés à accès sériel. Pour rappel, les caches à accès sériel vérifient si il y a succès ou défaut de cache, avant d'accéder aux lignes de cache en cas de succès. Ils font donc différemment des autres caches, qui accèdent à une ligne de cache, avant de déterminer s'il y a succès ou défaut en lisant le tag de la ligne de cache. Les caches sériels disposent de deux SRAM : une pour les tags des lignes de cache et une pour les données. Ils accèdent à la SRAM pour les tags, avant d’accéder à la SRAM des données en cas de succès de cache. Vu que l'accès se fait en deux étapes, une vérification des tags suivie de la lecture/écriture des données, il est facile à pipeliner. Pipeliner le cache permet de régler le problème des accès au cache L1, et elle est tout le temps utilisé sur les processeurs modernes. Mais que faire en cas de défaut de cache ? ==Les caches non bloquants== Un '''cache bloquant''' est un cache auquel le processeur ne peut pas accéder pendant un défaut de cache. Il faut attendre que la lecture ou écriture en RAM soit terminée avant de pouvoir utiliser de nouveau le cache. Un '''cache non bloquant''' n'a pas ce problème : on peut l'utiliser pendant un défaut de cache. Les caches non bloquants permettent de démarrer une nouvelle lecture ou écriture alors qu'une autre est en cours, ce qui permet d'exécuter plusieurs lectures ou écritures en même temps. ===Les ''Miss Handling Status Registers''=== Lors d'un défaut de cache, la mémoire RAM est consultée pendant le défaut de cache, mais le cache est inutilisé. Un défaut de cache n'utilise pas le cache, ce n'est qu'un accès en mémoire RAM, sur le bus mémoire (ou un accès aux niveaux de cache inférieurs, peu importe). Le cache en lui-même est laissé libre, rien n’empêche d'y accéder, il est en réalité intrinsèquement non-bloquant. Les caches, bloquants comme non-bloquants, sont en réalité composés d'une mémoire cache proprement dite, entourée de circuits qui servent d'interface entre le processeur et le cache lui-même. Et parmi les circuits tout autour, certains gèrent l'accès au cache lors d'un défaut de cache. Ils sont regroupés sous le terme de '''''Miss Handling Architecture''''' (MHA). La différence entre un cache bloquant et un cache non-bloquant est en réalité liée à l'implémentation de la MHA. Les caches bloquants coupent volontairement l'accès au cache lors d'un défaut, car les défauts sont plus simples à gérer ainsi. Le défaut de cache rapatrie une donnée depuis la RAM, qui sera écrite dans le cache. Et il ne faut pas qu'une tentative d'accès à cette donnée ait lieu avant qu'elle ne soit chargée. Mais ce blocage est général et touche tout le cache, alors que seule une ligne de cache est concernée par le défaut de cache. L'idée derrière un cache non-bloquant est que seule la ligne de cache est bloquée, mais les autres sont accessibles. L'idée est alors de mémoriser les lignes de cache concernées par le défaut de cache, afin d'en bloquer l'accès. À chaque accès, on vérifie si la ligne de cache est déjà réservée par un défaut de cache. La lecture/écriture est alors bloquée si c'est le cas, mais elle accepte les accès sinon. Pour cela, la ''Miss Handling Architecture'' contient des registres qui mémorisent des informations sur les défauts de cache en cours. Ils portent le nom de '''''miss status handling registers''''', que j’appellerais dorénavant MSHR, qui sont aussi appelés des '''''miss buffer'''''. Le contenu des MSHR varie beaucoup suivant le processeur, mais ces derniers stockent au minimum les informations suivantes : * Le numéro de la ligne de cache dans laquelle les données sont chargées. * Un bit de validité qui indique si le MSHR est vide ou pas, qui est mis à 0 quand le défaut de cache est résolu. * Un ou plusieurs champs de lecture/écriture, qui contiennent des informations sur la lecture/écriture. ** Pour une lecture, elle contient des informations sur la destination de la donnée, à savoir qui prévenir quand le défaut de cache est terminé. C'est parfois un nom/numéro de registre (celui dans lequel charger la donnée), mais c'est souvent le numéro de l'entrée dans le ''load/store queue''. ** Pour les écritures, elle contient la donnée à écrire, ou éventuellement un numéro de ''load/store queue'' où se trouve la donnée à écrire. Il faut noter que le bus mémoire ne peut gérer qu'un seul défaut de cache à la fois. Aussi, il est intéressant de regarder ce qui se passe lorsqu'un second défaut de cache survient, pendant qu'un premier est en cours. Dans ce cas, il y a deux réponses qui correspondent à deux types de caches non-bloquants, qui portent les noms barbares de caches de type succès après défaut et défaut après défaut. Sur le premier type, il ne peut pas y avoir plusieurs défauts de cache simultanés. Dès qu'un second défaut de cache survient, le cache stoppe son activité et on ne peut plus démarrer de nouvelle lecture/écriture, tant que le premier défaut de cache n'est pas résolu. Le second type est plus souple et autorise la survenue de plusieurs défauts de cache simultanés. Du moins, jusqu'à une certaine limite, car le cache ne peut supporter qu'un nombre limité d'accès mémoires simultanés (pipelinés). ===Les accès simultanés à une même ligne de cache=== Il arrive que le processeur fasse plusieurs accès mémoire simultanés à la même ligne de cache. Si la ligne de cache en question n'a pas encore été chargée dans le cache, alors on a plusieurs défauts de cache consécutifs pour la même ligne de cache, et le cache non-bloquant doit gérer la situation. Pour la suite, il va falloir faire une petite distinction entre les défauts primaires et secondaires. Imaginons qu'un défaut de cache ait lieu et demande à charger une donnée dans la ligne de cache numéro N. Il s'agit du premier défaut impliquant cette ligne de cache précise, ce qui lui vaut le nom de '''défaut de cache primaire'''. Mais par la suite, d'autres accès mémoire à la même ligne de cache ont lieu, alors que la ligne de cache n'est pas encore disponible. Dans ce cas, il s'agit de '''défauts de cache secondaires'''. Pour l'unité d'accès mémoire, les défauts de cache primaire et secondaire sont différents (ils prennent tous une entrée dans la ''load/store queue''). Mais pour le cache, ils ne correspondent qu'à un seul accès au cache : celui qui demande de charger la ligne de cache demandée. Les défauts de cache primaire et secondaire à la même ligne de cache se voient attribuer un MSHR unique. La gestion des défauts de cache secondaires dépend du cache non-bloquant. La solution la plus simple ne permet pas les défauts de cache secondaires. Le cache ne permet pas deux défauts de cache simultanés pour la même ligne de cache. Les autres solutions le permettent, en fusionnant des accès simultanés à la même ligne de cache en un seul au niveau des MSHR. Dans tous les cas, détecter les défauts de cache secondaires sont un problème qu'il faut détecter. La ''Miss Handling Architecture'' doit détecter les défauts de cache secondaire. Pour cela, elle procède comme suit. Lors de chaque défaut de cache, la MHA récupère le numéro de la ligne de cache associé. Il vérifie alors chaque MSHR pour vérifier s'il contient le numéro en question. S'il n'y a aucune correspondance dans les MSHR, alors c'est signe que le défaut de cache est un défaut primaire. Mais s'il y en a une, alors c'est un défaut secondaire. Évidemment, cela signifie que lors d'un défaut de cache, le numéro de ligne de cache est envoyé à tous les MSHR, pour comparaison. Les MSHR sont donc regroupés dans une mémoire associative, une sorte de mini-cache, faciliter l'implémentation. Lors d'un défaut de cache primaire, l'accès à un cache non-bloquant se fait comme suit : le processeur envoie une adresse au cache, accède à celui-ci, et détecte la survenue d'un défaut de cache. Il en profite alors pour attribuer une ligne de cache dans laquelle sera chargée ce défaut. L'attribution est très simple dans le cas des caches ''direct mapped'', ou associatifs par voie, pour lesquels l'attribution se fait assez simplement. Il mémorise alors cette information dans les MSHR, après avoir vérifié que le défaut de cache n'était pas un défaut secondaire. Lors des accès ultérieurs à une adresse proche, censée être dans la même ligne de cache, le processeur va encore une fois rencontrer un défaut de cache. Il va alors déterminer le numéro de la ligne de cache associée à l'adresse, et comparer ce numéro avec les MSHRs. Si un MSHR contient ce numéro, c'est signe que le défaut de cache est un défaut secondaire. La MHA réagit alors différemment selon le processeur considéré. Une première solution n'autorise pas les défauts de cache secondaires. Si l'un d'entre eux survient, le processeur est gelé par un ''pipeline stall'', une bulle de pipeline. Une autre solution fusionne les défauts de cache secondaires avec le défaut de cache primaire : tout cela ne correspond qu'à un seul défaut de cache pour lui. ====Les MSHR simples==== Un cache non-bloquant à '''MSHR simple''' contient juste plusieurs MSHR qui mémorisent juste un numéro de ligne de cache, un bit de validité, le champ de lecture/écriture, et l'adresse exacte de lecture/écriture. Avec cette organisation, il est possible d'avoir plusieurs défauts de cache séparés, mais à la condition que chaque défaut accède à une ligne de cache différente. Deux accès simultanés à une même ligne de cache ne sont pas possibles, les défauts de cache secondaires ne sont pas autorisés. Ainsi, chaque défaut de cache se voit attribuer son propre MSHR, chacun contient un numéro de ligne de cache différent. Pour comprendre pourquoi c'est impossible de gérer les défauts secondaires, il faut regarder le champ de lecture/écriture. Si on veut effectuer plusieurs écritures consécutives à la même adresse, le MSHR n'aura pas de quoi mémoriser les deux données à écrire. Il pourra mémoriser la première donnée à écrire, pas la seconde. Même chose lors d'une lecture : le champ lecture/écriture peut mémorisr la destination de la première lecture, pas de la seconde. L'avantage est que la MHA n'a besoin que des MSHR et de quelques circuits annexes. Les autres solutions rajoutent des circuits annexes pour gérer les défauts de cache secondaires, qui utilisent beaucoup de circuits. Le cout en circuit est donc élevé, mais le gain en performance est là. Passons maintenant aux caches non-bloquants qui autorisent les défauts de cache secondaire. La solution la plus simple consiste à utiliser ====Les MSHR adressés implicitement==== Avec les '''MSHR adressés implicitement''', il est possible de fusionner plusieurs accès mémoire à une même ligne de cache, mais sous une condition très importante : ces accès lisent/écrivent des mots mémoire différents. Par exemple, imaginons qu'une ligne de cache contienne 8 mots mémoire de 64 bits. Si un premier accès mémoire lit le mot mémoire numéro 7 (dernier mot mémoire de la ligne), et le second accès le mot mémoire numéro 3, alors la fusion est possible. Mais si deux accès mémoire veulent lire/écrire le mot mémoire numéro 7, alors la fusion n'est pas possible et le processeur se bloque, un ''pipeline stall'' survient. Un MSHR adressé implicitement est un MSHR simple, qui contient naturellement un numéro de ligne de cache (tag) et un bit de validité, sauf que le champ de lecture/écriture est dupliqué. Les différents champs lecture/écriture d'un MSHR sont regroupés dans une mémoire RAM/cache qui contient autant d'entrées qu'il y a de mot mémoire dans une ligne de cache. Chaque entrée de la table est associée à un mot mémoire de la ligne de cache et stocke des informations sur celui-ci. Une entrée mémorise, au minimum : * Un champ de lecture/écriture qui contient soit la destination de la lecture, soit la donnée à écrire. * Un bit R/W permettant d'interpréter correctement le champ de lecture/écriture. * Un bit de validité pour chaque entrée de la table, qui dit si un défaut de cache antérieur accède déjà à ce mot mémoire. La fusion de deux défauts de cache est ainsi assez simple. Un défaut de cache primaire/secondaire configure l'entrée associée au mot mémoire lu/écrit. Si un défaut secondaire ultérieur lit/écrit un mot mémoire différent (dont le bit de validité de l'entrée dans la table est à zéro), pas de conflit : il configure une autre entrée, vide. Mais s'il lit/écrit un mot mémoire pour lequel l'entrée de la table est occupée, il y a conflit, le processeur est bloqué par un ''pipeline stall'', une bulle de pipeline. Avec cette organisation, le nombre de MSHR indique combien de lignes de cache peuvent être lues en même temps depuis la mémoire. Quant au nombre d'entrées par MSHR, il détermine combien d'accès mémoires qui ne se recouvrent pas peuvent avoir lieu en même temps. ====Les MSHR adressés explicitement==== Les '''MSHR adressés explicitement''' n'ont pas les contraintes des MSHR adressés implicitement. Avec eux, il est possible d'avoir plusieurs défauts de cache pointant vers la même ligne de cache, mais aussi vers le même mot mémoire. De tels défauts de cache apparaissent sur les processeurs à exécution dans le désordre, mais sont très rares, voire inexistants, sur les processeurs ''in-order''. L'idée est encore que chaque MSHR est associée à une table mémoire qui mémorise des entrées. Cette table n'est autre qu'une mémoire FIFO. Sauf que cette fois-ci, une entrée n'est pas associée à un mot mémoire. Une entrée est associée à un défaut de cache. Une entrée mémorise là encore la destination de la lecture ou la donnée de l'écriture suivi du bit R/W, ainsi que d'un bit de validité par entrée, mais aussi : la position du mot mémoire lu dans la ligne de cache. C'est cette dernière information qui n'était pas présente dans les MSHR adressés implicitement. De plus, le nombre d'entrée par MSHR n'est pas égal au nombre de mots mémoires dans une ligne de cache, mais peut être arbitrairement grand. L'avantage d'un tel cache est qu'il est capable de traité les défauts secondaires concernant un même mot mémoire, et ce, car les accès à la ligne de cache concernée se produisent dans l'ordre de génération des défauts (la table mémoire est une FIFO). '''Les MSHRs inversés''' Généralement, plus on veut supporter de défauts de cache, plus le nombre de MSHR et d'entrées augmente. Mais au-delà d'un certain nombre d'entrées et de MSHR, les MSHR adressés implicitement et explicitement ont tendance à bouffer un peu trop de circuits. Utiliser une organisation un peu moins gourmande en circuits est donc une nécessité. Cette organisation plus économe se base sur des MSHR inversés. Les MSHR inversés ne contiennent qu'une seule entrée, en quelque sorte : au lieu d’utiliser n MSHR de m entrées chacun, on va utiliser n × m MSHR inversés. La différence, c'est que plusieurs MSHR peuvent contenir un tag identique, contrairement aux MSHR adressés implicitement et explicitement. Lorsqu'un défaut de cache a lieu, chaque MSHR est vérifié. Si jamais aucun MSHR ne contient de tag identique à celui utilisé par le défaut, un MSHR vide est choisi pour stocker ce défaut, et une requête de lecture en mémoire est lancée. Dans le cas contraire, un MSHR est réservé au défaut de cache, mais la requête n'est pas lancée. Quand la donnée est disponible, les MSHR correspondant à la ligne qui vient d'être chargée vont être utilisés un par un pour résoudre les défauts de cache en attente. ====Les MSHR intégrés au cache==== Certains chercheurs ont remarqué que pendant qu'une ligne de cache est en train de subir un défaut de cache, celle-ci reste inutilisée, et son contenu est destiné à être perdu une fois le défaut de cache résolu. Ils se sont dit que, plutôt que d'utiliser des MSHR séparés, il vaudrait mieux utiliser la ligne de cache pour stocker les informations sur les défauts de cache en attente dans cette ligne de cache. Pour éviter tout problème, il faut rajouter un bit dans les bits de contrôle de la ligne de cache, qui sert à indiquer que la ligne de cache est occupée : un défaut de cache a eu lieu dans cette ligne, et elle stocke donc des informations pour résoudre les défauts de cache. ==L'entrelacement mémoire== Nous venons de voir comment une mémoire cache peut gérer plusieurs accès mémoire simultanés. Il se trouve que ce genre d'optimisation dépasse largement le cadre du cache. Les mémoires RAM ne sont pas en reste, elles aussi ! Il est possible de pipeliner l'accès à une mémoire RAM, en utilisant des techniques dites d''''entrelacement'''. Précisons cependant que cette optimisation n'est pas utilisée sur les mémoires SDRAM ou DDR modernes, du moins pas sans modifications majeures. Mais divers ordinateurs assez anciens, dont des superordinateurs, ont utilisé cette technique. La technique demande d'utiliser plusieurs mémoires séparées. Sur les anciens ordinateurs, les mémoires en question sont des chips mémoire, à savoir des circuits intégrés de DRAM. Pour faire plus général, nous allons utiliser le terme de '''banques''', ou encore de bancs mémoire. La différence est qu'il existe des mémoires multi-banque, qui regroupent plusieurs banques indépendantes dans un seul boitier, dans un seul chip mémoire. En clair, de telles mémoires regroupent plusieurs sous-mémoires dans un seul circuit intégré. Il est aussi possible d'utiliser plusieurs chips mémoire séparés, chacun ayant une banque. Reste à combiner ces banques pour former un pseudo-pipeline. ===L'entrelacement classique=== Sans optimisation particulière, les adresses sont réparties dans les banques comme indiqué ci-dessous. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Avec l''''entrelacement classique''', des adresses consécutives sont réparties dans des banques différentes. [[File:Répartition des adresses dans une mémoire interleaved.png|centre|vignette|upright=1.5|Répartition des adresses dans une mémoire interleaved.]] L'entrelacement simple permet d'accélérer les accès mémoire qui se font à des adresses consécutives. Avec l'entrelacement, chaque accès mémoire tombe dans une banque mémoire différente, ce qui fait qu'on peut démarrer un nouvel accès mémoire à chaque cycle d'horloge. Pendant qu'une banque est occupée par un accès mémoire, on démarre le suivant dans une autre banque, et ainsi de suite. Le tout se termine soit quand on a épuisé toutes les banques libres, soit quand l'accès en rafales se termine. Pas besoin d'attendre que la mémoire ait fini sa lecture/écriture avant de démarrer la lecture/écriture suivante. [[File:Pipemining mémoire.png|centre|vignette|upright=2.5|Pipelining mémoire]] L'animation suivante illustre bien le processus, avec quatres banques. [[File:Interleaving.gif|centre|vignette|upright=2.5|Pipelining mémoire]] L'entrelacement simple utilise les bits de poids faible pour sélectionner la banque, et les bits de poids fort pour la case mémoire. [[File:Adresse mémoire d'une mémoire entrelacée.png|centre|vignette|upright=2|Adresse mémoire d'une mémoire entrelacée]] Précisons que le temps d'accès mémoire ne change pas beaucoup avec l'entrelacement. Par contre, on peut faire plus d'accès mémoire simultanés. On peut démarrer un accès mémoire par cycle d'horloge, mais l'accès en lui-même prend plusieurs cycles. Les mémoires à entrelacement ont donc un débit supérieur aux mémoires qui ne l'utilisent pas. ===L'entrelacement par décalage=== L'entrelacement simple est très utile pour les accès en rafle ou équivalents. Par contre, il existe de rares situations où il n'est pas optimal. Une de ces situations est celle des accès en ''stride'', où l'on accède en série à des adresses sont séparées par N mots mémoires. De tels accès surviennent quand un logiciel accède à des structures de données spécifiques, à savoir des tableaux de structures, des matrices, ou d'autres structures de données dans le genre. Nous verrons dans quels chapitres que de tels accès étaient autrefois fréquents sur les anciens processeurs vectoriels, qui disposaient d'instructions capables de lire/écrire plusieurs données en mémoire d'un seul coup, avec un accès en ''stride''. Avec l'entrelacement simple, les accès en ''stride'' ont performances dégradées, mais cela ne fonctionne pas trop mal. Le pire cas est celui où l'on a N banques et où les données sont justement placées toutes les N adresses. Des accès consécutifs vont tous tomber dans la même banque, on ne peut plus accéder à des banques différentes en parallèle. Pour résoudre ce problème, il faut répartir les mots mémoires dans la mémoire autrement. : Dans les explications qui vont suivre, la variable N représente le nombre de banques, qui sont numérotées de 0 à N-1. L'organisation idéale est la suivante. On commence par organiser les N premières adresses comme une mémoire entrelacée simple : l'adresse 0 correspond à la banque 0, l'adresse 1 à la banque 1, etc. Pour le bloc suivant, on décale tout d'une adresse, à savoir qu'on commence à remplir les banques à partir de la seconde, non de la première. Une fois la fin du bloc atteinte, on finit de remplir le bloc en repartant du début du bloc. Le troisième bloc d'adresse subit le même traitement, sauf qu'on commence à remplir à partir de la seconde banque. Et on poursuit l’assignation des adresses en décalant d'un cran en plus à chaque bloc. Ainsi, chaque bloc verra ses adresses décalées d'un cran en plus comparé au bloc précédent. Si jamais le décalage dépasse la fin d'un bloc, alors on reprend au début. [[File:Mémoire interleaved par décalage.png|centre|vignette|upright=1.5|Mémoire entrelacée par décalage.]] En faisant cela, on remarque que les banques situées à N adresses d'intervalle sont différentes. Dans l'exemple du dessus, nous avons ajouté un décalage de 1 à chaque nouveau bloc à remplir. Mais on aurait tout aussi bien pu prendre un décalage de 2, 3, etc. Dans tous les cas, on obtient un entrelacement par décalage. Ce décalage est appelé le pas d'entrelacement, noté P. Le calcul de l'adresse à envoyer à la banque, ainsi que la banque à sélectionner se fait en utilisant les formules suivantes : * adresse à envoyer à la banque = adresse totale / N ; * numéro de la banque = (adresse + décalage) modulo N, avec décalage = (adresse totale * P) mod N. Avec cet entrelacement par décalage, on peut prouver que la bande passante maximale est atteinte si le nombre de banques est un nombre premier. Seulement, utiliser un nombre de banques premier peut créer des trous dans la mémoire, des mots mémoires inadressables. Pour éviter cela, il faut faire en sorte que N et la taille d'une banque soient premiers entre eux : ils ne doivent pas avoir de diviseur commun. Dans ce cas, les formules se simplifient : * adresse à envoyer à la banque = adresse totale / taille de la banque ; * numéro de la banque = adresse modulo N. ===L'entrelacement pseudo-aléatoire=== Une dernière méthode de répartition consiste à répartir les adresses dans les banques de manière "pseudo-aléatoire". L'idée exacte est de faire passer l'adresse dans une '''fonction de hachage''', plus ou moins complexe. Pour rappel, une fonction de hachage prend une entrée de grande taille et fournit en sortie un résultat de petite taille. Idéalement, elles doivent donner des résultats aussi différents que possible pour des entrées similaires, histoire de simuler une sorte de pseudo-aléatoire. Ici, une adresse est transformée en un numéro de banque. Une fonction de hachage simple ne fait que permuter des bits de l'adresse pour obtenir son résultat. Elle ne fait qu'échanger des bits de place avant de couper l'adresse en deux morceaux : un pour la sélection de la banque, et un autre pour la sélection de l'adresse dans la banque. Cette permutation est fixe, et ne change pas suivant l'adresse. Des fonctions de hachage plus complexes font des XOR bit à bit entre certains bits de l'adresse. Il existe aussi d'autres techniques qui donnent le numéro de banque à partir d'un polynôme modulo N, appliqué sur l'adresse. ==Les contrôleurs mémoires SDRAM/DDR optimisés== Après avoir vu le cas des mémoires RAM, nous allons nous concentrer sur le cas particulier des mémoires SDRAM. Les mémoires SDRAM et DDR modernes sont capables de gérer plusieurs accès simultanés à la mémoire RAM. Et les contrôleurs mémoire intègrent des optimisations spécifiques afin d'en profiter au maximum. ===La mise en attente des accès mémoire=== Comme pour les mémoires caches, il existe des contrôleurs mémoire bloquants et non-bloquants. Les premiers n'acceptent qu'un seul accès mémoire à la fois, les seconds acceptent plusieurs accès mémoire simultanés. Pour simplifier, nous allons dire que le contrôleur mémoire n’exécute qu'un seul accès mémoire à la fois, et qu'ils met en attente les accès en trop. C'est une simplification assez irréaliste, vu que les mémoires modernes disposent de plusieurs banques accessibles en parallèle. Mais nous introduirons ce détail un peu plus tard. Avec un contrôleur mémoire de ce type, les accès mémoire sont mis en attente dans une mémoire FIFO, histoire de les exécuter dans leur ordre d'arrivée. Les accès mémoire quittent alors la mémoire dans leur ordre d'arrivée, les lectures sont renvoyées dans l'ordre demandé. En plus d'une mémoire FIFO pour les commandes mémoire, on trouve une mémoire FIFO pour les données à écrire. Elle permet d'accumuler plusieurs écritures, tout en permettant que la donnée adéquate soit envoyée à la RAM au bon moment. Il y a aussi une FIFO pour les données lues, qui sert au cas où le cache ne soit pas disponible quand on lui envoie la donnée lue. [[File:Module d'interface avec la mémoire.png|centre|vignette|upright=2.5|Contrôleur mémoire avec mise en attente des requêtes processeur et autres optimisations.]] Une optimisation regroupe plusieurs accès à des données consécutives en un seul accès en rafale. Le contrôleur mémoire analyse les requêtes mises en attente et détecte si certaines se font à des adresses consécutives. Si c'est le cas, il fusionne ces requêtes en une lecture/écriture en rafale. Une telle optimisation est appelée la '''combinaison de lecture''' pour les lectures, et la '''combinaison d'écriture''' pour les écritures. ===Le pipeline des mémoires SDRAM=== Les SDRAM ont une forme de pseudo-pipeline très limité. Elles sont parfois capables de démarrer une commande avant que la précédente soit terminée. Même si elle parait très limitée, cette possibilité est exploitée au mieux par la présence de plusieurs banques dans la mémoire SDRAM. Les chips mémoires de SDRAM sont composés de plusieurs banques, chacune étant une sorte de mini-mémoire miniature. Chaque banque a son propre décodeur, son propre tampon de ligne, ses propres multiplexeurs de colonne, sa logique de rafraichissement mémoire, etc. C'est comme si une SDRAM regroupait plusieurs mémoires séparées dans un même circuit intégré. Et cela permet de faire plusieurs accès mémoire en même temps, dans des banques différentes. Il est possible de lancer un accès mémoire dans deux banques en même temps, par exemple. Ou encore, on peut lancer un nouvel accès mémoire dans une banque si elle est inoccupée. La possibilité améliore grandement la performance de la SDRAM. Mais nous en reparlerons dans un chapitre ultérieur. {|class="wikitable" |+ Pipelining basique sur les SDRAM |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 |- ! Banque Numéro 1 | || || || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || || || |- ! Banque Numéro 2 | || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || |- ! Banque Numéro 3 | || || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || |} L'implémentation n'a rien de vraiment compliqué. Il suffit d'utiliser une mémoire FIFO par banque. Ainsi, les accès dans une même banque se feront en série, dans l'ordre d'arrivée, mais des accès à des banques différentes se feront dans le désordre. Cela ne pose pas de problème car les accès se font à des adresses différentes, ce qui fait qu'il n'y a pas de conflits majeurs. [[File:Gestion parallèle des banques.png|centre|vignette|upright=2.5|Gestion parallèle des banques.]] ===Le ré-ordonnancement des commandes mémoires=== Un contrôleur mémoire moderne est capable de changer l'ordre des requêtes mémoires, pour gagner en performances. En clair : il peut faire l'équivalent mémoire de l'exécution dans le désordre des processeurs haute performance. Une différence majeure est que le contrôleur mémoire ne remet pas les accès mémoire dans l'ordre avant de les envoyer au processeur. C'est le processeur qui remet en ordre les accès mémoire, dans sa ''load queue''. Pour cela, les requêtes mémoire sont "numérotées", à savoir qu'on leur attribue un identifiant binaire. Le contrôleur mémoire exécute les lectures/écritures dans le désordre, mais garde la trace de leur identifiant. Il sait dans quel ordre il exécute les accès mémoire et connait leur latence. Ce qui fait qu'il sait que la donnée lue à tel instant est associée à la requête mémoire numéro X. Pour les lectures, il renvoie la donnée lue avec l'identifiant. Le processeur sait alors à quelle lecture la donnée lue correspond et il se débrouille en interne pour gérer la situation. En clair : le processeur voit les accès mémoire dans le désordre, c'est lui qui les remet en ordre. Maintenant que ces précisions sont faites, posons cette question : quel est l'intérêt de faire les accès mémoire dans le désordre ? Accéder à des banques en parallèle est une réponse possible, mais le ré-ordonnancement est surtout utile si tous les accès mémoire atterrissent dans la même banque. Pour comprendre pourquoi, faisons un rappel rapide sur le tampon de ligne. Les mémoires SDRAM sont des mémoires à tampon de ligne. Elles font un accès mémoire en deux étapes. La première étape recopie une ligne de N * 64 bits dans un tampon interne à la SDRAM. La seconde étape sélectionne une colonne, à savoir une donnée de 64 bits, qui est soit envoyée sur le bus de données pour une lecture, soit modifiée par une écriture. [[File:Mémoire à tampon de ligne.png|centre|vignette|upright=2|Mémoire à tampon de ligne]] Les deux étapes correspondent à deux commandes séparées : une commande ACT qui précise l'adresse de la ligne, et une commande READ ou WRITE qui précise l'adresse de la colonne. [[File:Signaux RAS et CAS.png|centre|vignette|upright=2|Signaux RAS et CAS.]] Les SDRAM permettent de se passer de la première étape si des accès consécutifs se font dans la même ligne. Une fois activée, la ligne reste ouverte et on peut accéder plusieurs fois de suite dedans. On a alors juste à préciser l'adresse de colonne dedans. [[File:Sélection d'une ligne sur une mémoire FPM ou EDO.png|centre|vignette|upright=2|Sélection d'une ligne sur une mémoire FPM ou EDO.]] L'exécution dans le désordre des lectures/écritures SDRAM vise à faire en sorte que des accès consécutifs se fassent dans une même ligne. Elle marche si plusieurs accès à une même ligne ne sont pas consécutifs, et qu'il y a des accès entre les deux. Après ré-ordonnancement, ces accès mémoire deviennent consécutifs, ils sont exécutés l'un à la suite de l'autre. Pour rendre le tout plus concret, voici un exemple. Imaginez que l'on sait les 3 accès mémoire suivants : * Une lecture ligne A ; * Une écriture ligne B ; * Une lecture ligne A. Sans réordonnancent, on doit émettre deux commandes PRECHARGE : une quand on passe de la ligne A à la ligne B, et une autre pour le passage inverse. pour éviter cela, le contrôleur mémoire peut retarder l'écriture, ou au contraire avancer la seconde lecture. le résultat est alors le suivant : * Lecture ligne A ; * Lecture ligne A ; * Une écriture ligne B. On a donc deux accès consécutifs à la même ligne, suivi par une écriture dans une autre ligne. La technique marche parce que l'on a un mix adéquat de lectures et d'écritures. Mais il existe des possibilités de réorganisation autres qui ne sont pas valides. Il faut par exemple prendre garde à éviter d'intervertir lectures et écritures à une même adresse, sous peine de problèmes. Encore une fois, il faut faire attention aux dépendances mémoires. Le controleur mémoire teste les dépendances mémoires avant d'envoyer des commandes à la mémoire DDR/SDRAM. ===L'entrelacement sur les mémoires SDRAM=== L''''entrelacement''' fonctionne sur les mémoires SDRAM, mais il doit être fortement modifié. Concrètement, tout ce qui a étré dit plus est inapplicable pour les SDRAM, du fait de la présence du tampon de ligne. Des adresses consécutives atterrissent déjà dans la même ligne, ce qui fait qu'on n'a pas besoin d'entrelacement au niveau d'une ligne. Et ne parlons pas de ce la présence de rangées et de canaux mémoire ! Cependant, il peut y avoir un entrelacement lié à la présence des banques SDRAM. Pour gérer l'entrelacement, le contrôleur de SDRAM prend en entrée l'adresse envoyée par le processeur, et la découpe en plusieurs champs : un pour sélectionner la banque adéquate, un autre pour la ligne, un autre pour la colonne, un autre pour la rangée. Une solution simple désactive l'entrelacement. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de banque || Adresse de ligne || Adresse de colonne |} Si cette solution fonctionne bien si les accès mémoire sont assez aléatoires, en pratique, mieux vaut utiliser l'entrelacement. Une première méthode d'entrelacement est l''''entrelacement de ligne'''. Elle répartit deux lignes consécutives dans deux banques différentes. Avec N banques, la première ligne ira dans la première banque, la seconde ligne dans la seconde, et ainsi de suite jusqu'à la énième banque qui va dans la banque numéro N. Et la ligne N+1 repart dans la première banque. L'avantage est que passer d'une ligne à la suivante est assez rapide. Au lieu de devoir fermer la première ligne et ouvrir la suivante on ouvrira directement la ligne suivante dans une banque différente, ce qui sera plus rapide. La commande PRECHARGE pourra être envoyée à la seconde banque en avance. Pour cela, l'adresse est découpée comme suit. {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne (précise à l'octet) |} D'autres méthodes d'entrelacement sont possibles, mais elles sont plus complexes. Chaque contrôleur mémoire fait un peu à sa sauce. Ils permettent en général de choisir entre plusieurs entrelacements. Au minimum, ils permettent de désactiver l'entrelacement, d'activer un entrelacement de ligne, et d'autres entrelacements plus complexes, dont certains sont propriétaires et tenus secrets par le fabricant. L'usage du ''dual channel'' complique encore l'entrelacement. Là encore, il y a deux grandes solutions : avec et sans entrelacement des canaux mémoire. Rappelons le principe : deux barrettes de RAM sont accédées en parallèle. Et pour cela, il faut utiliser l'entrelacement. Typiquement, chaque barrette mémoire fournit 64 bits, ce qui fait que l'on peut accéder à 128 bits d'un seul coup, par exemple avec un accès en rafale. Une solution pour cela est de ne pas utiliser l'entrelacement. Avec deux barrettes, la première barrette regroupera la moitié haute de la RAM, la seconde barrette utilisera la moitié basse. Les possibilité d’accéder à deux données séparées de 64 bits en même temps est cependant très rare. Aussi, on préfère utiliser l'entrelacement. Pour cela, deux blocs de 64 bits sont placés dans des canaux mémoire séparés. Idem avec du triple ou quadruple canal, mais c'est alors trois ou quatre blocs de 64 bits qui sont dispersés dans des canaux consécutifs. {|class="wikitable" |+ Adresse mémoire |- ! colspan="6" | Sans entrelacement |- | Numéro de canal/barrette || Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne || Position de l'octet dans un bloc de 64 bits |- | colspan="6" | |- ! colspan="6" | Avec entrelacement |- | Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne || Numéro de canal/barrette || Position de l'octet dans un bloc de 64 bits |} Les contrôleurs de SDRAM précédents sont assez basiques et ne représentent pas les contrôleurs les plus évolués. Ils étaient utilisés sur les anciens PC, à une époque où ils étaient encore sur la carte mère du processeur. Mais de nos jours, le contrôleur mémoire est intégré au processeur. Et il incorpore de nombreuses optimisations afin de gagner en performances. Malheureusement, ces optimisations sont elles-mêmes dépendantes des optimisations intégrées dans le processeur, ce qui fait qu'on ne peut pas en parler ici. Elles demandent que le contrôleur mémoire reçoive plusieurs accès mémoire simultanés, ce qui n'a aucun sens à ce stade du cours. Aussi, nous allons les passer sous silence pour le moment. Nous les verrons dans le chapitre sur le parallélisme mémoire. L'entrelacement est géré juste avant le séquenceur mémoire. Le '''circuit d'entrelacement''' intervertit certains bits de l'adresse lors des accès mémoires. On trouve aussi des mémoires FIFOs pour mettre en attente les requêtes mémoire provenant du processeur. Elles permettent de recevoir plusieurs accès mémoire consécutifs. Si vous vous demandez comment plusieurs accès mémoire consécutifs peuvent avoir lieu, sachez que c'est une bonne question. Pour le moment, nous allons dire que ces accès proviennent du processeur ou de la mémoire cache, sans expliquer comment. Le contrôleur mémoire reçoit une série d'accès mémoire assez rapprochés, qu'il doit exécuter au mieux. [[File:Controleur mémoire d'une SDRAM avec entrelacement.png|centre|vignette|upright=2.5|Contrôleur mémoire d'une SDRAM avec entrelacement.]] <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=La désambiguïsation mémoire | prevText=La désambiguïsation mémoire | next=Les processeurs superscalaires | nextText=Les processeurs superscalaires }} </noinclude> bbmvcmvwgwj4xemhfumxmsct6mhfg17 764641 764635 2026-04-23T13:47:44Z Mewtow 31375 764641 wikitext text/x-wiki Dans ce chapitre, nous allons voir les techniques qui permettent de gérer plusieurs accès mémoire simultanés directement au niveau du cache ou de la mémoire RAM. Par plusieurs accès mémoire simultanés, vous pensez sans doute à l'usage de cache multiports, voire de mémoires RAM multiport. C'est en effet une solution, mais ce n'est clairement pas la seule. Par exemple, il est possible de pipeliner l'accès au cache, voire à la mémoire RAM. Il existe de nombreuses techniques de '''parallélisme mémoire''', et nous allons les voir dans ce chapitre. Pour donner un exemple, il est possible de pipeliner les accès mémoire. Il est possible de pipeliner l'accès au cache et/ou l'accès à la mémoire, et nous verrons comment faire dans ce chapitre. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, qu'ils aient ou non un pipeline dynamique. Par contre, pipeliner la mémoire est une technique ancienne, aujourd'hui peu utilisée. Elle était utilisée sur des très vieux ordinateurs, qui avaient un pipeline mais pas de mémoire cache (et qui avaient souvent de l'exécution dans le désordre et du renommage de registres). Mais de nos jours, les mémoires SDRAM et DDR ne sont pas adaptées pour ça, il est très difficile de les pipeliner correctement. Le parallélisme mémoire est utile aussi bien sur des processeurs à émission dans l'ordre que dans le désordre. Par exemple, un processeur ''in-order'' avec des lectures non-bloquantes peut e, profiter. Il peut techniquement lancer une seconde lecture, après avoir rencontré une première lecture non-bloquante. En général, le nombre de lectures consécutives est cependant limité à moins d'une dizaine. À l'opposé, les processeurs sans lectures non-bloquantes profitent pas du parallélisme mémoire. À la rigueur, ils peuvent en profiter s'ils intègrent des techniques de préchargement. Pendant que le processeur exécute une lecture/écriture, il peut précharger une autre donnée en parallèle. Inutile de dire que sans pipeline processeur, le parallélisme mémoire ne sert pas à grand-chose. ==Le ''line fill buffer'' et les techniques associées== Lors d'un défaut de cache, le processeur doit attendre que toute la ligne de cache soit chargée avant d'être utilisable. Or, la taille d'une ligne de cache est supérieure à la largeur du bus mémoire, ce qui fait qu'une ligne de cache est chargée en plusieurs fois, morceaux par morceaux, mot mémoire par mot mémoire. Le chargement peut se faire directement dans le cache, mais ce n'est pas une solution très pratique. À la place, beaucoup de processeurs ajoutent une mémoire tampon entre la RAM et le cache, appelée le '''tampon de remplissage de ligne''' (''line-fill buffer'') dans les processeurs modernes. Lors d'un défaut de cache, le processeur charge la donnée de la RAM dans le tampon de remplissage de ligne, mot mémoire par mot mémoire. Une fois plein, le tampon de remplissage de ligne est recopié dans la ligne de cache. Le tampon de remplissage de ligne contient une ligne de cache, si ce n'est que certains bits de contrôle ne sont pas présents. Le tampon de remplissage de ligne contient un bit de validité pour chaque mot mémoire de la ligne de cache, qui indiquent si le mot mémoire a été chargé. Par exemple, prenons un processeur 64 bits, qui gère donc des mots mémoire de 8 octets, avec des lignes de cache 256 octets/32 mots mémoire. Le tampon de remplissage de ligne contiendra 32 bits de validité. Si le processeur a chargé les 6 premiers mots mémoire, les 6 premiers bits de validité seront mis à 1, les autres seront encore à zéro. Il faut noter que la disponibilité de la ligne complète se détermine assez facilement en faisant un ET logique entre tous les bits de validité. Le processeur sait ainsi quand la ligne de cache est disponible entièrement et donc quand la transférer dans le cache. La même chose existe avec une hiérarchie de cache, sauf que l'on trouve une mémoire tampon entre chaque niveau de cache. Il y a un tampon de remplissage de ligne entre le cache L1 et le cache L2, entre le cache L2 et le L3, etc. Si le cache ne gère qu'un seul défaut de cache à la fois, le ''fill line buffer'' est une mémoire très simple, qui ne mémorise qu'une seule ligne de cache. Et cela vaut aussi bien pour un cache bloquant que non-bloquant. Mais sur les caches capables de gérer plusieurs défauts simultanés, le tampon de remplissage de ligne est une mémoire de type FIFO ou LIFO, capable de mémoriser plusieurs lignes de cache. Notons que le tampon de remplissage de ligne est très utile pour implémenter certaines techniques, par exemple le contournement du cache. Nous avions vu dans le chapitre sur le cache que certains accès mémoire doivent contourner le cache, pour des raisons de cohérence des caches. C'est notamment nécessaire pour accéder aux périphériques, mais c'est aussi utile pour des raisons de performances dans des cas très spécifiques. Les accès qui contournent le cache se font directement dans le tampon de remplissage de ligne : le processeur écrit ou lit les données depuis ce tampon de remplissage de ligne, sans accéder au cache. ===L'''early restart'' et le ''critical word load''=== La présence du ''line buffer'' permet une optimisation assez intéressante, qui permet de réduire la latence des défauts de cache. L'optimisation consiste à lire un mot mémoire dans le tampon de remplissage de ligne, même si la ligne de cache complète n'a pas encore été chargée. Il existe deux manières de faire cela, qui portent les noms d'''early restart'' et de ''critical word load''. La première est la version la plus simple, la seconde est plus complexe mais plus performante. Avec la technique d''''''early restart''''', la ligne de cache est chargée normalement, en partant de son premier mot mémoire. Dès que le mot mémoire lu/écrit par le processeur est copié dans le ''line fill buffer'', il est envoyé au processeur immédiatement. Illustrons le tout par un exemple, où une ligne de cache fait 16 mots mémoire. Le processeur effectue une lecture, qui lit le 5ème mot mémoire. Sans ''early restart'', le processeur doit charger les 16 mots mémoire avant de faire la lecture dans le cache. Avec ''early restart'', le processeur reçoit la donnée dès que le 5ème mot mémoire est disponible. Le processeur doit attendre que les 4 premiers mots mémoire soient chargés, puis le 5ème mot mémoire arrive et est envoyé directement au processeur, il est lu directement depuis le ''line fill buffer''. Les 11 mots mémoire suivants sont ensuite chargés dans le cache pendant que le processeur fait des calculs dans son coin. Le ''critical word load'' est une optimisation de la technique précédente où le chargement de la ligne de cache commence directement à la donnée demandée par le processeur. Pour reprendre l'exemple précédent, où le processeur demande le 5ème mot mémoire sur 16, le ''critical word load'' charge le 5ème mot mémoire en premier et l'envoie au processeur, ce qui fait qu'il est chargé très rapidement. Pas besoin d'attendre que le processeur charge les 4 mots mémoire précédents comme avec l'''early restart''. Dans le détail, le processeur charge le 5ème mot mémoire en premier, puis charge les 11 suivants, et termine par les 4 mots mémoire du début. En clair, le ''critical word load'' commence par charger le mot mémoire lu, puis les blocs suivants, avant de revenir au début du bloc pour charger les blocs restants. Ainsi, la donnée demandée par le processeur sera la première disponible. Pour cela, l'organisation du tampon de remplissage de ligne est modifiée de manière à rendre cela possible. Il a une taille égale à une ligne de cache complète, qui contient elle-même plusieurs mots mémoire. Dans le ''line-fill buffer'', chaque mot mémoire est stocké avec un tag, qui indique l'adresse du mot mémoire stocké dans le ''line-fill buffer''. Le ''line-fill buffer'' est donc un cache un peu particulier, qui fonctionne comme un cache du point de vue du processeur, comme une mémoire FIFO pour les transferts avec le cache. Ainsi, un processeur qui veut lire dans le cache après un défaut peut accéder à la donnée directement depuis le tampon de remplissage de ligne, alors que la ligne de cache n'a pas encore été totalement recopiée en mémoire. ==Les caches pipelinés== Il est possible de pipeliner l'accès au cache, ce qui demande juste de rajouter des registres au bon endroits et quelques circuits de contrôle. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, même ceux avec un pipeline dynamique. Et l'implémentation peut se faire de plusieurs manières différentes. Il faut dire que découper une mémoire cache en plusieurs étapes peut se faire de plusieurs manière. Commençons par la plus simple. Beaucoup de processeurs des années 2000 avaient une fréquence peu élevée comparée aux standards d'aujourd'hui, ce qui fait que leur cache avait bien un temps d'accès d'un cycle d'horloge. Mais l'accès au cache se faisait en deux cycles d'horloge : un cycle pour calculer une adresse et un cycle pour l'accès mémoire proprement dit. Et ces deux étapes étaient pipelinées, à savoir que deux micro-opérations mémoire peuvent s'exécuter en même temps : une dans l'étage de calcul d'adresse, une autre dans le cache. L'unité mémoire était donc pipelinée, alors que l'accès au cache ne l'était pas. Les processeurs qui implémentaient cette technique regroupent les micro-architectures K5 et K6 d'AMD, les processeurs Intel de micro-architecture P6 (Pentium 2 et 3) et quelques autres. Il est aussi possible de pipeliner l'accès au cache lui-même. Avec un cache pipeliné, l'accès au cache ne se fait pas en un seul cycle, mais en plusieurs. Cependant, on peut lancer un nouvel accès au cache à chaque cycle d'horloge, comme un processeur pipeliné lance une nouvelle instruction à chaque cycle. L'implémentation est assez simple : il suffit d'ajouter des registres dans le cache. Pour cela, on profite que le cache est composé de plusieurs composants séparés, qui échangent des données dans un ordre bien précis, d'un composant à un autre. De plus, le trajet des informations dans un cache est linéaire, ce qui les rend parfois pour l'usage d'un pipeline. ===Le ''pipelining'' des caches ''direct-mapped''=== Voici ce qui se passe avec un cache directement adressé. Pour rappel ce genre de cache est conçu en combinant une mémoire RAM, généralement une SRAM, avec quelques circuits de comparaison et un MUX. L'accès au cache se fait globalement en deux étapes : la première lit la donnée et le ''tag'' dans la SRAM, et on utilise les deux dans une seconde étape. Nous avions vu il y a quelques chapitre comment pipeliner des mémoires, dans le chapitre sur les mémoires évoluées. Et bien ces méthodes peuvent s'utiliser pour la mémoire RAM interne au cache !L'idée est d'insérer un registre entre la sortie de la RAM et la suite du cache, pour en faire un cache pipéliné, à deux étages. Le premier étage lit dans la SRAM, le second fait le reste. L'implémentation sur les caches associatifs à plusieurs voies est globalement la même à quelques détails près. [[File:Cache directement adressé pipeliné - deux étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - deux étages]] Avec le circuit précédent, il est possible d'aller plus loin, cette fois en pipelinant l'accès à la mémoire RAM interne au cache. Pour comprendre comment, rappelons qu'une mémoire SRAM est composée d'un plan mémoire et d'un décodeur. L'accès à la mémoire demande d'abord que le décodeur fasse son travail pour sélectionner la case mémoire adéquate, puis ensuite la lecture ou écriture a lieu dans cette case mémoire. L'accès se fait donc en deux étapes successives séparées, on a juste à mettre un registre entre les deux. Il suffit donc de mettre un gros registre entre le décodeur et le plan mémoire. [[File:Cache directement adressé pipeliné - trois étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - trois étages]] Et on peut aller encore plus loin en découpant le décodeur en deux circuits séparés. En effet, rappelez-vous le chapitre sur les circuits de sélection : nous avions vu qu'il est possible de créer des décodeurs en assemblant des décodeurs plus petits, contrôlés par un circuit de prédécodage. Et bien on peut encore une ajouter un registre entre ce circuit de prédécodage et les petits décodeurs. : Théoriquement, toute l'adresse est fournie d'un seul coup au cache, la quasi-totalité des processeurs présentent l'adresse complète à un cache pipeliné. Mais le Pentium 4 fait autrement. Il faut noter que les premiers étages manipulent l'indice dans la SRAM, qui est dans les bits de poids faible, alors que les étages ultérieurs manipulent le ''tag'' qui est dans les bits de poids fort. Les concepteurs du Pentium 4 ont alors décidé de présenter les bits de poids faible lors du premier cycle d'accès au cache, puis ceux de poids fort au second cycle. Pour cela, l'ALU fonctionnait à une fréquence double de celle du processeur, tout comme le cache L1. Il n'y avait pas de pipeline proprement dit, mais cela réduisait grandement la latence d'accès au cache. ===Le ''pipelining'' des caches splittés/sériels=== Il est aussi possible de pipeliner un cache dits splittés, aussi appelés à accès sériel. Pour rappel, les caches à accès sériel vérifient si il y a succès ou défaut de cache, avant d'accéder aux lignes de cache en cas de succès. Ils font donc différemment des autres caches, qui accèdent à une ligne de cache, avant de déterminer s'il y a succès ou défaut en lisant le tag de la ligne de cache. Les caches sériels disposent de deux SRAM : une pour les tags des lignes de cache et une pour les données. Ils accèdent à la SRAM pour les tags, avant d’accéder à la SRAM des données en cas de succès de cache. Vu que l'accès se fait en deux étapes, une vérification des tags suivie de la lecture/écriture des données, il est facile à pipeliner. Pipeliner le cache permet de régler le problème des accès au cache L1, et elle est tout le temps utilisé sur les processeurs modernes. Mais que faire en cas de défaut de cache ? ==Les caches non bloquants== Un '''cache bloquant''' est un cache auquel le processeur ne peut pas accéder pendant un défaut de cache. Il faut attendre que la lecture ou écriture en RAM soit terminée avant de pouvoir utiliser de nouveau le cache. Un '''cache non bloquant''' n'a pas ce problème : on peut l'utiliser pendant un défaut de cache. Les caches non bloquants permettent de démarrer une nouvelle lecture ou écriture alors qu'une autre est en cours, ce qui permet d'exécuter plusieurs lectures ou écritures en même temps. ===Les ''Miss Handling Status Registers''=== Lors d'un défaut de cache, la mémoire RAM est consultée pendant le défaut de cache, mais le cache est inutilisé. Un défaut de cache n'utilise pas le cache, ce n'est qu'un accès en mémoire RAM, sur le bus mémoire (ou un accès aux niveaux de cache inférieurs, peu importe). Le cache en lui-même est laissé libre, rien n’empêche d'y accéder, il est en réalité intrinsèquement non-bloquant. Les caches, bloquants comme non-bloquants, sont en réalité composés d'une mémoire cache proprement dite, entourée de circuits qui servent d'interface entre le processeur et le cache lui-même. Et parmi les circuits tout autour, certains gèrent l'accès au cache lors d'un défaut de cache. Ils sont regroupés sous le terme de '''''Miss Handling Architecture''''' (MHA). La différence entre un cache bloquant et un cache non-bloquant est en réalité liée à l'implémentation de la MHA. Les caches bloquants coupent volontairement l'accès au cache lors d'un défaut, car les défauts sont plus simples à gérer ainsi. Le défaut de cache rapatrie une donnée depuis la RAM, qui sera écrite dans le cache. Et il ne faut pas qu'une tentative d'accès à cette donnée ait lieu avant qu'elle ne soit chargée. Mais ce blocage est général et touche tout le cache, alors que seule une ligne de cache est concernée par le défaut de cache. L'idée derrière un cache non-bloquant est que seule la ligne de cache est bloquée, mais les autres sont accessibles. L'idée est alors de mémoriser les lignes de cache concernées par le défaut de cache, afin d'en bloquer l'accès. À chaque accès, on vérifie si la ligne de cache est déjà réservée par un défaut de cache. La lecture/écriture est alors bloquée si c'est le cas, mais elle accepte les accès sinon. Pour cela, la ''Miss Handling Architecture'' contient des registres qui mémorisent des informations sur les défauts de cache en cours. Ils portent le nom de '''''miss status handling registers''''', que j’appellerais dorénavant MSHR, qui sont aussi appelés des '''''miss buffer'''''. Le contenu des MSHR varie beaucoup suivant le processeur, mais ces derniers stockent au minimum les informations suivantes : * Le numéro de la ligne de cache dans laquelle les données sont chargées. * Un bit de validité qui indique si le MSHR est vide ou pas, qui est mis à 0 quand le défaut de cache est résolu. * Un ou plusieurs champs de lecture/écriture, qui contiennent des informations sur la lecture/écriture. ** Pour une lecture, elle contient des informations sur la destination de la donnée, à savoir qui prévenir quand le défaut de cache est terminé. C'est parfois un nom/numéro de registre (celui dans lequel charger la donnée), mais c'est souvent le numéro de l'entrée dans le ''load/store queue''. ** Pour les écritures, elle contient la donnée à écrire, ou éventuellement un numéro de ''load/store queue'' où se trouve la donnée à écrire. Il faut noter que le bus mémoire ne peut gérer qu'un seul défaut de cache à la fois. Aussi, il est intéressant de regarder ce qui se passe lorsqu'un second défaut de cache survient, pendant qu'un premier est en cours. Dans ce cas, il y a deux réponses qui correspondent à deux types de caches non-bloquants, qui portent les noms barbares de caches de type succès après défaut et défaut après défaut. Sur le premier type, il ne peut pas y avoir plusieurs défauts de cache simultanés. Dès qu'un second défaut de cache survient, le cache stoppe son activité et on ne peut plus démarrer de nouvelle lecture/écriture, tant que le premier défaut de cache n'est pas résolu. Le second type est plus souple et autorise la survenue de plusieurs défauts de cache simultanés. Du moins, jusqu'à une certaine limite, car le cache ne peut supporter qu'un nombre limité d'accès mémoires simultanés (pipelinés). ===Les accès simultanés à une même ligne de cache=== Il arrive que le processeur fasse plusieurs accès mémoire simultanés à la même ligne de cache. Si la ligne de cache en question n'a pas encore été chargée dans le cache, alors on a plusieurs défauts de cache consécutifs pour la même ligne de cache, et le cache non-bloquant doit gérer la situation. Pour la suite, il va falloir faire une petite distinction entre les défauts primaires et secondaires. Imaginons qu'un défaut de cache ait lieu et demande à charger une donnée dans la ligne de cache numéro N. Il s'agit du premier défaut impliquant cette ligne de cache précise, ce qui lui vaut le nom de '''défaut de cache primaire'''. Mais par la suite, d'autres accès mémoire à la même ligne de cache ont lieu, alors que la ligne de cache n'est pas encore disponible. Dans ce cas, il s'agit de '''défauts de cache secondaires'''. Pour l'unité d'accès mémoire, les défauts de cache primaire et secondaire sont différents (ils prennent tous une entrée dans la ''load/store queue''). Mais pour le cache, ils ne correspondent qu'à un seul accès au cache : celui qui demande de charger la ligne de cache demandée. Les défauts de cache primaire et secondaire à la même ligne de cache se voient attribuer un MSHR unique. La gestion des défauts de cache secondaires dépend du cache non-bloquant. La solution la plus simple ne permet pas les défauts de cache secondaires. Le cache ne permet pas deux défauts de cache simultanés pour la même ligne de cache. Les autres solutions le permettent, en fusionnant des accès simultanés à la même ligne de cache en un seul au niveau des MSHR. Dans tous les cas, détecter les défauts de cache secondaires sont un problème qu'il faut détecter. La ''Miss Handling Architecture'' doit détecter les défauts de cache secondaire. Pour cela, elle procède comme suit. Lors de chaque défaut de cache, la MHA récupère le numéro de la ligne de cache associé. Il vérifie alors chaque MSHR pour vérifier s'il contient le numéro en question. S'il n'y a aucune correspondance dans les MSHR, alors c'est signe que le défaut de cache est un défaut primaire. Mais s'il y en a une, alors c'est un défaut secondaire. Évidemment, cela signifie que lors d'un défaut de cache, le numéro de ligne de cache est envoyé à tous les MSHR, pour comparaison. Les MSHR sont donc regroupés dans une mémoire associative, une sorte de mini-cache, faciliter l'implémentation. Lors d'un défaut de cache primaire, l'accès à un cache non-bloquant se fait comme suit : le processeur envoie une adresse au cache, accède à celui-ci, et détecte la survenue d'un défaut de cache. Il en profite alors pour attribuer une ligne de cache dans laquelle sera chargée ce défaut. L'attribution est très simple dans le cas des caches ''direct mapped'', ou associatifs par voie, pour lesquels l'attribution se fait assez simplement. Il mémorise alors cette information dans les MSHR, après avoir vérifié que le défaut de cache n'était pas un défaut secondaire. Lors des accès ultérieurs à une adresse proche, censée être dans la même ligne de cache, le processeur va encore une fois rencontrer un défaut de cache. Il va alors déterminer le numéro de la ligne de cache associée à l'adresse, et comparer ce numéro avec les MSHRs. Si un MSHR contient ce numéro, c'est signe que le défaut de cache est un défaut secondaire. La MHA réagit alors différemment selon le processeur considéré. Une première solution n'autorise pas les défauts de cache secondaires. Si l'un d'entre eux survient, le processeur est gelé par un ''pipeline stall'', une bulle de pipeline. Une autre solution fusionne les défauts de cache secondaires avec le défaut de cache primaire : tout cela ne correspond qu'à un seul défaut de cache pour lui. ====Les MSHR simples==== Un cache non-bloquant à '''MSHR simple''' contient juste plusieurs MSHR qui mémorisent juste un numéro de ligne de cache, un bit de validité, le champ de lecture/écriture, et l'adresse exacte de lecture/écriture. Avec cette organisation, il est possible d'avoir plusieurs défauts de cache séparés, mais à la condition que chaque défaut accède à une ligne de cache différente. Deux accès simultanés à une même ligne de cache ne sont pas possibles, les défauts de cache secondaires ne sont pas autorisés. Ainsi, chaque défaut de cache se voit attribuer son propre MSHR, chacun contient un numéro de ligne de cache différent. Pour comprendre pourquoi c'est impossible de gérer les défauts secondaires, il faut regarder le champ de lecture/écriture. Si on veut effectuer plusieurs écritures consécutives à la même adresse, le MSHR n'aura pas de quoi mémoriser les deux données à écrire. Il pourra mémoriser la première donnée à écrire, pas la seconde. Même chose lors d'une lecture : le champ lecture/écriture peut mémorisr la destination de la première lecture, pas de la seconde. L'avantage est que la MHA n'a besoin que des MSHR et de quelques circuits annexes. Les autres solutions rajoutent des circuits annexes pour gérer les défauts de cache secondaires, qui utilisent beaucoup de circuits. Le cout en circuit est donc élevé, mais le gain en performance est là. Passons maintenant aux caches non-bloquants qui autorisent les défauts de cache secondaire. La solution la plus simple consiste à utiliser ====Les MSHR adressés implicitement==== Avec les '''MSHR adressés implicitement''', il est possible de fusionner plusieurs accès mémoire à une même ligne de cache, mais sous une condition très importante : ces accès lisent/écrivent des mots mémoire différents. Par exemple, imaginons qu'une ligne de cache contienne 8 mots mémoire de 64 bits. Si un premier accès mémoire lit le mot mémoire numéro 7 (dernier mot mémoire de la ligne), et le second accès le mot mémoire numéro 3, alors la fusion est possible. Mais si deux accès mémoire veulent lire/écrire le mot mémoire numéro 7, alors la fusion n'est pas possible et le processeur se bloque, un ''pipeline stall'' survient. Un MSHR adressé implicitement est un MSHR simple, qui contient naturellement un numéro de ligne de cache (tag) et un bit de validité, sauf que le champ de lecture/écriture est dupliqué. Les différents champs lecture/écriture d'un MSHR sont regroupés dans une mémoire RAM/cache qui contient autant d'entrées qu'il y a de mot mémoire dans une ligne de cache. Chaque entrée de la table est associée à un mot mémoire de la ligne de cache et stocke des informations sur celui-ci. Une entrée mémorise, au minimum : * Un champ de lecture/écriture qui contient soit la destination de la lecture, soit la donnée à écrire. * Un bit R/W permettant d'interpréter correctement le champ de lecture/écriture. * Un bit de validité pour chaque entrée de la table, qui dit si un défaut de cache antérieur accède déjà à ce mot mémoire. La fusion de deux défauts de cache est ainsi assez simple. Un défaut de cache primaire/secondaire configure l'entrée associée au mot mémoire lu/écrit. Si un défaut secondaire ultérieur lit/écrit un mot mémoire différent (dont le bit de validité de l'entrée dans la table est à zéro), pas de conflit : il configure une autre entrée, vide. Mais s'il lit/écrit un mot mémoire pour lequel l'entrée de la table est occupée, il y a conflit, le processeur est bloqué par un ''pipeline stall'', une bulle de pipeline. Avec cette organisation, le nombre de MSHR indique combien de lignes de cache peuvent être lues en même temps depuis la mémoire. Quant au nombre d'entrées par MSHR, il détermine combien d'accès mémoires qui ne se recouvrent pas peuvent avoir lieu en même temps. ====Les MSHR adressés explicitement==== Les '''MSHR adressés explicitement''' n'ont pas les contraintes des MSHR adressés implicitement. Avec eux, il est possible d'avoir plusieurs défauts de cache pointant vers la même ligne de cache, mais aussi vers le même mot mémoire. De tels défauts de cache apparaissent sur les processeurs à exécution dans le désordre, mais sont très rares, voire inexistants, sur les processeurs ''in-order''. L'idée est encore que chaque MSHR est associée à une table mémoire qui mémorise des entrées. Cette table n'est autre qu'une mémoire FIFO. Sauf que cette fois-ci, une entrée n'est pas associée à un mot mémoire. Une entrée est associée à un défaut de cache. Une entrée mémorise là encore la destination de la lecture ou la donnée de l'écriture suivi du bit R/W, ainsi que d'un bit de validité par entrée, mais aussi : la position du mot mémoire lu dans la ligne de cache. C'est cette dernière information qui n'était pas présente dans les MSHR adressés implicitement. De plus, le nombre d'entrée par MSHR n'est pas égal au nombre de mots mémoires dans une ligne de cache, mais peut être arbitrairement grand. L'avantage d'un tel cache est qu'il est capable de traité les défauts secondaires concernant un même mot mémoire, et ce, car les accès à la ligne de cache concernée se produisent dans l'ordre de génération des défauts (la table mémoire est une FIFO). '''Les MSHRs inversés''' Généralement, plus on veut supporter de défauts de cache, plus le nombre de MSHR et d'entrées augmente. Mais au-delà d'un certain nombre d'entrées et de MSHR, les MSHR adressés implicitement et explicitement ont tendance à bouffer un peu trop de circuits. Utiliser une organisation un peu moins gourmande en circuits est donc une nécessité. Cette organisation plus économe se base sur des MSHR inversés. Les MSHR inversés ne contiennent qu'une seule entrée, en quelque sorte : au lieu d’utiliser n MSHR de m entrées chacun, on va utiliser n × m MSHR inversés. La différence, c'est que plusieurs MSHR peuvent contenir un tag identique, contrairement aux MSHR adressés implicitement et explicitement. Lorsqu'un défaut de cache a lieu, chaque MSHR est vérifié. Si jamais aucun MSHR ne contient de tag identique à celui utilisé par le défaut, un MSHR vide est choisi pour stocker ce défaut, et une requête de lecture en mémoire est lancée. Dans le cas contraire, un MSHR est réservé au défaut de cache, mais la requête n'est pas lancée. Quand la donnée est disponible, les MSHR correspondant à la ligne qui vient d'être chargée vont être utilisés un par un pour résoudre les défauts de cache en attente. ====Les MSHR intégrés au cache==== Certains chercheurs ont remarqué que pendant qu'une ligne de cache est en train de subir un défaut de cache, celle-ci reste inutilisée, et son contenu est destiné à être perdu une fois le défaut de cache résolu. Ils se sont dit que, plutôt que d'utiliser des MSHR séparés, il vaudrait mieux utiliser la ligne de cache pour stocker les informations sur les défauts de cache en attente dans cette ligne de cache. Pour éviter tout problème, il faut rajouter un bit dans les bits de contrôle de la ligne de cache, qui sert à indiquer que la ligne de cache est occupée : un défaut de cache a eu lieu dans cette ligne, et elle stocke donc des informations pour résoudre les défauts de cache. ==L'entrelacement mémoire== Nous venons de voir comment une mémoire cache peut gérer plusieurs accès mémoire simultanés. Il se trouve que ce genre d'optimisation dépasse largement le cadre du cache. Les mémoires RAM ne sont pas en reste, elles aussi ! Il est possible de pipeliner l'accès à une mémoire RAM, en utilisant des techniques dites d''''entrelacement'''. Précisons cependant que cette optimisation n'est pas utilisée sur les mémoires SDRAM ou DDR modernes, du moins pas sans modifications majeures. Mais divers ordinateurs assez anciens, dont des superordinateurs, ont utilisé cette technique. La technique demande d'utiliser plusieurs mémoires séparées. Sur les anciens ordinateurs, les mémoires en question sont des chips mémoire, à savoir des circuits intégrés de DRAM. Pour faire plus général, nous allons utiliser le terme de '''banques''', ou encore de bancs mémoire. La différence est qu'il existe des mémoires multi-banque, qui regroupent plusieurs banques indépendantes dans un seul boitier, dans un seul chip mémoire. En clair, de telles mémoires regroupent plusieurs sous-mémoires dans un seul circuit intégré. Il est aussi possible d'utiliser plusieurs chips mémoire séparés, chacun ayant une banque. Reste à combiner ces banques pour former un pseudo-pipeline. ===L'entrelacement classique=== Sans optimisation particulière, les adresses sont réparties dans les banques comme indiqué ci-dessous. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Avec l''''entrelacement classique''', des adresses consécutives sont réparties dans des banques différentes. [[File:Répartition des adresses dans une mémoire interleaved.png|centre|vignette|upright=1.5|Répartition des adresses dans une mémoire interleaved.]] L'entrelacement simple permet d'accélérer les accès mémoire qui se font à des adresses consécutives. Avec l'entrelacement, chaque accès mémoire tombe dans une banque mémoire différente, ce qui fait qu'on peut démarrer un nouvel accès mémoire à chaque cycle d'horloge. Pendant qu'une banque est occupée par un accès mémoire, on démarre le suivant dans une autre banque, et ainsi de suite. Le tout se termine soit quand on a épuisé toutes les banques libres, soit quand l'accès en rafales se termine. Pas besoin d'attendre que la mémoire ait fini sa lecture/écriture avant de démarrer la lecture/écriture suivante. [[File:Pipemining mémoire.png|centre|vignette|upright=2.5|Pipelining mémoire]] L'animation suivante illustre bien le processus, avec quatres banques. [[File:Interleaving.gif|centre|vignette|upright=2.5|Pipelining mémoire]] L'entrelacement simple utilise les bits de poids faible pour sélectionner la banque, et les bits de poids fort pour la case mémoire. [[File:Adresse mémoire d'une mémoire entrelacée.png|centre|vignette|upright=2|Adresse mémoire d'une mémoire entrelacée]] Précisons que le temps d'accès mémoire ne change pas beaucoup avec l'entrelacement. Par contre, on peut faire plus d'accès mémoire simultanés. On peut démarrer un accès mémoire par cycle d'horloge, mais l'accès en lui-même prend plusieurs cycles. Les mémoires à entrelacement ont donc un débit supérieur aux mémoires qui ne l'utilisent pas. ===L'entrelacement par décalage=== L'entrelacement simple est très utile pour les accès en rafle ou équivalents. Par contre, il existe de rares situations où il n'est pas optimal. Une de ces situations est celle des accès en ''stride'', où l'on accède en série à des adresses sont séparées par N mots mémoires. De tels accès surviennent quand un logiciel accède à des structures de données spécifiques, à savoir des tableaux de structures, des matrices, ou d'autres structures de données dans le genre. Nous verrons dans quels chapitres que de tels accès étaient autrefois fréquents sur les anciens processeurs vectoriels, qui disposaient d'instructions capables de lire/écrire plusieurs données en mémoire d'un seul coup, avec un accès en ''stride''. Avec l'entrelacement simple, les accès en ''stride'' ont performances dégradées, mais cela ne fonctionne pas trop mal. Le pire cas est celui où l'on a N banques et où les données sont justement placées toutes les N adresses. Des accès consécutifs vont tous tomber dans la même banque, on ne peut plus accéder à des banques différentes en parallèle. Pour résoudre ce problème, il faut répartir les mots mémoires dans la mémoire autrement. : Dans les explications qui vont suivre, la variable N représente le nombre de banques, qui sont numérotées de 0 à N-1. L'organisation idéale est la suivante. On commence par organiser les N premières adresses comme une mémoire entrelacée simple : l'adresse 0 correspond à la banque 0, l'adresse 1 à la banque 1, etc. Pour le bloc suivant, on décale tout d'une adresse, à savoir qu'on commence à remplir les banques à partir de la seconde, non de la première. Une fois la fin du bloc atteinte, on finit de remplir le bloc en repartant du début du bloc. Le troisième bloc d'adresse subit le même traitement, sauf qu'on commence à remplir à partir de la seconde banque. Et on poursuit l’assignation des adresses en décalant d'un cran en plus à chaque bloc. Ainsi, chaque bloc verra ses adresses décalées d'un cran en plus comparé au bloc précédent. Si jamais le décalage dépasse la fin d'un bloc, alors on reprend au début. [[File:Mémoire interleaved par décalage.png|centre|vignette|upright=1.5|Mémoire entrelacée par décalage.]] En faisant cela, on remarque que les banques situées à N adresses d'intervalle sont différentes. Dans l'exemple du dessus, nous avons ajouté un décalage de 1 à chaque nouveau bloc à remplir. Mais on aurait tout aussi bien pu prendre un décalage de 2, 3, etc. Dans tous les cas, on obtient un entrelacement par décalage. Ce décalage est appelé le pas d'entrelacement, noté P. Le calcul de l'adresse à envoyer à la banque, ainsi que la banque à sélectionner se fait en utilisant les formules suivantes : * adresse à envoyer à la banque = adresse totale / N ; * numéro de la banque = (adresse + décalage) modulo N, avec décalage = (adresse totale * P) mod N. Avec cet entrelacement par décalage, on peut prouver que la bande passante maximale est atteinte si le nombre de banques est un nombre premier. Seulement, utiliser un nombre de banques premier peut créer des trous dans la mémoire, des mots mémoires inadressables. Pour éviter cela, il faut faire en sorte que N et la taille d'une banque soient premiers entre eux : ils ne doivent pas avoir de diviseur commun. Dans ce cas, les formules se simplifient : * adresse à envoyer à la banque = adresse totale / taille de la banque ; * numéro de la banque = adresse modulo N. ===L'entrelacement pseudo-aléatoire=== Une dernière méthode de répartition consiste à répartir les adresses dans les banques de manière "pseudo-aléatoire". L'idée exacte est de faire passer l'adresse dans une '''fonction de hachage''', plus ou moins complexe. Pour rappel, une fonction de hachage prend une entrée de grande taille et fournit en sortie un résultat de petite taille. Idéalement, elles doivent donner des résultats aussi différents que possible pour des entrées similaires, histoire de simuler une sorte de pseudo-aléatoire. Ici, une adresse est transformée en un numéro de banque. Une fonction de hachage simple ne fait que permuter des bits de l'adresse pour obtenir son résultat. Elle ne fait qu'échanger des bits de place avant de couper l'adresse en deux morceaux : un pour la sélection de la banque, et un autre pour la sélection de l'adresse dans la banque. Cette permutation est fixe, et ne change pas suivant l'adresse. Des fonctions de hachage plus complexes font des XOR bit à bit entre certains bits de l'adresse. Il existe aussi d'autres techniques qui donnent le numéro de banque à partir d'un polynôme modulo N, appliqué sur l'adresse. ==Les contrôleurs mémoires SDRAM/DDR optimisés== Après avoir vu le cas des mémoires RAM, nous allons nous concentrer sur le cas particulier des mémoires SDRAM. Les mémoires SDRAM et DDR modernes sont capables de gérer plusieurs accès simultanés à la mémoire RAM. Et les contrôleurs mémoire intègrent des optimisations spécifiques afin d'en profiter au maximum. ===La mise en attente des accès mémoire=== Comme pour les mémoires caches, il existe des contrôleurs mémoire bloquants et non-bloquants. Les premiers n'acceptent qu'un seul accès mémoire à la fois, les seconds acceptent plusieurs accès mémoire simultanés. Pour simplifier, nous allons dire que le contrôleur mémoire n’exécute qu'un seul accès mémoire à la fois, et qu'ils met en attente les accès en trop. C'est une simplification assez irréaliste, vu que les mémoires modernes disposent de plusieurs banques accessibles en parallèle. Mais nous introduirons ce détail un peu plus tard. Avec un contrôleur mémoire de ce type, les accès mémoire sont mis en attente dans une mémoire FIFO, histoire de les exécuter dans leur ordre d'arrivée. Les accès mémoire quittent alors la mémoire dans leur ordre d'arrivée, les lectures sont renvoyées dans l'ordre demandé. En plus d'une mémoire FIFO pour les commandes mémoire, on trouve une mémoire FIFO pour les données à écrire. Elle permet d'accumuler plusieurs écritures, tout en permettant que la donnée adéquate soit envoyée à la RAM au bon moment. Il y a aussi une FIFO pour les données lues, qui sert au cas où le cache ne soit pas disponible quand on lui envoie la donnée lue. [[File:Module d'interface avec la mémoire.png|centre|vignette|upright=2.5|Contrôleur mémoire avec mise en attente des requêtes processeur et autres optimisations.]] Une optimisation regroupe plusieurs accès à des données consécutives en un seul accès en rafale. Le contrôleur mémoire analyse les requêtes mises en attente et détecte si certaines se font à des adresses consécutives. Si c'est le cas, il fusionne ces requêtes en une lecture/écriture en rafale. Une telle optimisation est appelée la '''combinaison de lecture''' pour les lectures, et la '''combinaison d'écriture''' pour les écritures. ===Le pipeline des mémoires SDRAM=== Les SDRAM ont une forme de pseudo-pipeline très limité. Elles sont parfois capables de démarrer une commande avant que la précédente soit terminée. Même si elle parait très limitée, cette possibilité est exploitée au mieux par la présence de plusieurs banques dans la mémoire SDRAM. Les chips mémoires de SDRAM sont composés de plusieurs banques, chacune étant une sorte de mini-mémoire miniature. Chaque banque a son propre décodeur, son propre tampon de ligne, ses propres multiplexeurs de colonne, sa logique de rafraichissement mémoire, etc. C'est comme si une SDRAM regroupait plusieurs mémoires séparées dans un même circuit intégré. Et cela permet de faire plusieurs accès mémoire en même temps, dans des banques différentes. Il est possible de lancer un accès mémoire dans deux banques en même temps, par exemple. Ou encore, on peut lancer un nouvel accès mémoire dans une banque si elle est inoccupée. La possibilité améliore grandement la performance de la SDRAM. Mais nous en reparlerons dans un chapitre ultérieur. {|class="wikitable" |+ Pipelining basique sur les SDRAM |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 |- ! Banque Numéro 1 | || || || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || || || |- ! Banque Numéro 2 | || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || |- ! Banque Numéro 3 | || || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || |} L'implémentation n'a rien de vraiment compliqué. Il suffit d'utiliser une mémoire FIFO par banque. Ainsi, les accès dans une même banque se feront en série, dans l'ordre d'arrivée, mais des accès à des banques différentes se feront dans le désordre. Cela ne pose pas de problème car les accès se font à des adresses différentes, ce qui fait qu'il n'y a pas de conflits majeurs. [[File:Gestion parallèle des banques.png|centre|vignette|upright=2.5|Gestion parallèle des banques.]] ===Le ré-ordonnancement des commandes mémoires=== Un contrôleur mémoire moderne est capable de changer l'ordre des requêtes mémoires, pour gagner en performances. En clair : il peut faire l'équivalent mémoire de l'exécution dans le désordre des processeurs haute performance. Une différence majeure est que le contrôleur mémoire ne remet pas les accès mémoire dans l'ordre avant de les envoyer au processeur. C'est le processeur qui remet en ordre les accès mémoire, dans sa ''load queue''. Pour cela, les requêtes mémoire sont "numérotées", à savoir qu'on leur attribue un identifiant binaire. Le contrôleur mémoire exécute les lectures/écritures dans le désordre, mais garde la trace de leur identifiant. Il sait dans quel ordre il exécute les accès mémoire et connait leur latence. Ce qui fait qu'il sait que la donnée lue à tel instant est associée à la requête mémoire numéro X. Pour les lectures, il renvoie la donnée lue avec l'identifiant. Le processeur sait alors à quelle lecture la donnée lue correspond et il se débrouille en interne pour gérer la situation. En clair : le processeur voit les accès mémoire dans le désordre, c'est lui qui les remet en ordre. Maintenant que ces précisions sont faites, posons cette question : quel est l'intérêt de faire les accès mémoire dans le désordre ? Accéder à des banques en parallèle est une réponse possible, mais le ré-ordonnancement est surtout utile si tous les accès mémoire atterrissent dans la même banque. Pour comprendre pourquoi, faisons un rappel rapide sur le tampon de ligne. Les mémoires SDRAM sont des mémoires à tampon de ligne. Elles font un accès mémoire en deux étapes. La première étape recopie une ligne de N * 64 bits dans un tampon interne à la SDRAM. La seconde étape sélectionne une colonne, à savoir une donnée de 64 bits, qui est soit envoyée sur le bus de données pour une lecture, soit modifiée par une écriture. [[File:Mémoire à tampon de ligne.png|centre|vignette|upright=2|Mémoire à tampon de ligne]] Les deux étapes correspondent à deux commandes séparées : une commande ACT qui précise l'adresse de la ligne, et une commande READ ou WRITE qui précise l'adresse de la colonne. [[File:Signaux RAS et CAS.png|centre|vignette|upright=2|Signaux RAS et CAS.]] Les SDRAM permettent de se passer de la première étape si des accès consécutifs se font dans la même ligne. Une fois activée, la ligne reste ouverte et on peut accéder plusieurs fois de suite dedans. On a alors juste à préciser l'adresse de colonne dedans. [[File:Sélection d'une ligne sur une mémoire FPM ou EDO.png|centre|vignette|upright=2|Sélection d'une ligne sur une mémoire FPM ou EDO.]] L'exécution dans le désordre des lectures/écritures SDRAM vise à faire en sorte que des accès consécutifs se fassent dans une même ligne. Elle marche si plusieurs accès à une même ligne ne sont pas consécutifs, et qu'il y a des accès entre les deux. Après ré-ordonnancement, ces accès mémoire deviennent consécutifs, ils sont exécutés l'un à la suite de l'autre. Pour rendre le tout plus concret, voici un exemple. Imaginez que l'on sait les 3 accès mémoire suivants : * Une lecture ligne A ; * Une écriture ligne B ; * Une lecture ligne A. Sans réordonnancent, on doit émettre deux commandes PRECHARGE : une quand on passe de la ligne A à la ligne B, et une autre pour le passage inverse. pour éviter cela, le contrôleur mémoire peut retarder l'écriture, ou au contraire avancer la seconde lecture. le résultat est alors le suivant : * Lecture ligne A ; * Lecture ligne A ; * Une écriture ligne B. On a donc deux accès consécutifs à la même ligne, suivi par une écriture dans une autre ligne. La technique marche parce que l'on a un mix adéquat de lectures et d'écritures. Mais il existe des possibilités de réorganisation autres qui ne sont pas valides. Il faut par exemple prendre garde à éviter d'intervertir lectures et écritures à une même adresse, sous peine de problèmes. Encore une fois, il faut faire attention aux dépendances mémoires. Le controleur mémoire teste les dépendances mémoires avant d'envoyer des commandes à la mémoire DDR/SDRAM. ===L'entrelacement sur les mémoires SDRAM=== L''''entrelacement''' fonctionne sur les mémoires SDRAM, mais il doit être fortement modifié. Concrètement, tout ce qui a étré dit plus est inapplicable pour les SDRAM, du fait de la présence du tampon de ligne. Des adresses consécutives atterrissent déjà dans la même ligne, ce qui fait qu'on n'a pas besoin d'entrelacement au niveau d'une ligne. Et ne parlons pas de ce la présence de rangées et de canaux mémoire ! Cependant, il peut y avoir un entrelacement lié à la présence des banques SDRAM. Pour gérer l'entrelacement, le contrôleur de SDRAM prend en entrée l'adresse envoyée par le processeur, et la découpe en plusieurs champs : un pour sélectionner la banque adéquate, un autre pour la ligne, un autre pour la colonne, un autre pour la rangée. Une solution simple désactive l'entrelacement. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de banque || Adresse de ligne || Adresse de colonne |} Si cette solution fonctionne bien si les accès mémoire sont assez aléatoires, en pratique, mieux vaut utiliser l'entrelacement. Une première méthode d'entrelacement est l''''entrelacement de ligne'''. Elle répartit deux lignes consécutives dans deux banques différentes. Avec N banques, la première ligne ira dans la première banque, la seconde ligne dans la seconde, et ainsi de suite jusqu'à la énième banque qui va dans la banque numéro N. Et la ligne N+1 repart dans la première banque. L'avantage est que passer d'une ligne à la suivante est assez rapide. Au lieu de devoir fermer la première ligne et ouvrir la suivante on ouvrira directement la ligne suivante dans une banque différente, ce qui sera plus rapide. La commande PRECHARGE pourra être envoyée à la seconde banque en avance. Pour cela, l'adresse est découpée comme suit. {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne (précise à l'octet) |} D'autres méthodes d'entrelacement sont possibles, mais elles sont plus complexes. Chaque contrôleur mémoire fait un peu à sa sauce. Ils permettent en général de choisir entre plusieurs entrelacements. Au minimum, ils permettent de désactiver l'entrelacement, d'activer un entrelacement de ligne, et d'autres entrelacements plus complexes, dont certains sont propriétaires et tenus secrets par le fabricant. L'usage du ''dual channel'' complique encore l'entrelacement. Là encore, il y a deux grandes solutions : avec et sans entrelacement des canaux mémoire. Rappelons le principe : deux barrettes de RAM sont accédées en parallèle. Et pour cela, il faut utiliser l'entrelacement. Typiquement, chaque barrette mémoire fournit 64 bits, ce qui fait que l'on peut accéder à 128 bits d'un seul coup, par exemple avec un accès en rafale. Une solution pour cela est de ne pas utiliser l'entrelacement. Avec deux barrettes, la première barrette regroupera la moitié haute de la RAM, la seconde barrette utilisera la moitié basse. Les possibilité d’accéder à deux données séparées de 64 bits en même temps est cependant très rare. Aussi, on préfère utiliser l'entrelacement. Pour cela, deux blocs de 64 bits sont placés dans des canaux mémoire séparés. Idem avec du triple ou quadruple canal, mais c'est alors trois ou quatre blocs de 64 bits qui sont dispersés dans des canaux consécutifs. {|class="wikitable" |+ Adresse mémoire |- ! colspan="6" | Sans entrelacement |- | Numéro de canal/barrette || Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne || Position de l'octet dans un bloc de 64 bits |- | colspan="6" | |- ! colspan="6" | Avec entrelacement |- | Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne || Numéro de canal/barrette || Position de l'octet dans un bloc de 64 bits |} Les contrôleurs de SDRAM précédents sont assez basiques et ne représentent pas les contrôleurs les plus évolués. Ils étaient utilisés sur les anciens PC, à une époque où ils étaient encore sur la carte mère du processeur. Mais de nos jours, le contrôleur mémoire est intégré au processeur. Et il incorpore de nombreuses optimisations afin de gagner en performances. Malheureusement, ces optimisations sont elles-mêmes dépendantes des optimisations intégrées dans le processeur, ce qui fait qu'on ne peut pas en parler ici. Elles demandent que le contrôleur mémoire reçoive plusieurs accès mémoire simultanés, ce qui n'a aucun sens à ce stade du cours. Aussi, nous allons les passer sous silence pour le moment. Nous les verrons dans le chapitre sur le parallélisme mémoire. L'entrelacement est géré juste avant le séquenceur mémoire. Le '''circuit d'entrelacement''' intervertit certains bits de l'adresse lors des accès mémoires. On trouve aussi des mémoires FIFOs pour mettre en attente les requêtes mémoire provenant du processeur. Elles permettent de recevoir plusieurs accès mémoire consécutifs. Si vous vous demandez comment plusieurs accès mémoire consécutifs peuvent avoir lieu, sachez que c'est une bonne question. Pour le moment, nous allons dire que ces accès proviennent du processeur ou de la mémoire cache, sans expliquer comment. Le contrôleur mémoire reçoit une série d'accès mémoire assez rapprochés, qu'il doit exécuter au mieux. [[File:Controleur mémoire d'une SDRAM avec entrelacement.png|centre|vignette|upright=2.5|Contrôleur mémoire d'une SDRAM avec entrelacement.]] <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=La désambiguïsation mémoire | prevText=La désambiguïsation mémoire | next=Les processeurs superscalaires | nextText=Les processeurs superscalaires }} </noinclude> rbb50hlin5ca5h1v9yzmbq6bkkwfksk 764642 764641 2026-04-23T13:49:50Z Mewtow 31375 /* Les accès simultanés à une même ligne de cache */ 764642 wikitext text/x-wiki Dans ce chapitre, nous allons voir les techniques qui permettent de gérer plusieurs accès mémoire simultanés directement au niveau du cache ou de la mémoire RAM. Par plusieurs accès mémoire simultanés, vous pensez sans doute à l'usage de cache multiports, voire de mémoires RAM multiport. C'est en effet une solution, mais ce n'est clairement pas la seule. Par exemple, il est possible de pipeliner l'accès au cache, voire à la mémoire RAM. Il existe de nombreuses techniques de '''parallélisme mémoire''', et nous allons les voir dans ce chapitre. Pour donner un exemple, il est possible de pipeliner les accès mémoire. Il est possible de pipeliner l'accès au cache et/ou l'accès à la mémoire, et nous verrons comment faire dans ce chapitre. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, qu'ils aient ou non un pipeline dynamique. Par contre, pipeliner la mémoire est une technique ancienne, aujourd'hui peu utilisée. Elle était utilisée sur des très vieux ordinateurs, qui avaient un pipeline mais pas de mémoire cache (et qui avaient souvent de l'exécution dans le désordre et du renommage de registres). Mais de nos jours, les mémoires SDRAM et DDR ne sont pas adaptées pour ça, il est très difficile de les pipeliner correctement. Le parallélisme mémoire est utile aussi bien sur des processeurs à émission dans l'ordre que dans le désordre. Par exemple, un processeur ''in-order'' avec des lectures non-bloquantes peut e, profiter. Il peut techniquement lancer une seconde lecture, après avoir rencontré une première lecture non-bloquante. En général, le nombre de lectures consécutives est cependant limité à moins d'une dizaine. À l'opposé, les processeurs sans lectures non-bloquantes profitent pas du parallélisme mémoire. À la rigueur, ils peuvent en profiter s'ils intègrent des techniques de préchargement. Pendant que le processeur exécute une lecture/écriture, il peut précharger une autre donnée en parallèle. Inutile de dire que sans pipeline processeur, le parallélisme mémoire ne sert pas à grand-chose. ==Le ''line fill buffer'' et les techniques associées== Lors d'un défaut de cache, le processeur doit attendre que toute la ligne de cache soit chargée avant d'être utilisable. Or, la taille d'une ligne de cache est supérieure à la largeur du bus mémoire, ce qui fait qu'une ligne de cache est chargée en plusieurs fois, morceaux par morceaux, mot mémoire par mot mémoire. Le chargement peut se faire directement dans le cache, mais ce n'est pas une solution très pratique. À la place, beaucoup de processeurs ajoutent une mémoire tampon entre la RAM et le cache, appelée le '''tampon de remplissage de ligne''' (''line-fill buffer'') dans les processeurs modernes. Lors d'un défaut de cache, le processeur charge la donnée de la RAM dans le tampon de remplissage de ligne, mot mémoire par mot mémoire. Une fois plein, le tampon de remplissage de ligne est recopié dans la ligne de cache. Le tampon de remplissage de ligne contient une ligne de cache, si ce n'est que certains bits de contrôle ne sont pas présents. Le tampon de remplissage de ligne contient un bit de validité pour chaque mot mémoire de la ligne de cache, qui indiquent si le mot mémoire a été chargé. Par exemple, prenons un processeur 64 bits, qui gère donc des mots mémoire de 8 octets, avec des lignes de cache 256 octets/32 mots mémoire. Le tampon de remplissage de ligne contiendra 32 bits de validité. Si le processeur a chargé les 6 premiers mots mémoire, les 6 premiers bits de validité seront mis à 1, les autres seront encore à zéro. Il faut noter que la disponibilité de la ligne complète se détermine assez facilement en faisant un ET logique entre tous les bits de validité. Le processeur sait ainsi quand la ligne de cache est disponible entièrement et donc quand la transférer dans le cache. La même chose existe avec une hiérarchie de cache, sauf que l'on trouve une mémoire tampon entre chaque niveau de cache. Il y a un tampon de remplissage de ligne entre le cache L1 et le cache L2, entre le cache L2 et le L3, etc. Si le cache ne gère qu'un seul défaut de cache à la fois, le ''fill line buffer'' est une mémoire très simple, qui ne mémorise qu'une seule ligne de cache. Et cela vaut aussi bien pour un cache bloquant que non-bloquant. Mais sur les caches capables de gérer plusieurs défauts simultanés, le tampon de remplissage de ligne est une mémoire de type FIFO ou LIFO, capable de mémoriser plusieurs lignes de cache. Notons que le tampon de remplissage de ligne est très utile pour implémenter certaines techniques, par exemple le contournement du cache. Nous avions vu dans le chapitre sur le cache que certains accès mémoire doivent contourner le cache, pour des raisons de cohérence des caches. C'est notamment nécessaire pour accéder aux périphériques, mais c'est aussi utile pour des raisons de performances dans des cas très spécifiques. Les accès qui contournent le cache se font directement dans le tampon de remplissage de ligne : le processeur écrit ou lit les données depuis ce tampon de remplissage de ligne, sans accéder au cache. ===L'''early restart'' et le ''critical word load''=== La présence du ''line buffer'' permet une optimisation assez intéressante, qui permet de réduire la latence des défauts de cache. L'optimisation consiste à lire un mot mémoire dans le tampon de remplissage de ligne, même si la ligne de cache complète n'a pas encore été chargée. Il existe deux manières de faire cela, qui portent les noms d'''early restart'' et de ''critical word load''. La première est la version la plus simple, la seconde est plus complexe mais plus performante. Avec la technique d''''''early restart''''', la ligne de cache est chargée normalement, en partant de son premier mot mémoire. Dès que le mot mémoire lu/écrit par le processeur est copié dans le ''line fill buffer'', il est envoyé au processeur immédiatement. Illustrons le tout par un exemple, où une ligne de cache fait 16 mots mémoire. Le processeur effectue une lecture, qui lit le 5ème mot mémoire. Sans ''early restart'', le processeur doit charger les 16 mots mémoire avant de faire la lecture dans le cache. Avec ''early restart'', le processeur reçoit la donnée dès que le 5ème mot mémoire est disponible. Le processeur doit attendre que les 4 premiers mots mémoire soient chargés, puis le 5ème mot mémoire arrive et est envoyé directement au processeur, il est lu directement depuis le ''line fill buffer''. Les 11 mots mémoire suivants sont ensuite chargés dans le cache pendant que le processeur fait des calculs dans son coin. Le ''critical word load'' est une optimisation de la technique précédente où le chargement de la ligne de cache commence directement à la donnée demandée par le processeur. Pour reprendre l'exemple précédent, où le processeur demande le 5ème mot mémoire sur 16, le ''critical word load'' charge le 5ème mot mémoire en premier et l'envoie au processeur, ce qui fait qu'il est chargé très rapidement. Pas besoin d'attendre que le processeur charge les 4 mots mémoire précédents comme avec l'''early restart''. Dans le détail, le processeur charge le 5ème mot mémoire en premier, puis charge les 11 suivants, et termine par les 4 mots mémoire du début. En clair, le ''critical word load'' commence par charger le mot mémoire lu, puis les blocs suivants, avant de revenir au début du bloc pour charger les blocs restants. Ainsi, la donnée demandée par le processeur sera la première disponible. Pour cela, l'organisation du tampon de remplissage de ligne est modifiée de manière à rendre cela possible. Il a une taille égale à une ligne de cache complète, qui contient elle-même plusieurs mots mémoire. Dans le ''line-fill buffer'', chaque mot mémoire est stocké avec un tag, qui indique l'adresse du mot mémoire stocké dans le ''line-fill buffer''. Le ''line-fill buffer'' est donc un cache un peu particulier, qui fonctionne comme un cache du point de vue du processeur, comme une mémoire FIFO pour les transferts avec le cache. Ainsi, un processeur qui veut lire dans le cache après un défaut peut accéder à la donnée directement depuis le tampon de remplissage de ligne, alors que la ligne de cache n'a pas encore été totalement recopiée en mémoire. ==Les caches pipelinés== Il est possible de pipeliner l'accès au cache, ce qui demande juste de rajouter des registres au bon endroits et quelques circuits de contrôle. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, même ceux avec un pipeline dynamique. Et l'implémentation peut se faire de plusieurs manières différentes. Il faut dire que découper une mémoire cache en plusieurs étapes peut se faire de plusieurs manière. Commençons par la plus simple. Beaucoup de processeurs des années 2000 avaient une fréquence peu élevée comparée aux standards d'aujourd'hui, ce qui fait que leur cache avait bien un temps d'accès d'un cycle d'horloge. Mais l'accès au cache se faisait en deux cycles d'horloge : un cycle pour calculer une adresse et un cycle pour l'accès mémoire proprement dit. Et ces deux étapes étaient pipelinées, à savoir que deux micro-opérations mémoire peuvent s'exécuter en même temps : une dans l'étage de calcul d'adresse, une autre dans le cache. L'unité mémoire était donc pipelinée, alors que l'accès au cache ne l'était pas. Les processeurs qui implémentaient cette technique regroupent les micro-architectures K5 et K6 d'AMD, les processeurs Intel de micro-architecture P6 (Pentium 2 et 3) et quelques autres. Il est aussi possible de pipeliner l'accès au cache lui-même. Avec un cache pipeliné, l'accès au cache ne se fait pas en un seul cycle, mais en plusieurs. Cependant, on peut lancer un nouvel accès au cache à chaque cycle d'horloge, comme un processeur pipeliné lance une nouvelle instruction à chaque cycle. L'implémentation est assez simple : il suffit d'ajouter des registres dans le cache. Pour cela, on profite que le cache est composé de plusieurs composants séparés, qui échangent des données dans un ordre bien précis, d'un composant à un autre. De plus, le trajet des informations dans un cache est linéaire, ce qui les rend parfois pour l'usage d'un pipeline. ===Le ''pipelining'' des caches ''direct-mapped''=== Voici ce qui se passe avec un cache directement adressé. Pour rappel ce genre de cache est conçu en combinant une mémoire RAM, généralement une SRAM, avec quelques circuits de comparaison et un MUX. L'accès au cache se fait globalement en deux étapes : la première lit la donnée et le ''tag'' dans la SRAM, et on utilise les deux dans une seconde étape. Nous avions vu il y a quelques chapitre comment pipeliner des mémoires, dans le chapitre sur les mémoires évoluées. Et bien ces méthodes peuvent s'utiliser pour la mémoire RAM interne au cache !L'idée est d'insérer un registre entre la sortie de la RAM et la suite du cache, pour en faire un cache pipéliné, à deux étages. Le premier étage lit dans la SRAM, le second fait le reste. L'implémentation sur les caches associatifs à plusieurs voies est globalement la même à quelques détails près. [[File:Cache directement adressé pipeliné - deux étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - deux étages]] Avec le circuit précédent, il est possible d'aller plus loin, cette fois en pipelinant l'accès à la mémoire RAM interne au cache. Pour comprendre comment, rappelons qu'une mémoire SRAM est composée d'un plan mémoire et d'un décodeur. L'accès à la mémoire demande d'abord que le décodeur fasse son travail pour sélectionner la case mémoire adéquate, puis ensuite la lecture ou écriture a lieu dans cette case mémoire. L'accès se fait donc en deux étapes successives séparées, on a juste à mettre un registre entre les deux. Il suffit donc de mettre un gros registre entre le décodeur et le plan mémoire. [[File:Cache directement adressé pipeliné - trois étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - trois étages]] Et on peut aller encore plus loin en découpant le décodeur en deux circuits séparés. En effet, rappelez-vous le chapitre sur les circuits de sélection : nous avions vu qu'il est possible de créer des décodeurs en assemblant des décodeurs plus petits, contrôlés par un circuit de prédécodage. Et bien on peut encore une ajouter un registre entre ce circuit de prédécodage et les petits décodeurs. : Théoriquement, toute l'adresse est fournie d'un seul coup au cache, la quasi-totalité des processeurs présentent l'adresse complète à un cache pipeliné. Mais le Pentium 4 fait autrement. Il faut noter que les premiers étages manipulent l'indice dans la SRAM, qui est dans les bits de poids faible, alors que les étages ultérieurs manipulent le ''tag'' qui est dans les bits de poids fort. Les concepteurs du Pentium 4 ont alors décidé de présenter les bits de poids faible lors du premier cycle d'accès au cache, puis ceux de poids fort au second cycle. Pour cela, l'ALU fonctionnait à une fréquence double de celle du processeur, tout comme le cache L1. Il n'y avait pas de pipeline proprement dit, mais cela réduisait grandement la latence d'accès au cache. ===Le ''pipelining'' des caches splittés/sériels=== Il est aussi possible de pipeliner un cache dits splittés, aussi appelés à accès sériel. Pour rappel, les caches à accès sériel vérifient si il y a succès ou défaut de cache, avant d'accéder aux lignes de cache en cas de succès. Ils font donc différemment des autres caches, qui accèdent à une ligne de cache, avant de déterminer s'il y a succès ou défaut en lisant le tag de la ligne de cache. Les caches sériels disposent de deux SRAM : une pour les tags des lignes de cache et une pour les données. Ils accèdent à la SRAM pour les tags, avant d’accéder à la SRAM des données en cas de succès de cache. Vu que l'accès se fait en deux étapes, une vérification des tags suivie de la lecture/écriture des données, il est facile à pipeliner. Pipeliner le cache permet de régler le problème des accès au cache L1, et elle est tout le temps utilisé sur les processeurs modernes. Mais que faire en cas de défaut de cache ? ==Les caches non bloquants== Un '''cache bloquant''' est un cache auquel le processeur ne peut pas accéder pendant un défaut de cache. Il faut attendre que la lecture ou écriture en RAM soit terminée avant de pouvoir utiliser de nouveau le cache. Un '''cache non bloquant''' n'a pas ce problème : on peut l'utiliser pendant un défaut de cache. Les caches non bloquants permettent de démarrer une nouvelle lecture ou écriture alors qu'une autre est en cours, ce qui permet d'exécuter plusieurs lectures ou écritures en même temps. ===Les ''Miss Handling Status Registers''=== Lors d'un défaut de cache, la mémoire RAM est consultée pendant le défaut de cache, mais le cache est inutilisé. Un défaut de cache n'utilise pas le cache, ce n'est qu'un accès en mémoire RAM, sur le bus mémoire (ou un accès aux niveaux de cache inférieurs, peu importe). Le cache en lui-même est laissé libre, rien n’empêche d'y accéder, il est en réalité intrinsèquement non-bloquant. Les caches, bloquants comme non-bloquants, sont en réalité composés d'une mémoire cache proprement dite, entourée de circuits qui servent d'interface entre le processeur et le cache lui-même. Et parmi les circuits tout autour, certains gèrent l'accès au cache lors d'un défaut de cache. Ils sont regroupés sous le terme de '''''Miss Handling Architecture''''' (MHA). La différence entre un cache bloquant et un cache non-bloquant est en réalité liée à l'implémentation de la MHA. Les caches bloquants coupent volontairement l'accès au cache lors d'un défaut, car les défauts sont plus simples à gérer ainsi. Le défaut de cache rapatrie une donnée depuis la RAM, qui sera écrite dans le cache. Et il ne faut pas qu'une tentative d'accès à cette donnée ait lieu avant qu'elle ne soit chargée. Mais ce blocage est général et touche tout le cache, alors que seule une ligne de cache est concernée par le défaut de cache. L'idée derrière un cache non-bloquant est que seule la ligne de cache est bloquée, mais les autres sont accessibles. L'idée est alors de mémoriser les lignes de cache concernées par le défaut de cache, afin d'en bloquer l'accès. À chaque accès, on vérifie si la ligne de cache est déjà réservée par un défaut de cache. La lecture/écriture est alors bloquée si c'est le cas, mais elle accepte les accès sinon. Pour cela, la ''Miss Handling Architecture'' contient des registres qui mémorisent des informations sur les défauts de cache en cours. Ils portent le nom de '''''miss status handling registers''''', que j’appellerais dorénavant MSHR, qui sont aussi appelés des '''''miss buffer'''''. Le contenu des MSHR varie beaucoup suivant le processeur, mais ces derniers stockent au minimum les informations suivantes : * Le numéro de la ligne de cache dans laquelle les données sont chargées. * Un bit de validité qui indique si le MSHR est vide ou pas, qui est mis à 0 quand le défaut de cache est résolu. * Un ou plusieurs champs de lecture/écriture, qui contiennent des informations sur la lecture/écriture. ** Pour une lecture, elle contient des informations sur la destination de la donnée, à savoir qui prévenir quand le défaut de cache est terminé. C'est parfois un nom/numéro de registre (celui dans lequel charger la donnée), mais c'est souvent le numéro de l'entrée dans le ''load/store queue''. ** Pour les écritures, elle contient la donnée à écrire, ou éventuellement un numéro de ''load/store queue'' où se trouve la donnée à écrire. Il faut noter que le bus mémoire ne peut gérer qu'un seul défaut de cache à la fois. Aussi, il est intéressant de regarder ce qui se passe lorsqu'un second défaut de cache survient, pendant qu'un premier est en cours. Dans ce cas, il y a deux réponses qui correspondent à deux types de caches non-bloquants, qui portent les noms barbares de caches de type succès après défaut et défaut après défaut. Sur le premier type, il ne peut pas y avoir plusieurs défauts de cache simultanés. Dès qu'un second défaut de cache survient, le cache stoppe son activité et on ne peut plus démarrer de nouvelle lecture/écriture, tant que le premier défaut de cache n'est pas résolu. Le second type est plus souple et autorise la survenue de plusieurs défauts de cache simultanés. Du moins, jusqu'à une certaine limite, car le cache ne peut supporter qu'un nombre limité d'accès mémoires simultanés (pipelinés). ===Les accès simultanés à une même ligne de cache=== Il arrive que le processeur fasse plusieurs accès mémoire simultanés à la même ligne de cache. Si la ligne de cache en question n'a pas encore été chargée dans le cache, alors on a plusieurs défauts de cache consécutifs pour la même ligne de cache, et le cache non-bloquant doit gérer la situation. Pour la suite, il va falloir faire une petite distinction entre les défauts primaires et secondaires. Imaginons qu'un défaut de cache ait lieu et demande à charger une donnée dans la ligne de cache numéro N. Il s'agit du premier défaut impliquant cette ligne de cache précise, ce qui lui vaut le nom de '''défaut de cache primaire'''. Mais par la suite, d'autres accès mémoire à la même ligne de cache ont lieu, alors que la ligne de cache n'est pas encore disponible. Dans ce cas, il s'agit de '''défauts de cache secondaires'''. Pour l'unité d'accès mémoire, les défauts de cache primaire et secondaire sont différents (ils prennent tous une entrée dans la ''load/store queue''). Mais pour le cache, ils ne correspondent qu'à un seul accès au cache : celui qui demande de charger la ligne de cache demandée. Les défauts de cache primaire et secondaire à la même ligne de cache se voient attribuer un MSHR unique. La gestion des défauts de cache secondaires dépend du cache non-bloquant. La solution la plus simple ne permet pas les défauts de cache secondaires. Le cache ne permet pas deux défauts de cache simultanés pour la même ligne de cache. Les autres solutions le permettent, en fusionnant des accès simultanés à la même ligne de cache en un seul au niveau des MSHR. Dans tous les cas, détecter les défauts de cache secondaires sont un problème qu'il faut détecter. La ''Miss Handling Architecture'' doit détecter les défauts de cache secondaire. Pour cela, elle procède comme suit. Lors de chaque défaut de cache, la MHA récupère le numéro de la ligne de cache associé. Il vérifie alors chaque MSHR pour vérifier s'il contient le numéro en question. S'il n'y a aucune correspondance dans les MSHR, alors c'est signe que le défaut de cache est un défaut primaire. Mais s'il y en a une, alors c'est un défaut secondaire. Évidemment, cela signifie que lors d'un défaut de cache, le numéro de ligne de cache est envoyé à tous les MSHR, pour comparaison. Les MSHR sont donc regroupés dans une mémoire associative, une sorte de mini-cache, faciliter l'implémentation. Lors d'un défaut de cache primaire, l'accès à un cache non-bloquant se fait comme suit : le processeur envoie une adresse au cache, accède à celui-ci, et détecte la survenue d'un défaut de cache. Il en profite alors pour attribuer une ligne de cache dans laquelle sera chargée ce défaut. L'attribution est très simple dans le cas des caches ''direct mapped'', ou associatifs par voie, pour lesquels l'attribution se fait assez simplement. Il mémorise alors cette information dans les MSHR, après avoir vérifié que le défaut de cache n'était pas un défaut secondaire. Lors des accès ultérieurs à une adresse proche, censée être dans la même ligne de cache, le processeur va encore une fois rencontrer un défaut de cache. Il va alors déterminer le numéro de la ligne de cache associée à l'adresse, et comparer ce numéro avec les MSHRs. Si un MSHR contient ce numéro, c'est signe que le défaut de cache est un défaut secondaire. La MHA réagit alors différemment selon le processeur considéré. Une première solution n'autorise pas les défauts de cache secondaires. Si l'un d'entre eux survient, le processeur est gelé par un ''pipeline stall'', une bulle de pipeline. Une autre solution fusionne les défauts de cache secondaires avec le défaut de cache primaire : tout cela ne correspond qu'à un seul défaut de cache pour lui. ===Les MSHR simples=== Un cache non-bloquant à '''MSHR simple''' contient juste plusieurs MSHR qui mémorisent juste un numéro de ligne de cache, un bit de validité, le champ de lecture/écriture, et l'adresse exacte de lecture/écriture. Avec cette organisation, il est possible d'avoir plusieurs défauts de cache séparés, mais à la condition que chaque défaut accède à une ligne de cache différente. Deux accès simultanés à une même ligne de cache ne sont pas possibles, les défauts de cache secondaires ne sont pas autorisés. Ainsi, chaque défaut de cache se voit attribuer son propre MSHR, chacun contient un numéro de ligne de cache différent. Pour comprendre pourquoi c'est impossible de gérer les défauts secondaires, il faut regarder le champ de lecture/écriture. Si on veut effectuer plusieurs écritures consécutives à la même adresse, le MSHR n'aura pas de quoi mémoriser les deux données à écrire. Il pourra mémoriser la première donnée à écrire, pas la seconde. Même chose lors d'une lecture : le champ lecture/écriture peut mémorisr la destination de la première lecture, pas de la seconde. L'avantage est que la MHA n'a besoin que des MSHR et de quelques circuits annexes. Les autres solutions rajoutent des circuits annexes pour gérer les défauts de cache secondaires, qui utilisent beaucoup de circuits. Le cout en circuit est donc élevé, mais le gain en performance est là. Passons maintenant aux caches non-bloquants qui autorisent les défauts de cache secondaire. La solution la plus simple consiste à utiliser ===Les MSHR adressés implicitement=== Avec les '''MSHR adressés implicitement''', il est possible de fusionner plusieurs accès mémoire à une même ligne de cache, mais sous une condition très importante : ces accès lisent/écrivent des mots mémoire différents. Par exemple, imaginons qu'une ligne de cache contienne 8 mots mémoire de 64 bits. Si un premier accès mémoire lit le mot mémoire numéro 7 (dernier mot mémoire de la ligne), et le second accès le mot mémoire numéro 3, alors la fusion est possible. Mais si deux accès mémoire veulent lire/écrire le mot mémoire numéro 7, alors la fusion n'est pas possible et le processeur se bloque, un ''pipeline stall'' survient. Un MSHR adressé implicitement est un MSHR simple, qui contient naturellement un numéro de ligne de cache (tag) et un bit de validité, sauf que le champ de lecture/écriture est dupliqué. Les différents champs lecture/écriture d'un MSHR sont regroupés dans une mémoire RAM/cache qui contient autant d'entrées qu'il y a de mot mémoire dans une ligne de cache. Chaque entrée de la table est associée à un mot mémoire de la ligne de cache et stocke des informations sur celui-ci. Une entrée mémorise, au minimum : * Un champ de lecture/écriture qui contient soit la destination de la lecture, soit la donnée à écrire. * Un bit R/W permettant d'interpréter correctement le champ de lecture/écriture. * Un bit de validité pour chaque entrée de la table, qui dit si un défaut de cache antérieur accède déjà à ce mot mémoire. La fusion de deux défauts de cache est ainsi assez simple. Un défaut de cache primaire/secondaire configure l'entrée associée au mot mémoire lu/écrit. Si un défaut secondaire ultérieur lit/écrit un mot mémoire différent (dont le bit de validité de l'entrée dans la table est à zéro), pas de conflit : il configure une autre entrée, vide. Mais s'il lit/écrit un mot mémoire pour lequel l'entrée de la table est occupée, il y a conflit, le processeur est bloqué par un ''pipeline stall'', une bulle de pipeline. Avec cette organisation, le nombre de MSHR indique combien de lignes de cache peuvent être lues en même temps depuis la mémoire. Quant au nombre d'entrées par MSHR, il détermine combien d'accès mémoires qui ne se recouvrent pas peuvent avoir lieu en même temps. ===Les MSHR adressés explicitement=== Les '''MSHR adressés explicitement''' n'ont pas les contraintes des MSHR adressés implicitement. Avec eux, il est possible d'avoir plusieurs défauts de cache pointant vers la même ligne de cache, mais aussi vers le même mot mémoire. De tels défauts de cache apparaissent sur les processeurs à exécution dans le désordre, mais sont très rares, voire inexistants, sur les processeurs ''in-order''. L'idée est encore que chaque MSHR est associée à une table mémoire qui mémorise des entrées. Cette table n'est autre qu'une mémoire FIFO. Sauf que cette fois-ci, une entrée n'est pas associée à un mot mémoire. Une entrée est associée à un défaut de cache. Une entrée mémorise là encore la destination de la lecture ou la donnée de l'écriture suivi du bit R/W, ainsi que d'un bit de validité par entrée, mais aussi : la position du mot mémoire lu dans la ligne de cache. C'est cette dernière information qui n'était pas présente dans les MSHR adressés implicitement. De plus, le nombre d'entrée par MSHR n'est pas égal au nombre de mots mémoires dans une ligne de cache, mais peut être arbitrairement grand. L'avantage d'un tel cache est qu'il est capable de traité les défauts secondaires concernant un même mot mémoire, et ce, car les accès à la ligne de cache concernée se produisent dans l'ordre de génération des défauts (la table mémoire est une FIFO). ===Les MSHRs inversés=== Généralement, plus on veut supporter de défauts de cache, plus le nombre de MSHR et d'entrées augmente. Mais au-delà d'un certain nombre d'entrées et de MSHR, les MSHR adressés implicitement et explicitement ont tendance à bouffer un peu trop de circuits. Utiliser une organisation un peu moins gourmande en circuits est donc une nécessité. Cette organisation plus économe se base sur des MSHR inversés. Les MSHR inversés ne contiennent qu'une seule entrée, en quelque sorte : au lieu d’utiliser n MSHR de m entrées chacun, on va utiliser n × m MSHR inversés. La différence, c'est que plusieurs MSHR peuvent contenir un tag identique, contrairement aux MSHR adressés implicitement et explicitement. Lorsqu'un défaut de cache a lieu, chaque MSHR est vérifié. Si jamais aucun MSHR ne contient de tag identique à celui utilisé par le défaut, un MSHR vide est choisi pour stocker ce défaut, et une requête de lecture en mémoire est lancée. Dans le cas contraire, un MSHR est réservé au défaut de cache, mais la requête n'est pas lancée. Quand la donnée est disponible, les MSHR correspondant à la ligne qui vient d'être chargée vont être utilisés un par un pour résoudre les défauts de cache en attente. ===Les MSHR intégrés au cache=== Certains chercheurs ont remarqué que pendant qu'une ligne de cache est en train de subir un défaut de cache, celle-ci reste inutilisée, et son contenu est destiné à être perdu une fois le défaut de cache résolu. Ils se sont dit que, plutôt que d'utiliser des MSHR séparés, il vaudrait mieux utiliser la ligne de cache pour stocker les informations sur les défauts de cache en attente dans cette ligne de cache. Pour éviter tout problème, il faut rajouter un bit dans les bits de contrôle de la ligne de cache, qui sert à indiquer que la ligne de cache est occupée : un défaut de cache a eu lieu dans cette ligne, et elle stocke donc des informations pour résoudre les défauts de cache. ==L'entrelacement mémoire== Nous venons de voir comment une mémoire cache peut gérer plusieurs accès mémoire simultanés. Il se trouve que ce genre d'optimisation dépasse largement le cadre du cache. Les mémoires RAM ne sont pas en reste, elles aussi ! Il est possible de pipeliner l'accès à une mémoire RAM, en utilisant des techniques dites d''''entrelacement'''. Précisons cependant que cette optimisation n'est pas utilisée sur les mémoires SDRAM ou DDR modernes, du moins pas sans modifications majeures. Mais divers ordinateurs assez anciens, dont des superordinateurs, ont utilisé cette technique. La technique demande d'utiliser plusieurs mémoires séparées. Sur les anciens ordinateurs, les mémoires en question sont des chips mémoire, à savoir des circuits intégrés de DRAM. Pour faire plus général, nous allons utiliser le terme de '''banques''', ou encore de bancs mémoire. La différence est qu'il existe des mémoires multi-banque, qui regroupent plusieurs banques indépendantes dans un seul boitier, dans un seul chip mémoire. En clair, de telles mémoires regroupent plusieurs sous-mémoires dans un seul circuit intégré. Il est aussi possible d'utiliser plusieurs chips mémoire séparés, chacun ayant une banque. Reste à combiner ces banques pour former un pseudo-pipeline. ===L'entrelacement classique=== Sans optimisation particulière, les adresses sont réparties dans les banques comme indiqué ci-dessous. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Avec l''''entrelacement classique''', des adresses consécutives sont réparties dans des banques différentes. [[File:Répartition des adresses dans une mémoire interleaved.png|centre|vignette|upright=1.5|Répartition des adresses dans une mémoire interleaved.]] L'entrelacement simple permet d'accélérer les accès mémoire qui se font à des adresses consécutives. Avec l'entrelacement, chaque accès mémoire tombe dans une banque mémoire différente, ce qui fait qu'on peut démarrer un nouvel accès mémoire à chaque cycle d'horloge. Pendant qu'une banque est occupée par un accès mémoire, on démarre le suivant dans une autre banque, et ainsi de suite. Le tout se termine soit quand on a épuisé toutes les banques libres, soit quand l'accès en rafales se termine. Pas besoin d'attendre que la mémoire ait fini sa lecture/écriture avant de démarrer la lecture/écriture suivante. [[File:Pipemining mémoire.png|centre|vignette|upright=2.5|Pipelining mémoire]] L'animation suivante illustre bien le processus, avec quatres banques. [[File:Interleaving.gif|centre|vignette|upright=2.5|Pipelining mémoire]] L'entrelacement simple utilise les bits de poids faible pour sélectionner la banque, et les bits de poids fort pour la case mémoire. [[File:Adresse mémoire d'une mémoire entrelacée.png|centre|vignette|upright=2|Adresse mémoire d'une mémoire entrelacée]] Précisons que le temps d'accès mémoire ne change pas beaucoup avec l'entrelacement. Par contre, on peut faire plus d'accès mémoire simultanés. On peut démarrer un accès mémoire par cycle d'horloge, mais l'accès en lui-même prend plusieurs cycles. Les mémoires à entrelacement ont donc un débit supérieur aux mémoires qui ne l'utilisent pas. ===L'entrelacement par décalage=== L'entrelacement simple est très utile pour les accès en rafle ou équivalents. Par contre, il existe de rares situations où il n'est pas optimal. Une de ces situations est celle des accès en ''stride'', où l'on accède en série à des adresses sont séparées par N mots mémoires. De tels accès surviennent quand un logiciel accède à des structures de données spécifiques, à savoir des tableaux de structures, des matrices, ou d'autres structures de données dans le genre. Nous verrons dans quels chapitres que de tels accès étaient autrefois fréquents sur les anciens processeurs vectoriels, qui disposaient d'instructions capables de lire/écrire plusieurs données en mémoire d'un seul coup, avec un accès en ''stride''. Avec l'entrelacement simple, les accès en ''stride'' ont performances dégradées, mais cela ne fonctionne pas trop mal. Le pire cas est celui où l'on a N banques et où les données sont justement placées toutes les N adresses. Des accès consécutifs vont tous tomber dans la même banque, on ne peut plus accéder à des banques différentes en parallèle. Pour résoudre ce problème, il faut répartir les mots mémoires dans la mémoire autrement. : Dans les explications qui vont suivre, la variable N représente le nombre de banques, qui sont numérotées de 0 à N-1. L'organisation idéale est la suivante. On commence par organiser les N premières adresses comme une mémoire entrelacée simple : l'adresse 0 correspond à la banque 0, l'adresse 1 à la banque 1, etc. Pour le bloc suivant, on décale tout d'une adresse, à savoir qu'on commence à remplir les banques à partir de la seconde, non de la première. Une fois la fin du bloc atteinte, on finit de remplir le bloc en repartant du début du bloc. Le troisième bloc d'adresse subit le même traitement, sauf qu'on commence à remplir à partir de la seconde banque. Et on poursuit l’assignation des adresses en décalant d'un cran en plus à chaque bloc. Ainsi, chaque bloc verra ses adresses décalées d'un cran en plus comparé au bloc précédent. Si jamais le décalage dépasse la fin d'un bloc, alors on reprend au début. [[File:Mémoire interleaved par décalage.png|centre|vignette|upright=1.5|Mémoire entrelacée par décalage.]] En faisant cela, on remarque que les banques situées à N adresses d'intervalle sont différentes. Dans l'exemple du dessus, nous avons ajouté un décalage de 1 à chaque nouveau bloc à remplir. Mais on aurait tout aussi bien pu prendre un décalage de 2, 3, etc. Dans tous les cas, on obtient un entrelacement par décalage. Ce décalage est appelé le pas d'entrelacement, noté P. Le calcul de l'adresse à envoyer à la banque, ainsi que la banque à sélectionner se fait en utilisant les formules suivantes : * adresse à envoyer à la banque = adresse totale / N ; * numéro de la banque = (adresse + décalage) modulo N, avec décalage = (adresse totale * P) mod N. Avec cet entrelacement par décalage, on peut prouver que la bande passante maximale est atteinte si le nombre de banques est un nombre premier. Seulement, utiliser un nombre de banques premier peut créer des trous dans la mémoire, des mots mémoires inadressables. Pour éviter cela, il faut faire en sorte que N et la taille d'une banque soient premiers entre eux : ils ne doivent pas avoir de diviseur commun. Dans ce cas, les formules se simplifient : * adresse à envoyer à la banque = adresse totale / taille de la banque ; * numéro de la banque = adresse modulo N. ===L'entrelacement pseudo-aléatoire=== Une dernière méthode de répartition consiste à répartir les adresses dans les banques de manière "pseudo-aléatoire". L'idée exacte est de faire passer l'adresse dans une '''fonction de hachage''', plus ou moins complexe. Pour rappel, une fonction de hachage prend une entrée de grande taille et fournit en sortie un résultat de petite taille. Idéalement, elles doivent donner des résultats aussi différents que possible pour des entrées similaires, histoire de simuler une sorte de pseudo-aléatoire. Ici, une adresse est transformée en un numéro de banque. Une fonction de hachage simple ne fait que permuter des bits de l'adresse pour obtenir son résultat. Elle ne fait qu'échanger des bits de place avant de couper l'adresse en deux morceaux : un pour la sélection de la banque, et un autre pour la sélection de l'adresse dans la banque. Cette permutation est fixe, et ne change pas suivant l'adresse. Des fonctions de hachage plus complexes font des XOR bit à bit entre certains bits de l'adresse. Il existe aussi d'autres techniques qui donnent le numéro de banque à partir d'un polynôme modulo N, appliqué sur l'adresse. ==Les contrôleurs mémoires SDRAM/DDR optimisés== Après avoir vu le cas des mémoires RAM, nous allons nous concentrer sur le cas particulier des mémoires SDRAM. Les mémoires SDRAM et DDR modernes sont capables de gérer plusieurs accès simultanés à la mémoire RAM. Et les contrôleurs mémoire intègrent des optimisations spécifiques afin d'en profiter au maximum. ===La mise en attente des accès mémoire=== Comme pour les mémoires caches, il existe des contrôleurs mémoire bloquants et non-bloquants. Les premiers n'acceptent qu'un seul accès mémoire à la fois, les seconds acceptent plusieurs accès mémoire simultanés. Pour simplifier, nous allons dire que le contrôleur mémoire n’exécute qu'un seul accès mémoire à la fois, et qu'ils met en attente les accès en trop. C'est une simplification assez irréaliste, vu que les mémoires modernes disposent de plusieurs banques accessibles en parallèle. Mais nous introduirons ce détail un peu plus tard. Avec un contrôleur mémoire de ce type, les accès mémoire sont mis en attente dans une mémoire FIFO, histoire de les exécuter dans leur ordre d'arrivée. Les accès mémoire quittent alors la mémoire dans leur ordre d'arrivée, les lectures sont renvoyées dans l'ordre demandé. En plus d'une mémoire FIFO pour les commandes mémoire, on trouve une mémoire FIFO pour les données à écrire. Elle permet d'accumuler plusieurs écritures, tout en permettant que la donnée adéquate soit envoyée à la RAM au bon moment. Il y a aussi une FIFO pour les données lues, qui sert au cas où le cache ne soit pas disponible quand on lui envoie la donnée lue. [[File:Module d'interface avec la mémoire.png|centre|vignette|upright=2.5|Contrôleur mémoire avec mise en attente des requêtes processeur et autres optimisations.]] Une optimisation regroupe plusieurs accès à des données consécutives en un seul accès en rafale. Le contrôleur mémoire analyse les requêtes mises en attente et détecte si certaines se font à des adresses consécutives. Si c'est le cas, il fusionne ces requêtes en une lecture/écriture en rafale. Une telle optimisation est appelée la '''combinaison de lecture''' pour les lectures, et la '''combinaison d'écriture''' pour les écritures. ===Le pipeline des mémoires SDRAM=== Les SDRAM ont une forme de pseudo-pipeline très limité. Elles sont parfois capables de démarrer une commande avant que la précédente soit terminée. Même si elle parait très limitée, cette possibilité est exploitée au mieux par la présence de plusieurs banques dans la mémoire SDRAM. Les chips mémoires de SDRAM sont composés de plusieurs banques, chacune étant une sorte de mini-mémoire miniature. Chaque banque a son propre décodeur, son propre tampon de ligne, ses propres multiplexeurs de colonne, sa logique de rafraichissement mémoire, etc. C'est comme si une SDRAM regroupait plusieurs mémoires séparées dans un même circuit intégré. Et cela permet de faire plusieurs accès mémoire en même temps, dans des banques différentes. Il est possible de lancer un accès mémoire dans deux banques en même temps, par exemple. Ou encore, on peut lancer un nouvel accès mémoire dans une banque si elle est inoccupée. La possibilité améliore grandement la performance de la SDRAM. Mais nous en reparlerons dans un chapitre ultérieur. {|class="wikitable" |+ Pipelining basique sur les SDRAM |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 |- ! Banque Numéro 1 | || || || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || || || |- ! Banque Numéro 2 | || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || |- ! Banque Numéro 3 | || || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || |} L'implémentation n'a rien de vraiment compliqué. Il suffit d'utiliser une mémoire FIFO par banque. Ainsi, les accès dans une même banque se feront en série, dans l'ordre d'arrivée, mais des accès à des banques différentes se feront dans le désordre. Cela ne pose pas de problème car les accès se font à des adresses différentes, ce qui fait qu'il n'y a pas de conflits majeurs. [[File:Gestion parallèle des banques.png|centre|vignette|upright=2.5|Gestion parallèle des banques.]] ===Le ré-ordonnancement des commandes mémoires=== Un contrôleur mémoire moderne est capable de changer l'ordre des requêtes mémoires, pour gagner en performances. En clair : il peut faire l'équivalent mémoire de l'exécution dans le désordre des processeurs haute performance. Une différence majeure est que le contrôleur mémoire ne remet pas les accès mémoire dans l'ordre avant de les envoyer au processeur. C'est le processeur qui remet en ordre les accès mémoire, dans sa ''load queue''. Pour cela, les requêtes mémoire sont "numérotées", à savoir qu'on leur attribue un identifiant binaire. Le contrôleur mémoire exécute les lectures/écritures dans le désordre, mais garde la trace de leur identifiant. Il sait dans quel ordre il exécute les accès mémoire et connait leur latence. Ce qui fait qu'il sait que la donnée lue à tel instant est associée à la requête mémoire numéro X. Pour les lectures, il renvoie la donnée lue avec l'identifiant. Le processeur sait alors à quelle lecture la donnée lue correspond et il se débrouille en interne pour gérer la situation. En clair : le processeur voit les accès mémoire dans le désordre, c'est lui qui les remet en ordre. Maintenant que ces précisions sont faites, posons cette question : quel est l'intérêt de faire les accès mémoire dans le désordre ? Accéder à des banques en parallèle est une réponse possible, mais le ré-ordonnancement est surtout utile si tous les accès mémoire atterrissent dans la même banque. Pour comprendre pourquoi, faisons un rappel rapide sur le tampon de ligne. Les mémoires SDRAM sont des mémoires à tampon de ligne. Elles font un accès mémoire en deux étapes. La première étape recopie une ligne de N * 64 bits dans un tampon interne à la SDRAM. La seconde étape sélectionne une colonne, à savoir une donnée de 64 bits, qui est soit envoyée sur le bus de données pour une lecture, soit modifiée par une écriture. [[File:Mémoire à tampon de ligne.png|centre|vignette|upright=2|Mémoire à tampon de ligne]] Les deux étapes correspondent à deux commandes séparées : une commande ACT qui précise l'adresse de la ligne, et une commande READ ou WRITE qui précise l'adresse de la colonne. [[File:Signaux RAS et CAS.png|centre|vignette|upright=2|Signaux RAS et CAS.]] Les SDRAM permettent de se passer de la première étape si des accès consécutifs se font dans la même ligne. Une fois activée, la ligne reste ouverte et on peut accéder plusieurs fois de suite dedans. On a alors juste à préciser l'adresse de colonne dedans. [[File:Sélection d'une ligne sur une mémoire FPM ou EDO.png|centre|vignette|upright=2|Sélection d'une ligne sur une mémoire FPM ou EDO.]] L'exécution dans le désordre des lectures/écritures SDRAM vise à faire en sorte que des accès consécutifs se fassent dans une même ligne. Elle marche si plusieurs accès à une même ligne ne sont pas consécutifs, et qu'il y a des accès entre les deux. Après ré-ordonnancement, ces accès mémoire deviennent consécutifs, ils sont exécutés l'un à la suite de l'autre. Pour rendre le tout plus concret, voici un exemple. Imaginez que l'on sait les 3 accès mémoire suivants : * Une lecture ligne A ; * Une écriture ligne B ; * Une lecture ligne A. Sans réordonnancent, on doit émettre deux commandes PRECHARGE : une quand on passe de la ligne A à la ligne B, et une autre pour le passage inverse. pour éviter cela, le contrôleur mémoire peut retarder l'écriture, ou au contraire avancer la seconde lecture. le résultat est alors le suivant : * Lecture ligne A ; * Lecture ligne A ; * Une écriture ligne B. On a donc deux accès consécutifs à la même ligne, suivi par une écriture dans une autre ligne. La technique marche parce que l'on a un mix adéquat de lectures et d'écritures. Mais il existe des possibilités de réorganisation autres qui ne sont pas valides. Il faut par exemple prendre garde à éviter d'intervertir lectures et écritures à une même adresse, sous peine de problèmes. Encore une fois, il faut faire attention aux dépendances mémoires. Le controleur mémoire teste les dépendances mémoires avant d'envoyer des commandes à la mémoire DDR/SDRAM. ===L'entrelacement sur les mémoires SDRAM=== L''''entrelacement''' fonctionne sur les mémoires SDRAM, mais il doit être fortement modifié. Concrètement, tout ce qui a étré dit plus est inapplicable pour les SDRAM, du fait de la présence du tampon de ligne. Des adresses consécutives atterrissent déjà dans la même ligne, ce qui fait qu'on n'a pas besoin d'entrelacement au niveau d'une ligne. Et ne parlons pas de ce la présence de rangées et de canaux mémoire ! Cependant, il peut y avoir un entrelacement lié à la présence des banques SDRAM. Pour gérer l'entrelacement, le contrôleur de SDRAM prend en entrée l'adresse envoyée par le processeur, et la découpe en plusieurs champs : un pour sélectionner la banque adéquate, un autre pour la ligne, un autre pour la colonne, un autre pour la rangée. Une solution simple désactive l'entrelacement. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de banque || Adresse de ligne || Adresse de colonne |} Si cette solution fonctionne bien si les accès mémoire sont assez aléatoires, en pratique, mieux vaut utiliser l'entrelacement. Une première méthode d'entrelacement est l''''entrelacement de ligne'''. Elle répartit deux lignes consécutives dans deux banques différentes. Avec N banques, la première ligne ira dans la première banque, la seconde ligne dans la seconde, et ainsi de suite jusqu'à la énième banque qui va dans la banque numéro N. Et la ligne N+1 repart dans la première banque. L'avantage est que passer d'une ligne à la suivante est assez rapide. Au lieu de devoir fermer la première ligne et ouvrir la suivante on ouvrira directement la ligne suivante dans une banque différente, ce qui sera plus rapide. La commande PRECHARGE pourra être envoyée à la seconde banque en avance. Pour cela, l'adresse est découpée comme suit. {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne (précise à l'octet) |} D'autres méthodes d'entrelacement sont possibles, mais elles sont plus complexes. Chaque contrôleur mémoire fait un peu à sa sauce. Ils permettent en général de choisir entre plusieurs entrelacements. Au minimum, ils permettent de désactiver l'entrelacement, d'activer un entrelacement de ligne, et d'autres entrelacements plus complexes, dont certains sont propriétaires et tenus secrets par le fabricant. L'usage du ''dual channel'' complique encore l'entrelacement. Là encore, il y a deux grandes solutions : avec et sans entrelacement des canaux mémoire. Rappelons le principe : deux barrettes de RAM sont accédées en parallèle. Et pour cela, il faut utiliser l'entrelacement. Typiquement, chaque barrette mémoire fournit 64 bits, ce qui fait que l'on peut accéder à 128 bits d'un seul coup, par exemple avec un accès en rafale. Une solution pour cela est de ne pas utiliser l'entrelacement. Avec deux barrettes, la première barrette regroupera la moitié haute de la RAM, la seconde barrette utilisera la moitié basse. Les possibilité d’accéder à deux données séparées de 64 bits en même temps est cependant très rare. Aussi, on préfère utiliser l'entrelacement. Pour cela, deux blocs de 64 bits sont placés dans des canaux mémoire séparés. Idem avec du triple ou quadruple canal, mais c'est alors trois ou quatre blocs de 64 bits qui sont dispersés dans des canaux consécutifs. {|class="wikitable" |+ Adresse mémoire |- ! colspan="6" | Sans entrelacement |- | Numéro de canal/barrette || Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne || Position de l'octet dans un bloc de 64 bits |- | colspan="6" | |- ! colspan="6" | Avec entrelacement |- | Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne || Numéro de canal/barrette || Position de l'octet dans un bloc de 64 bits |} Les contrôleurs de SDRAM précédents sont assez basiques et ne représentent pas les contrôleurs les plus évolués. Ils étaient utilisés sur les anciens PC, à une époque où ils étaient encore sur la carte mère du processeur. Mais de nos jours, le contrôleur mémoire est intégré au processeur. Et il incorpore de nombreuses optimisations afin de gagner en performances. Malheureusement, ces optimisations sont elles-mêmes dépendantes des optimisations intégrées dans le processeur, ce qui fait qu'on ne peut pas en parler ici. Elles demandent que le contrôleur mémoire reçoive plusieurs accès mémoire simultanés, ce qui n'a aucun sens à ce stade du cours. Aussi, nous allons les passer sous silence pour le moment. Nous les verrons dans le chapitre sur le parallélisme mémoire. L'entrelacement est géré juste avant le séquenceur mémoire. Le '''circuit d'entrelacement''' intervertit certains bits de l'adresse lors des accès mémoires. On trouve aussi des mémoires FIFOs pour mettre en attente les requêtes mémoire provenant du processeur. Elles permettent de recevoir plusieurs accès mémoire consécutifs. Si vous vous demandez comment plusieurs accès mémoire consécutifs peuvent avoir lieu, sachez que c'est une bonne question. Pour le moment, nous allons dire que ces accès proviennent du processeur ou de la mémoire cache, sans expliquer comment. Le contrôleur mémoire reçoit une série d'accès mémoire assez rapprochés, qu'il doit exécuter au mieux. [[File:Controleur mémoire d'une SDRAM avec entrelacement.png|centre|vignette|upright=2.5|Contrôleur mémoire d'une SDRAM avec entrelacement.]] <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=La désambiguïsation mémoire | prevText=La désambiguïsation mémoire | next=Les processeurs superscalaires | nextText=Les processeurs superscalaires }} </noinclude> f5n2v1h0xbf7cfo0gjwupkkj4bqji6l 764643 764642 2026-04-23T13:56:21Z Mewtow 31375 /* L'entrelacement mémoire */ 764643 wikitext text/x-wiki Dans ce chapitre, nous allons voir les techniques qui permettent de gérer plusieurs accès mémoire simultanés directement au niveau du cache ou de la mémoire RAM. Par plusieurs accès mémoire simultanés, vous pensez sans doute à l'usage de cache multiports, voire de mémoires RAM multiport. C'est en effet une solution, mais ce n'est clairement pas la seule. Par exemple, il est possible de pipeliner l'accès au cache, voire à la mémoire RAM. Il existe de nombreuses techniques de '''parallélisme mémoire''', et nous allons les voir dans ce chapitre. Pour donner un exemple, il est possible de pipeliner les accès mémoire. Il est possible de pipeliner l'accès au cache et/ou l'accès à la mémoire, et nous verrons comment faire dans ce chapitre. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, qu'ils aient ou non un pipeline dynamique. Par contre, pipeliner la mémoire est une technique ancienne, aujourd'hui peu utilisée. Elle était utilisée sur des très vieux ordinateurs, qui avaient un pipeline mais pas de mémoire cache (et qui avaient souvent de l'exécution dans le désordre et du renommage de registres). Mais de nos jours, les mémoires SDRAM et DDR ne sont pas adaptées pour ça, il est très difficile de les pipeliner correctement. Le parallélisme mémoire est utile aussi bien sur des processeurs à émission dans l'ordre que dans le désordre. Par exemple, un processeur ''in-order'' avec des lectures non-bloquantes peut e, profiter. Il peut techniquement lancer une seconde lecture, après avoir rencontré une première lecture non-bloquante. En général, le nombre de lectures consécutives est cependant limité à moins d'une dizaine. À l'opposé, les processeurs sans lectures non-bloquantes profitent pas du parallélisme mémoire. À la rigueur, ils peuvent en profiter s'ils intègrent des techniques de préchargement. Pendant que le processeur exécute une lecture/écriture, il peut précharger une autre donnée en parallèle. Inutile de dire que sans pipeline processeur, le parallélisme mémoire ne sert pas à grand-chose. ==Le ''line fill buffer'' et les techniques associées== Lors d'un défaut de cache, le processeur doit attendre que toute la ligne de cache soit chargée avant d'être utilisable. Or, la taille d'une ligne de cache est supérieure à la largeur du bus mémoire, ce qui fait qu'une ligne de cache est chargée en plusieurs fois, morceaux par morceaux, mot mémoire par mot mémoire. Le chargement peut se faire directement dans le cache, mais ce n'est pas une solution très pratique. À la place, beaucoup de processeurs ajoutent une mémoire tampon entre la RAM et le cache, appelée le '''tampon de remplissage de ligne''' (''line-fill buffer'') dans les processeurs modernes. Lors d'un défaut de cache, le processeur charge la donnée de la RAM dans le tampon de remplissage de ligne, mot mémoire par mot mémoire. Une fois plein, le tampon de remplissage de ligne est recopié dans la ligne de cache. Le tampon de remplissage de ligne contient une ligne de cache, si ce n'est que certains bits de contrôle ne sont pas présents. Le tampon de remplissage de ligne contient un bit de validité pour chaque mot mémoire de la ligne de cache, qui indiquent si le mot mémoire a été chargé. Par exemple, prenons un processeur 64 bits, qui gère donc des mots mémoire de 8 octets, avec des lignes de cache 256 octets/32 mots mémoire. Le tampon de remplissage de ligne contiendra 32 bits de validité. Si le processeur a chargé les 6 premiers mots mémoire, les 6 premiers bits de validité seront mis à 1, les autres seront encore à zéro. Il faut noter que la disponibilité de la ligne complète se détermine assez facilement en faisant un ET logique entre tous les bits de validité. Le processeur sait ainsi quand la ligne de cache est disponible entièrement et donc quand la transférer dans le cache. La même chose existe avec une hiérarchie de cache, sauf que l'on trouve une mémoire tampon entre chaque niveau de cache. Il y a un tampon de remplissage de ligne entre le cache L1 et le cache L2, entre le cache L2 et le L3, etc. Si le cache ne gère qu'un seul défaut de cache à la fois, le ''fill line buffer'' est une mémoire très simple, qui ne mémorise qu'une seule ligne de cache. Et cela vaut aussi bien pour un cache bloquant que non-bloquant. Mais sur les caches capables de gérer plusieurs défauts simultanés, le tampon de remplissage de ligne est une mémoire de type FIFO ou LIFO, capable de mémoriser plusieurs lignes de cache. Notons que le tampon de remplissage de ligne est très utile pour implémenter certaines techniques, par exemple le contournement du cache. Nous avions vu dans le chapitre sur le cache que certains accès mémoire doivent contourner le cache, pour des raisons de cohérence des caches. C'est notamment nécessaire pour accéder aux périphériques, mais c'est aussi utile pour des raisons de performances dans des cas très spécifiques. Les accès qui contournent le cache se font directement dans le tampon de remplissage de ligne : le processeur écrit ou lit les données depuis ce tampon de remplissage de ligne, sans accéder au cache. ===L'''early restart'' et le ''critical word load''=== La présence du ''line buffer'' permet une optimisation assez intéressante, qui permet de réduire la latence des défauts de cache. L'optimisation consiste à lire un mot mémoire dans le tampon de remplissage de ligne, même si la ligne de cache complète n'a pas encore été chargée. Il existe deux manières de faire cela, qui portent les noms d'''early restart'' et de ''critical word load''. La première est la version la plus simple, la seconde est plus complexe mais plus performante. Avec la technique d''''''early restart''''', la ligne de cache est chargée normalement, en partant de son premier mot mémoire. Dès que le mot mémoire lu/écrit par le processeur est copié dans le ''line fill buffer'', il est envoyé au processeur immédiatement. Illustrons le tout par un exemple, où une ligne de cache fait 16 mots mémoire. Le processeur effectue une lecture, qui lit le 5ème mot mémoire. Sans ''early restart'', le processeur doit charger les 16 mots mémoire avant de faire la lecture dans le cache. Avec ''early restart'', le processeur reçoit la donnée dès que le 5ème mot mémoire est disponible. Le processeur doit attendre que les 4 premiers mots mémoire soient chargés, puis le 5ème mot mémoire arrive et est envoyé directement au processeur, il est lu directement depuis le ''line fill buffer''. Les 11 mots mémoire suivants sont ensuite chargés dans le cache pendant que le processeur fait des calculs dans son coin. Le ''critical word load'' est une optimisation de la technique précédente où le chargement de la ligne de cache commence directement à la donnée demandée par le processeur. Pour reprendre l'exemple précédent, où le processeur demande le 5ème mot mémoire sur 16, le ''critical word load'' charge le 5ème mot mémoire en premier et l'envoie au processeur, ce qui fait qu'il est chargé très rapidement. Pas besoin d'attendre que le processeur charge les 4 mots mémoire précédents comme avec l'''early restart''. Dans le détail, le processeur charge le 5ème mot mémoire en premier, puis charge les 11 suivants, et termine par les 4 mots mémoire du début. En clair, le ''critical word load'' commence par charger le mot mémoire lu, puis les blocs suivants, avant de revenir au début du bloc pour charger les blocs restants. Ainsi, la donnée demandée par le processeur sera la première disponible. Pour cela, l'organisation du tampon de remplissage de ligne est modifiée de manière à rendre cela possible. Il a une taille égale à une ligne de cache complète, qui contient elle-même plusieurs mots mémoire. Dans le ''line-fill buffer'', chaque mot mémoire est stocké avec un tag, qui indique l'adresse du mot mémoire stocké dans le ''line-fill buffer''. Le ''line-fill buffer'' est donc un cache un peu particulier, qui fonctionne comme un cache du point de vue du processeur, comme une mémoire FIFO pour les transferts avec le cache. Ainsi, un processeur qui veut lire dans le cache après un défaut peut accéder à la donnée directement depuis le tampon de remplissage de ligne, alors que la ligne de cache n'a pas encore été totalement recopiée en mémoire. ==Les caches pipelinés== Il est possible de pipeliner l'accès au cache, ce qui demande juste de rajouter des registres au bon endroits et quelques circuits de contrôle. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, même ceux avec un pipeline dynamique. Et l'implémentation peut se faire de plusieurs manières différentes. Il faut dire que découper une mémoire cache en plusieurs étapes peut se faire de plusieurs manière. Commençons par la plus simple. Beaucoup de processeurs des années 2000 avaient une fréquence peu élevée comparée aux standards d'aujourd'hui, ce qui fait que leur cache avait bien un temps d'accès d'un cycle d'horloge. Mais l'accès au cache se faisait en deux cycles d'horloge : un cycle pour calculer une adresse et un cycle pour l'accès mémoire proprement dit. Et ces deux étapes étaient pipelinées, à savoir que deux micro-opérations mémoire peuvent s'exécuter en même temps : une dans l'étage de calcul d'adresse, une autre dans le cache. L'unité mémoire était donc pipelinée, alors que l'accès au cache ne l'était pas. Les processeurs qui implémentaient cette technique regroupent les micro-architectures K5 et K6 d'AMD, les processeurs Intel de micro-architecture P6 (Pentium 2 et 3) et quelques autres. Il est aussi possible de pipeliner l'accès au cache lui-même. Avec un cache pipeliné, l'accès au cache ne se fait pas en un seul cycle, mais en plusieurs. Cependant, on peut lancer un nouvel accès au cache à chaque cycle d'horloge, comme un processeur pipeliné lance une nouvelle instruction à chaque cycle. L'implémentation est assez simple : il suffit d'ajouter des registres dans le cache. Pour cela, on profite que le cache est composé de plusieurs composants séparés, qui échangent des données dans un ordre bien précis, d'un composant à un autre. De plus, le trajet des informations dans un cache est linéaire, ce qui les rend parfois pour l'usage d'un pipeline. ===Le ''pipelining'' des caches ''direct-mapped''=== Voici ce qui se passe avec un cache directement adressé. Pour rappel ce genre de cache est conçu en combinant une mémoire RAM, généralement une SRAM, avec quelques circuits de comparaison et un MUX. L'accès au cache se fait globalement en deux étapes : la première lit la donnée et le ''tag'' dans la SRAM, et on utilise les deux dans une seconde étape. Nous avions vu il y a quelques chapitre comment pipeliner des mémoires, dans le chapitre sur les mémoires évoluées. Et bien ces méthodes peuvent s'utiliser pour la mémoire RAM interne au cache !L'idée est d'insérer un registre entre la sortie de la RAM et la suite du cache, pour en faire un cache pipéliné, à deux étages. Le premier étage lit dans la SRAM, le second fait le reste. L'implémentation sur les caches associatifs à plusieurs voies est globalement la même à quelques détails près. [[File:Cache directement adressé pipeliné - deux étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - deux étages]] Avec le circuit précédent, il est possible d'aller plus loin, cette fois en pipelinant l'accès à la mémoire RAM interne au cache. Pour comprendre comment, rappelons qu'une mémoire SRAM est composée d'un plan mémoire et d'un décodeur. L'accès à la mémoire demande d'abord que le décodeur fasse son travail pour sélectionner la case mémoire adéquate, puis ensuite la lecture ou écriture a lieu dans cette case mémoire. L'accès se fait donc en deux étapes successives séparées, on a juste à mettre un registre entre les deux. Il suffit donc de mettre un gros registre entre le décodeur et le plan mémoire. [[File:Cache directement adressé pipeliné - trois étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - trois étages]] Et on peut aller encore plus loin en découpant le décodeur en deux circuits séparés. En effet, rappelez-vous le chapitre sur les circuits de sélection : nous avions vu qu'il est possible de créer des décodeurs en assemblant des décodeurs plus petits, contrôlés par un circuit de prédécodage. Et bien on peut encore une ajouter un registre entre ce circuit de prédécodage et les petits décodeurs. : Théoriquement, toute l'adresse est fournie d'un seul coup au cache, la quasi-totalité des processeurs présentent l'adresse complète à un cache pipeliné. Mais le Pentium 4 fait autrement. Il faut noter que les premiers étages manipulent l'indice dans la SRAM, qui est dans les bits de poids faible, alors que les étages ultérieurs manipulent le ''tag'' qui est dans les bits de poids fort. Les concepteurs du Pentium 4 ont alors décidé de présenter les bits de poids faible lors du premier cycle d'accès au cache, puis ceux de poids fort au second cycle. Pour cela, l'ALU fonctionnait à une fréquence double de celle du processeur, tout comme le cache L1. Il n'y avait pas de pipeline proprement dit, mais cela réduisait grandement la latence d'accès au cache. ===Le ''pipelining'' des caches splittés/sériels=== Il est aussi possible de pipeliner un cache dits splittés, aussi appelés à accès sériel. Pour rappel, les caches à accès sériel vérifient si il y a succès ou défaut de cache, avant d'accéder aux lignes de cache en cas de succès. Ils font donc différemment des autres caches, qui accèdent à une ligne de cache, avant de déterminer s'il y a succès ou défaut en lisant le tag de la ligne de cache. Les caches sériels disposent de deux SRAM : une pour les tags des lignes de cache et une pour les données. Ils accèdent à la SRAM pour les tags, avant d’accéder à la SRAM des données en cas de succès de cache. Vu que l'accès se fait en deux étapes, une vérification des tags suivie de la lecture/écriture des données, il est facile à pipeliner. Pipeliner le cache permet de régler le problème des accès au cache L1, et elle est tout le temps utilisé sur les processeurs modernes. Mais que faire en cas de défaut de cache ? ==Les caches non bloquants== Un '''cache bloquant''' est un cache auquel le processeur ne peut pas accéder pendant un défaut de cache. Il faut attendre que la lecture ou écriture en RAM soit terminée avant de pouvoir utiliser de nouveau le cache. Un '''cache non bloquant''' n'a pas ce problème : on peut l'utiliser pendant un défaut de cache. Les caches non bloquants permettent de démarrer une nouvelle lecture ou écriture alors qu'une autre est en cours, ce qui permet d'exécuter plusieurs lectures ou écritures en même temps. ===Les ''Miss Handling Status Registers''=== Lors d'un défaut de cache, la mémoire RAM est consultée pendant le défaut de cache, mais le cache est inutilisé. Un défaut de cache n'utilise pas le cache, ce n'est qu'un accès en mémoire RAM, sur le bus mémoire (ou un accès aux niveaux de cache inférieurs, peu importe). Le cache en lui-même est laissé libre, rien n’empêche d'y accéder, il est en réalité intrinsèquement non-bloquant. Les caches, bloquants comme non-bloquants, sont en réalité composés d'une mémoire cache proprement dite, entourée de circuits qui servent d'interface entre le processeur et le cache lui-même. Et parmi les circuits tout autour, certains gèrent l'accès au cache lors d'un défaut de cache. Ils sont regroupés sous le terme de '''''Miss Handling Architecture''''' (MHA). La différence entre un cache bloquant et un cache non-bloquant est en réalité liée à l'implémentation de la MHA. Les caches bloquants coupent volontairement l'accès au cache lors d'un défaut, car les défauts sont plus simples à gérer ainsi. Le défaut de cache rapatrie une donnée depuis la RAM, qui sera écrite dans le cache. Et il ne faut pas qu'une tentative d'accès à cette donnée ait lieu avant qu'elle ne soit chargée. Mais ce blocage est général et touche tout le cache, alors que seule une ligne de cache est concernée par le défaut de cache. L'idée derrière un cache non-bloquant est que seule la ligne de cache est bloquée, mais les autres sont accessibles. L'idée est alors de mémoriser les lignes de cache concernées par le défaut de cache, afin d'en bloquer l'accès. À chaque accès, on vérifie si la ligne de cache est déjà réservée par un défaut de cache. La lecture/écriture est alors bloquée si c'est le cas, mais elle accepte les accès sinon. Pour cela, la ''Miss Handling Architecture'' contient des registres qui mémorisent des informations sur les défauts de cache en cours. Ils portent le nom de '''''miss status handling registers''''', que j’appellerais dorénavant MSHR, qui sont aussi appelés des '''''miss buffer'''''. Le contenu des MSHR varie beaucoup suivant le processeur, mais ces derniers stockent au minimum les informations suivantes : * Le numéro de la ligne de cache dans laquelle les données sont chargées. * Un bit de validité qui indique si le MSHR est vide ou pas, qui est mis à 0 quand le défaut de cache est résolu. * Un ou plusieurs champs de lecture/écriture, qui contiennent des informations sur la lecture/écriture. ** Pour une lecture, elle contient des informations sur la destination de la donnée, à savoir qui prévenir quand le défaut de cache est terminé. C'est parfois un nom/numéro de registre (celui dans lequel charger la donnée), mais c'est souvent le numéro de l'entrée dans le ''load/store queue''. ** Pour les écritures, elle contient la donnée à écrire, ou éventuellement un numéro de ''load/store queue'' où se trouve la donnée à écrire. Il faut noter que le bus mémoire ne peut gérer qu'un seul défaut de cache à la fois. Aussi, il est intéressant de regarder ce qui se passe lorsqu'un second défaut de cache survient, pendant qu'un premier est en cours. Dans ce cas, il y a deux réponses qui correspondent à deux types de caches non-bloquants, qui portent les noms barbares de caches de type succès après défaut et défaut après défaut. Sur le premier type, il ne peut pas y avoir plusieurs défauts de cache simultanés. Dès qu'un second défaut de cache survient, le cache stoppe son activité et on ne peut plus démarrer de nouvelle lecture/écriture, tant que le premier défaut de cache n'est pas résolu. Le second type est plus souple et autorise la survenue de plusieurs défauts de cache simultanés. Du moins, jusqu'à une certaine limite, car le cache ne peut supporter qu'un nombre limité d'accès mémoires simultanés (pipelinés). ===Les accès simultanés à une même ligne de cache=== Il arrive que le processeur fasse plusieurs accès mémoire simultanés à la même ligne de cache. Si la ligne de cache en question n'a pas encore été chargée dans le cache, alors on a plusieurs défauts de cache consécutifs pour la même ligne de cache, et le cache non-bloquant doit gérer la situation. Pour la suite, il va falloir faire une petite distinction entre les défauts primaires et secondaires. Imaginons qu'un défaut de cache ait lieu et demande à charger une donnée dans la ligne de cache numéro N. Il s'agit du premier défaut impliquant cette ligne de cache précise, ce qui lui vaut le nom de '''défaut de cache primaire'''. Mais par la suite, d'autres accès mémoire à la même ligne de cache ont lieu, alors que la ligne de cache n'est pas encore disponible. Dans ce cas, il s'agit de '''défauts de cache secondaires'''. Pour l'unité d'accès mémoire, les défauts de cache primaire et secondaire sont différents (ils prennent tous une entrée dans la ''load/store queue''). Mais pour le cache, ils ne correspondent qu'à un seul accès au cache : celui qui demande de charger la ligne de cache demandée. Les défauts de cache primaire et secondaire à la même ligne de cache se voient attribuer un MSHR unique. La gestion des défauts de cache secondaires dépend du cache non-bloquant. La solution la plus simple ne permet pas les défauts de cache secondaires. Le cache ne permet pas deux défauts de cache simultanés pour la même ligne de cache. Les autres solutions le permettent, en fusionnant des accès simultanés à la même ligne de cache en un seul au niveau des MSHR. Dans tous les cas, détecter les défauts de cache secondaires sont un problème qu'il faut détecter. La ''Miss Handling Architecture'' doit détecter les défauts de cache secondaire. Pour cela, elle procède comme suit. Lors de chaque défaut de cache, la MHA récupère le numéro de la ligne de cache associé. Il vérifie alors chaque MSHR pour vérifier s'il contient le numéro en question. S'il n'y a aucune correspondance dans les MSHR, alors c'est signe que le défaut de cache est un défaut primaire. Mais s'il y en a une, alors c'est un défaut secondaire. Évidemment, cela signifie que lors d'un défaut de cache, le numéro de ligne de cache est envoyé à tous les MSHR, pour comparaison. Les MSHR sont donc regroupés dans une mémoire associative, une sorte de mini-cache, faciliter l'implémentation. Lors d'un défaut de cache primaire, l'accès à un cache non-bloquant se fait comme suit : le processeur envoie une adresse au cache, accède à celui-ci, et détecte la survenue d'un défaut de cache. Il en profite alors pour attribuer une ligne de cache dans laquelle sera chargée ce défaut. L'attribution est très simple dans le cas des caches ''direct mapped'', ou associatifs par voie, pour lesquels l'attribution se fait assez simplement. Il mémorise alors cette information dans les MSHR, après avoir vérifié que le défaut de cache n'était pas un défaut secondaire. Lors des accès ultérieurs à une adresse proche, censée être dans la même ligne de cache, le processeur va encore une fois rencontrer un défaut de cache. Il va alors déterminer le numéro de la ligne de cache associée à l'adresse, et comparer ce numéro avec les MSHRs. Si un MSHR contient ce numéro, c'est signe que le défaut de cache est un défaut secondaire. La MHA réagit alors différemment selon le processeur considéré. Une première solution n'autorise pas les défauts de cache secondaires. Si l'un d'entre eux survient, le processeur est gelé par un ''pipeline stall'', une bulle de pipeline. Une autre solution fusionne les défauts de cache secondaires avec le défaut de cache primaire : tout cela ne correspond qu'à un seul défaut de cache pour lui. ===Les MSHR simples=== Un cache non-bloquant à '''MSHR simple''' contient juste plusieurs MSHR qui mémorisent juste un numéro de ligne de cache, un bit de validité, le champ de lecture/écriture, et l'adresse exacte de lecture/écriture. Avec cette organisation, il est possible d'avoir plusieurs défauts de cache séparés, mais à la condition que chaque défaut accède à une ligne de cache différente. Deux accès simultanés à une même ligne de cache ne sont pas possibles, les défauts de cache secondaires ne sont pas autorisés. Ainsi, chaque défaut de cache se voit attribuer son propre MSHR, chacun contient un numéro de ligne de cache différent. Pour comprendre pourquoi c'est impossible de gérer les défauts secondaires, il faut regarder le champ de lecture/écriture. Si on veut effectuer plusieurs écritures consécutives à la même adresse, le MSHR n'aura pas de quoi mémoriser les deux données à écrire. Il pourra mémoriser la première donnée à écrire, pas la seconde. Même chose lors d'une lecture : le champ lecture/écriture peut mémorisr la destination de la première lecture, pas de la seconde. L'avantage est que la MHA n'a besoin que des MSHR et de quelques circuits annexes. Les autres solutions rajoutent des circuits annexes pour gérer les défauts de cache secondaires, qui utilisent beaucoup de circuits. Le cout en circuit est donc élevé, mais le gain en performance est là. Passons maintenant aux caches non-bloquants qui autorisent les défauts de cache secondaire. La solution la plus simple consiste à utiliser ===Les MSHR adressés implicitement=== Avec les '''MSHR adressés implicitement''', il est possible de fusionner plusieurs accès mémoire à une même ligne de cache, mais sous une condition très importante : ces accès lisent/écrivent des mots mémoire différents. Par exemple, imaginons qu'une ligne de cache contienne 8 mots mémoire de 64 bits. Si un premier accès mémoire lit le mot mémoire numéro 7 (dernier mot mémoire de la ligne), et le second accès le mot mémoire numéro 3, alors la fusion est possible. Mais si deux accès mémoire veulent lire/écrire le mot mémoire numéro 7, alors la fusion n'est pas possible et le processeur se bloque, un ''pipeline stall'' survient. Un MSHR adressé implicitement est un MSHR simple, qui contient naturellement un numéro de ligne de cache (tag) et un bit de validité, sauf que le champ de lecture/écriture est dupliqué. Les différents champs lecture/écriture d'un MSHR sont regroupés dans une mémoire RAM/cache qui contient autant d'entrées qu'il y a de mot mémoire dans une ligne de cache. Chaque entrée de la table est associée à un mot mémoire de la ligne de cache et stocke des informations sur celui-ci. Une entrée mémorise, au minimum : * Un champ de lecture/écriture qui contient soit la destination de la lecture, soit la donnée à écrire. * Un bit R/W permettant d'interpréter correctement le champ de lecture/écriture. * Un bit de validité pour chaque entrée de la table, qui dit si un défaut de cache antérieur accède déjà à ce mot mémoire. La fusion de deux défauts de cache est ainsi assez simple. Un défaut de cache primaire/secondaire configure l'entrée associée au mot mémoire lu/écrit. Si un défaut secondaire ultérieur lit/écrit un mot mémoire différent (dont le bit de validité de l'entrée dans la table est à zéro), pas de conflit : il configure une autre entrée, vide. Mais s'il lit/écrit un mot mémoire pour lequel l'entrée de la table est occupée, il y a conflit, le processeur est bloqué par un ''pipeline stall'', une bulle de pipeline. Avec cette organisation, le nombre de MSHR indique combien de lignes de cache peuvent être lues en même temps depuis la mémoire. Quant au nombre d'entrées par MSHR, il détermine combien d'accès mémoires qui ne se recouvrent pas peuvent avoir lieu en même temps. ===Les MSHR adressés explicitement=== Les '''MSHR adressés explicitement''' n'ont pas les contraintes des MSHR adressés implicitement. Avec eux, il est possible d'avoir plusieurs défauts de cache pointant vers la même ligne de cache, mais aussi vers le même mot mémoire. De tels défauts de cache apparaissent sur les processeurs à exécution dans le désordre, mais sont très rares, voire inexistants, sur les processeurs ''in-order''. L'idée est encore que chaque MSHR est associée à une table mémoire qui mémorise des entrées. Cette table n'est autre qu'une mémoire FIFO. Sauf que cette fois-ci, une entrée n'est pas associée à un mot mémoire. Une entrée est associée à un défaut de cache. Une entrée mémorise là encore la destination de la lecture ou la donnée de l'écriture suivi du bit R/W, ainsi que d'un bit de validité par entrée, mais aussi : la position du mot mémoire lu dans la ligne de cache. C'est cette dernière information qui n'était pas présente dans les MSHR adressés implicitement. De plus, le nombre d'entrée par MSHR n'est pas égal au nombre de mots mémoires dans une ligne de cache, mais peut être arbitrairement grand. L'avantage d'un tel cache est qu'il est capable de traité les défauts secondaires concernant un même mot mémoire, et ce, car les accès à la ligne de cache concernée se produisent dans l'ordre de génération des défauts (la table mémoire est une FIFO). ===Les MSHRs inversés=== Généralement, plus on veut supporter de défauts de cache, plus le nombre de MSHR et d'entrées augmente. Mais au-delà d'un certain nombre d'entrées et de MSHR, les MSHR adressés implicitement et explicitement ont tendance à bouffer un peu trop de circuits. Utiliser une organisation un peu moins gourmande en circuits est donc une nécessité. Cette organisation plus économe se base sur des MSHR inversés. Les MSHR inversés ne contiennent qu'une seule entrée, en quelque sorte : au lieu d’utiliser n MSHR de m entrées chacun, on va utiliser n × m MSHR inversés. La différence, c'est que plusieurs MSHR peuvent contenir un tag identique, contrairement aux MSHR adressés implicitement et explicitement. Lorsqu'un défaut de cache a lieu, chaque MSHR est vérifié. Si jamais aucun MSHR ne contient de tag identique à celui utilisé par le défaut, un MSHR vide est choisi pour stocker ce défaut, et une requête de lecture en mémoire est lancée. Dans le cas contraire, un MSHR est réservé au défaut de cache, mais la requête n'est pas lancée. Quand la donnée est disponible, les MSHR correspondant à la ligne qui vient d'être chargée vont être utilisés un par un pour résoudre les défauts de cache en attente. ===Les MSHR intégrés au cache=== Certains chercheurs ont remarqué que pendant qu'une ligne de cache est en train de subir un défaut de cache, celle-ci reste inutilisée, et son contenu est destiné à être perdu une fois le défaut de cache résolu. Ils se sont dit que, plutôt que d'utiliser des MSHR séparés, il vaudrait mieux utiliser la ligne de cache pour stocker les informations sur les défauts de cache en attente dans cette ligne de cache. Pour éviter tout problème, il faut rajouter un bit dans les bits de contrôle de la ligne de cache, qui sert à indiquer que la ligne de cache est occupée : un défaut de cache a eu lieu dans cette ligne, et elle stocke donc des informations pour résoudre les défauts de cache. ==L'entrelacement mémoire== Nous venons de voir comment une mémoire cache peut gérer plusieurs accès mémoire simultanés. Il se trouve que ce genre d'optimisation dépasse largement le cadre du cache. Les mémoires RAM ne sont pas en reste, elles aussi ! Il est possible de pipeliner l'accès à une mémoire RAM, en utilisant des techniques dites d''''entrelacement'''. Précisons cependant que cette optimisation n'est pas utilisée sur les mémoires SDRAM ou DDR modernes, du moins pas sans modifications majeures. Mais divers ordinateurs assez anciens, dont des superordinateurs, ont utilisé cette technique. La technique demande d'utiliser plusieurs mémoires séparées. Sur les anciens ordinateurs, les mémoires en question sont des chips mémoire, à savoir des circuits intégrés de DRAM. Pour faire plus général, nous allons utiliser le terme de '''banques''', ou encore de bancs mémoire. La différence est qu'il existe des mémoires multi-banque, qui regroupent plusieurs banques indépendantes dans un seul boitier, dans un seul chip mémoire. En clair, de telles mémoires regroupent plusieurs sous-mémoires dans un seul circuit intégré. Il est aussi possible d'utiliser plusieurs chips mémoire séparés, chacun ayant une banque. Reste à combiner ces banques pour former un pseudo-pipeline. ===Utiliser plusieurs banques sans entrelacement=== Sans optimisation particulière, les adresses sont réparties dans les banques comme indiqué ci-dessous. Il s'agit de ce qui s'appelle un arrangement horizontal, et nous avions vu celui-ci dans le chapiutre sur les mémoires SDRAM. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Avec cet arrangement, les bits de poids fort de l'adresse sont utilisées pour sélectionner la banque adéquate, et le reste de l'adresse est envoyé sur le bus d'adresse. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Adresse de banque !! Adresse dans la banque |- | Quelques bits de poids fort || Reste de l'adresse |} L'entrelacement change cette répartition, afin d'accélérer les accès mémoire. L'entrelacement classique vise à améliorer les accès à des adresses mémoires consécutives, mais des formes plus évoluées d'entrelacement visent à optimiser des accès mémoire arbitraires. Voyons-les dans l'ordre, du plus simple au plus complexe. ===L'entrelacement classique=== Avec l''''entrelacement classique''', des adresses consécutives sont réparties dans des banques différentes. [[File:Répartition des adresses dans une mémoire interleaved.png|centre|vignette|upright=1.5|Répartition des adresses dans une mémoire interleaved.]] L'entrelacement simple permet d'accélérer les accès mémoire qui se font à des adresses consécutives. Avec l'entrelacement, chaque accès mémoire tombe dans une banque mémoire différente, ce qui fait qu'on peut démarrer un nouvel accès mémoire à chaque cycle d'horloge. Pendant qu'une banque est occupée par un accès mémoire, on démarre le suivant dans une autre banque, et ainsi de suite. Le tout se termine soit quand on a épuisé toutes les banques libres, soit quand l'accès en rafales se termine. Pas besoin d'attendre que la mémoire ait fini sa lecture/écriture avant de démarrer la lecture/écriture suivante. [[File:Pipemining mémoire.png|centre|vignette|upright=2.5|Pipelining mémoire]] L'animation suivante illustre bien le processus, avec quatres banques. [[File:Interleaving.gif|centre|vignette|upright=2.5|Pipelining mémoire]] L'entrelacement simple utilise les bits de poids faible pour sélectionner la banque, et les bits de poids fort pour la case mémoire. [[File:Adresse mémoire d'une mémoire entrelacée.png|centre|vignette|upright=2|Adresse mémoire d'une mémoire entrelacée]] Précisons que le temps d'accès mémoire ne change pas beaucoup avec l'entrelacement. Par contre, on peut faire plus d'accès mémoire simultanés. On peut démarrer un accès mémoire par cycle d'horloge, mais l'accès en lui-même prend plusieurs cycles. Les mémoires à entrelacement ont donc un débit supérieur aux mémoires qui ne l'utilisent pas. ===L'entrelacement par décalage=== L'entrelacement simple est très utile pour les accès en rafle ou équivalents. Par contre, il existe de rares situations où il n'est pas optimal. Une de ces situations est celle des accès en ''stride'', où l'on accède en série à des adresses sont séparées par N mots mémoires. De tels accès surviennent quand un logiciel accède à des structures de données spécifiques, à savoir des tableaux de structures, des matrices, ou d'autres structures de données dans le genre. Nous verrons dans quels chapitres que de tels accès étaient autrefois fréquents sur les anciens processeurs vectoriels, qui disposaient d'instructions capables de lire/écrire plusieurs données en mémoire d'un seul coup, avec un accès en ''stride''. Avec l'entrelacement simple, les accès en ''stride'' ont performances dégradées, mais cela ne fonctionne pas trop mal. Le pire cas est celui où l'on a N banques et où les données sont justement placées toutes les N adresses. Des accès consécutifs vont tous tomber dans la même banque, on ne peut plus accéder à des banques différentes en parallèle. Pour résoudre ce problème, il faut répartir les mots mémoires dans la mémoire autrement. : Dans les explications qui vont suivre, la variable N représente le nombre de banques, qui sont numérotées de 0 à N-1. L'organisation idéale est la suivante. On commence par organiser les N premières adresses comme une mémoire entrelacée simple : l'adresse 0 correspond à la banque 0, l'adresse 1 à la banque 1, etc. Pour le bloc suivant, on décale tout d'une adresse, à savoir qu'on commence à remplir les banques à partir de la seconde, non de la première. Une fois la fin du bloc atteinte, on finit de remplir le bloc en repartant du début du bloc. Le troisième bloc d'adresse subit le même traitement, sauf qu'on commence à remplir à partir de la seconde banque. Et on poursuit l’assignation des adresses en décalant d'un cran en plus à chaque bloc. Ainsi, chaque bloc verra ses adresses décalées d'un cran en plus comparé au bloc précédent. Si jamais le décalage dépasse la fin d'un bloc, alors on reprend au début. [[File:Mémoire interleaved par décalage.png|centre|vignette|upright=1.5|Mémoire entrelacée par décalage.]] En faisant cela, on remarque que les banques situées à N adresses d'intervalle sont différentes. Dans l'exemple du dessus, nous avons ajouté un décalage de 1 à chaque nouveau bloc à remplir. Mais on aurait tout aussi bien pu prendre un décalage de 2, 3, etc. Dans tous les cas, on obtient un entrelacement par décalage. Ce décalage est appelé le pas d'entrelacement, noté P. Le calcul de l'adresse à envoyer à la banque, ainsi que la banque à sélectionner se fait en utilisant les formules suivantes : * adresse à envoyer à la banque = adresse totale / N ; * numéro de la banque = (adresse + décalage) modulo N, avec décalage = (adresse totale * P) mod N. Avec cet entrelacement par décalage, on peut prouver que la bande passante maximale est atteinte si le nombre de banques est un nombre premier. Seulement, utiliser un nombre de banques premier peut créer des trous dans la mémoire, des mots mémoires inadressables. Pour éviter cela, il faut faire en sorte que N et la taille d'une banque soient premiers entre eux : ils ne doivent pas avoir de diviseur commun. Dans ce cas, les formules se simplifient : * adresse à envoyer à la banque = adresse totale / taille de la banque ; * numéro de la banque = adresse modulo N. ===L'entrelacement pseudo-aléatoire=== Une dernière méthode de répartition consiste à répartir les adresses dans les banques de manière "pseudo-aléatoire". L'idée exacte est de faire passer l'adresse dans une '''fonction de hachage''', plus ou moins complexe. Pour rappel, une fonction de hachage prend une entrée de grande taille et fournit en sortie un résultat de petite taille. Idéalement, elles doivent donner des résultats aussi différents que possible pour des entrées similaires, histoire de simuler une sorte de pseudo-aléatoire. Ici, une adresse est transformée en un numéro de banque. Une fonction de hachage simple ne fait que permuter des bits de l'adresse pour obtenir son résultat. Elle ne fait qu'échanger des bits de place avant de couper l'adresse en deux morceaux : un pour la sélection de la banque, et un autre pour la sélection de l'adresse dans la banque. Cette permutation est fixe, et ne change pas suivant l'adresse. Des fonctions de hachage plus complexes font des XOR bit à bit entre certains bits de l'adresse. Il existe aussi d'autres techniques qui donnent le numéro de banque à partir d'un polynôme modulo N, appliqué sur l'adresse. ==Les contrôleurs mémoires SDRAM/DDR optimisés== Après avoir vu le cas des mémoires RAM, nous allons nous concentrer sur le cas particulier des mémoires SDRAM. Les mémoires SDRAM et DDR modernes sont capables de gérer plusieurs accès simultanés à la mémoire RAM. Et les contrôleurs mémoire intègrent des optimisations spécifiques afin d'en profiter au maximum. ===La mise en attente des accès mémoire=== Comme pour les mémoires caches, il existe des contrôleurs mémoire bloquants et non-bloquants. Les premiers n'acceptent qu'un seul accès mémoire à la fois, les seconds acceptent plusieurs accès mémoire simultanés. Pour simplifier, nous allons dire que le contrôleur mémoire n’exécute qu'un seul accès mémoire à la fois, et qu'ils met en attente les accès en trop. C'est une simplification assez irréaliste, vu que les mémoires modernes disposent de plusieurs banques accessibles en parallèle. Mais nous introduirons ce détail un peu plus tard. Avec un contrôleur mémoire de ce type, les accès mémoire sont mis en attente dans une mémoire FIFO, histoire de les exécuter dans leur ordre d'arrivée. Les accès mémoire quittent alors la mémoire dans leur ordre d'arrivée, les lectures sont renvoyées dans l'ordre demandé. En plus d'une mémoire FIFO pour les commandes mémoire, on trouve une mémoire FIFO pour les données à écrire. Elle permet d'accumuler plusieurs écritures, tout en permettant que la donnée adéquate soit envoyée à la RAM au bon moment. Il y a aussi une FIFO pour les données lues, qui sert au cas où le cache ne soit pas disponible quand on lui envoie la donnée lue. [[File:Module d'interface avec la mémoire.png|centre|vignette|upright=2.5|Contrôleur mémoire avec mise en attente des requêtes processeur et autres optimisations.]] Une optimisation regroupe plusieurs accès à des données consécutives en un seul accès en rafale. Le contrôleur mémoire analyse les requêtes mises en attente et détecte si certaines se font à des adresses consécutives. Si c'est le cas, il fusionne ces requêtes en une lecture/écriture en rafale. Une telle optimisation est appelée la '''combinaison de lecture''' pour les lectures, et la '''combinaison d'écriture''' pour les écritures. ===Le pipeline des mémoires SDRAM=== Les SDRAM ont une forme de pseudo-pipeline très limité. Elles sont parfois capables de démarrer une commande avant que la précédente soit terminée. Même si elle parait très limitée, cette possibilité est exploitée au mieux par la présence de plusieurs banques dans la mémoire SDRAM. Les chips mémoires de SDRAM sont composés de plusieurs banques, chacune étant une sorte de mini-mémoire miniature. Chaque banque a son propre décodeur, son propre tampon de ligne, ses propres multiplexeurs de colonne, sa logique de rafraichissement mémoire, etc. C'est comme si une SDRAM regroupait plusieurs mémoires séparées dans un même circuit intégré. Et cela permet de faire plusieurs accès mémoire en même temps, dans des banques différentes. Il est possible de lancer un accès mémoire dans deux banques en même temps, par exemple. Ou encore, on peut lancer un nouvel accès mémoire dans une banque si elle est inoccupée. La possibilité améliore grandement la performance de la SDRAM. Mais nous en reparlerons dans un chapitre ultérieur. {|class="wikitable" |+ Pipelining basique sur les SDRAM |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 |- ! Banque Numéro 1 | || || || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || || || |- ! Banque Numéro 2 | || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || |- ! Banque Numéro 3 | || || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || |} L'implémentation n'a rien de vraiment compliqué. Il suffit d'utiliser une mémoire FIFO par banque. Ainsi, les accès dans une même banque se feront en série, dans l'ordre d'arrivée, mais des accès à des banques différentes se feront dans le désordre. Cela ne pose pas de problème car les accès se font à des adresses différentes, ce qui fait qu'il n'y a pas de conflits majeurs. [[File:Gestion parallèle des banques.png|centre|vignette|upright=2.5|Gestion parallèle des banques.]] ===Le ré-ordonnancement des commandes mémoires=== Un contrôleur mémoire moderne est capable de changer l'ordre des requêtes mémoires, pour gagner en performances. En clair : il peut faire l'équivalent mémoire de l'exécution dans le désordre des processeurs haute performance. Une différence majeure est que le contrôleur mémoire ne remet pas les accès mémoire dans l'ordre avant de les envoyer au processeur. C'est le processeur qui remet en ordre les accès mémoire, dans sa ''load queue''. Pour cela, les requêtes mémoire sont "numérotées", à savoir qu'on leur attribue un identifiant binaire. Le contrôleur mémoire exécute les lectures/écritures dans le désordre, mais garde la trace de leur identifiant. Il sait dans quel ordre il exécute les accès mémoire et connait leur latence. Ce qui fait qu'il sait que la donnée lue à tel instant est associée à la requête mémoire numéro X. Pour les lectures, il renvoie la donnée lue avec l'identifiant. Le processeur sait alors à quelle lecture la donnée lue correspond et il se débrouille en interne pour gérer la situation. En clair : le processeur voit les accès mémoire dans le désordre, c'est lui qui les remet en ordre. Maintenant que ces précisions sont faites, posons cette question : quel est l'intérêt de faire les accès mémoire dans le désordre ? Accéder à des banques en parallèle est une réponse possible, mais le ré-ordonnancement est surtout utile si tous les accès mémoire atterrissent dans la même banque. Pour comprendre pourquoi, faisons un rappel rapide sur le tampon de ligne. Les mémoires SDRAM sont des mémoires à tampon de ligne. Elles font un accès mémoire en deux étapes. La première étape recopie une ligne de N * 64 bits dans un tampon interne à la SDRAM. La seconde étape sélectionne une colonne, à savoir une donnée de 64 bits, qui est soit envoyée sur le bus de données pour une lecture, soit modifiée par une écriture. [[File:Mémoire à tampon de ligne.png|centre|vignette|upright=2|Mémoire à tampon de ligne]] Les deux étapes correspondent à deux commandes séparées : une commande ACT qui précise l'adresse de la ligne, et une commande READ ou WRITE qui précise l'adresse de la colonne. [[File:Signaux RAS et CAS.png|centre|vignette|upright=2|Signaux RAS et CAS.]] Les SDRAM permettent de se passer de la première étape si des accès consécutifs se font dans la même ligne. Une fois activée, la ligne reste ouverte et on peut accéder plusieurs fois de suite dedans. On a alors juste à préciser l'adresse de colonne dedans. [[File:Sélection d'une ligne sur une mémoire FPM ou EDO.png|centre|vignette|upright=2|Sélection d'une ligne sur une mémoire FPM ou EDO.]] L'exécution dans le désordre des lectures/écritures SDRAM vise à faire en sorte que des accès consécutifs se fassent dans une même ligne. Elle marche si plusieurs accès à une même ligne ne sont pas consécutifs, et qu'il y a des accès entre les deux. Après ré-ordonnancement, ces accès mémoire deviennent consécutifs, ils sont exécutés l'un à la suite de l'autre. Pour rendre le tout plus concret, voici un exemple. Imaginez que l'on sait les 3 accès mémoire suivants : * Une lecture ligne A ; * Une écriture ligne B ; * Une lecture ligne A. Sans réordonnancent, on doit émettre deux commandes PRECHARGE : une quand on passe de la ligne A à la ligne B, et une autre pour le passage inverse. pour éviter cela, le contrôleur mémoire peut retarder l'écriture, ou au contraire avancer la seconde lecture. le résultat est alors le suivant : * Lecture ligne A ; * Lecture ligne A ; * Une écriture ligne B. On a donc deux accès consécutifs à la même ligne, suivi par une écriture dans une autre ligne. La technique marche parce que l'on a un mix adéquat de lectures et d'écritures. Mais il existe des possibilités de réorganisation autres qui ne sont pas valides. Il faut par exemple prendre garde à éviter d'intervertir lectures et écritures à une même adresse, sous peine de problèmes. Encore une fois, il faut faire attention aux dépendances mémoires. Le controleur mémoire teste les dépendances mémoires avant d'envoyer des commandes à la mémoire DDR/SDRAM. ===L'entrelacement sur les mémoires SDRAM=== L''''entrelacement''' fonctionne sur les mémoires SDRAM, mais il doit être fortement modifié. Concrètement, tout ce qui a étré dit plus est inapplicable pour les SDRAM, du fait de la présence du tampon de ligne. Des adresses consécutives atterrissent déjà dans la même ligne, ce qui fait qu'on n'a pas besoin d'entrelacement au niveau d'une ligne. Et ne parlons pas de ce la présence de rangées et de canaux mémoire ! Cependant, il peut y avoir un entrelacement lié à la présence des banques SDRAM. Pour gérer l'entrelacement, le contrôleur de SDRAM prend en entrée l'adresse envoyée par le processeur, et la découpe en plusieurs champs : un pour sélectionner la banque adéquate, un autre pour la ligne, un autre pour la colonne, un autre pour la rangée. Une solution simple désactive l'entrelacement. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de banque || Adresse de ligne || Adresse de colonne |} Si cette solution fonctionne bien si les accès mémoire sont assez aléatoires, en pratique, mieux vaut utiliser l'entrelacement. Une première méthode d'entrelacement est l''''entrelacement de ligne'''. Elle répartit deux lignes consécutives dans deux banques différentes. Avec N banques, la première ligne ira dans la première banque, la seconde ligne dans la seconde, et ainsi de suite jusqu'à la énième banque qui va dans la banque numéro N. Et la ligne N+1 repart dans la première banque. L'avantage est que passer d'une ligne à la suivante est assez rapide. Au lieu de devoir fermer la première ligne et ouvrir la suivante on ouvrira directement la ligne suivante dans une banque différente, ce qui sera plus rapide. La commande PRECHARGE pourra être envoyée à la seconde banque en avance. Pour cela, l'adresse est découpée comme suit. {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne (précise à l'octet) |} D'autres méthodes d'entrelacement sont possibles, mais elles sont plus complexes. Chaque contrôleur mémoire fait un peu à sa sauce. Ils permettent en général de choisir entre plusieurs entrelacements. Au minimum, ils permettent de désactiver l'entrelacement, d'activer un entrelacement de ligne, et d'autres entrelacements plus complexes, dont certains sont propriétaires et tenus secrets par le fabricant. L'usage du ''dual channel'' complique encore l'entrelacement. Là encore, il y a deux grandes solutions : avec et sans entrelacement des canaux mémoire. Rappelons le principe : deux barrettes de RAM sont accédées en parallèle. Et pour cela, il faut utiliser l'entrelacement. Typiquement, chaque barrette mémoire fournit 64 bits, ce qui fait que l'on peut accéder à 128 bits d'un seul coup, par exemple avec un accès en rafale. Une solution pour cela est de ne pas utiliser l'entrelacement. Avec deux barrettes, la première barrette regroupera la moitié haute de la RAM, la seconde barrette utilisera la moitié basse. Les possibilité d’accéder à deux données séparées de 64 bits en même temps est cependant très rare. Aussi, on préfère utiliser l'entrelacement. Pour cela, deux blocs de 64 bits sont placés dans des canaux mémoire séparés. Idem avec du triple ou quadruple canal, mais c'est alors trois ou quatre blocs de 64 bits qui sont dispersés dans des canaux consécutifs. {|class="wikitable" |+ Adresse mémoire |- ! colspan="6" | Sans entrelacement |- | Numéro de canal/barrette || Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne || Position de l'octet dans un bloc de 64 bits |- | colspan="6" | |- ! colspan="6" | Avec entrelacement |- | Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne || Numéro de canal/barrette || Position de l'octet dans un bloc de 64 bits |} Les contrôleurs de SDRAM précédents sont assez basiques et ne représentent pas les contrôleurs les plus évolués. Ils étaient utilisés sur les anciens PC, à une époque où ils étaient encore sur la carte mère du processeur. Mais de nos jours, le contrôleur mémoire est intégré au processeur. Et il incorpore de nombreuses optimisations afin de gagner en performances. Malheureusement, ces optimisations sont elles-mêmes dépendantes des optimisations intégrées dans le processeur, ce qui fait qu'on ne peut pas en parler ici. Elles demandent que le contrôleur mémoire reçoive plusieurs accès mémoire simultanés, ce qui n'a aucun sens à ce stade du cours. Aussi, nous allons les passer sous silence pour le moment. Nous les verrons dans le chapitre sur le parallélisme mémoire. L'entrelacement est géré juste avant le séquenceur mémoire. Le '''circuit d'entrelacement''' intervertit certains bits de l'adresse lors des accès mémoires. On trouve aussi des mémoires FIFOs pour mettre en attente les requêtes mémoire provenant du processeur. Elles permettent de recevoir plusieurs accès mémoire consécutifs. Si vous vous demandez comment plusieurs accès mémoire consécutifs peuvent avoir lieu, sachez que c'est une bonne question. Pour le moment, nous allons dire que ces accès proviennent du processeur ou de la mémoire cache, sans expliquer comment. Le contrôleur mémoire reçoit une série d'accès mémoire assez rapprochés, qu'il doit exécuter au mieux. [[File:Controleur mémoire d'une SDRAM avec entrelacement.png|centre|vignette|upright=2.5|Contrôleur mémoire d'une SDRAM avec entrelacement.]] <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=La désambiguïsation mémoire | prevText=La désambiguïsation mémoire | next=Les processeurs superscalaires | nextText=Les processeurs superscalaires }} </noinclude> 7iyqi58fys0ljwe63yc0rb0w14l2d7z 764648 764643 2026-04-23T14:04:34Z Mewtow 31375 /* L'entrelacement par décalage */ 764648 wikitext text/x-wiki Dans ce chapitre, nous allons voir les techniques qui permettent de gérer plusieurs accès mémoire simultanés directement au niveau du cache ou de la mémoire RAM. Par plusieurs accès mémoire simultanés, vous pensez sans doute à l'usage de cache multiports, voire de mémoires RAM multiport. C'est en effet une solution, mais ce n'est clairement pas la seule. Par exemple, il est possible de pipeliner l'accès au cache, voire à la mémoire RAM. Il existe de nombreuses techniques de '''parallélisme mémoire''', et nous allons les voir dans ce chapitre. Pour donner un exemple, il est possible de pipeliner les accès mémoire. Il est possible de pipeliner l'accès au cache et/ou l'accès à la mémoire, et nous verrons comment faire dans ce chapitre. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, qu'ils aient ou non un pipeline dynamique. Par contre, pipeliner la mémoire est une technique ancienne, aujourd'hui peu utilisée. Elle était utilisée sur des très vieux ordinateurs, qui avaient un pipeline mais pas de mémoire cache (et qui avaient souvent de l'exécution dans le désordre et du renommage de registres). Mais de nos jours, les mémoires SDRAM et DDR ne sont pas adaptées pour ça, il est très difficile de les pipeliner correctement. Le parallélisme mémoire est utile aussi bien sur des processeurs à émission dans l'ordre que dans le désordre. Par exemple, un processeur ''in-order'' avec des lectures non-bloquantes peut e, profiter. Il peut techniquement lancer une seconde lecture, après avoir rencontré une première lecture non-bloquante. En général, le nombre de lectures consécutives est cependant limité à moins d'une dizaine. À l'opposé, les processeurs sans lectures non-bloquantes profitent pas du parallélisme mémoire. À la rigueur, ils peuvent en profiter s'ils intègrent des techniques de préchargement. Pendant que le processeur exécute une lecture/écriture, il peut précharger une autre donnée en parallèle. Inutile de dire que sans pipeline processeur, le parallélisme mémoire ne sert pas à grand-chose. ==Le ''line fill buffer'' et les techniques associées== Lors d'un défaut de cache, le processeur doit attendre que toute la ligne de cache soit chargée avant d'être utilisable. Or, la taille d'une ligne de cache est supérieure à la largeur du bus mémoire, ce qui fait qu'une ligne de cache est chargée en plusieurs fois, morceaux par morceaux, mot mémoire par mot mémoire. Le chargement peut se faire directement dans le cache, mais ce n'est pas une solution très pratique. À la place, beaucoup de processeurs ajoutent une mémoire tampon entre la RAM et le cache, appelée le '''tampon de remplissage de ligne''' (''line-fill buffer'') dans les processeurs modernes. Lors d'un défaut de cache, le processeur charge la donnée de la RAM dans le tampon de remplissage de ligne, mot mémoire par mot mémoire. Une fois plein, le tampon de remplissage de ligne est recopié dans la ligne de cache. Le tampon de remplissage de ligne contient une ligne de cache, si ce n'est que certains bits de contrôle ne sont pas présents. Le tampon de remplissage de ligne contient un bit de validité pour chaque mot mémoire de la ligne de cache, qui indiquent si le mot mémoire a été chargé. Par exemple, prenons un processeur 64 bits, qui gère donc des mots mémoire de 8 octets, avec des lignes de cache 256 octets/32 mots mémoire. Le tampon de remplissage de ligne contiendra 32 bits de validité. Si le processeur a chargé les 6 premiers mots mémoire, les 6 premiers bits de validité seront mis à 1, les autres seront encore à zéro. Il faut noter que la disponibilité de la ligne complète se détermine assez facilement en faisant un ET logique entre tous les bits de validité. Le processeur sait ainsi quand la ligne de cache est disponible entièrement et donc quand la transférer dans le cache. La même chose existe avec une hiérarchie de cache, sauf que l'on trouve une mémoire tampon entre chaque niveau de cache. Il y a un tampon de remplissage de ligne entre le cache L1 et le cache L2, entre le cache L2 et le L3, etc. Si le cache ne gère qu'un seul défaut de cache à la fois, le ''fill line buffer'' est une mémoire très simple, qui ne mémorise qu'une seule ligne de cache. Et cela vaut aussi bien pour un cache bloquant que non-bloquant. Mais sur les caches capables de gérer plusieurs défauts simultanés, le tampon de remplissage de ligne est une mémoire de type FIFO ou LIFO, capable de mémoriser plusieurs lignes de cache. Notons que le tampon de remplissage de ligne est très utile pour implémenter certaines techniques, par exemple le contournement du cache. Nous avions vu dans le chapitre sur le cache que certains accès mémoire doivent contourner le cache, pour des raisons de cohérence des caches. C'est notamment nécessaire pour accéder aux périphériques, mais c'est aussi utile pour des raisons de performances dans des cas très spécifiques. Les accès qui contournent le cache se font directement dans le tampon de remplissage de ligne : le processeur écrit ou lit les données depuis ce tampon de remplissage de ligne, sans accéder au cache. ===L'''early restart'' et le ''critical word load''=== La présence du ''line buffer'' permet une optimisation assez intéressante, qui permet de réduire la latence des défauts de cache. L'optimisation consiste à lire un mot mémoire dans le tampon de remplissage de ligne, même si la ligne de cache complète n'a pas encore été chargée. Il existe deux manières de faire cela, qui portent les noms d'''early restart'' et de ''critical word load''. La première est la version la plus simple, la seconde est plus complexe mais plus performante. Avec la technique d''''''early restart''''', la ligne de cache est chargée normalement, en partant de son premier mot mémoire. Dès que le mot mémoire lu/écrit par le processeur est copié dans le ''line fill buffer'', il est envoyé au processeur immédiatement. Illustrons le tout par un exemple, où une ligne de cache fait 16 mots mémoire. Le processeur effectue une lecture, qui lit le 5ème mot mémoire. Sans ''early restart'', le processeur doit charger les 16 mots mémoire avant de faire la lecture dans le cache. Avec ''early restart'', le processeur reçoit la donnée dès que le 5ème mot mémoire est disponible. Le processeur doit attendre que les 4 premiers mots mémoire soient chargés, puis le 5ème mot mémoire arrive et est envoyé directement au processeur, il est lu directement depuis le ''line fill buffer''. Les 11 mots mémoire suivants sont ensuite chargés dans le cache pendant que le processeur fait des calculs dans son coin. Le ''critical word load'' est une optimisation de la technique précédente où le chargement de la ligne de cache commence directement à la donnée demandée par le processeur. Pour reprendre l'exemple précédent, où le processeur demande le 5ème mot mémoire sur 16, le ''critical word load'' charge le 5ème mot mémoire en premier et l'envoie au processeur, ce qui fait qu'il est chargé très rapidement. Pas besoin d'attendre que le processeur charge les 4 mots mémoire précédents comme avec l'''early restart''. Dans le détail, le processeur charge le 5ème mot mémoire en premier, puis charge les 11 suivants, et termine par les 4 mots mémoire du début. En clair, le ''critical word load'' commence par charger le mot mémoire lu, puis les blocs suivants, avant de revenir au début du bloc pour charger les blocs restants. Ainsi, la donnée demandée par le processeur sera la première disponible. Pour cela, l'organisation du tampon de remplissage de ligne est modifiée de manière à rendre cela possible. Il a une taille égale à une ligne de cache complète, qui contient elle-même plusieurs mots mémoire. Dans le ''line-fill buffer'', chaque mot mémoire est stocké avec un tag, qui indique l'adresse du mot mémoire stocké dans le ''line-fill buffer''. Le ''line-fill buffer'' est donc un cache un peu particulier, qui fonctionne comme un cache du point de vue du processeur, comme une mémoire FIFO pour les transferts avec le cache. Ainsi, un processeur qui veut lire dans le cache après un défaut peut accéder à la donnée directement depuis le tampon de remplissage de ligne, alors que la ligne de cache n'a pas encore été totalement recopiée en mémoire. ==Les caches pipelinés== Il est possible de pipeliner l'accès au cache, ce qui demande juste de rajouter des registres au bon endroits et quelques circuits de contrôle. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, même ceux avec un pipeline dynamique. Et l'implémentation peut se faire de plusieurs manières différentes. Il faut dire que découper une mémoire cache en plusieurs étapes peut se faire de plusieurs manière. Commençons par la plus simple. Beaucoup de processeurs des années 2000 avaient une fréquence peu élevée comparée aux standards d'aujourd'hui, ce qui fait que leur cache avait bien un temps d'accès d'un cycle d'horloge. Mais l'accès au cache se faisait en deux cycles d'horloge : un cycle pour calculer une adresse et un cycle pour l'accès mémoire proprement dit. Et ces deux étapes étaient pipelinées, à savoir que deux micro-opérations mémoire peuvent s'exécuter en même temps : une dans l'étage de calcul d'adresse, une autre dans le cache. L'unité mémoire était donc pipelinée, alors que l'accès au cache ne l'était pas. Les processeurs qui implémentaient cette technique regroupent les micro-architectures K5 et K6 d'AMD, les processeurs Intel de micro-architecture P6 (Pentium 2 et 3) et quelques autres. Il est aussi possible de pipeliner l'accès au cache lui-même. Avec un cache pipeliné, l'accès au cache ne se fait pas en un seul cycle, mais en plusieurs. Cependant, on peut lancer un nouvel accès au cache à chaque cycle d'horloge, comme un processeur pipeliné lance une nouvelle instruction à chaque cycle. L'implémentation est assez simple : il suffit d'ajouter des registres dans le cache. Pour cela, on profite que le cache est composé de plusieurs composants séparés, qui échangent des données dans un ordre bien précis, d'un composant à un autre. De plus, le trajet des informations dans un cache est linéaire, ce qui les rend parfois pour l'usage d'un pipeline. ===Le ''pipelining'' des caches ''direct-mapped''=== Voici ce qui se passe avec un cache directement adressé. Pour rappel ce genre de cache est conçu en combinant une mémoire RAM, généralement une SRAM, avec quelques circuits de comparaison et un MUX. L'accès au cache se fait globalement en deux étapes : la première lit la donnée et le ''tag'' dans la SRAM, et on utilise les deux dans une seconde étape. Nous avions vu il y a quelques chapitre comment pipeliner des mémoires, dans le chapitre sur les mémoires évoluées. Et bien ces méthodes peuvent s'utiliser pour la mémoire RAM interne au cache !L'idée est d'insérer un registre entre la sortie de la RAM et la suite du cache, pour en faire un cache pipéliné, à deux étages. Le premier étage lit dans la SRAM, le second fait le reste. L'implémentation sur les caches associatifs à plusieurs voies est globalement la même à quelques détails près. [[File:Cache directement adressé pipeliné - deux étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - deux étages]] Avec le circuit précédent, il est possible d'aller plus loin, cette fois en pipelinant l'accès à la mémoire RAM interne au cache. Pour comprendre comment, rappelons qu'une mémoire SRAM est composée d'un plan mémoire et d'un décodeur. L'accès à la mémoire demande d'abord que le décodeur fasse son travail pour sélectionner la case mémoire adéquate, puis ensuite la lecture ou écriture a lieu dans cette case mémoire. L'accès se fait donc en deux étapes successives séparées, on a juste à mettre un registre entre les deux. Il suffit donc de mettre un gros registre entre le décodeur et le plan mémoire. [[File:Cache directement adressé pipeliné - trois étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - trois étages]] Et on peut aller encore plus loin en découpant le décodeur en deux circuits séparés. En effet, rappelez-vous le chapitre sur les circuits de sélection : nous avions vu qu'il est possible de créer des décodeurs en assemblant des décodeurs plus petits, contrôlés par un circuit de prédécodage. Et bien on peut encore une ajouter un registre entre ce circuit de prédécodage et les petits décodeurs. : Théoriquement, toute l'adresse est fournie d'un seul coup au cache, la quasi-totalité des processeurs présentent l'adresse complète à un cache pipeliné. Mais le Pentium 4 fait autrement. Il faut noter que les premiers étages manipulent l'indice dans la SRAM, qui est dans les bits de poids faible, alors que les étages ultérieurs manipulent le ''tag'' qui est dans les bits de poids fort. Les concepteurs du Pentium 4 ont alors décidé de présenter les bits de poids faible lors du premier cycle d'accès au cache, puis ceux de poids fort au second cycle. Pour cela, l'ALU fonctionnait à une fréquence double de celle du processeur, tout comme le cache L1. Il n'y avait pas de pipeline proprement dit, mais cela réduisait grandement la latence d'accès au cache. ===Le ''pipelining'' des caches splittés/sériels=== Il est aussi possible de pipeliner un cache dits splittés, aussi appelés à accès sériel. Pour rappel, les caches à accès sériel vérifient si il y a succès ou défaut de cache, avant d'accéder aux lignes de cache en cas de succès. Ils font donc différemment des autres caches, qui accèdent à une ligne de cache, avant de déterminer s'il y a succès ou défaut en lisant le tag de la ligne de cache. Les caches sériels disposent de deux SRAM : une pour les tags des lignes de cache et une pour les données. Ils accèdent à la SRAM pour les tags, avant d’accéder à la SRAM des données en cas de succès de cache. Vu que l'accès se fait en deux étapes, une vérification des tags suivie de la lecture/écriture des données, il est facile à pipeliner. Pipeliner le cache permet de régler le problème des accès au cache L1, et elle est tout le temps utilisé sur les processeurs modernes. Mais que faire en cas de défaut de cache ? ==Les caches non bloquants== Un '''cache bloquant''' est un cache auquel le processeur ne peut pas accéder pendant un défaut de cache. Il faut attendre que la lecture ou écriture en RAM soit terminée avant de pouvoir utiliser de nouveau le cache. Un '''cache non bloquant''' n'a pas ce problème : on peut l'utiliser pendant un défaut de cache. Les caches non bloquants permettent de démarrer une nouvelle lecture ou écriture alors qu'une autre est en cours, ce qui permet d'exécuter plusieurs lectures ou écritures en même temps. ===Les ''Miss Handling Status Registers''=== Lors d'un défaut de cache, la mémoire RAM est consultée pendant le défaut de cache, mais le cache est inutilisé. Un défaut de cache n'utilise pas le cache, ce n'est qu'un accès en mémoire RAM, sur le bus mémoire (ou un accès aux niveaux de cache inférieurs, peu importe). Le cache en lui-même est laissé libre, rien n’empêche d'y accéder, il est en réalité intrinsèquement non-bloquant. Les caches, bloquants comme non-bloquants, sont en réalité composés d'une mémoire cache proprement dite, entourée de circuits qui servent d'interface entre le processeur et le cache lui-même. Et parmi les circuits tout autour, certains gèrent l'accès au cache lors d'un défaut de cache. Ils sont regroupés sous le terme de '''''Miss Handling Architecture''''' (MHA). La différence entre un cache bloquant et un cache non-bloquant est en réalité liée à l'implémentation de la MHA. Les caches bloquants coupent volontairement l'accès au cache lors d'un défaut, car les défauts sont plus simples à gérer ainsi. Le défaut de cache rapatrie une donnée depuis la RAM, qui sera écrite dans le cache. Et il ne faut pas qu'une tentative d'accès à cette donnée ait lieu avant qu'elle ne soit chargée. Mais ce blocage est général et touche tout le cache, alors que seule une ligne de cache est concernée par le défaut de cache. L'idée derrière un cache non-bloquant est que seule la ligne de cache est bloquée, mais les autres sont accessibles. L'idée est alors de mémoriser les lignes de cache concernées par le défaut de cache, afin d'en bloquer l'accès. À chaque accès, on vérifie si la ligne de cache est déjà réservée par un défaut de cache. La lecture/écriture est alors bloquée si c'est le cas, mais elle accepte les accès sinon. Pour cela, la ''Miss Handling Architecture'' contient des registres qui mémorisent des informations sur les défauts de cache en cours. Ils portent le nom de '''''miss status handling registers''''', que j’appellerais dorénavant MSHR, qui sont aussi appelés des '''''miss buffer'''''. Le contenu des MSHR varie beaucoup suivant le processeur, mais ces derniers stockent au minimum les informations suivantes : * Le numéro de la ligne de cache dans laquelle les données sont chargées. * Un bit de validité qui indique si le MSHR est vide ou pas, qui est mis à 0 quand le défaut de cache est résolu. * Un ou plusieurs champs de lecture/écriture, qui contiennent des informations sur la lecture/écriture. ** Pour une lecture, elle contient des informations sur la destination de la donnée, à savoir qui prévenir quand le défaut de cache est terminé. C'est parfois un nom/numéro de registre (celui dans lequel charger la donnée), mais c'est souvent le numéro de l'entrée dans le ''load/store queue''. ** Pour les écritures, elle contient la donnée à écrire, ou éventuellement un numéro de ''load/store queue'' où se trouve la donnée à écrire. Il faut noter que le bus mémoire ne peut gérer qu'un seul défaut de cache à la fois. Aussi, il est intéressant de regarder ce qui se passe lorsqu'un second défaut de cache survient, pendant qu'un premier est en cours. Dans ce cas, il y a deux réponses qui correspondent à deux types de caches non-bloquants, qui portent les noms barbares de caches de type succès après défaut et défaut après défaut. Sur le premier type, il ne peut pas y avoir plusieurs défauts de cache simultanés. Dès qu'un second défaut de cache survient, le cache stoppe son activité et on ne peut plus démarrer de nouvelle lecture/écriture, tant que le premier défaut de cache n'est pas résolu. Le second type est plus souple et autorise la survenue de plusieurs défauts de cache simultanés. Du moins, jusqu'à une certaine limite, car le cache ne peut supporter qu'un nombre limité d'accès mémoires simultanés (pipelinés). ===Les accès simultanés à une même ligne de cache=== Il arrive que le processeur fasse plusieurs accès mémoire simultanés à la même ligne de cache. Si la ligne de cache en question n'a pas encore été chargée dans le cache, alors on a plusieurs défauts de cache consécutifs pour la même ligne de cache, et le cache non-bloquant doit gérer la situation. Pour la suite, il va falloir faire une petite distinction entre les défauts primaires et secondaires. Imaginons qu'un défaut de cache ait lieu et demande à charger une donnée dans la ligne de cache numéro N. Il s'agit du premier défaut impliquant cette ligne de cache précise, ce qui lui vaut le nom de '''défaut de cache primaire'''. Mais par la suite, d'autres accès mémoire à la même ligne de cache ont lieu, alors que la ligne de cache n'est pas encore disponible. Dans ce cas, il s'agit de '''défauts de cache secondaires'''. Pour l'unité d'accès mémoire, les défauts de cache primaire et secondaire sont différents (ils prennent tous une entrée dans la ''load/store queue''). Mais pour le cache, ils ne correspondent qu'à un seul accès au cache : celui qui demande de charger la ligne de cache demandée. Les défauts de cache primaire et secondaire à la même ligne de cache se voient attribuer un MSHR unique. La gestion des défauts de cache secondaires dépend du cache non-bloquant. La solution la plus simple ne permet pas les défauts de cache secondaires. Le cache ne permet pas deux défauts de cache simultanés pour la même ligne de cache. Les autres solutions le permettent, en fusionnant des accès simultanés à la même ligne de cache en un seul au niveau des MSHR. Dans tous les cas, détecter les défauts de cache secondaires sont un problème qu'il faut détecter. La ''Miss Handling Architecture'' doit détecter les défauts de cache secondaire. Pour cela, elle procède comme suit. Lors de chaque défaut de cache, la MHA récupère le numéro de la ligne de cache associé. Il vérifie alors chaque MSHR pour vérifier s'il contient le numéro en question. S'il n'y a aucune correspondance dans les MSHR, alors c'est signe que le défaut de cache est un défaut primaire. Mais s'il y en a une, alors c'est un défaut secondaire. Évidemment, cela signifie que lors d'un défaut de cache, le numéro de ligne de cache est envoyé à tous les MSHR, pour comparaison. Les MSHR sont donc regroupés dans une mémoire associative, une sorte de mini-cache, faciliter l'implémentation. Lors d'un défaut de cache primaire, l'accès à un cache non-bloquant se fait comme suit : le processeur envoie une adresse au cache, accède à celui-ci, et détecte la survenue d'un défaut de cache. Il en profite alors pour attribuer une ligne de cache dans laquelle sera chargée ce défaut. L'attribution est très simple dans le cas des caches ''direct mapped'', ou associatifs par voie, pour lesquels l'attribution se fait assez simplement. Il mémorise alors cette information dans les MSHR, après avoir vérifié que le défaut de cache n'était pas un défaut secondaire. Lors des accès ultérieurs à une adresse proche, censée être dans la même ligne de cache, le processeur va encore une fois rencontrer un défaut de cache. Il va alors déterminer le numéro de la ligne de cache associée à l'adresse, et comparer ce numéro avec les MSHRs. Si un MSHR contient ce numéro, c'est signe que le défaut de cache est un défaut secondaire. La MHA réagit alors différemment selon le processeur considéré. Une première solution n'autorise pas les défauts de cache secondaires. Si l'un d'entre eux survient, le processeur est gelé par un ''pipeline stall'', une bulle de pipeline. Une autre solution fusionne les défauts de cache secondaires avec le défaut de cache primaire : tout cela ne correspond qu'à un seul défaut de cache pour lui. ===Les MSHR simples=== Un cache non-bloquant à '''MSHR simple''' contient juste plusieurs MSHR qui mémorisent juste un numéro de ligne de cache, un bit de validité, le champ de lecture/écriture, et l'adresse exacte de lecture/écriture. Avec cette organisation, il est possible d'avoir plusieurs défauts de cache séparés, mais à la condition que chaque défaut accède à une ligne de cache différente. Deux accès simultanés à une même ligne de cache ne sont pas possibles, les défauts de cache secondaires ne sont pas autorisés. Ainsi, chaque défaut de cache se voit attribuer son propre MSHR, chacun contient un numéro de ligne de cache différent. Pour comprendre pourquoi c'est impossible de gérer les défauts secondaires, il faut regarder le champ de lecture/écriture. Si on veut effectuer plusieurs écritures consécutives à la même adresse, le MSHR n'aura pas de quoi mémoriser les deux données à écrire. Il pourra mémoriser la première donnée à écrire, pas la seconde. Même chose lors d'une lecture : le champ lecture/écriture peut mémorisr la destination de la première lecture, pas de la seconde. L'avantage est que la MHA n'a besoin que des MSHR et de quelques circuits annexes. Les autres solutions rajoutent des circuits annexes pour gérer les défauts de cache secondaires, qui utilisent beaucoup de circuits. Le cout en circuit est donc élevé, mais le gain en performance est là. Passons maintenant aux caches non-bloquants qui autorisent les défauts de cache secondaire. La solution la plus simple consiste à utiliser ===Les MSHR adressés implicitement=== Avec les '''MSHR adressés implicitement''', il est possible de fusionner plusieurs accès mémoire à une même ligne de cache, mais sous une condition très importante : ces accès lisent/écrivent des mots mémoire différents. Par exemple, imaginons qu'une ligne de cache contienne 8 mots mémoire de 64 bits. Si un premier accès mémoire lit le mot mémoire numéro 7 (dernier mot mémoire de la ligne), et le second accès le mot mémoire numéro 3, alors la fusion est possible. Mais si deux accès mémoire veulent lire/écrire le mot mémoire numéro 7, alors la fusion n'est pas possible et le processeur se bloque, un ''pipeline stall'' survient. Un MSHR adressé implicitement est un MSHR simple, qui contient naturellement un numéro de ligne de cache (tag) et un bit de validité, sauf que le champ de lecture/écriture est dupliqué. Les différents champs lecture/écriture d'un MSHR sont regroupés dans une mémoire RAM/cache qui contient autant d'entrées qu'il y a de mot mémoire dans une ligne de cache. Chaque entrée de la table est associée à un mot mémoire de la ligne de cache et stocke des informations sur celui-ci. Une entrée mémorise, au minimum : * Un champ de lecture/écriture qui contient soit la destination de la lecture, soit la donnée à écrire. * Un bit R/W permettant d'interpréter correctement le champ de lecture/écriture. * Un bit de validité pour chaque entrée de la table, qui dit si un défaut de cache antérieur accède déjà à ce mot mémoire. La fusion de deux défauts de cache est ainsi assez simple. Un défaut de cache primaire/secondaire configure l'entrée associée au mot mémoire lu/écrit. Si un défaut secondaire ultérieur lit/écrit un mot mémoire différent (dont le bit de validité de l'entrée dans la table est à zéro), pas de conflit : il configure une autre entrée, vide. Mais s'il lit/écrit un mot mémoire pour lequel l'entrée de la table est occupée, il y a conflit, le processeur est bloqué par un ''pipeline stall'', une bulle de pipeline. Avec cette organisation, le nombre de MSHR indique combien de lignes de cache peuvent être lues en même temps depuis la mémoire. Quant au nombre d'entrées par MSHR, il détermine combien d'accès mémoires qui ne se recouvrent pas peuvent avoir lieu en même temps. ===Les MSHR adressés explicitement=== Les '''MSHR adressés explicitement''' n'ont pas les contraintes des MSHR adressés implicitement. Avec eux, il est possible d'avoir plusieurs défauts de cache pointant vers la même ligne de cache, mais aussi vers le même mot mémoire. De tels défauts de cache apparaissent sur les processeurs à exécution dans le désordre, mais sont très rares, voire inexistants, sur les processeurs ''in-order''. L'idée est encore que chaque MSHR est associée à une table mémoire qui mémorise des entrées. Cette table n'est autre qu'une mémoire FIFO. Sauf que cette fois-ci, une entrée n'est pas associée à un mot mémoire. Une entrée est associée à un défaut de cache. Une entrée mémorise là encore la destination de la lecture ou la donnée de l'écriture suivi du bit R/W, ainsi que d'un bit de validité par entrée, mais aussi : la position du mot mémoire lu dans la ligne de cache. C'est cette dernière information qui n'était pas présente dans les MSHR adressés implicitement. De plus, le nombre d'entrée par MSHR n'est pas égal au nombre de mots mémoires dans une ligne de cache, mais peut être arbitrairement grand. L'avantage d'un tel cache est qu'il est capable de traité les défauts secondaires concernant un même mot mémoire, et ce, car les accès à la ligne de cache concernée se produisent dans l'ordre de génération des défauts (la table mémoire est une FIFO). ===Les MSHRs inversés=== Généralement, plus on veut supporter de défauts de cache, plus le nombre de MSHR et d'entrées augmente. Mais au-delà d'un certain nombre d'entrées et de MSHR, les MSHR adressés implicitement et explicitement ont tendance à bouffer un peu trop de circuits. Utiliser une organisation un peu moins gourmande en circuits est donc une nécessité. Cette organisation plus économe se base sur des MSHR inversés. Les MSHR inversés ne contiennent qu'une seule entrée, en quelque sorte : au lieu d’utiliser n MSHR de m entrées chacun, on va utiliser n × m MSHR inversés. La différence, c'est que plusieurs MSHR peuvent contenir un tag identique, contrairement aux MSHR adressés implicitement et explicitement. Lorsqu'un défaut de cache a lieu, chaque MSHR est vérifié. Si jamais aucun MSHR ne contient de tag identique à celui utilisé par le défaut, un MSHR vide est choisi pour stocker ce défaut, et une requête de lecture en mémoire est lancée. Dans le cas contraire, un MSHR est réservé au défaut de cache, mais la requête n'est pas lancée. Quand la donnée est disponible, les MSHR correspondant à la ligne qui vient d'être chargée vont être utilisés un par un pour résoudre les défauts de cache en attente. ===Les MSHR intégrés au cache=== Certains chercheurs ont remarqué que pendant qu'une ligne de cache est en train de subir un défaut de cache, celle-ci reste inutilisée, et son contenu est destiné à être perdu une fois le défaut de cache résolu. Ils se sont dit que, plutôt que d'utiliser des MSHR séparés, il vaudrait mieux utiliser la ligne de cache pour stocker les informations sur les défauts de cache en attente dans cette ligne de cache. Pour éviter tout problème, il faut rajouter un bit dans les bits de contrôle de la ligne de cache, qui sert à indiquer que la ligne de cache est occupée : un défaut de cache a eu lieu dans cette ligne, et elle stocke donc des informations pour résoudre les défauts de cache. ==L'entrelacement mémoire== Nous venons de voir comment une mémoire cache peut gérer plusieurs accès mémoire simultanés. Il se trouve que ce genre d'optimisation dépasse largement le cadre du cache. Les mémoires RAM ne sont pas en reste, elles aussi ! Il est possible de pipeliner l'accès à une mémoire RAM, en utilisant des techniques dites d''''entrelacement'''. Précisons cependant que cette optimisation n'est pas utilisée sur les mémoires SDRAM ou DDR modernes, du moins pas sans modifications majeures. Mais divers ordinateurs assez anciens, dont des superordinateurs, ont utilisé cette technique. La technique demande d'utiliser plusieurs mémoires séparées. Sur les anciens ordinateurs, les mémoires en question sont des chips mémoire, à savoir des circuits intégrés de DRAM. Pour faire plus général, nous allons utiliser le terme de '''banques''', ou encore de bancs mémoire. La différence est qu'il existe des mémoires multi-banque, qui regroupent plusieurs banques indépendantes dans un seul boitier, dans un seul chip mémoire. En clair, de telles mémoires regroupent plusieurs sous-mémoires dans un seul circuit intégré. Il est aussi possible d'utiliser plusieurs chips mémoire séparés, chacun ayant une banque. Reste à combiner ces banques pour former un pseudo-pipeline. ===Utiliser plusieurs banques sans entrelacement=== Sans optimisation particulière, les adresses sont réparties dans les banques comme indiqué ci-dessous. Il s'agit de ce qui s'appelle un arrangement horizontal, et nous avions vu celui-ci dans le chapiutre sur les mémoires SDRAM. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Avec cet arrangement, les bits de poids fort de l'adresse sont utilisées pour sélectionner la banque adéquate, et le reste de l'adresse est envoyé sur le bus d'adresse. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Adresse de banque !! Adresse dans la banque |- | Quelques bits de poids fort || Reste de l'adresse |} L'entrelacement change cette répartition, afin d'accélérer les accès mémoire. L'entrelacement classique vise à améliorer les accès à des adresses mémoires consécutives, mais des formes plus évoluées d'entrelacement visent à optimiser des accès mémoire arbitraires. Voyons-les dans l'ordre, du plus simple au plus complexe. ===L'entrelacement classique=== Avec l''''entrelacement classique''', des adresses consécutives sont réparties dans des banques différentes. [[File:Répartition des adresses dans une mémoire interleaved.png|centre|vignette|upright=1.5|Répartition des adresses dans une mémoire interleaved.]] L'entrelacement simple permet d'accélérer les accès mémoire qui se font à des adresses consécutives. Avec l'entrelacement, chaque accès mémoire tombe dans une banque mémoire différente, ce qui fait qu'on peut démarrer un nouvel accès mémoire à chaque cycle d'horloge. Pendant qu'une banque est occupée par un accès mémoire, on démarre le suivant dans une autre banque, et ainsi de suite. Le tout se termine soit quand on a épuisé toutes les banques libres, soit quand l'accès en rafales se termine. Pas besoin d'attendre que la mémoire ait fini sa lecture/écriture avant de démarrer la lecture/écriture suivante. [[File:Pipemining mémoire.png|centre|vignette|upright=2.5|Pipelining mémoire]] L'animation suivante illustre bien le processus, avec quatres banques. [[File:Interleaving.gif|centre|vignette|upright=2.5|Pipelining mémoire]] L'entrelacement simple utilise les bits de poids faible pour sélectionner la banque, et les bits de poids fort pour la case mémoire. [[File:Adresse mémoire d'une mémoire entrelacée.png|centre|vignette|upright=2|Adresse mémoire d'une mémoire entrelacée]] Précisons que le temps d'accès mémoire ne change pas beaucoup avec l'entrelacement. Par contre, on peut faire plus d'accès mémoire simultanés. On peut démarrer un accès mémoire par cycle d'horloge, mais l'accès en lui-même prend plusieurs cycles. Les mémoires à entrelacement ont donc un débit supérieur aux mémoires qui ne l'utilisent pas. ===L'entrelacement par décalage=== L'entrelacement simple est très utile pour les accès en rafle ou équivalents. Par contre, il existe de rares situations où il n'est pas optimal. Une de ces situations est celle des accès en enjambée (en ''stride''), où l'on accède en série à des adresses sont séparées par N mots mémoires. De tels accès surviennent quand un logiciel accède à des structures de données spécifiques, à savoir des tableaux de structures, des matrices, ou d'autres structures de données dans le genre. Un cas classique est celui du parcours d'une matrice colonne par colonne. Les matrices de nombres sont mémorisées ligne par ligne, ce qui fait que parcourir une colonne demande de passer d'une ligne à la suivante et de faire des grands sauts en mémoire RAM, mais tous espacés par la même distance. Avec l'entrelacement simple, les accès en ''stride'' ont performances dégradées, mais cela ne fonctionne pas trop mal. Le pire cas est celui où l'on a N banques et où les données sont justement placées toutes les N adresses. Des accès consécutifs vont tous tomber dans la même banque, on ne peut plus accéder à des banques différentes en parallèle. Pour résoudre ce problème, il faut répartir les mots mémoires dans la mémoire autrement. : Dans les explications qui vont suivre, la variable N représente le nombre de banques, qui sont numérotées de 0 à N-1. L'organisation idéale est la suivante. On commence par organiser les N premières adresses comme une mémoire entrelacée simple : l'adresse 0 correspond à la banque 0, l'adresse 1 à la banque 1, etc. Pour le bloc suivant, on décale tout d'une adresse, à savoir qu'on commence à remplir les banques à partir de la seconde, non de la première. Une fois la fin du bloc atteinte, on finit de remplir le bloc en repartant du début du bloc. Le troisième bloc d'adresse subit le même traitement, sauf qu'on commence à remplir à partir de la seconde banque. Et on poursuit l’assignation des adresses en décalant d'un cran en plus à chaque bloc. Ainsi, chaque bloc verra ses adresses décalées d'un cran en plus comparé au bloc précédent. Si jamais le décalage dépasse la fin d'un bloc, alors on reprend au début. [[File:Mémoire interleaved par décalage.png|centre|vignette|upright=1.5|Mémoire entrelacée par décalage.]] En faisant cela, on remarque que les banques situées à N adresses d'intervalle sont différentes. Dans l'exemple du dessus, nous avons ajouté un décalage de 1 à chaque nouveau bloc à remplir. Mais on aurait tout aussi bien pu prendre un décalage de 2, 3, etc. Dans tous les cas, on obtient un entrelacement par décalage. Ce décalage est appelé le pas d'entrelacement, noté P. Le calcul de l'adresse à envoyer à la banque, ainsi que la banque à sélectionner se fait en utilisant les formules suivantes : * adresse à envoyer à la banque = adresse totale / N ; * numéro de la banque = (adresse + décalage) modulo N, avec décalage = (adresse totale * P) mod N. Avec cet entrelacement par décalage, on peut prouver que la bande passante maximale est atteinte si le nombre de banques est un nombre premier. Seulement, utiliser un nombre de banques premier peut créer des trous dans la mémoire, des mots mémoires inadressables. Pour éviter cela, il faut faire en sorte que N et la taille d'une banque soient premiers entre eux : ils ne doivent pas avoir de diviseur commun. Dans ce cas, les formules se simplifient : * adresse à envoyer à la banque = adresse totale / taille de la banque ; * numéro de la banque = adresse modulo N. ===L'entrelacement pseudo-aléatoire=== Une dernière méthode de répartition consiste à répartir les adresses dans les banques de manière "pseudo-aléatoire". L'idée exacte est de faire passer l'adresse dans une '''fonction de hachage''', plus ou moins complexe. Pour rappel, une fonction de hachage prend une entrée de grande taille et fournit en sortie un résultat de petite taille. Idéalement, elles doivent donner des résultats aussi différents que possible pour des entrées similaires, histoire de simuler une sorte de pseudo-aléatoire. Ici, une adresse est transformée en un numéro de banque. Une fonction de hachage simple ne fait que permuter des bits de l'adresse pour obtenir son résultat. Elle ne fait qu'échanger des bits de place avant de couper l'adresse en deux morceaux : un pour la sélection de la banque, et un autre pour la sélection de l'adresse dans la banque. Cette permutation est fixe, et ne change pas suivant l'adresse. Des fonctions de hachage plus complexes font des XOR bit à bit entre certains bits de l'adresse. Il existe aussi d'autres techniques qui donnent le numéro de banque à partir d'un polynôme modulo N, appliqué sur l'adresse. ==Les contrôleurs mémoires SDRAM/DDR optimisés== Après avoir vu le cas des mémoires RAM, nous allons nous concentrer sur le cas particulier des mémoires SDRAM. Les mémoires SDRAM et DDR modernes sont capables de gérer plusieurs accès simultanés à la mémoire RAM. Et les contrôleurs mémoire intègrent des optimisations spécifiques afin d'en profiter au maximum. ===La mise en attente des accès mémoire=== Comme pour les mémoires caches, il existe des contrôleurs mémoire bloquants et non-bloquants. Les premiers n'acceptent qu'un seul accès mémoire à la fois, les seconds acceptent plusieurs accès mémoire simultanés. Pour simplifier, nous allons dire que le contrôleur mémoire n’exécute qu'un seul accès mémoire à la fois, et qu'ils met en attente les accès en trop. C'est une simplification assez irréaliste, vu que les mémoires modernes disposent de plusieurs banques accessibles en parallèle. Mais nous introduirons ce détail un peu plus tard. Avec un contrôleur mémoire de ce type, les accès mémoire sont mis en attente dans une mémoire FIFO, histoire de les exécuter dans leur ordre d'arrivée. Les accès mémoire quittent alors la mémoire dans leur ordre d'arrivée, les lectures sont renvoyées dans l'ordre demandé. En plus d'une mémoire FIFO pour les commandes mémoire, on trouve une mémoire FIFO pour les données à écrire. Elle permet d'accumuler plusieurs écritures, tout en permettant que la donnée adéquate soit envoyée à la RAM au bon moment. Il y a aussi une FIFO pour les données lues, qui sert au cas où le cache ne soit pas disponible quand on lui envoie la donnée lue. [[File:Module d'interface avec la mémoire.png|centre|vignette|upright=2.5|Contrôleur mémoire avec mise en attente des requêtes processeur et autres optimisations.]] Une optimisation regroupe plusieurs accès à des données consécutives en un seul accès en rafale. Le contrôleur mémoire analyse les requêtes mises en attente et détecte si certaines se font à des adresses consécutives. Si c'est le cas, il fusionne ces requêtes en une lecture/écriture en rafale. Une telle optimisation est appelée la '''combinaison de lecture''' pour les lectures, et la '''combinaison d'écriture''' pour les écritures. ===Le pipeline des mémoires SDRAM=== Les SDRAM ont une forme de pseudo-pipeline très limité. Elles sont parfois capables de démarrer une commande avant que la précédente soit terminée. Même si elle parait très limitée, cette possibilité est exploitée au mieux par la présence de plusieurs banques dans la mémoire SDRAM. Les chips mémoires de SDRAM sont composés de plusieurs banques, chacune étant une sorte de mini-mémoire miniature. Chaque banque a son propre décodeur, son propre tampon de ligne, ses propres multiplexeurs de colonne, sa logique de rafraichissement mémoire, etc. C'est comme si une SDRAM regroupait plusieurs mémoires séparées dans un même circuit intégré. Et cela permet de faire plusieurs accès mémoire en même temps, dans des banques différentes. Il est possible de lancer un accès mémoire dans deux banques en même temps, par exemple. Ou encore, on peut lancer un nouvel accès mémoire dans une banque si elle est inoccupée. La possibilité améliore grandement la performance de la SDRAM. Mais nous en reparlerons dans un chapitre ultérieur. {|class="wikitable" |+ Pipelining basique sur les SDRAM |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 |- ! Banque Numéro 1 | || || || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || || || |- ! Banque Numéro 2 | || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || |- ! Banque Numéro 3 | || || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || |} L'implémentation n'a rien de vraiment compliqué. Il suffit d'utiliser une mémoire FIFO par banque. Ainsi, les accès dans une même banque se feront en série, dans l'ordre d'arrivée, mais des accès à des banques différentes se feront dans le désordre. Cela ne pose pas de problème car les accès se font à des adresses différentes, ce qui fait qu'il n'y a pas de conflits majeurs. [[File:Gestion parallèle des banques.png|centre|vignette|upright=2.5|Gestion parallèle des banques.]] ===Le ré-ordonnancement des commandes mémoires=== Un contrôleur mémoire moderne est capable de changer l'ordre des requêtes mémoires, pour gagner en performances. En clair : il peut faire l'équivalent mémoire de l'exécution dans le désordre des processeurs haute performance. Une différence majeure est que le contrôleur mémoire ne remet pas les accès mémoire dans l'ordre avant de les envoyer au processeur. C'est le processeur qui remet en ordre les accès mémoire, dans sa ''load queue''. Pour cela, les requêtes mémoire sont "numérotées", à savoir qu'on leur attribue un identifiant binaire. Le contrôleur mémoire exécute les lectures/écritures dans le désordre, mais garde la trace de leur identifiant. Il sait dans quel ordre il exécute les accès mémoire et connait leur latence. Ce qui fait qu'il sait que la donnée lue à tel instant est associée à la requête mémoire numéro X. Pour les lectures, il renvoie la donnée lue avec l'identifiant. Le processeur sait alors à quelle lecture la donnée lue correspond et il se débrouille en interne pour gérer la situation. En clair : le processeur voit les accès mémoire dans le désordre, c'est lui qui les remet en ordre. Maintenant que ces précisions sont faites, posons cette question : quel est l'intérêt de faire les accès mémoire dans le désordre ? Accéder à des banques en parallèle est une réponse possible, mais le ré-ordonnancement est surtout utile si tous les accès mémoire atterrissent dans la même banque. Pour comprendre pourquoi, faisons un rappel rapide sur le tampon de ligne. Les mémoires SDRAM sont des mémoires à tampon de ligne. Elles font un accès mémoire en deux étapes. La première étape recopie une ligne de N * 64 bits dans un tampon interne à la SDRAM. La seconde étape sélectionne une colonne, à savoir une donnée de 64 bits, qui est soit envoyée sur le bus de données pour une lecture, soit modifiée par une écriture. [[File:Mémoire à tampon de ligne.png|centre|vignette|upright=2|Mémoire à tampon de ligne]] Les deux étapes correspondent à deux commandes séparées : une commande ACT qui précise l'adresse de la ligne, et une commande READ ou WRITE qui précise l'adresse de la colonne. [[File:Signaux RAS et CAS.png|centre|vignette|upright=2|Signaux RAS et CAS.]] Les SDRAM permettent de se passer de la première étape si des accès consécutifs se font dans la même ligne. Une fois activée, la ligne reste ouverte et on peut accéder plusieurs fois de suite dedans. On a alors juste à préciser l'adresse de colonne dedans. [[File:Sélection d'une ligne sur une mémoire FPM ou EDO.png|centre|vignette|upright=2|Sélection d'une ligne sur une mémoire FPM ou EDO.]] L'exécution dans le désordre des lectures/écritures SDRAM vise à faire en sorte que des accès consécutifs se fassent dans une même ligne. Elle marche si plusieurs accès à une même ligne ne sont pas consécutifs, et qu'il y a des accès entre les deux. Après ré-ordonnancement, ces accès mémoire deviennent consécutifs, ils sont exécutés l'un à la suite de l'autre. Pour rendre le tout plus concret, voici un exemple. Imaginez que l'on sait les 3 accès mémoire suivants : * Une lecture ligne A ; * Une écriture ligne B ; * Une lecture ligne A. Sans réordonnancent, on doit émettre deux commandes PRECHARGE : une quand on passe de la ligne A à la ligne B, et une autre pour le passage inverse. pour éviter cela, le contrôleur mémoire peut retarder l'écriture, ou au contraire avancer la seconde lecture. le résultat est alors le suivant : * Lecture ligne A ; * Lecture ligne A ; * Une écriture ligne B. On a donc deux accès consécutifs à la même ligne, suivi par une écriture dans une autre ligne. La technique marche parce que l'on a un mix adéquat de lectures et d'écritures. Mais il existe des possibilités de réorganisation autres qui ne sont pas valides. Il faut par exemple prendre garde à éviter d'intervertir lectures et écritures à une même adresse, sous peine de problèmes. Encore une fois, il faut faire attention aux dépendances mémoires. Le controleur mémoire teste les dépendances mémoires avant d'envoyer des commandes à la mémoire DDR/SDRAM. ===L'entrelacement sur les mémoires SDRAM=== L''''entrelacement''' fonctionne sur les mémoires SDRAM, mais il doit être fortement modifié. Concrètement, tout ce qui a étré dit plus est inapplicable pour les SDRAM, du fait de la présence du tampon de ligne. Des adresses consécutives atterrissent déjà dans la même ligne, ce qui fait qu'on n'a pas besoin d'entrelacement au niveau d'une ligne. Et ne parlons pas de ce la présence de rangées et de canaux mémoire ! Cependant, il peut y avoir un entrelacement lié à la présence des banques SDRAM. Pour gérer l'entrelacement, le contrôleur de SDRAM prend en entrée l'adresse envoyée par le processeur, et la découpe en plusieurs champs : un pour sélectionner la banque adéquate, un autre pour la ligne, un autre pour la colonne, un autre pour la rangée. Une solution simple désactive l'entrelacement. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de banque || Adresse de ligne || Adresse de colonne |} Si cette solution fonctionne bien si les accès mémoire sont assez aléatoires, en pratique, mieux vaut utiliser l'entrelacement. Une première méthode d'entrelacement est l''''entrelacement de ligne'''. Elle répartit deux lignes consécutives dans deux banques différentes. Avec N banques, la première ligne ira dans la première banque, la seconde ligne dans la seconde, et ainsi de suite jusqu'à la énième banque qui va dans la banque numéro N. Et la ligne N+1 repart dans la première banque. L'avantage est que passer d'une ligne à la suivante est assez rapide. Au lieu de devoir fermer la première ligne et ouvrir la suivante on ouvrira directement la ligne suivante dans une banque différente, ce qui sera plus rapide. La commande PRECHARGE pourra être envoyée à la seconde banque en avance. Pour cela, l'adresse est découpée comme suit. {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne (précise à l'octet) |} D'autres méthodes d'entrelacement sont possibles, mais elles sont plus complexes. Chaque contrôleur mémoire fait un peu à sa sauce. Ils permettent en général de choisir entre plusieurs entrelacements. Au minimum, ils permettent de désactiver l'entrelacement, d'activer un entrelacement de ligne, et d'autres entrelacements plus complexes, dont certains sont propriétaires et tenus secrets par le fabricant. L'usage du ''dual channel'' complique encore l'entrelacement. Là encore, il y a deux grandes solutions : avec et sans entrelacement des canaux mémoire. Rappelons le principe : deux barrettes de RAM sont accédées en parallèle. Et pour cela, il faut utiliser l'entrelacement. Typiquement, chaque barrette mémoire fournit 64 bits, ce qui fait que l'on peut accéder à 128 bits d'un seul coup, par exemple avec un accès en rafale. Une solution pour cela est de ne pas utiliser l'entrelacement. Avec deux barrettes, la première barrette regroupera la moitié haute de la RAM, la seconde barrette utilisera la moitié basse. Les possibilité d’accéder à deux données séparées de 64 bits en même temps est cependant très rare. Aussi, on préfère utiliser l'entrelacement. Pour cela, deux blocs de 64 bits sont placés dans des canaux mémoire séparés. Idem avec du triple ou quadruple canal, mais c'est alors trois ou quatre blocs de 64 bits qui sont dispersés dans des canaux consécutifs. {|class="wikitable" |+ Adresse mémoire |- ! colspan="6" | Sans entrelacement |- | Numéro de canal/barrette || Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne || Position de l'octet dans un bloc de 64 bits |- | colspan="6" | |- ! colspan="6" | Avec entrelacement |- | Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne || Numéro de canal/barrette || Position de l'octet dans un bloc de 64 bits |} Les contrôleurs de SDRAM précédents sont assez basiques et ne représentent pas les contrôleurs les plus évolués. Ils étaient utilisés sur les anciens PC, à une époque où ils étaient encore sur la carte mère du processeur. Mais de nos jours, le contrôleur mémoire est intégré au processeur. Et il incorpore de nombreuses optimisations afin de gagner en performances. Malheureusement, ces optimisations sont elles-mêmes dépendantes des optimisations intégrées dans le processeur, ce qui fait qu'on ne peut pas en parler ici. Elles demandent que le contrôleur mémoire reçoive plusieurs accès mémoire simultanés, ce qui n'a aucun sens à ce stade du cours. Aussi, nous allons les passer sous silence pour le moment. Nous les verrons dans le chapitre sur le parallélisme mémoire. L'entrelacement est géré juste avant le séquenceur mémoire. Le '''circuit d'entrelacement''' intervertit certains bits de l'adresse lors des accès mémoires. On trouve aussi des mémoires FIFOs pour mettre en attente les requêtes mémoire provenant du processeur. Elles permettent de recevoir plusieurs accès mémoire consécutifs. Si vous vous demandez comment plusieurs accès mémoire consécutifs peuvent avoir lieu, sachez que c'est une bonne question. Pour le moment, nous allons dire que ces accès proviennent du processeur ou de la mémoire cache, sans expliquer comment. Le contrôleur mémoire reçoit une série d'accès mémoire assez rapprochés, qu'il doit exécuter au mieux. [[File:Controleur mémoire d'une SDRAM avec entrelacement.png|centre|vignette|upright=2.5|Contrôleur mémoire d'une SDRAM avec entrelacement.]] <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=La désambiguïsation mémoire | prevText=La désambiguïsation mémoire | next=Les processeurs superscalaires | nextText=Les processeurs superscalaires }} </noinclude> p0q0pune6xaf17xwe9trw99zdettnyj 764649 764648 2026-04-23T14:07:42Z Mewtow 31375 /* L'entrelacement par décalage */ 764649 wikitext text/x-wiki Dans ce chapitre, nous allons voir les techniques qui permettent de gérer plusieurs accès mémoire simultanés directement au niveau du cache ou de la mémoire RAM. Par plusieurs accès mémoire simultanés, vous pensez sans doute à l'usage de cache multiports, voire de mémoires RAM multiport. C'est en effet une solution, mais ce n'est clairement pas la seule. Par exemple, il est possible de pipeliner l'accès au cache, voire à la mémoire RAM. Il existe de nombreuses techniques de '''parallélisme mémoire''', et nous allons les voir dans ce chapitre. Pour donner un exemple, il est possible de pipeliner les accès mémoire. Il est possible de pipeliner l'accès au cache et/ou l'accès à la mémoire, et nous verrons comment faire dans ce chapitre. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, qu'ils aient ou non un pipeline dynamique. Par contre, pipeliner la mémoire est une technique ancienne, aujourd'hui peu utilisée. Elle était utilisée sur des très vieux ordinateurs, qui avaient un pipeline mais pas de mémoire cache (et qui avaient souvent de l'exécution dans le désordre et du renommage de registres). Mais de nos jours, les mémoires SDRAM et DDR ne sont pas adaptées pour ça, il est très difficile de les pipeliner correctement. Le parallélisme mémoire est utile aussi bien sur des processeurs à émission dans l'ordre que dans le désordre. Par exemple, un processeur ''in-order'' avec des lectures non-bloquantes peut e, profiter. Il peut techniquement lancer une seconde lecture, après avoir rencontré une première lecture non-bloquante. En général, le nombre de lectures consécutives est cependant limité à moins d'une dizaine. À l'opposé, les processeurs sans lectures non-bloquantes profitent pas du parallélisme mémoire. À la rigueur, ils peuvent en profiter s'ils intègrent des techniques de préchargement. Pendant que le processeur exécute une lecture/écriture, il peut précharger une autre donnée en parallèle. Inutile de dire que sans pipeline processeur, le parallélisme mémoire ne sert pas à grand-chose. ==Le ''line fill buffer'' et les techniques associées== Lors d'un défaut de cache, le processeur doit attendre que toute la ligne de cache soit chargée avant d'être utilisable. Or, la taille d'une ligne de cache est supérieure à la largeur du bus mémoire, ce qui fait qu'une ligne de cache est chargée en plusieurs fois, morceaux par morceaux, mot mémoire par mot mémoire. Le chargement peut se faire directement dans le cache, mais ce n'est pas une solution très pratique. À la place, beaucoup de processeurs ajoutent une mémoire tampon entre la RAM et le cache, appelée le '''tampon de remplissage de ligne''' (''line-fill buffer'') dans les processeurs modernes. Lors d'un défaut de cache, le processeur charge la donnée de la RAM dans le tampon de remplissage de ligne, mot mémoire par mot mémoire. Une fois plein, le tampon de remplissage de ligne est recopié dans la ligne de cache. Le tampon de remplissage de ligne contient une ligne de cache, si ce n'est que certains bits de contrôle ne sont pas présents. Le tampon de remplissage de ligne contient un bit de validité pour chaque mot mémoire de la ligne de cache, qui indiquent si le mot mémoire a été chargé. Par exemple, prenons un processeur 64 bits, qui gère donc des mots mémoire de 8 octets, avec des lignes de cache 256 octets/32 mots mémoire. Le tampon de remplissage de ligne contiendra 32 bits de validité. Si le processeur a chargé les 6 premiers mots mémoire, les 6 premiers bits de validité seront mis à 1, les autres seront encore à zéro. Il faut noter que la disponibilité de la ligne complète se détermine assez facilement en faisant un ET logique entre tous les bits de validité. Le processeur sait ainsi quand la ligne de cache est disponible entièrement et donc quand la transférer dans le cache. La même chose existe avec une hiérarchie de cache, sauf que l'on trouve une mémoire tampon entre chaque niveau de cache. Il y a un tampon de remplissage de ligne entre le cache L1 et le cache L2, entre le cache L2 et le L3, etc. Si le cache ne gère qu'un seul défaut de cache à la fois, le ''fill line buffer'' est une mémoire très simple, qui ne mémorise qu'une seule ligne de cache. Et cela vaut aussi bien pour un cache bloquant que non-bloquant. Mais sur les caches capables de gérer plusieurs défauts simultanés, le tampon de remplissage de ligne est une mémoire de type FIFO ou LIFO, capable de mémoriser plusieurs lignes de cache. Notons que le tampon de remplissage de ligne est très utile pour implémenter certaines techniques, par exemple le contournement du cache. Nous avions vu dans le chapitre sur le cache que certains accès mémoire doivent contourner le cache, pour des raisons de cohérence des caches. C'est notamment nécessaire pour accéder aux périphériques, mais c'est aussi utile pour des raisons de performances dans des cas très spécifiques. Les accès qui contournent le cache se font directement dans le tampon de remplissage de ligne : le processeur écrit ou lit les données depuis ce tampon de remplissage de ligne, sans accéder au cache. ===L'''early restart'' et le ''critical word load''=== La présence du ''line buffer'' permet une optimisation assez intéressante, qui permet de réduire la latence des défauts de cache. L'optimisation consiste à lire un mot mémoire dans le tampon de remplissage de ligne, même si la ligne de cache complète n'a pas encore été chargée. Il existe deux manières de faire cela, qui portent les noms d'''early restart'' et de ''critical word load''. La première est la version la plus simple, la seconde est plus complexe mais plus performante. Avec la technique d''''''early restart''''', la ligne de cache est chargée normalement, en partant de son premier mot mémoire. Dès que le mot mémoire lu/écrit par le processeur est copié dans le ''line fill buffer'', il est envoyé au processeur immédiatement. Illustrons le tout par un exemple, où une ligne de cache fait 16 mots mémoire. Le processeur effectue une lecture, qui lit le 5ème mot mémoire. Sans ''early restart'', le processeur doit charger les 16 mots mémoire avant de faire la lecture dans le cache. Avec ''early restart'', le processeur reçoit la donnée dès que le 5ème mot mémoire est disponible. Le processeur doit attendre que les 4 premiers mots mémoire soient chargés, puis le 5ème mot mémoire arrive et est envoyé directement au processeur, il est lu directement depuis le ''line fill buffer''. Les 11 mots mémoire suivants sont ensuite chargés dans le cache pendant que le processeur fait des calculs dans son coin. Le ''critical word load'' est une optimisation de la technique précédente où le chargement de la ligne de cache commence directement à la donnée demandée par le processeur. Pour reprendre l'exemple précédent, où le processeur demande le 5ème mot mémoire sur 16, le ''critical word load'' charge le 5ème mot mémoire en premier et l'envoie au processeur, ce qui fait qu'il est chargé très rapidement. Pas besoin d'attendre que le processeur charge les 4 mots mémoire précédents comme avec l'''early restart''. Dans le détail, le processeur charge le 5ème mot mémoire en premier, puis charge les 11 suivants, et termine par les 4 mots mémoire du début. En clair, le ''critical word load'' commence par charger le mot mémoire lu, puis les blocs suivants, avant de revenir au début du bloc pour charger les blocs restants. Ainsi, la donnée demandée par le processeur sera la première disponible. Pour cela, l'organisation du tampon de remplissage de ligne est modifiée de manière à rendre cela possible. Il a une taille égale à une ligne de cache complète, qui contient elle-même plusieurs mots mémoire. Dans le ''line-fill buffer'', chaque mot mémoire est stocké avec un tag, qui indique l'adresse du mot mémoire stocké dans le ''line-fill buffer''. Le ''line-fill buffer'' est donc un cache un peu particulier, qui fonctionne comme un cache du point de vue du processeur, comme une mémoire FIFO pour les transferts avec le cache. Ainsi, un processeur qui veut lire dans le cache après un défaut peut accéder à la donnée directement depuis le tampon de remplissage de ligne, alors que la ligne de cache n'a pas encore été totalement recopiée en mémoire. ==Les caches pipelinés== Il est possible de pipeliner l'accès au cache, ce qui demande juste de rajouter des registres au bon endroits et quelques circuits de contrôle. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, même ceux avec un pipeline dynamique. Et l'implémentation peut se faire de plusieurs manières différentes. Il faut dire que découper une mémoire cache en plusieurs étapes peut se faire de plusieurs manière. Commençons par la plus simple. Beaucoup de processeurs des années 2000 avaient une fréquence peu élevée comparée aux standards d'aujourd'hui, ce qui fait que leur cache avait bien un temps d'accès d'un cycle d'horloge. Mais l'accès au cache se faisait en deux cycles d'horloge : un cycle pour calculer une adresse et un cycle pour l'accès mémoire proprement dit. Et ces deux étapes étaient pipelinées, à savoir que deux micro-opérations mémoire peuvent s'exécuter en même temps : une dans l'étage de calcul d'adresse, une autre dans le cache. L'unité mémoire était donc pipelinée, alors que l'accès au cache ne l'était pas. Les processeurs qui implémentaient cette technique regroupent les micro-architectures K5 et K6 d'AMD, les processeurs Intel de micro-architecture P6 (Pentium 2 et 3) et quelques autres. Il est aussi possible de pipeliner l'accès au cache lui-même. Avec un cache pipeliné, l'accès au cache ne se fait pas en un seul cycle, mais en plusieurs. Cependant, on peut lancer un nouvel accès au cache à chaque cycle d'horloge, comme un processeur pipeliné lance une nouvelle instruction à chaque cycle. L'implémentation est assez simple : il suffit d'ajouter des registres dans le cache. Pour cela, on profite que le cache est composé de plusieurs composants séparés, qui échangent des données dans un ordre bien précis, d'un composant à un autre. De plus, le trajet des informations dans un cache est linéaire, ce qui les rend parfois pour l'usage d'un pipeline. ===Le ''pipelining'' des caches ''direct-mapped''=== Voici ce qui se passe avec un cache directement adressé. Pour rappel ce genre de cache est conçu en combinant une mémoire RAM, généralement une SRAM, avec quelques circuits de comparaison et un MUX. L'accès au cache se fait globalement en deux étapes : la première lit la donnée et le ''tag'' dans la SRAM, et on utilise les deux dans une seconde étape. Nous avions vu il y a quelques chapitre comment pipeliner des mémoires, dans le chapitre sur les mémoires évoluées. Et bien ces méthodes peuvent s'utiliser pour la mémoire RAM interne au cache !L'idée est d'insérer un registre entre la sortie de la RAM et la suite du cache, pour en faire un cache pipéliné, à deux étages. Le premier étage lit dans la SRAM, le second fait le reste. L'implémentation sur les caches associatifs à plusieurs voies est globalement la même à quelques détails près. [[File:Cache directement adressé pipeliné - deux étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - deux étages]] Avec le circuit précédent, il est possible d'aller plus loin, cette fois en pipelinant l'accès à la mémoire RAM interne au cache. Pour comprendre comment, rappelons qu'une mémoire SRAM est composée d'un plan mémoire et d'un décodeur. L'accès à la mémoire demande d'abord que le décodeur fasse son travail pour sélectionner la case mémoire adéquate, puis ensuite la lecture ou écriture a lieu dans cette case mémoire. L'accès se fait donc en deux étapes successives séparées, on a juste à mettre un registre entre les deux. Il suffit donc de mettre un gros registre entre le décodeur et le plan mémoire. [[File:Cache directement adressé pipeliné - trois étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - trois étages]] Et on peut aller encore plus loin en découpant le décodeur en deux circuits séparés. En effet, rappelez-vous le chapitre sur les circuits de sélection : nous avions vu qu'il est possible de créer des décodeurs en assemblant des décodeurs plus petits, contrôlés par un circuit de prédécodage. Et bien on peut encore une ajouter un registre entre ce circuit de prédécodage et les petits décodeurs. : Théoriquement, toute l'adresse est fournie d'un seul coup au cache, la quasi-totalité des processeurs présentent l'adresse complète à un cache pipeliné. Mais le Pentium 4 fait autrement. Il faut noter que les premiers étages manipulent l'indice dans la SRAM, qui est dans les bits de poids faible, alors que les étages ultérieurs manipulent le ''tag'' qui est dans les bits de poids fort. Les concepteurs du Pentium 4 ont alors décidé de présenter les bits de poids faible lors du premier cycle d'accès au cache, puis ceux de poids fort au second cycle. Pour cela, l'ALU fonctionnait à une fréquence double de celle du processeur, tout comme le cache L1. Il n'y avait pas de pipeline proprement dit, mais cela réduisait grandement la latence d'accès au cache. ===Le ''pipelining'' des caches splittés/sériels=== Il est aussi possible de pipeliner un cache dits splittés, aussi appelés à accès sériel. Pour rappel, les caches à accès sériel vérifient si il y a succès ou défaut de cache, avant d'accéder aux lignes de cache en cas de succès. Ils font donc différemment des autres caches, qui accèdent à une ligne de cache, avant de déterminer s'il y a succès ou défaut en lisant le tag de la ligne de cache. Les caches sériels disposent de deux SRAM : une pour les tags des lignes de cache et une pour les données. Ils accèdent à la SRAM pour les tags, avant d’accéder à la SRAM des données en cas de succès de cache. Vu que l'accès se fait en deux étapes, une vérification des tags suivie de la lecture/écriture des données, il est facile à pipeliner. Pipeliner le cache permet de régler le problème des accès au cache L1, et elle est tout le temps utilisé sur les processeurs modernes. Mais que faire en cas de défaut de cache ? ==Les caches non bloquants== Un '''cache bloquant''' est un cache auquel le processeur ne peut pas accéder pendant un défaut de cache. Il faut attendre que la lecture ou écriture en RAM soit terminée avant de pouvoir utiliser de nouveau le cache. Un '''cache non bloquant''' n'a pas ce problème : on peut l'utiliser pendant un défaut de cache. Les caches non bloquants permettent de démarrer une nouvelle lecture ou écriture alors qu'une autre est en cours, ce qui permet d'exécuter plusieurs lectures ou écritures en même temps. ===Les ''Miss Handling Status Registers''=== Lors d'un défaut de cache, la mémoire RAM est consultée pendant le défaut de cache, mais le cache est inutilisé. Un défaut de cache n'utilise pas le cache, ce n'est qu'un accès en mémoire RAM, sur le bus mémoire (ou un accès aux niveaux de cache inférieurs, peu importe). Le cache en lui-même est laissé libre, rien n’empêche d'y accéder, il est en réalité intrinsèquement non-bloquant. Les caches, bloquants comme non-bloquants, sont en réalité composés d'une mémoire cache proprement dite, entourée de circuits qui servent d'interface entre le processeur et le cache lui-même. Et parmi les circuits tout autour, certains gèrent l'accès au cache lors d'un défaut de cache. Ils sont regroupés sous le terme de '''''Miss Handling Architecture''''' (MHA). La différence entre un cache bloquant et un cache non-bloquant est en réalité liée à l'implémentation de la MHA. Les caches bloquants coupent volontairement l'accès au cache lors d'un défaut, car les défauts sont plus simples à gérer ainsi. Le défaut de cache rapatrie une donnée depuis la RAM, qui sera écrite dans le cache. Et il ne faut pas qu'une tentative d'accès à cette donnée ait lieu avant qu'elle ne soit chargée. Mais ce blocage est général et touche tout le cache, alors que seule une ligne de cache est concernée par le défaut de cache. L'idée derrière un cache non-bloquant est que seule la ligne de cache est bloquée, mais les autres sont accessibles. L'idée est alors de mémoriser les lignes de cache concernées par le défaut de cache, afin d'en bloquer l'accès. À chaque accès, on vérifie si la ligne de cache est déjà réservée par un défaut de cache. La lecture/écriture est alors bloquée si c'est le cas, mais elle accepte les accès sinon. Pour cela, la ''Miss Handling Architecture'' contient des registres qui mémorisent des informations sur les défauts de cache en cours. Ils portent le nom de '''''miss status handling registers''''', que j’appellerais dorénavant MSHR, qui sont aussi appelés des '''''miss buffer'''''. Le contenu des MSHR varie beaucoup suivant le processeur, mais ces derniers stockent au minimum les informations suivantes : * Le numéro de la ligne de cache dans laquelle les données sont chargées. * Un bit de validité qui indique si le MSHR est vide ou pas, qui est mis à 0 quand le défaut de cache est résolu. * Un ou plusieurs champs de lecture/écriture, qui contiennent des informations sur la lecture/écriture. ** Pour une lecture, elle contient des informations sur la destination de la donnée, à savoir qui prévenir quand le défaut de cache est terminé. C'est parfois un nom/numéro de registre (celui dans lequel charger la donnée), mais c'est souvent le numéro de l'entrée dans le ''load/store queue''. ** Pour les écritures, elle contient la donnée à écrire, ou éventuellement un numéro de ''load/store queue'' où se trouve la donnée à écrire. Il faut noter que le bus mémoire ne peut gérer qu'un seul défaut de cache à la fois. Aussi, il est intéressant de regarder ce qui se passe lorsqu'un second défaut de cache survient, pendant qu'un premier est en cours. Dans ce cas, il y a deux réponses qui correspondent à deux types de caches non-bloquants, qui portent les noms barbares de caches de type succès après défaut et défaut après défaut. Sur le premier type, il ne peut pas y avoir plusieurs défauts de cache simultanés. Dès qu'un second défaut de cache survient, le cache stoppe son activité et on ne peut plus démarrer de nouvelle lecture/écriture, tant que le premier défaut de cache n'est pas résolu. Le second type est plus souple et autorise la survenue de plusieurs défauts de cache simultanés. Du moins, jusqu'à une certaine limite, car le cache ne peut supporter qu'un nombre limité d'accès mémoires simultanés (pipelinés). ===Les accès simultanés à une même ligne de cache=== Il arrive que le processeur fasse plusieurs accès mémoire simultanés à la même ligne de cache. Si la ligne de cache en question n'a pas encore été chargée dans le cache, alors on a plusieurs défauts de cache consécutifs pour la même ligne de cache, et le cache non-bloquant doit gérer la situation. Pour la suite, il va falloir faire une petite distinction entre les défauts primaires et secondaires. Imaginons qu'un défaut de cache ait lieu et demande à charger une donnée dans la ligne de cache numéro N. Il s'agit du premier défaut impliquant cette ligne de cache précise, ce qui lui vaut le nom de '''défaut de cache primaire'''. Mais par la suite, d'autres accès mémoire à la même ligne de cache ont lieu, alors que la ligne de cache n'est pas encore disponible. Dans ce cas, il s'agit de '''défauts de cache secondaires'''. Pour l'unité d'accès mémoire, les défauts de cache primaire et secondaire sont différents (ils prennent tous une entrée dans la ''load/store queue''). Mais pour le cache, ils ne correspondent qu'à un seul accès au cache : celui qui demande de charger la ligne de cache demandée. Les défauts de cache primaire et secondaire à la même ligne de cache se voient attribuer un MSHR unique. La gestion des défauts de cache secondaires dépend du cache non-bloquant. La solution la plus simple ne permet pas les défauts de cache secondaires. Le cache ne permet pas deux défauts de cache simultanés pour la même ligne de cache. Les autres solutions le permettent, en fusionnant des accès simultanés à la même ligne de cache en un seul au niveau des MSHR. Dans tous les cas, détecter les défauts de cache secondaires sont un problème qu'il faut détecter. La ''Miss Handling Architecture'' doit détecter les défauts de cache secondaire. Pour cela, elle procède comme suit. Lors de chaque défaut de cache, la MHA récupère le numéro de la ligne de cache associé. Il vérifie alors chaque MSHR pour vérifier s'il contient le numéro en question. S'il n'y a aucune correspondance dans les MSHR, alors c'est signe que le défaut de cache est un défaut primaire. Mais s'il y en a une, alors c'est un défaut secondaire. Évidemment, cela signifie que lors d'un défaut de cache, le numéro de ligne de cache est envoyé à tous les MSHR, pour comparaison. Les MSHR sont donc regroupés dans une mémoire associative, une sorte de mini-cache, faciliter l'implémentation. Lors d'un défaut de cache primaire, l'accès à un cache non-bloquant se fait comme suit : le processeur envoie une adresse au cache, accède à celui-ci, et détecte la survenue d'un défaut de cache. Il en profite alors pour attribuer une ligne de cache dans laquelle sera chargée ce défaut. L'attribution est très simple dans le cas des caches ''direct mapped'', ou associatifs par voie, pour lesquels l'attribution se fait assez simplement. Il mémorise alors cette information dans les MSHR, après avoir vérifié que le défaut de cache n'était pas un défaut secondaire. Lors des accès ultérieurs à une adresse proche, censée être dans la même ligne de cache, le processeur va encore une fois rencontrer un défaut de cache. Il va alors déterminer le numéro de la ligne de cache associée à l'adresse, et comparer ce numéro avec les MSHRs. Si un MSHR contient ce numéro, c'est signe que le défaut de cache est un défaut secondaire. La MHA réagit alors différemment selon le processeur considéré. Une première solution n'autorise pas les défauts de cache secondaires. Si l'un d'entre eux survient, le processeur est gelé par un ''pipeline stall'', une bulle de pipeline. Une autre solution fusionne les défauts de cache secondaires avec le défaut de cache primaire : tout cela ne correspond qu'à un seul défaut de cache pour lui. ===Les MSHR simples=== Un cache non-bloquant à '''MSHR simple''' contient juste plusieurs MSHR qui mémorisent juste un numéro de ligne de cache, un bit de validité, le champ de lecture/écriture, et l'adresse exacte de lecture/écriture. Avec cette organisation, il est possible d'avoir plusieurs défauts de cache séparés, mais à la condition que chaque défaut accède à une ligne de cache différente. Deux accès simultanés à une même ligne de cache ne sont pas possibles, les défauts de cache secondaires ne sont pas autorisés. Ainsi, chaque défaut de cache se voit attribuer son propre MSHR, chacun contient un numéro de ligne de cache différent. Pour comprendre pourquoi c'est impossible de gérer les défauts secondaires, il faut regarder le champ de lecture/écriture. Si on veut effectuer plusieurs écritures consécutives à la même adresse, le MSHR n'aura pas de quoi mémoriser les deux données à écrire. Il pourra mémoriser la première donnée à écrire, pas la seconde. Même chose lors d'une lecture : le champ lecture/écriture peut mémorisr la destination de la première lecture, pas de la seconde. L'avantage est que la MHA n'a besoin que des MSHR et de quelques circuits annexes. Les autres solutions rajoutent des circuits annexes pour gérer les défauts de cache secondaires, qui utilisent beaucoup de circuits. Le cout en circuit est donc élevé, mais le gain en performance est là. Passons maintenant aux caches non-bloquants qui autorisent les défauts de cache secondaire. La solution la plus simple consiste à utiliser ===Les MSHR adressés implicitement=== Avec les '''MSHR adressés implicitement''', il est possible de fusionner plusieurs accès mémoire à une même ligne de cache, mais sous une condition très importante : ces accès lisent/écrivent des mots mémoire différents. Par exemple, imaginons qu'une ligne de cache contienne 8 mots mémoire de 64 bits. Si un premier accès mémoire lit le mot mémoire numéro 7 (dernier mot mémoire de la ligne), et le second accès le mot mémoire numéro 3, alors la fusion est possible. Mais si deux accès mémoire veulent lire/écrire le mot mémoire numéro 7, alors la fusion n'est pas possible et le processeur se bloque, un ''pipeline stall'' survient. Un MSHR adressé implicitement est un MSHR simple, qui contient naturellement un numéro de ligne de cache (tag) et un bit de validité, sauf que le champ de lecture/écriture est dupliqué. Les différents champs lecture/écriture d'un MSHR sont regroupés dans une mémoire RAM/cache qui contient autant d'entrées qu'il y a de mot mémoire dans une ligne de cache. Chaque entrée de la table est associée à un mot mémoire de la ligne de cache et stocke des informations sur celui-ci. Une entrée mémorise, au minimum : * Un champ de lecture/écriture qui contient soit la destination de la lecture, soit la donnée à écrire. * Un bit R/W permettant d'interpréter correctement le champ de lecture/écriture. * Un bit de validité pour chaque entrée de la table, qui dit si un défaut de cache antérieur accède déjà à ce mot mémoire. La fusion de deux défauts de cache est ainsi assez simple. Un défaut de cache primaire/secondaire configure l'entrée associée au mot mémoire lu/écrit. Si un défaut secondaire ultérieur lit/écrit un mot mémoire différent (dont le bit de validité de l'entrée dans la table est à zéro), pas de conflit : il configure une autre entrée, vide. Mais s'il lit/écrit un mot mémoire pour lequel l'entrée de la table est occupée, il y a conflit, le processeur est bloqué par un ''pipeline stall'', une bulle de pipeline. Avec cette organisation, le nombre de MSHR indique combien de lignes de cache peuvent être lues en même temps depuis la mémoire. Quant au nombre d'entrées par MSHR, il détermine combien d'accès mémoires qui ne se recouvrent pas peuvent avoir lieu en même temps. ===Les MSHR adressés explicitement=== Les '''MSHR adressés explicitement''' n'ont pas les contraintes des MSHR adressés implicitement. Avec eux, il est possible d'avoir plusieurs défauts de cache pointant vers la même ligne de cache, mais aussi vers le même mot mémoire. De tels défauts de cache apparaissent sur les processeurs à exécution dans le désordre, mais sont très rares, voire inexistants, sur les processeurs ''in-order''. L'idée est encore que chaque MSHR est associée à une table mémoire qui mémorise des entrées. Cette table n'est autre qu'une mémoire FIFO. Sauf que cette fois-ci, une entrée n'est pas associée à un mot mémoire. Une entrée est associée à un défaut de cache. Une entrée mémorise là encore la destination de la lecture ou la donnée de l'écriture suivi du bit R/W, ainsi que d'un bit de validité par entrée, mais aussi : la position du mot mémoire lu dans la ligne de cache. C'est cette dernière information qui n'était pas présente dans les MSHR adressés implicitement. De plus, le nombre d'entrée par MSHR n'est pas égal au nombre de mots mémoires dans une ligne de cache, mais peut être arbitrairement grand. L'avantage d'un tel cache est qu'il est capable de traité les défauts secondaires concernant un même mot mémoire, et ce, car les accès à la ligne de cache concernée se produisent dans l'ordre de génération des défauts (la table mémoire est une FIFO). ===Les MSHRs inversés=== Généralement, plus on veut supporter de défauts de cache, plus le nombre de MSHR et d'entrées augmente. Mais au-delà d'un certain nombre d'entrées et de MSHR, les MSHR adressés implicitement et explicitement ont tendance à bouffer un peu trop de circuits. Utiliser une organisation un peu moins gourmande en circuits est donc une nécessité. Cette organisation plus économe se base sur des MSHR inversés. Les MSHR inversés ne contiennent qu'une seule entrée, en quelque sorte : au lieu d’utiliser n MSHR de m entrées chacun, on va utiliser n × m MSHR inversés. La différence, c'est que plusieurs MSHR peuvent contenir un tag identique, contrairement aux MSHR adressés implicitement et explicitement. Lorsqu'un défaut de cache a lieu, chaque MSHR est vérifié. Si jamais aucun MSHR ne contient de tag identique à celui utilisé par le défaut, un MSHR vide est choisi pour stocker ce défaut, et une requête de lecture en mémoire est lancée. Dans le cas contraire, un MSHR est réservé au défaut de cache, mais la requête n'est pas lancée. Quand la donnée est disponible, les MSHR correspondant à la ligne qui vient d'être chargée vont être utilisés un par un pour résoudre les défauts de cache en attente. ===Les MSHR intégrés au cache=== Certains chercheurs ont remarqué que pendant qu'une ligne de cache est en train de subir un défaut de cache, celle-ci reste inutilisée, et son contenu est destiné à être perdu une fois le défaut de cache résolu. Ils se sont dit que, plutôt que d'utiliser des MSHR séparés, il vaudrait mieux utiliser la ligne de cache pour stocker les informations sur les défauts de cache en attente dans cette ligne de cache. Pour éviter tout problème, il faut rajouter un bit dans les bits de contrôle de la ligne de cache, qui sert à indiquer que la ligne de cache est occupée : un défaut de cache a eu lieu dans cette ligne, et elle stocke donc des informations pour résoudre les défauts de cache. ==L'entrelacement mémoire== Nous venons de voir comment une mémoire cache peut gérer plusieurs accès mémoire simultanés. Il se trouve que ce genre d'optimisation dépasse largement le cadre du cache. Les mémoires RAM ne sont pas en reste, elles aussi ! Il est possible de pipeliner l'accès à une mémoire RAM, en utilisant des techniques dites d''''entrelacement'''. Précisons cependant que cette optimisation n'est pas utilisée sur les mémoires SDRAM ou DDR modernes, du moins pas sans modifications majeures. Mais divers ordinateurs assez anciens, dont des superordinateurs, ont utilisé cette technique. La technique demande d'utiliser plusieurs mémoires séparées. Sur les anciens ordinateurs, les mémoires en question sont des chips mémoire, à savoir des circuits intégrés de DRAM. Pour faire plus général, nous allons utiliser le terme de '''banques''', ou encore de bancs mémoire. La différence est qu'il existe des mémoires multi-banque, qui regroupent plusieurs banques indépendantes dans un seul boitier, dans un seul chip mémoire. En clair, de telles mémoires regroupent plusieurs sous-mémoires dans un seul circuit intégré. Il est aussi possible d'utiliser plusieurs chips mémoire séparés, chacun ayant une banque. Reste à combiner ces banques pour former un pseudo-pipeline. ===Utiliser plusieurs banques sans entrelacement=== Sans optimisation particulière, les adresses sont réparties dans les banques comme indiqué ci-dessous. Il s'agit de ce qui s'appelle un arrangement horizontal, et nous avions vu celui-ci dans le chapiutre sur les mémoires SDRAM. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Avec cet arrangement, les bits de poids fort de l'adresse sont utilisées pour sélectionner la banque adéquate, et le reste de l'adresse est envoyé sur le bus d'adresse. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Adresse de banque !! Adresse dans la banque |- | Quelques bits de poids fort || Reste de l'adresse |} L'entrelacement change cette répartition, afin d'accélérer les accès mémoire. L'entrelacement classique vise à améliorer les accès à des adresses mémoires consécutives, mais des formes plus évoluées d'entrelacement visent à optimiser des accès mémoire arbitraires. Voyons-les dans l'ordre, du plus simple au plus complexe. ===L'entrelacement classique=== Avec l''''entrelacement classique''', des adresses consécutives sont réparties dans des banques différentes. [[File:Répartition des adresses dans une mémoire interleaved.png|centre|vignette|upright=1.5|Répartition des adresses dans une mémoire interleaved.]] L'entrelacement simple permet d'accélérer les accès mémoire qui se font à des adresses consécutives. Avec l'entrelacement, chaque accès mémoire tombe dans une banque mémoire différente, ce qui fait qu'on peut démarrer un nouvel accès mémoire à chaque cycle d'horloge. Pendant qu'une banque est occupée par un accès mémoire, on démarre le suivant dans une autre banque, et ainsi de suite. Le tout se termine soit quand on a épuisé toutes les banques libres, soit quand l'accès en rafales se termine. Pas besoin d'attendre que la mémoire ait fini sa lecture/écriture avant de démarrer la lecture/écriture suivante. [[File:Pipemining mémoire.png|centre|vignette|upright=2.5|Pipelining mémoire]] L'animation suivante illustre bien le processus, avec quatres banques. [[File:Interleaving.gif|centre|vignette|upright=2.5|Pipelining mémoire]] L'entrelacement simple utilise les bits de poids faible pour sélectionner la banque, et les bits de poids fort pour la case mémoire. [[File:Adresse mémoire d'une mémoire entrelacée.png|centre|vignette|upright=2|Adresse mémoire d'une mémoire entrelacée]] Précisons que le temps d'accès mémoire ne change pas beaucoup avec l'entrelacement. Par contre, on peut faire plus d'accès mémoire simultanés. On peut démarrer un accès mémoire par cycle d'horloge, mais l'accès en lui-même prend plusieurs cycles. Les mémoires à entrelacement ont donc un débit supérieur aux mémoires qui ne l'utilisent pas. ===L'entrelacement par décalage=== L'entrelacement simple est très utile pour les accès en rafle ou équivalents. Par contre, il existe de rares situations où il n'est pas optimal. Une de ces situations est celle des accès en enjambée (en ''stride''), où l'on accède en série à des adresses sont séparées par N mots mémoires. De tels accès surviennent quand un logiciel accède à des structures de données spécifiques, à savoir des tableaux de structures, des matrices, ou d'autres structures de données dans le genre. Un cas classique est celui du parcours d'une matrice colonne par colonne. Les matrices de nombres sont mémorisées ligne par ligne, ce qui fait que parcourir une colonne demande de passer d'une ligne à la suivante et de faire des grands sauts en mémoire RAM, mais tous espacés par la même distance. Avec l'entrelacement simple, les accès en enjambée sont moins performants que les accès à des adresses consécutives, mais cela ne fonctionne pas trop mal. Le pire cas est celui où l'on a N banques et où les données sont justement placées toutes les N adresses. Des accès consécutifs vont tous tomber dans la même banque, on ne peut plus accéder à des banques différentes en parallèle. Mais en dehors de ce cas, on voit une amélioration par rapport à la situation sans entrelacement. Pour obtenir des performances maximales pour les accès en enjambées, il faut répartir les mots mémoires dans la mémoire autrement. L'organisation idéale est la suivante. : Dans les explications qui vont suivre, la variable N représente le nombre de banques, qui sont numérotées de 0 à N-1. On commence par organiser les N premières adresses comme une mémoire entrelacée simple : l'adresse 0 correspond à la banque 0, l'adresse 1 à la banque 1, etc. Pour le bloc suivant, on décale tout d'une adresse, à savoir qu'on commence à remplir les banques à partir de la seconde, non de la première. Une fois la fin du bloc atteinte, on finit de remplir le bloc en repartant du début du bloc. Le troisième bloc d'adresse subit le même traitement, sauf qu'on commence à remplir à partir de la seconde banque. Et on poursuit l’assignation des adresses en décalant d'un cran en plus à chaque bloc. Ainsi, chaque bloc verra ses adresses décalées d'un cran en plus comparé au bloc précédent. Si jamais le décalage dépasse la fin d'un bloc, alors on reprend au début. [[File:Mémoire interleaved par décalage.png|centre|vignette|upright=1.5|Mémoire entrelacée par décalage.]] En faisant cela, on remarque que les banques situées à N adresses d'intervalle sont différentes. Dans l'exemple du dessus, nous avons ajouté un décalage de 1 à chaque nouveau bloc à remplir. Mais on aurait tout aussi bien pu prendre un décalage de 2, 3, etc. Dans tous les cas, on obtient un entrelacement par décalage. Ce décalage est appelé le pas d'entrelacement, noté P. Le calcul de l'adresse à envoyer à la banque, ainsi que la banque à sélectionner se fait en utilisant les formules suivantes : * adresse à envoyer à la banque = adresse totale / N ; * numéro de la banque = (adresse + décalage) modulo N, avec décalage = (adresse totale * P) mod N. Avec cet entrelacement par décalage, on peut prouver que la bande passante maximale est atteinte si le nombre de banques est un nombre premier. Seulement, utiliser un nombre de banques premier peut créer des trous dans la mémoire, des mots mémoires inadressables. Pour éviter cela, il faut faire en sorte que N et la taille d'une banque soient premiers entre eux : ils ne doivent pas avoir de diviseur commun. Dans ce cas, les formules se simplifient : * adresse à envoyer à la banque = adresse totale / taille de la banque ; * numéro de la banque = adresse modulo N. ===L'entrelacement pseudo-aléatoire=== Une dernière méthode de répartition consiste à répartir les adresses dans les banques de manière "pseudo-aléatoire". L'idée exacte est de faire passer l'adresse dans une '''fonction de hachage''', plus ou moins complexe. Pour rappel, une fonction de hachage prend une entrée de grande taille et fournit en sortie un résultat de petite taille. Idéalement, elles doivent donner des résultats aussi différents que possible pour des entrées similaires, histoire de simuler une sorte de pseudo-aléatoire. Ici, une adresse est transformée en un numéro de banque. Une fonction de hachage simple ne fait que permuter des bits de l'adresse pour obtenir son résultat. Elle ne fait qu'échanger des bits de place avant de couper l'adresse en deux morceaux : un pour la sélection de la banque, et un autre pour la sélection de l'adresse dans la banque. Cette permutation est fixe, et ne change pas suivant l'adresse. Des fonctions de hachage plus complexes font des XOR bit à bit entre certains bits de l'adresse. Il existe aussi d'autres techniques qui donnent le numéro de banque à partir d'un polynôme modulo N, appliqué sur l'adresse. ==Les contrôleurs mémoires SDRAM/DDR optimisés== Après avoir vu le cas des mémoires RAM, nous allons nous concentrer sur le cas particulier des mémoires SDRAM. Les mémoires SDRAM et DDR modernes sont capables de gérer plusieurs accès simultanés à la mémoire RAM. Et les contrôleurs mémoire intègrent des optimisations spécifiques afin d'en profiter au maximum. ===La mise en attente des accès mémoire=== Comme pour les mémoires caches, il existe des contrôleurs mémoire bloquants et non-bloquants. Les premiers n'acceptent qu'un seul accès mémoire à la fois, les seconds acceptent plusieurs accès mémoire simultanés. Pour simplifier, nous allons dire que le contrôleur mémoire n’exécute qu'un seul accès mémoire à la fois, et qu'ils met en attente les accès en trop. C'est une simplification assez irréaliste, vu que les mémoires modernes disposent de plusieurs banques accessibles en parallèle. Mais nous introduirons ce détail un peu plus tard. Avec un contrôleur mémoire de ce type, les accès mémoire sont mis en attente dans une mémoire FIFO, histoire de les exécuter dans leur ordre d'arrivée. Les accès mémoire quittent alors la mémoire dans leur ordre d'arrivée, les lectures sont renvoyées dans l'ordre demandé. En plus d'une mémoire FIFO pour les commandes mémoire, on trouve une mémoire FIFO pour les données à écrire. Elle permet d'accumuler plusieurs écritures, tout en permettant que la donnée adéquate soit envoyée à la RAM au bon moment. Il y a aussi une FIFO pour les données lues, qui sert au cas où le cache ne soit pas disponible quand on lui envoie la donnée lue. [[File:Module d'interface avec la mémoire.png|centre|vignette|upright=2.5|Contrôleur mémoire avec mise en attente des requêtes processeur et autres optimisations.]] Une optimisation regroupe plusieurs accès à des données consécutives en un seul accès en rafale. Le contrôleur mémoire analyse les requêtes mises en attente et détecte si certaines se font à des adresses consécutives. Si c'est le cas, il fusionne ces requêtes en une lecture/écriture en rafale. Une telle optimisation est appelée la '''combinaison de lecture''' pour les lectures, et la '''combinaison d'écriture''' pour les écritures. ===Le pipeline des mémoires SDRAM=== Les SDRAM ont une forme de pseudo-pipeline très limité. Elles sont parfois capables de démarrer une commande avant que la précédente soit terminée. Même si elle parait très limitée, cette possibilité est exploitée au mieux par la présence de plusieurs banques dans la mémoire SDRAM. Les chips mémoires de SDRAM sont composés de plusieurs banques, chacune étant une sorte de mini-mémoire miniature. Chaque banque a son propre décodeur, son propre tampon de ligne, ses propres multiplexeurs de colonne, sa logique de rafraichissement mémoire, etc. C'est comme si une SDRAM regroupait plusieurs mémoires séparées dans un même circuit intégré. Et cela permet de faire plusieurs accès mémoire en même temps, dans des banques différentes. Il est possible de lancer un accès mémoire dans deux banques en même temps, par exemple. Ou encore, on peut lancer un nouvel accès mémoire dans une banque si elle est inoccupée. La possibilité améliore grandement la performance de la SDRAM. Mais nous en reparlerons dans un chapitre ultérieur. {|class="wikitable" |+ Pipelining basique sur les SDRAM |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 |- ! Banque Numéro 1 | || || || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || || || |- ! Banque Numéro 2 | || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || |- ! Banque Numéro 3 | || || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || |} L'implémentation n'a rien de vraiment compliqué. Il suffit d'utiliser une mémoire FIFO par banque. Ainsi, les accès dans une même banque se feront en série, dans l'ordre d'arrivée, mais des accès à des banques différentes se feront dans le désordre. Cela ne pose pas de problème car les accès se font à des adresses différentes, ce qui fait qu'il n'y a pas de conflits majeurs. [[File:Gestion parallèle des banques.png|centre|vignette|upright=2.5|Gestion parallèle des banques.]] ===Le ré-ordonnancement des commandes mémoires=== Un contrôleur mémoire moderne est capable de changer l'ordre des requêtes mémoires, pour gagner en performances. En clair : il peut faire l'équivalent mémoire de l'exécution dans le désordre des processeurs haute performance. Une différence majeure est que le contrôleur mémoire ne remet pas les accès mémoire dans l'ordre avant de les envoyer au processeur. C'est le processeur qui remet en ordre les accès mémoire, dans sa ''load queue''. Pour cela, les requêtes mémoire sont "numérotées", à savoir qu'on leur attribue un identifiant binaire. Le contrôleur mémoire exécute les lectures/écritures dans le désordre, mais garde la trace de leur identifiant. Il sait dans quel ordre il exécute les accès mémoire et connait leur latence. Ce qui fait qu'il sait que la donnée lue à tel instant est associée à la requête mémoire numéro X. Pour les lectures, il renvoie la donnée lue avec l'identifiant. Le processeur sait alors à quelle lecture la donnée lue correspond et il se débrouille en interne pour gérer la situation. En clair : le processeur voit les accès mémoire dans le désordre, c'est lui qui les remet en ordre. Maintenant que ces précisions sont faites, posons cette question : quel est l'intérêt de faire les accès mémoire dans le désordre ? Accéder à des banques en parallèle est une réponse possible, mais le ré-ordonnancement est surtout utile si tous les accès mémoire atterrissent dans la même banque. Pour comprendre pourquoi, faisons un rappel rapide sur le tampon de ligne. Les mémoires SDRAM sont des mémoires à tampon de ligne. Elles font un accès mémoire en deux étapes. La première étape recopie une ligne de N * 64 bits dans un tampon interne à la SDRAM. La seconde étape sélectionne une colonne, à savoir une donnée de 64 bits, qui est soit envoyée sur le bus de données pour une lecture, soit modifiée par une écriture. [[File:Mémoire à tampon de ligne.png|centre|vignette|upright=2|Mémoire à tampon de ligne]] Les deux étapes correspondent à deux commandes séparées : une commande ACT qui précise l'adresse de la ligne, et une commande READ ou WRITE qui précise l'adresse de la colonne. [[File:Signaux RAS et CAS.png|centre|vignette|upright=2|Signaux RAS et CAS.]] Les SDRAM permettent de se passer de la première étape si des accès consécutifs se font dans la même ligne. Une fois activée, la ligne reste ouverte et on peut accéder plusieurs fois de suite dedans. On a alors juste à préciser l'adresse de colonne dedans. [[File:Sélection d'une ligne sur une mémoire FPM ou EDO.png|centre|vignette|upright=2|Sélection d'une ligne sur une mémoire FPM ou EDO.]] L'exécution dans le désordre des lectures/écritures SDRAM vise à faire en sorte que des accès consécutifs se fassent dans une même ligne. Elle marche si plusieurs accès à une même ligne ne sont pas consécutifs, et qu'il y a des accès entre les deux. Après ré-ordonnancement, ces accès mémoire deviennent consécutifs, ils sont exécutés l'un à la suite de l'autre. Pour rendre le tout plus concret, voici un exemple. Imaginez que l'on sait les 3 accès mémoire suivants : * Une lecture ligne A ; * Une écriture ligne B ; * Une lecture ligne A. Sans réordonnancent, on doit émettre deux commandes PRECHARGE : une quand on passe de la ligne A à la ligne B, et une autre pour le passage inverse. pour éviter cela, le contrôleur mémoire peut retarder l'écriture, ou au contraire avancer la seconde lecture. le résultat est alors le suivant : * Lecture ligne A ; * Lecture ligne A ; * Une écriture ligne B. On a donc deux accès consécutifs à la même ligne, suivi par une écriture dans une autre ligne. La technique marche parce que l'on a un mix adéquat de lectures et d'écritures. Mais il existe des possibilités de réorganisation autres qui ne sont pas valides. Il faut par exemple prendre garde à éviter d'intervertir lectures et écritures à une même adresse, sous peine de problèmes. Encore une fois, il faut faire attention aux dépendances mémoires. Le controleur mémoire teste les dépendances mémoires avant d'envoyer des commandes à la mémoire DDR/SDRAM. ===L'entrelacement sur les mémoires SDRAM=== L''''entrelacement''' fonctionne sur les mémoires SDRAM, mais il doit être fortement modifié. Concrètement, tout ce qui a étré dit plus est inapplicable pour les SDRAM, du fait de la présence du tampon de ligne. Des adresses consécutives atterrissent déjà dans la même ligne, ce qui fait qu'on n'a pas besoin d'entrelacement au niveau d'une ligne. Et ne parlons pas de ce la présence de rangées et de canaux mémoire ! Cependant, il peut y avoir un entrelacement lié à la présence des banques SDRAM. Pour gérer l'entrelacement, le contrôleur de SDRAM prend en entrée l'adresse envoyée par le processeur, et la découpe en plusieurs champs : un pour sélectionner la banque adéquate, un autre pour la ligne, un autre pour la colonne, un autre pour la rangée. Une solution simple désactive l'entrelacement. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de banque || Adresse de ligne || Adresse de colonne |} Si cette solution fonctionne bien si les accès mémoire sont assez aléatoires, en pratique, mieux vaut utiliser l'entrelacement. Une première méthode d'entrelacement est l''''entrelacement de ligne'''. Elle répartit deux lignes consécutives dans deux banques différentes. Avec N banques, la première ligne ira dans la première banque, la seconde ligne dans la seconde, et ainsi de suite jusqu'à la énième banque qui va dans la banque numéro N. Et la ligne N+1 repart dans la première banque. L'avantage est que passer d'une ligne à la suivante est assez rapide. Au lieu de devoir fermer la première ligne et ouvrir la suivante on ouvrira directement la ligne suivante dans une banque différente, ce qui sera plus rapide. La commande PRECHARGE pourra être envoyée à la seconde banque en avance. Pour cela, l'adresse est découpée comme suit. {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne (précise à l'octet) |} D'autres méthodes d'entrelacement sont possibles, mais elles sont plus complexes. Chaque contrôleur mémoire fait un peu à sa sauce. Ils permettent en général de choisir entre plusieurs entrelacements. Au minimum, ils permettent de désactiver l'entrelacement, d'activer un entrelacement de ligne, et d'autres entrelacements plus complexes, dont certains sont propriétaires et tenus secrets par le fabricant. L'usage du ''dual channel'' complique encore l'entrelacement. Là encore, il y a deux grandes solutions : avec et sans entrelacement des canaux mémoire. Rappelons le principe : deux barrettes de RAM sont accédées en parallèle. Et pour cela, il faut utiliser l'entrelacement. Typiquement, chaque barrette mémoire fournit 64 bits, ce qui fait que l'on peut accéder à 128 bits d'un seul coup, par exemple avec un accès en rafale. Une solution pour cela est de ne pas utiliser l'entrelacement. Avec deux barrettes, la première barrette regroupera la moitié haute de la RAM, la seconde barrette utilisera la moitié basse. Les possibilité d’accéder à deux données séparées de 64 bits en même temps est cependant très rare. Aussi, on préfère utiliser l'entrelacement. Pour cela, deux blocs de 64 bits sont placés dans des canaux mémoire séparés. Idem avec du triple ou quadruple canal, mais c'est alors trois ou quatre blocs de 64 bits qui sont dispersés dans des canaux consécutifs. {|class="wikitable" |+ Adresse mémoire |- ! colspan="6" | Sans entrelacement |- | Numéro de canal/barrette || Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne || Position de l'octet dans un bloc de 64 bits |- | colspan="6" | |- ! colspan="6" | Avec entrelacement |- | Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne || Numéro de canal/barrette || Position de l'octet dans un bloc de 64 bits |} Les contrôleurs de SDRAM précédents sont assez basiques et ne représentent pas les contrôleurs les plus évolués. Ils étaient utilisés sur les anciens PC, à une époque où ils étaient encore sur la carte mère du processeur. Mais de nos jours, le contrôleur mémoire est intégré au processeur. Et il incorpore de nombreuses optimisations afin de gagner en performances. Malheureusement, ces optimisations sont elles-mêmes dépendantes des optimisations intégrées dans le processeur, ce qui fait qu'on ne peut pas en parler ici. Elles demandent que le contrôleur mémoire reçoive plusieurs accès mémoire simultanés, ce qui n'a aucun sens à ce stade du cours. Aussi, nous allons les passer sous silence pour le moment. Nous les verrons dans le chapitre sur le parallélisme mémoire. L'entrelacement est géré juste avant le séquenceur mémoire. Le '''circuit d'entrelacement''' intervertit certains bits de l'adresse lors des accès mémoires. On trouve aussi des mémoires FIFOs pour mettre en attente les requêtes mémoire provenant du processeur. Elles permettent de recevoir plusieurs accès mémoire consécutifs. Si vous vous demandez comment plusieurs accès mémoire consécutifs peuvent avoir lieu, sachez que c'est une bonne question. Pour le moment, nous allons dire que ces accès proviennent du processeur ou de la mémoire cache, sans expliquer comment. Le contrôleur mémoire reçoit une série d'accès mémoire assez rapprochés, qu'il doit exécuter au mieux. [[File:Controleur mémoire d'une SDRAM avec entrelacement.png|centre|vignette|upright=2.5|Contrôleur mémoire d'une SDRAM avec entrelacement.]] <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=La désambiguïsation mémoire | prevText=La désambiguïsation mémoire | next=Les processeurs superscalaires | nextText=Les processeurs superscalaires }} </noinclude> 1p9f2rtty7j5uc4a6fh1emo9bz6vqni 764650 764649 2026-04-23T14:11:05Z Mewtow 31375 764650 wikitext text/x-wiki Dans ce chapitre, nous allons voir les techniques qui permettent de gérer plusieurs accès mémoire simultanés directement au niveau du cache ou de la mémoire RAM. Par plusieurs accès mémoire simultanés, vous pensez sans doute à l'usage de cache multiports, voire de mémoires RAM multiport. C'est en effet une solution, mais ce n'est clairement pas la seule. Par exemple, il est possible de pipeliner l'accès au cache, voire à la mémoire RAM. Il existe de nombreuses techniques de '''parallélisme mémoire''', et nous allons les voir dans ce chapitre. Pour donner un exemple, il est possible de pipeliner les accès mémoire. Il est possible de pipeliner l'accès au cache et/ou l'accès à la mémoire, et nous verrons comment faire dans ce chapitre. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, qu'ils aient ou non un pipeline dynamique. Par contre, pipeliner la mémoire est une technique ancienne, aujourd'hui peu utilisée. Elle était utilisée sur des très vieux ordinateurs, qui avaient un pipeline mais pas de mémoire cache. Mais de nos jours, les mémoires SDRAM et DDR ne sont pas adaptées pour ça, il est très difficile de les pipeliner correctement. Le parallélisme mémoire est utile aussi bien sur des processeurs à émission dans l'ordre que dans le désordre. Par exemple, un processeur ''in-order'' avec des lectures non-bloquantes peut e, profiter. Il peut techniquement lancer une seconde lecture, après avoir rencontré une première lecture non-bloquante. En général, le nombre de lectures consécutives est cependant limité à moins d'une dizaine. À l'opposé, les processeurs sans lectures non-bloquantes profitent pas du parallélisme mémoire. À la rigueur, ils peuvent en profiter s'ils intègrent des techniques de préchargement. Pendant que le processeur exécute une lecture/écriture, il peut précharger une autre donnée en parallèle. Inutile de dire que sans pipeline processeur, le parallélisme mémoire ne sert pas à grand-chose. ==Le ''line fill buffer'' et les techniques associées== Lors d'un défaut de cache, le processeur doit attendre que toute la ligne de cache soit chargée avant d'être utilisable. Or, la taille d'une ligne de cache est supérieure à la largeur du bus mémoire, ce qui fait qu'une ligne de cache est chargée en plusieurs fois, morceaux par morceaux, mot mémoire par mot mémoire. Le chargement peut se faire directement dans le cache, mais ce n'est pas une solution très pratique. À la place, beaucoup de processeurs ajoutent une mémoire tampon entre la RAM et le cache, appelée le '''tampon de remplissage de ligne''' (''line-fill buffer'') dans les processeurs modernes. Lors d'un défaut de cache, le processeur charge la donnée de la RAM dans le tampon de remplissage de ligne, mot mémoire par mot mémoire. Une fois plein, le tampon de remplissage de ligne est recopié dans la ligne de cache. Le tampon de remplissage de ligne contient une ligne de cache, si ce n'est que certains bits de contrôle ne sont pas présents. Le tampon de remplissage de ligne contient un bit de validité pour chaque mot mémoire de la ligne de cache, qui indiquent si le mot mémoire a été chargé. Par exemple, prenons un processeur 64 bits, qui gère donc des mots mémoire de 8 octets, avec des lignes de cache 256 octets/32 mots mémoire. Le tampon de remplissage de ligne contiendra 32 bits de validité. Si le processeur a chargé les 6 premiers mots mémoire, les 6 premiers bits de validité seront mis à 1, les autres seront encore à zéro. Il faut noter que la disponibilité de la ligne complète se détermine assez facilement en faisant un ET logique entre tous les bits de validité. Le processeur sait ainsi quand la ligne de cache est disponible entièrement et donc quand la transférer dans le cache. La même chose existe avec une hiérarchie de cache, sauf que l'on trouve une mémoire tampon entre chaque niveau de cache. Il y a un tampon de remplissage de ligne entre le cache L1 et le cache L2, entre le cache L2 et le L3, etc. Si le cache ne gère qu'un seul défaut de cache à la fois, le ''fill line buffer'' est une mémoire très simple, qui ne mémorise qu'une seule ligne de cache. Et cela vaut aussi bien pour un cache bloquant que non-bloquant. Mais sur les caches capables de gérer plusieurs défauts simultanés, le tampon de remplissage de ligne est une mémoire de type FIFO ou LIFO, capable de mémoriser plusieurs lignes de cache. Notons que le tampon de remplissage de ligne est très utile pour implémenter certaines techniques, par exemple le contournement du cache. Nous avions vu dans le chapitre sur le cache que certains accès mémoire doivent contourner le cache, pour des raisons de cohérence des caches. C'est notamment nécessaire pour accéder aux périphériques, mais c'est aussi utile pour des raisons de performances dans des cas très spécifiques. Les accès qui contournent le cache se font directement dans le tampon de remplissage de ligne : le processeur écrit ou lit les données depuis ce tampon de remplissage de ligne, sans accéder au cache. ===L'''early restart'' et le ''critical word load''=== La présence du ''line buffer'' permet une optimisation assez intéressante, qui permet de réduire la latence des défauts de cache. L'optimisation consiste à lire un mot mémoire dans le tampon de remplissage de ligne, même si la ligne de cache complète n'a pas encore été chargée. Il existe deux manières de faire cela, qui portent les noms d'''early restart'' et de ''critical word load''. La première est la version la plus simple, la seconde est plus complexe mais plus performante. Avec la technique d''''''early restart''''', la ligne de cache est chargée normalement, en partant de son premier mot mémoire. Dès que le mot mémoire lu/écrit par le processeur est copié dans le ''line fill buffer'', il est envoyé au processeur immédiatement. Illustrons le tout par un exemple, où une ligne de cache fait 16 mots mémoire. Le processeur effectue une lecture, qui lit le 5ème mot mémoire. Sans ''early restart'', le processeur doit charger les 16 mots mémoire avant de faire la lecture dans le cache. Avec ''early restart'', le processeur reçoit la donnée dès que le 5ème mot mémoire est disponible. Le processeur doit attendre que les 4 premiers mots mémoire soient chargés, puis le 5ème mot mémoire arrive et est envoyé directement au processeur, il est lu directement depuis le ''line fill buffer''. Les 11 mots mémoire suivants sont ensuite chargés dans le cache pendant que le processeur fait des calculs dans son coin. Le ''critical word load'' est une optimisation de la technique précédente où le chargement de la ligne de cache commence directement à la donnée demandée par le processeur. Pour reprendre l'exemple précédent, où le processeur demande le 5ème mot mémoire sur 16, le ''critical word load'' charge le 5ème mot mémoire en premier et l'envoie au processeur, ce qui fait qu'il est chargé très rapidement. Pas besoin d'attendre que le processeur charge les 4 mots mémoire précédents comme avec l'''early restart''. Dans le détail, le processeur charge le 5ème mot mémoire en premier, puis charge les 11 suivants, et termine par les 4 mots mémoire du début. En clair, le ''critical word load'' commence par charger le mot mémoire lu, puis les blocs suivants, avant de revenir au début du bloc pour charger les blocs restants. Ainsi, la donnée demandée par le processeur sera la première disponible. Pour cela, l'organisation du tampon de remplissage de ligne est modifiée de manière à rendre cela possible. Il a une taille égale à une ligne de cache complète, qui contient elle-même plusieurs mots mémoire. Dans le ''line-fill buffer'', chaque mot mémoire est stocké avec un tag, qui indique l'adresse du mot mémoire stocké dans le ''line-fill buffer''. Le ''line-fill buffer'' est donc un cache un peu particulier, qui fonctionne comme un cache du point de vue du processeur, comme une mémoire FIFO pour les transferts avec le cache. Ainsi, un processeur qui veut lire dans le cache après un défaut peut accéder à la donnée directement depuis le tampon de remplissage de ligne, alors que la ligne de cache n'a pas encore été totalement recopiée en mémoire. ==Les caches pipelinés== Il est possible de pipeliner l'accès au cache, ce qui demande juste de rajouter des registres au bon endroits et quelques circuits de contrôle. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, même ceux avec un pipeline dynamique. Et l'implémentation peut se faire de plusieurs manières différentes. Il faut dire que découper une mémoire cache en plusieurs étapes peut se faire de plusieurs manière. Commençons par la plus simple. Beaucoup de processeurs des années 2000 avaient une fréquence peu élevée comparée aux standards d'aujourd'hui, ce qui fait que leur cache avait bien un temps d'accès d'un cycle d'horloge. Mais l'accès au cache se faisait en deux cycles d'horloge : un cycle pour calculer une adresse et un cycle pour l'accès mémoire proprement dit. Et ces deux étapes étaient pipelinées, à savoir que deux micro-opérations mémoire peuvent s'exécuter en même temps : une dans l'étage de calcul d'adresse, une autre dans le cache. L'unité mémoire était donc pipelinée, alors que l'accès au cache ne l'était pas. Les processeurs qui implémentaient cette technique regroupent les micro-architectures K5 et K6 d'AMD, les processeurs Intel de micro-architecture P6 (Pentium 2 et 3) et quelques autres. Il est aussi possible de pipeliner l'accès au cache lui-même. Avec un cache pipeliné, l'accès au cache ne se fait pas en un seul cycle, mais en plusieurs. Cependant, on peut lancer un nouvel accès au cache à chaque cycle d'horloge, comme un processeur pipeliné lance une nouvelle instruction à chaque cycle. L'implémentation est assez simple : il suffit d'ajouter des registres dans le cache. Pour cela, on profite que le cache est composé de plusieurs composants séparés, qui échangent des données dans un ordre bien précis, d'un composant à un autre. De plus, le trajet des informations dans un cache est linéaire, ce qui les rend parfois pour l'usage d'un pipeline. ===Le ''pipelining'' des caches ''direct-mapped''=== Voici ce qui se passe avec un cache directement adressé. Pour rappel ce genre de cache est conçu en combinant une mémoire RAM, généralement une SRAM, avec quelques circuits de comparaison et un MUX. L'accès au cache se fait globalement en deux étapes : la première lit la donnée et le ''tag'' dans la SRAM, et on utilise les deux dans une seconde étape. Nous avions vu il y a quelques chapitre comment pipeliner des mémoires, dans le chapitre sur les mémoires évoluées. Et bien ces méthodes peuvent s'utiliser pour la mémoire RAM interne au cache !L'idée est d'insérer un registre entre la sortie de la RAM et la suite du cache, pour en faire un cache pipéliné, à deux étages. Le premier étage lit dans la SRAM, le second fait le reste. L'implémentation sur les caches associatifs à plusieurs voies est globalement la même à quelques détails près. [[File:Cache directement adressé pipeliné - deux étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - deux étages]] Avec le circuit précédent, il est possible d'aller plus loin, cette fois en pipelinant l'accès à la mémoire RAM interne au cache. Pour comprendre comment, rappelons qu'une mémoire SRAM est composée d'un plan mémoire et d'un décodeur. L'accès à la mémoire demande d'abord que le décodeur fasse son travail pour sélectionner la case mémoire adéquate, puis ensuite la lecture ou écriture a lieu dans cette case mémoire. L'accès se fait donc en deux étapes successives séparées, on a juste à mettre un registre entre les deux. Il suffit donc de mettre un gros registre entre le décodeur et le plan mémoire. [[File:Cache directement adressé pipeliné - trois étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - trois étages]] Et on peut aller encore plus loin en découpant le décodeur en deux circuits séparés. En effet, rappelez-vous le chapitre sur les circuits de sélection : nous avions vu qu'il est possible de créer des décodeurs en assemblant des décodeurs plus petits, contrôlés par un circuit de prédécodage. Et bien on peut encore une ajouter un registre entre ce circuit de prédécodage et les petits décodeurs. : Théoriquement, toute l'adresse est fournie d'un seul coup au cache, la quasi-totalité des processeurs présentent l'adresse complète à un cache pipeliné. Mais le Pentium 4 fait autrement. Il faut noter que les premiers étages manipulent l'indice dans la SRAM, qui est dans les bits de poids faible, alors que les étages ultérieurs manipulent le ''tag'' qui est dans les bits de poids fort. Les concepteurs du Pentium 4 ont alors décidé de présenter les bits de poids faible lors du premier cycle d'accès au cache, puis ceux de poids fort au second cycle. Pour cela, l'ALU fonctionnait à une fréquence double de celle du processeur, tout comme le cache L1. Il n'y avait pas de pipeline proprement dit, mais cela réduisait grandement la latence d'accès au cache. ===Le ''pipelining'' des caches splittés/sériels=== Il est aussi possible de pipeliner un cache dits splittés, aussi appelés à accès sériel. Pour rappel, les caches à accès sériel vérifient si il y a succès ou défaut de cache, avant d'accéder aux lignes de cache en cas de succès. Ils font donc différemment des autres caches, qui accèdent à une ligne de cache, avant de déterminer s'il y a succès ou défaut en lisant le tag de la ligne de cache. Les caches sériels disposent de deux SRAM : une pour les tags des lignes de cache et une pour les données. Ils accèdent à la SRAM pour les tags, avant d’accéder à la SRAM des données en cas de succès de cache. Vu que l'accès se fait en deux étapes, une vérification des tags suivie de la lecture/écriture des données, il est facile à pipeliner. Pipeliner le cache permet de régler le problème des accès au cache L1, et elle est tout le temps utilisé sur les processeurs modernes. Mais que faire en cas de défaut de cache ? ==Les caches non bloquants== Un '''cache bloquant''' est un cache auquel le processeur ne peut pas accéder pendant un défaut de cache. Il faut attendre que la lecture ou écriture en RAM soit terminée avant de pouvoir utiliser de nouveau le cache. Un '''cache non bloquant''' n'a pas ce problème : on peut l'utiliser pendant un défaut de cache. Les caches non bloquants permettent de démarrer une nouvelle lecture ou écriture alors qu'une autre est en cours, ce qui permet d'exécuter plusieurs lectures ou écritures en même temps. ===Les ''Miss Handling Status Registers''=== Lors d'un défaut de cache, la mémoire RAM est consultée pendant le défaut de cache, mais le cache est inutilisé. Un défaut de cache n'utilise pas le cache, ce n'est qu'un accès en mémoire RAM, sur le bus mémoire (ou un accès aux niveaux de cache inférieurs, peu importe). Le cache en lui-même est laissé libre, rien n’empêche d'y accéder, il est en réalité intrinsèquement non-bloquant. Les caches, bloquants comme non-bloquants, sont en réalité composés d'une mémoire cache proprement dite, entourée de circuits qui servent d'interface entre le processeur et le cache lui-même. Et parmi les circuits tout autour, certains gèrent l'accès au cache lors d'un défaut de cache. Ils sont regroupés sous le terme de '''''Miss Handling Architecture''''' (MHA). La différence entre un cache bloquant et un cache non-bloquant est en réalité liée à l'implémentation de la MHA. Les caches bloquants coupent volontairement l'accès au cache lors d'un défaut, car les défauts sont plus simples à gérer ainsi. Le défaut de cache rapatrie une donnée depuis la RAM, qui sera écrite dans le cache. Et il ne faut pas qu'une tentative d'accès à cette donnée ait lieu avant qu'elle ne soit chargée. Mais ce blocage est général et touche tout le cache, alors que seule une ligne de cache est concernée par le défaut de cache. L'idée derrière un cache non-bloquant est que seule la ligne de cache est bloquée, mais les autres sont accessibles. L'idée est alors de mémoriser les lignes de cache concernées par le défaut de cache, afin d'en bloquer l'accès. À chaque accès, on vérifie si la ligne de cache est déjà réservée par un défaut de cache. La lecture/écriture est alors bloquée si c'est le cas, mais elle accepte les accès sinon. Pour cela, la ''Miss Handling Architecture'' contient des registres qui mémorisent des informations sur les défauts de cache en cours. Ils portent le nom de '''''miss status handling registers''''', que j’appellerais dorénavant MSHR, qui sont aussi appelés des '''''miss buffer'''''. Le contenu des MSHR varie beaucoup suivant le processeur, mais ces derniers stockent au minimum les informations suivantes : * Le numéro de la ligne de cache dans laquelle les données sont chargées. * Un bit de validité qui indique si le MSHR est vide ou pas, qui est mis à 0 quand le défaut de cache est résolu. * Un ou plusieurs champs de lecture/écriture, qui contiennent des informations sur la lecture/écriture. ** Pour une lecture, elle contient des informations sur la destination de la donnée, à savoir qui prévenir quand le défaut de cache est terminé. C'est parfois un nom/numéro de registre (celui dans lequel charger la donnée), mais c'est souvent le numéro de l'entrée dans le ''load/store queue''. ** Pour les écritures, elle contient la donnée à écrire, ou éventuellement un numéro de ''load/store queue'' où se trouve la donnée à écrire. Il faut noter que le bus mémoire ne peut gérer qu'un seul défaut de cache à la fois. Aussi, il est intéressant de regarder ce qui se passe lorsqu'un second défaut de cache survient, pendant qu'un premier est en cours. Dans ce cas, il y a deux réponses qui correspondent à deux types de caches non-bloquants, qui portent les noms barbares de caches de type succès après défaut et défaut après défaut. Sur le premier type, il ne peut pas y avoir plusieurs défauts de cache simultanés. Dès qu'un second défaut de cache survient, le cache stoppe son activité et on ne peut plus démarrer de nouvelle lecture/écriture, tant que le premier défaut de cache n'est pas résolu. Le second type est plus souple et autorise la survenue de plusieurs défauts de cache simultanés. Du moins, jusqu'à une certaine limite, car le cache ne peut supporter qu'un nombre limité d'accès mémoires simultanés (pipelinés). ===Les accès simultanés à une même ligne de cache=== Il arrive que le processeur fasse plusieurs accès mémoire simultanés à la même ligne de cache. Si la ligne de cache en question n'a pas encore été chargée dans le cache, alors on a plusieurs défauts de cache consécutifs pour la même ligne de cache, et le cache non-bloquant doit gérer la situation. Pour la suite, il va falloir faire une petite distinction entre les défauts primaires et secondaires. Imaginons qu'un défaut de cache ait lieu et demande à charger une donnée dans la ligne de cache numéro N. Il s'agit du premier défaut impliquant cette ligne de cache précise, ce qui lui vaut le nom de '''défaut de cache primaire'''. Mais par la suite, d'autres accès mémoire à la même ligne de cache ont lieu, alors que la ligne de cache n'est pas encore disponible. Dans ce cas, il s'agit de '''défauts de cache secondaires'''. Pour l'unité d'accès mémoire, les défauts de cache primaire et secondaire sont différents (ils prennent tous une entrée dans la ''load/store queue''). Mais pour le cache, ils ne correspondent qu'à un seul accès au cache : celui qui demande de charger la ligne de cache demandée. Les défauts de cache primaire et secondaire à la même ligne de cache se voient attribuer un MSHR unique. La gestion des défauts de cache secondaires dépend du cache non-bloquant. La solution la plus simple ne permet pas les défauts de cache secondaires. Le cache ne permet pas deux défauts de cache simultanés pour la même ligne de cache. Les autres solutions le permettent, en fusionnant des accès simultanés à la même ligne de cache en un seul au niveau des MSHR. Dans tous les cas, détecter les défauts de cache secondaires sont un problème qu'il faut détecter. La ''Miss Handling Architecture'' doit détecter les défauts de cache secondaire. Pour cela, elle procède comme suit. Lors de chaque défaut de cache, la MHA récupère le numéro de la ligne de cache associé. Il vérifie alors chaque MSHR pour vérifier s'il contient le numéro en question. S'il n'y a aucune correspondance dans les MSHR, alors c'est signe que le défaut de cache est un défaut primaire. Mais s'il y en a une, alors c'est un défaut secondaire. Évidemment, cela signifie que lors d'un défaut de cache, le numéro de ligne de cache est envoyé à tous les MSHR, pour comparaison. Les MSHR sont donc regroupés dans une mémoire associative, une sorte de mini-cache, faciliter l'implémentation. Lors d'un défaut de cache primaire, l'accès à un cache non-bloquant se fait comme suit : le processeur envoie une adresse au cache, accède à celui-ci, et détecte la survenue d'un défaut de cache. Il en profite alors pour attribuer une ligne de cache dans laquelle sera chargée ce défaut. L'attribution est très simple dans le cas des caches ''direct mapped'', ou associatifs par voie, pour lesquels l'attribution se fait assez simplement. Il mémorise alors cette information dans les MSHR, après avoir vérifié que le défaut de cache n'était pas un défaut secondaire. Lors des accès ultérieurs à une adresse proche, censée être dans la même ligne de cache, le processeur va encore une fois rencontrer un défaut de cache. Il va alors déterminer le numéro de la ligne de cache associée à l'adresse, et comparer ce numéro avec les MSHRs. Si un MSHR contient ce numéro, c'est signe que le défaut de cache est un défaut secondaire. La MHA réagit alors différemment selon le processeur considéré. Une première solution n'autorise pas les défauts de cache secondaires. Si l'un d'entre eux survient, le processeur est gelé par un ''pipeline stall'', une bulle de pipeline. Une autre solution fusionne les défauts de cache secondaires avec le défaut de cache primaire : tout cela ne correspond qu'à un seul défaut de cache pour lui. ===Les MSHR simples=== Un cache non-bloquant à '''MSHR simple''' contient juste plusieurs MSHR qui mémorisent juste un numéro de ligne de cache, un bit de validité, le champ de lecture/écriture, et l'adresse exacte de lecture/écriture. Avec cette organisation, il est possible d'avoir plusieurs défauts de cache séparés, mais à la condition que chaque défaut accède à une ligne de cache différente. Deux accès simultanés à une même ligne de cache ne sont pas possibles, les défauts de cache secondaires ne sont pas autorisés. Ainsi, chaque défaut de cache se voit attribuer son propre MSHR, chacun contient un numéro de ligne de cache différent. Pour comprendre pourquoi c'est impossible de gérer les défauts secondaires, il faut regarder le champ de lecture/écriture. Si on veut effectuer plusieurs écritures consécutives à la même adresse, le MSHR n'aura pas de quoi mémoriser les deux données à écrire. Il pourra mémoriser la première donnée à écrire, pas la seconde. Même chose lors d'une lecture : le champ lecture/écriture peut mémorisr la destination de la première lecture, pas de la seconde. L'avantage est que la MHA n'a besoin que des MSHR et de quelques circuits annexes. Les autres solutions rajoutent des circuits annexes pour gérer les défauts de cache secondaires, qui utilisent beaucoup de circuits. Le cout en circuit est donc élevé, mais le gain en performance est là. Passons maintenant aux caches non-bloquants qui autorisent les défauts de cache secondaire. La solution la plus simple consiste à utiliser ===Les MSHR adressés implicitement=== Avec les '''MSHR adressés implicitement''', il est possible de fusionner plusieurs accès mémoire à une même ligne de cache, mais sous une condition très importante : ces accès lisent/écrivent des mots mémoire différents. Par exemple, imaginons qu'une ligne de cache contienne 8 mots mémoire de 64 bits. Si un premier accès mémoire lit le mot mémoire numéro 7 (dernier mot mémoire de la ligne), et le second accès le mot mémoire numéro 3, alors la fusion est possible. Mais si deux accès mémoire veulent lire/écrire le mot mémoire numéro 7, alors la fusion n'est pas possible et le processeur se bloque, un ''pipeline stall'' survient. Un MSHR adressé implicitement est un MSHR simple, qui contient naturellement un numéro de ligne de cache (tag) et un bit de validité, sauf que le champ de lecture/écriture est dupliqué. Les différents champs lecture/écriture d'un MSHR sont regroupés dans une mémoire RAM/cache qui contient autant d'entrées qu'il y a de mot mémoire dans une ligne de cache. Chaque entrée de la table est associée à un mot mémoire de la ligne de cache et stocke des informations sur celui-ci. Une entrée mémorise, au minimum : * Un champ de lecture/écriture qui contient soit la destination de la lecture, soit la donnée à écrire. * Un bit R/W permettant d'interpréter correctement le champ de lecture/écriture. * Un bit de validité pour chaque entrée de la table, qui dit si un défaut de cache antérieur accède déjà à ce mot mémoire. La fusion de deux défauts de cache est ainsi assez simple. Un défaut de cache primaire/secondaire configure l'entrée associée au mot mémoire lu/écrit. Si un défaut secondaire ultérieur lit/écrit un mot mémoire différent (dont le bit de validité de l'entrée dans la table est à zéro), pas de conflit : il configure une autre entrée, vide. Mais s'il lit/écrit un mot mémoire pour lequel l'entrée de la table est occupée, il y a conflit, le processeur est bloqué par un ''pipeline stall'', une bulle de pipeline. Avec cette organisation, le nombre de MSHR indique combien de lignes de cache peuvent être lues en même temps depuis la mémoire. Quant au nombre d'entrées par MSHR, il détermine combien d'accès mémoires qui ne se recouvrent pas peuvent avoir lieu en même temps. ===Les MSHR adressés explicitement=== Les '''MSHR adressés explicitement''' n'ont pas les contraintes des MSHR adressés implicitement. Avec eux, il est possible d'avoir plusieurs défauts de cache pointant vers la même ligne de cache, mais aussi vers le même mot mémoire. De tels défauts de cache apparaissent sur les processeurs à exécution dans le désordre, mais sont très rares, voire inexistants, sur les processeurs ''in-order''. L'idée est encore que chaque MSHR est associée à une table mémoire qui mémorise des entrées. Cette table n'est autre qu'une mémoire FIFO. Sauf que cette fois-ci, une entrée n'est pas associée à un mot mémoire. Une entrée est associée à un défaut de cache. Une entrée mémorise là encore la destination de la lecture ou la donnée de l'écriture suivi du bit R/W, ainsi que d'un bit de validité par entrée, mais aussi : la position du mot mémoire lu dans la ligne de cache. C'est cette dernière information qui n'était pas présente dans les MSHR adressés implicitement. De plus, le nombre d'entrée par MSHR n'est pas égal au nombre de mots mémoires dans une ligne de cache, mais peut être arbitrairement grand. L'avantage d'un tel cache est qu'il est capable de traité les défauts secondaires concernant un même mot mémoire, et ce, car les accès à la ligne de cache concernée se produisent dans l'ordre de génération des défauts (la table mémoire est une FIFO). ===Les MSHRs inversés=== Généralement, plus on veut supporter de défauts de cache, plus le nombre de MSHR et d'entrées augmente. Mais au-delà d'un certain nombre d'entrées et de MSHR, les MSHR adressés implicitement et explicitement ont tendance à bouffer un peu trop de circuits. Utiliser une organisation un peu moins gourmande en circuits est donc une nécessité. Cette organisation plus économe se base sur des MSHR inversés. Les MSHR inversés ne contiennent qu'une seule entrée, en quelque sorte : au lieu d’utiliser n MSHR de m entrées chacun, on va utiliser n × m MSHR inversés. La différence, c'est que plusieurs MSHR peuvent contenir un tag identique, contrairement aux MSHR adressés implicitement et explicitement. Lorsqu'un défaut de cache a lieu, chaque MSHR est vérifié. Si jamais aucun MSHR ne contient de tag identique à celui utilisé par le défaut, un MSHR vide est choisi pour stocker ce défaut, et une requête de lecture en mémoire est lancée. Dans le cas contraire, un MSHR est réservé au défaut de cache, mais la requête n'est pas lancée. Quand la donnée est disponible, les MSHR correspondant à la ligne qui vient d'être chargée vont être utilisés un par un pour résoudre les défauts de cache en attente. ===Les MSHR intégrés au cache=== Certains chercheurs ont remarqué que pendant qu'une ligne de cache est en train de subir un défaut de cache, celle-ci reste inutilisée, et son contenu est destiné à être perdu une fois le défaut de cache résolu. Ils se sont dit que, plutôt que d'utiliser des MSHR séparés, il vaudrait mieux utiliser la ligne de cache pour stocker les informations sur les défauts de cache en attente dans cette ligne de cache. Pour éviter tout problème, il faut rajouter un bit dans les bits de contrôle de la ligne de cache, qui sert à indiquer que la ligne de cache est occupée : un défaut de cache a eu lieu dans cette ligne, et elle stocke donc des informations pour résoudre les défauts de cache. ==L'entrelacement mémoire== Nous venons de voir comment une mémoire cache peut gérer plusieurs accès mémoire simultanés. Il se trouve que ce genre d'optimisation dépasse largement le cadre du cache. Les mémoires RAM ne sont pas en reste, elles aussi ! Il est possible de pipeliner l'accès à une mémoire RAM, en utilisant des techniques dites d''''entrelacement'''. Précisons cependant que cette optimisation n'est pas utilisée sur les mémoires SDRAM ou DDR modernes, du moins pas sans modifications majeures. Mais divers ordinateurs assez anciens, dont des superordinateurs, ont utilisé cette technique. La technique demande d'utiliser plusieurs mémoires séparées. Sur les anciens ordinateurs, les mémoires en question sont des chips mémoire, à savoir des circuits intégrés de DRAM. Pour faire plus général, nous allons utiliser le terme de '''banques''', ou encore de bancs mémoire. La différence est qu'il existe des mémoires multi-banque, qui regroupent plusieurs banques indépendantes dans un seul boitier, dans un seul chip mémoire. En clair, de telles mémoires regroupent plusieurs sous-mémoires dans un seul circuit intégré. Il est aussi possible d'utiliser plusieurs chips mémoire séparés, chacun ayant une banque. Reste à combiner ces banques pour former un pseudo-pipeline. ===Utiliser plusieurs banques sans entrelacement=== Sans optimisation particulière, les adresses sont réparties dans les banques comme indiqué ci-dessous. Il s'agit de ce qui s'appelle un arrangement horizontal, et nous avions vu celui-ci dans le chapiutre sur les mémoires SDRAM. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Avec cet arrangement, les bits de poids fort de l'adresse sont utilisées pour sélectionner la banque adéquate, et le reste de l'adresse est envoyé sur le bus d'adresse. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Adresse de banque !! Adresse dans la banque |- | Quelques bits de poids fort || Reste de l'adresse |} L'entrelacement change cette répartition, afin d'accélérer les accès mémoire. L'entrelacement classique vise à améliorer les accès à des adresses mémoires consécutives, mais des formes plus évoluées d'entrelacement visent à optimiser des accès mémoire arbitraires. Voyons-les dans l'ordre, du plus simple au plus complexe. ===L'entrelacement classique=== Avec l''''entrelacement classique''', des adresses consécutives sont réparties dans des banques différentes. [[File:Répartition des adresses dans une mémoire interleaved.png|centre|vignette|upright=1.5|Répartition des adresses dans une mémoire interleaved.]] L'entrelacement simple permet d'accélérer les accès mémoire qui se font à des adresses consécutives. Avec l'entrelacement, chaque accès mémoire tombe dans une banque mémoire différente, ce qui fait qu'on peut démarrer un nouvel accès mémoire à chaque cycle d'horloge. Pendant qu'une banque est occupée par un accès mémoire, on démarre le suivant dans une autre banque, et ainsi de suite. Le tout se termine soit quand on a épuisé toutes les banques libres, soit quand l'accès en rafales se termine. Pas besoin d'attendre que la mémoire ait fini sa lecture/écriture avant de démarrer la lecture/écriture suivante. [[File:Pipemining mémoire.png|centre|vignette|upright=2.5|Pipelining mémoire]] L'animation suivante illustre bien le processus, avec quatres banques. [[File:Interleaving.gif|centre|vignette|upright=2.5|Pipelining mémoire]] L'entrelacement simple utilise les bits de poids faible pour sélectionner la banque, et les bits de poids fort pour la case mémoire. [[File:Adresse mémoire d'une mémoire entrelacée.png|centre|vignette|upright=2|Adresse mémoire d'une mémoire entrelacée]] Précisons que le temps d'accès mémoire ne change pas beaucoup avec l'entrelacement. Par contre, on peut faire plus d'accès mémoire simultanés. On peut démarrer un accès mémoire par cycle d'horloge, mais l'accès en lui-même prend plusieurs cycles. Les mémoires à entrelacement ont donc un débit supérieur aux mémoires qui ne l'utilisent pas. ===L'entrelacement par décalage=== L'entrelacement simple est très utile pour les accès en rafle ou équivalents. Par contre, il existe de rares situations où il n'est pas optimal. Une de ces situations est celle des accès en enjambée (en ''stride''), où l'on accède en série à des adresses sont séparées par N mots mémoires. De tels accès surviennent quand un logiciel accède à des structures de données spécifiques, à savoir des tableaux de structures, des matrices, ou d'autres structures de données dans le genre. Un cas classique est celui du parcours d'une matrice colonne par colonne. Les matrices de nombres sont mémorisées ligne par ligne, ce qui fait que parcourir une colonne demande de passer d'une ligne à la suivante et de faire des grands sauts en mémoire RAM, mais tous espacés par la même distance. Avec l'entrelacement simple, les accès en enjambée sont moins performants que les accès à des adresses consécutives, mais cela ne fonctionne pas trop mal. Le pire cas est celui où l'on a N banques et où les données sont justement placées toutes les N adresses. Des accès consécutifs vont tous tomber dans la même banque, on ne peut plus accéder à des banques différentes en parallèle. Mais en dehors de ce cas, on voit une amélioration par rapport à la situation sans entrelacement. Pour obtenir des performances maximales pour les accès en enjambées, il faut répartir les mots mémoires dans la mémoire autrement. L'organisation idéale est la suivante. : Dans les explications qui vont suivre, la variable N représente le nombre de banques, qui sont numérotées de 0 à N-1. On commence par organiser les N premières adresses comme une mémoire entrelacée simple : l'adresse 0 correspond à la banque 0, l'adresse 1 à la banque 1, etc. Pour le bloc suivant, on décale tout d'une adresse, à savoir qu'on commence à remplir les banques à partir de la seconde, non de la première. Une fois la fin du bloc atteinte, on finit de remplir le bloc en repartant du début du bloc. Le troisième bloc d'adresse subit le même traitement, sauf qu'on commence à remplir à partir de la seconde banque. Et on poursuit l’assignation des adresses en décalant d'un cran en plus à chaque bloc. Ainsi, chaque bloc verra ses adresses décalées d'un cran en plus comparé au bloc précédent. Si jamais le décalage dépasse la fin d'un bloc, alors on reprend au début. [[File:Mémoire interleaved par décalage.png|centre|vignette|upright=1.5|Mémoire entrelacée par décalage.]] En faisant cela, on remarque que les banques situées à N adresses d'intervalle sont différentes. Dans l'exemple du dessus, nous avons ajouté un décalage de 1 à chaque nouveau bloc à remplir. Mais on aurait tout aussi bien pu prendre un décalage de 2, 3, etc. Dans tous les cas, on obtient un entrelacement par décalage. Ce décalage est appelé le pas d'entrelacement, noté P. Le calcul de l'adresse à envoyer à la banque, ainsi que la banque à sélectionner se fait en utilisant les formules suivantes : * adresse à envoyer à la banque = adresse totale / N ; * numéro de la banque = (adresse + décalage) modulo N, avec décalage = (adresse totale * P) mod N. Avec cet entrelacement par décalage, on peut prouver que la bande passante maximale est atteinte si le nombre de banques est un nombre premier. Seulement, utiliser un nombre de banques premier peut créer des trous dans la mémoire, des mots mémoires inadressables. Pour éviter cela, il faut faire en sorte que N et la taille d'une banque soient premiers entre eux : ils ne doivent pas avoir de diviseur commun. Dans ce cas, les formules se simplifient : * adresse à envoyer à la banque = adresse totale / taille de la banque ; * numéro de la banque = adresse modulo N. ===L'entrelacement pseudo-aléatoire=== Une dernière méthode de répartition consiste à répartir les adresses dans les banques de manière "pseudo-aléatoire". L'idée exacte est de faire passer l'adresse dans une '''fonction de hachage''', plus ou moins complexe. Pour rappel, une fonction de hachage prend une entrée de grande taille et fournit en sortie un résultat de petite taille. Idéalement, elles doivent donner des résultats aussi différents que possible pour des entrées similaires, histoire de simuler une sorte de pseudo-aléatoire. Ici, une adresse est transformée en un numéro de banque. Une fonction de hachage simple ne fait que permuter des bits de l'adresse pour obtenir son résultat. Elle ne fait qu'échanger des bits de place avant de couper l'adresse en deux morceaux : un pour la sélection de la banque, et un autre pour la sélection de l'adresse dans la banque. Cette permutation est fixe, et ne change pas suivant l'adresse. Des fonctions de hachage plus complexes font des XOR bit à bit entre certains bits de l'adresse. Il existe aussi d'autres techniques qui donnent le numéro de banque à partir d'un polynôme modulo N, appliqué sur l'adresse. ==Les contrôleurs mémoires SDRAM/DDR optimisés== Après avoir vu le cas des mémoires RAM, nous allons nous concentrer sur le cas particulier des mémoires SDRAM. Les mémoires SDRAM et DDR modernes sont capables de gérer plusieurs accès simultanés à la mémoire RAM. Et les contrôleurs mémoire intègrent des optimisations spécifiques afin d'en profiter au maximum. ===La mise en attente des accès mémoire=== Comme pour les mémoires caches, il existe des contrôleurs mémoire bloquants et non-bloquants. Les premiers n'acceptent qu'un seul accès mémoire à la fois, les seconds acceptent plusieurs accès mémoire simultanés. Pour simplifier, nous allons dire que le contrôleur mémoire n’exécute qu'un seul accès mémoire à la fois, et qu'ils met en attente les accès en trop. C'est une simplification assez irréaliste, vu que les mémoires modernes disposent de plusieurs banques accessibles en parallèle. Mais nous introduirons ce détail un peu plus tard. Avec un contrôleur mémoire de ce type, les accès mémoire sont mis en attente dans une mémoire FIFO, histoire de les exécuter dans leur ordre d'arrivée. Les accès mémoire quittent alors la mémoire dans leur ordre d'arrivée, les lectures sont renvoyées dans l'ordre demandé. En plus d'une mémoire FIFO pour les commandes mémoire, on trouve une mémoire FIFO pour les données à écrire. Elle permet d'accumuler plusieurs écritures, tout en permettant que la donnée adéquate soit envoyée à la RAM au bon moment. Il y a aussi une FIFO pour les données lues, qui sert au cas où le cache ne soit pas disponible quand on lui envoie la donnée lue. [[File:Module d'interface avec la mémoire.png|centre|vignette|upright=2.5|Contrôleur mémoire avec mise en attente des requêtes processeur et autres optimisations.]] Une optimisation regroupe plusieurs accès à des données consécutives en un seul accès en rafale. Le contrôleur mémoire analyse les requêtes mises en attente et détecte si certaines se font à des adresses consécutives. Si c'est le cas, il fusionne ces requêtes en une lecture/écriture en rafale. Une telle optimisation est appelée la '''combinaison de lecture''' pour les lectures, et la '''combinaison d'écriture''' pour les écritures. ===Le pipeline des mémoires SDRAM=== Les SDRAM ont une forme de pseudo-pipeline très limité. Elles sont parfois capables de démarrer une commande avant que la précédente soit terminée. Même si elle parait très limitée, cette possibilité est exploitée au mieux par la présence de plusieurs banques dans la mémoire SDRAM. Les chips mémoires de SDRAM sont composés de plusieurs banques, chacune étant une sorte de mini-mémoire miniature. Chaque banque a son propre décodeur, son propre tampon de ligne, ses propres multiplexeurs de colonne, sa logique de rafraichissement mémoire, etc. C'est comme si une SDRAM regroupait plusieurs mémoires séparées dans un même circuit intégré. Et cela permet de faire plusieurs accès mémoire en même temps, dans des banques différentes. Il est possible de lancer un accès mémoire dans deux banques en même temps, par exemple. Ou encore, on peut lancer un nouvel accès mémoire dans une banque si elle est inoccupée. La possibilité améliore grandement la performance de la SDRAM. Mais nous en reparlerons dans un chapitre ultérieur. {|class="wikitable" |+ Pipelining basique sur les SDRAM |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 |- ! Banque Numéro 1 | || || || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || || || |- ! Banque Numéro 2 | || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || |- ! Banque Numéro 3 | || || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || |} L'implémentation n'a rien de vraiment compliqué. Il suffit d'utiliser une mémoire FIFO par banque. Ainsi, les accès dans une même banque se feront en série, dans l'ordre d'arrivée, mais des accès à des banques différentes se feront dans le désordre. Cela ne pose pas de problème car les accès se font à des adresses différentes, ce qui fait qu'il n'y a pas de conflits majeurs. [[File:Gestion parallèle des banques.png|centre|vignette|upright=2.5|Gestion parallèle des banques.]] ===Le ré-ordonnancement des commandes mémoires=== Un contrôleur mémoire moderne est capable de changer l'ordre des requêtes mémoires, pour gagner en performances. En clair : il peut faire l'équivalent mémoire de l'exécution dans le désordre des processeurs haute performance. Une différence majeure est que le contrôleur mémoire ne remet pas les accès mémoire dans l'ordre avant de les envoyer au processeur. C'est le processeur qui remet en ordre les accès mémoire, dans sa ''load queue''. Pour cela, les requêtes mémoire sont "numérotées", à savoir qu'on leur attribue un identifiant binaire. Le contrôleur mémoire exécute les lectures/écritures dans le désordre, mais garde la trace de leur identifiant. Il sait dans quel ordre il exécute les accès mémoire et connait leur latence. Ce qui fait qu'il sait que la donnée lue à tel instant est associée à la requête mémoire numéro X. Pour les lectures, il renvoie la donnée lue avec l'identifiant. Le processeur sait alors à quelle lecture la donnée lue correspond et il se débrouille en interne pour gérer la situation. En clair : le processeur voit les accès mémoire dans le désordre, c'est lui qui les remet en ordre. Maintenant que ces précisions sont faites, posons cette question : quel est l'intérêt de faire les accès mémoire dans le désordre ? Accéder à des banques en parallèle est une réponse possible, mais le ré-ordonnancement est surtout utile si tous les accès mémoire atterrissent dans la même banque. Pour comprendre pourquoi, faisons un rappel rapide sur le tampon de ligne. Les mémoires SDRAM sont des mémoires à tampon de ligne. Elles font un accès mémoire en deux étapes. La première étape recopie une ligne de N * 64 bits dans un tampon interne à la SDRAM. La seconde étape sélectionne une colonne, à savoir une donnée de 64 bits, qui est soit envoyée sur le bus de données pour une lecture, soit modifiée par une écriture. [[File:Mémoire à tampon de ligne.png|centre|vignette|upright=2|Mémoire à tampon de ligne]] Les deux étapes correspondent à deux commandes séparées : une commande ACT qui précise l'adresse de la ligne, et une commande READ ou WRITE qui précise l'adresse de la colonne. [[File:Signaux RAS et CAS.png|centre|vignette|upright=2|Signaux RAS et CAS.]] Les SDRAM permettent de se passer de la première étape si des accès consécutifs se font dans la même ligne. Une fois activée, la ligne reste ouverte et on peut accéder plusieurs fois de suite dedans. On a alors juste à préciser l'adresse de colonne dedans. [[File:Sélection d'une ligne sur une mémoire FPM ou EDO.png|centre|vignette|upright=2|Sélection d'une ligne sur une mémoire FPM ou EDO.]] L'exécution dans le désordre des lectures/écritures SDRAM vise à faire en sorte que des accès consécutifs se fassent dans une même ligne. Elle marche si plusieurs accès à une même ligne ne sont pas consécutifs, et qu'il y a des accès entre les deux. Après ré-ordonnancement, ces accès mémoire deviennent consécutifs, ils sont exécutés l'un à la suite de l'autre. Pour rendre le tout plus concret, voici un exemple. Imaginez que l'on sait les 3 accès mémoire suivants : * Une lecture ligne A ; * Une écriture ligne B ; * Une lecture ligne A. Sans réordonnancent, on doit émettre deux commandes PRECHARGE : une quand on passe de la ligne A à la ligne B, et une autre pour le passage inverse. pour éviter cela, le contrôleur mémoire peut retarder l'écriture, ou au contraire avancer la seconde lecture. le résultat est alors le suivant : * Lecture ligne A ; * Lecture ligne A ; * Une écriture ligne B. On a donc deux accès consécutifs à la même ligne, suivi par une écriture dans une autre ligne. La technique marche parce que l'on a un mix adéquat de lectures et d'écritures. Mais il existe des possibilités de réorganisation autres qui ne sont pas valides. Il faut par exemple prendre garde à éviter d'intervertir lectures et écritures à une même adresse, sous peine de problèmes. Encore une fois, il faut faire attention aux dépendances mémoires. Le controleur mémoire teste les dépendances mémoires avant d'envoyer des commandes à la mémoire DDR/SDRAM. ===L'entrelacement sur les mémoires SDRAM=== L''''entrelacement''' fonctionne sur les mémoires SDRAM, mais il doit être fortement modifié. Concrètement, tout ce qui a étré dit plus est inapplicable pour les SDRAM, du fait de la présence du tampon de ligne. Des adresses consécutives atterrissent déjà dans la même ligne, ce qui fait qu'on n'a pas besoin d'entrelacement au niveau d'une ligne. Et ne parlons pas de ce la présence de rangées et de canaux mémoire ! Cependant, il peut y avoir un entrelacement lié à la présence des banques SDRAM. Pour gérer l'entrelacement, le contrôleur de SDRAM prend en entrée l'adresse envoyée par le processeur, et la découpe en plusieurs champs : un pour sélectionner la banque adéquate, un autre pour la ligne, un autre pour la colonne, un autre pour la rangée. Une solution simple désactive l'entrelacement. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de banque || Adresse de ligne || Adresse de colonne |} Si cette solution fonctionne bien si les accès mémoire sont assez aléatoires, en pratique, mieux vaut utiliser l'entrelacement. Une première méthode d'entrelacement est l''''entrelacement de ligne'''. Elle répartit deux lignes consécutives dans deux banques différentes. Avec N banques, la première ligne ira dans la première banque, la seconde ligne dans la seconde, et ainsi de suite jusqu'à la énième banque qui va dans la banque numéro N. Et la ligne N+1 repart dans la première banque. L'avantage est que passer d'une ligne à la suivante est assez rapide. Au lieu de devoir fermer la première ligne et ouvrir la suivante on ouvrira directement la ligne suivante dans une banque différente, ce qui sera plus rapide. La commande PRECHARGE pourra être envoyée à la seconde banque en avance. Pour cela, l'adresse est découpée comme suit. {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne (précise à l'octet) |} D'autres méthodes d'entrelacement sont possibles, mais elles sont plus complexes. Chaque contrôleur mémoire fait un peu à sa sauce. Ils permettent en général de choisir entre plusieurs entrelacements. Au minimum, ils permettent de désactiver l'entrelacement, d'activer un entrelacement de ligne, et d'autres entrelacements plus complexes, dont certains sont propriétaires et tenus secrets par le fabricant. L'usage du ''dual channel'' complique encore l'entrelacement. Là encore, il y a deux grandes solutions : avec et sans entrelacement des canaux mémoire. Rappelons le principe : deux barrettes de RAM sont accédées en parallèle. Et pour cela, il faut utiliser l'entrelacement. Typiquement, chaque barrette mémoire fournit 64 bits, ce qui fait que l'on peut accéder à 128 bits d'un seul coup, par exemple avec un accès en rafale. Une solution pour cela est de ne pas utiliser l'entrelacement. Avec deux barrettes, la première barrette regroupera la moitié haute de la RAM, la seconde barrette utilisera la moitié basse. Les possibilité d’accéder à deux données séparées de 64 bits en même temps est cependant très rare. Aussi, on préfère utiliser l'entrelacement. Pour cela, deux blocs de 64 bits sont placés dans des canaux mémoire séparés. Idem avec du triple ou quadruple canal, mais c'est alors trois ou quatre blocs de 64 bits qui sont dispersés dans des canaux consécutifs. {|class="wikitable" |+ Adresse mémoire |- ! colspan="6" | Sans entrelacement |- | Numéro de canal/barrette || Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne || Position de l'octet dans un bloc de 64 bits |- | colspan="6" | |- ! colspan="6" | Avec entrelacement |- | Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne || Numéro de canal/barrette || Position de l'octet dans un bloc de 64 bits |} Les contrôleurs de SDRAM précédents sont assez basiques et ne représentent pas les contrôleurs les plus évolués. Ils étaient utilisés sur les anciens PC, à une époque où ils étaient encore sur la carte mère du processeur. Mais de nos jours, le contrôleur mémoire est intégré au processeur. Et il incorpore de nombreuses optimisations afin de gagner en performances. Malheureusement, ces optimisations sont elles-mêmes dépendantes des optimisations intégrées dans le processeur, ce qui fait qu'on ne peut pas en parler ici. Elles demandent que le contrôleur mémoire reçoive plusieurs accès mémoire simultanés, ce qui n'a aucun sens à ce stade du cours. Aussi, nous allons les passer sous silence pour le moment. Nous les verrons dans le chapitre sur le parallélisme mémoire. L'entrelacement est géré juste avant le séquenceur mémoire. Le '''circuit d'entrelacement''' intervertit certains bits de l'adresse lors des accès mémoires. On trouve aussi des mémoires FIFOs pour mettre en attente les requêtes mémoire provenant du processeur. Elles permettent de recevoir plusieurs accès mémoire consécutifs. Si vous vous demandez comment plusieurs accès mémoire consécutifs peuvent avoir lieu, sachez que c'est une bonne question. Pour le moment, nous allons dire que ces accès proviennent du processeur ou de la mémoire cache, sans expliquer comment. Le contrôleur mémoire reçoit une série d'accès mémoire assez rapprochés, qu'il doit exécuter au mieux. [[File:Controleur mémoire d'une SDRAM avec entrelacement.png|centre|vignette|upright=2.5|Contrôleur mémoire d'une SDRAM avec entrelacement.]] <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=La désambiguïsation mémoire | prevText=La désambiguïsation mémoire | next=Les processeurs superscalaires | nextText=Les processeurs superscalaires }} </noinclude> l1e5k4g8z4ht6y3loikgg8hkghk8h1f 764651 764650 2026-04-23T14:12:11Z Mewtow 31375 /* L'entrelacement classique */ 764651 wikitext text/x-wiki Dans ce chapitre, nous allons voir les techniques qui permettent de gérer plusieurs accès mémoire simultanés directement au niveau du cache ou de la mémoire RAM. Par plusieurs accès mémoire simultanés, vous pensez sans doute à l'usage de cache multiports, voire de mémoires RAM multiport. C'est en effet une solution, mais ce n'est clairement pas la seule. Par exemple, il est possible de pipeliner l'accès au cache, voire à la mémoire RAM. Il existe de nombreuses techniques de '''parallélisme mémoire''', et nous allons les voir dans ce chapitre. Pour donner un exemple, il est possible de pipeliner les accès mémoire. Il est possible de pipeliner l'accès au cache et/ou l'accès à la mémoire, et nous verrons comment faire dans ce chapitre. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, qu'ils aient ou non un pipeline dynamique. Par contre, pipeliner la mémoire est une technique ancienne, aujourd'hui peu utilisée. Elle était utilisée sur des très vieux ordinateurs, qui avaient un pipeline mais pas de mémoire cache. Mais de nos jours, les mémoires SDRAM et DDR ne sont pas adaptées pour ça, il est très difficile de les pipeliner correctement. Le parallélisme mémoire est utile aussi bien sur des processeurs à émission dans l'ordre que dans le désordre. Par exemple, un processeur ''in-order'' avec des lectures non-bloquantes peut e, profiter. Il peut techniquement lancer une seconde lecture, après avoir rencontré une première lecture non-bloquante. En général, le nombre de lectures consécutives est cependant limité à moins d'une dizaine. À l'opposé, les processeurs sans lectures non-bloquantes profitent pas du parallélisme mémoire. À la rigueur, ils peuvent en profiter s'ils intègrent des techniques de préchargement. Pendant que le processeur exécute une lecture/écriture, il peut précharger une autre donnée en parallèle. Inutile de dire que sans pipeline processeur, le parallélisme mémoire ne sert pas à grand-chose. ==Le ''line fill buffer'' et les techniques associées== Lors d'un défaut de cache, le processeur doit attendre que toute la ligne de cache soit chargée avant d'être utilisable. Or, la taille d'une ligne de cache est supérieure à la largeur du bus mémoire, ce qui fait qu'une ligne de cache est chargée en plusieurs fois, morceaux par morceaux, mot mémoire par mot mémoire. Le chargement peut se faire directement dans le cache, mais ce n'est pas une solution très pratique. À la place, beaucoup de processeurs ajoutent une mémoire tampon entre la RAM et le cache, appelée le '''tampon de remplissage de ligne''' (''line-fill buffer'') dans les processeurs modernes. Lors d'un défaut de cache, le processeur charge la donnée de la RAM dans le tampon de remplissage de ligne, mot mémoire par mot mémoire. Une fois plein, le tampon de remplissage de ligne est recopié dans la ligne de cache. Le tampon de remplissage de ligne contient une ligne de cache, si ce n'est que certains bits de contrôle ne sont pas présents. Le tampon de remplissage de ligne contient un bit de validité pour chaque mot mémoire de la ligne de cache, qui indiquent si le mot mémoire a été chargé. Par exemple, prenons un processeur 64 bits, qui gère donc des mots mémoire de 8 octets, avec des lignes de cache 256 octets/32 mots mémoire. Le tampon de remplissage de ligne contiendra 32 bits de validité. Si le processeur a chargé les 6 premiers mots mémoire, les 6 premiers bits de validité seront mis à 1, les autres seront encore à zéro. Il faut noter que la disponibilité de la ligne complète se détermine assez facilement en faisant un ET logique entre tous les bits de validité. Le processeur sait ainsi quand la ligne de cache est disponible entièrement et donc quand la transférer dans le cache. La même chose existe avec une hiérarchie de cache, sauf que l'on trouve une mémoire tampon entre chaque niveau de cache. Il y a un tampon de remplissage de ligne entre le cache L1 et le cache L2, entre le cache L2 et le L3, etc. Si le cache ne gère qu'un seul défaut de cache à la fois, le ''fill line buffer'' est une mémoire très simple, qui ne mémorise qu'une seule ligne de cache. Et cela vaut aussi bien pour un cache bloquant que non-bloquant. Mais sur les caches capables de gérer plusieurs défauts simultanés, le tampon de remplissage de ligne est une mémoire de type FIFO ou LIFO, capable de mémoriser plusieurs lignes de cache. Notons que le tampon de remplissage de ligne est très utile pour implémenter certaines techniques, par exemple le contournement du cache. Nous avions vu dans le chapitre sur le cache que certains accès mémoire doivent contourner le cache, pour des raisons de cohérence des caches. C'est notamment nécessaire pour accéder aux périphériques, mais c'est aussi utile pour des raisons de performances dans des cas très spécifiques. Les accès qui contournent le cache se font directement dans le tampon de remplissage de ligne : le processeur écrit ou lit les données depuis ce tampon de remplissage de ligne, sans accéder au cache. ===L'''early restart'' et le ''critical word load''=== La présence du ''line buffer'' permet une optimisation assez intéressante, qui permet de réduire la latence des défauts de cache. L'optimisation consiste à lire un mot mémoire dans le tampon de remplissage de ligne, même si la ligne de cache complète n'a pas encore été chargée. Il existe deux manières de faire cela, qui portent les noms d'''early restart'' et de ''critical word load''. La première est la version la plus simple, la seconde est plus complexe mais plus performante. Avec la technique d''''''early restart''''', la ligne de cache est chargée normalement, en partant de son premier mot mémoire. Dès que le mot mémoire lu/écrit par le processeur est copié dans le ''line fill buffer'', il est envoyé au processeur immédiatement. Illustrons le tout par un exemple, où une ligne de cache fait 16 mots mémoire. Le processeur effectue une lecture, qui lit le 5ème mot mémoire. Sans ''early restart'', le processeur doit charger les 16 mots mémoire avant de faire la lecture dans le cache. Avec ''early restart'', le processeur reçoit la donnée dès que le 5ème mot mémoire est disponible. Le processeur doit attendre que les 4 premiers mots mémoire soient chargés, puis le 5ème mot mémoire arrive et est envoyé directement au processeur, il est lu directement depuis le ''line fill buffer''. Les 11 mots mémoire suivants sont ensuite chargés dans le cache pendant que le processeur fait des calculs dans son coin. Le ''critical word load'' est une optimisation de la technique précédente où le chargement de la ligne de cache commence directement à la donnée demandée par le processeur. Pour reprendre l'exemple précédent, où le processeur demande le 5ème mot mémoire sur 16, le ''critical word load'' charge le 5ème mot mémoire en premier et l'envoie au processeur, ce qui fait qu'il est chargé très rapidement. Pas besoin d'attendre que le processeur charge les 4 mots mémoire précédents comme avec l'''early restart''. Dans le détail, le processeur charge le 5ème mot mémoire en premier, puis charge les 11 suivants, et termine par les 4 mots mémoire du début. En clair, le ''critical word load'' commence par charger le mot mémoire lu, puis les blocs suivants, avant de revenir au début du bloc pour charger les blocs restants. Ainsi, la donnée demandée par le processeur sera la première disponible. Pour cela, l'organisation du tampon de remplissage de ligne est modifiée de manière à rendre cela possible. Il a une taille égale à une ligne de cache complète, qui contient elle-même plusieurs mots mémoire. Dans le ''line-fill buffer'', chaque mot mémoire est stocké avec un tag, qui indique l'adresse du mot mémoire stocké dans le ''line-fill buffer''. Le ''line-fill buffer'' est donc un cache un peu particulier, qui fonctionne comme un cache du point de vue du processeur, comme une mémoire FIFO pour les transferts avec le cache. Ainsi, un processeur qui veut lire dans le cache après un défaut peut accéder à la donnée directement depuis le tampon de remplissage de ligne, alors que la ligne de cache n'a pas encore été totalement recopiée en mémoire. ==Les caches pipelinés== Il est possible de pipeliner l'accès au cache, ce qui demande juste de rajouter des registres au bon endroits et quelques circuits de contrôle. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, même ceux avec un pipeline dynamique. Et l'implémentation peut se faire de plusieurs manières différentes. Il faut dire que découper une mémoire cache en plusieurs étapes peut se faire de plusieurs manière. Commençons par la plus simple. Beaucoup de processeurs des années 2000 avaient une fréquence peu élevée comparée aux standards d'aujourd'hui, ce qui fait que leur cache avait bien un temps d'accès d'un cycle d'horloge. Mais l'accès au cache se faisait en deux cycles d'horloge : un cycle pour calculer une adresse et un cycle pour l'accès mémoire proprement dit. Et ces deux étapes étaient pipelinées, à savoir que deux micro-opérations mémoire peuvent s'exécuter en même temps : une dans l'étage de calcul d'adresse, une autre dans le cache. L'unité mémoire était donc pipelinée, alors que l'accès au cache ne l'était pas. Les processeurs qui implémentaient cette technique regroupent les micro-architectures K5 et K6 d'AMD, les processeurs Intel de micro-architecture P6 (Pentium 2 et 3) et quelques autres. Il est aussi possible de pipeliner l'accès au cache lui-même. Avec un cache pipeliné, l'accès au cache ne se fait pas en un seul cycle, mais en plusieurs. Cependant, on peut lancer un nouvel accès au cache à chaque cycle d'horloge, comme un processeur pipeliné lance une nouvelle instruction à chaque cycle. L'implémentation est assez simple : il suffit d'ajouter des registres dans le cache. Pour cela, on profite que le cache est composé de plusieurs composants séparés, qui échangent des données dans un ordre bien précis, d'un composant à un autre. De plus, le trajet des informations dans un cache est linéaire, ce qui les rend parfois pour l'usage d'un pipeline. ===Le ''pipelining'' des caches ''direct-mapped''=== Voici ce qui se passe avec un cache directement adressé. Pour rappel ce genre de cache est conçu en combinant une mémoire RAM, généralement une SRAM, avec quelques circuits de comparaison et un MUX. L'accès au cache se fait globalement en deux étapes : la première lit la donnée et le ''tag'' dans la SRAM, et on utilise les deux dans une seconde étape. Nous avions vu il y a quelques chapitre comment pipeliner des mémoires, dans le chapitre sur les mémoires évoluées. Et bien ces méthodes peuvent s'utiliser pour la mémoire RAM interne au cache !L'idée est d'insérer un registre entre la sortie de la RAM et la suite du cache, pour en faire un cache pipéliné, à deux étages. Le premier étage lit dans la SRAM, le second fait le reste. L'implémentation sur les caches associatifs à plusieurs voies est globalement la même à quelques détails près. [[File:Cache directement adressé pipeliné - deux étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - deux étages]] Avec le circuit précédent, il est possible d'aller plus loin, cette fois en pipelinant l'accès à la mémoire RAM interne au cache. Pour comprendre comment, rappelons qu'une mémoire SRAM est composée d'un plan mémoire et d'un décodeur. L'accès à la mémoire demande d'abord que le décodeur fasse son travail pour sélectionner la case mémoire adéquate, puis ensuite la lecture ou écriture a lieu dans cette case mémoire. L'accès se fait donc en deux étapes successives séparées, on a juste à mettre un registre entre les deux. Il suffit donc de mettre un gros registre entre le décodeur et le plan mémoire. [[File:Cache directement adressé pipeliné - trois étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - trois étages]] Et on peut aller encore plus loin en découpant le décodeur en deux circuits séparés. En effet, rappelez-vous le chapitre sur les circuits de sélection : nous avions vu qu'il est possible de créer des décodeurs en assemblant des décodeurs plus petits, contrôlés par un circuit de prédécodage. Et bien on peut encore une ajouter un registre entre ce circuit de prédécodage et les petits décodeurs. : Théoriquement, toute l'adresse est fournie d'un seul coup au cache, la quasi-totalité des processeurs présentent l'adresse complète à un cache pipeliné. Mais le Pentium 4 fait autrement. Il faut noter que les premiers étages manipulent l'indice dans la SRAM, qui est dans les bits de poids faible, alors que les étages ultérieurs manipulent le ''tag'' qui est dans les bits de poids fort. Les concepteurs du Pentium 4 ont alors décidé de présenter les bits de poids faible lors du premier cycle d'accès au cache, puis ceux de poids fort au second cycle. Pour cela, l'ALU fonctionnait à une fréquence double de celle du processeur, tout comme le cache L1. Il n'y avait pas de pipeline proprement dit, mais cela réduisait grandement la latence d'accès au cache. ===Le ''pipelining'' des caches splittés/sériels=== Il est aussi possible de pipeliner un cache dits splittés, aussi appelés à accès sériel. Pour rappel, les caches à accès sériel vérifient si il y a succès ou défaut de cache, avant d'accéder aux lignes de cache en cas de succès. Ils font donc différemment des autres caches, qui accèdent à une ligne de cache, avant de déterminer s'il y a succès ou défaut en lisant le tag de la ligne de cache. Les caches sériels disposent de deux SRAM : une pour les tags des lignes de cache et une pour les données. Ils accèdent à la SRAM pour les tags, avant d’accéder à la SRAM des données en cas de succès de cache. Vu que l'accès se fait en deux étapes, une vérification des tags suivie de la lecture/écriture des données, il est facile à pipeliner. Pipeliner le cache permet de régler le problème des accès au cache L1, et elle est tout le temps utilisé sur les processeurs modernes. Mais que faire en cas de défaut de cache ? ==Les caches non bloquants== Un '''cache bloquant''' est un cache auquel le processeur ne peut pas accéder pendant un défaut de cache. Il faut attendre que la lecture ou écriture en RAM soit terminée avant de pouvoir utiliser de nouveau le cache. Un '''cache non bloquant''' n'a pas ce problème : on peut l'utiliser pendant un défaut de cache. Les caches non bloquants permettent de démarrer une nouvelle lecture ou écriture alors qu'une autre est en cours, ce qui permet d'exécuter plusieurs lectures ou écritures en même temps. ===Les ''Miss Handling Status Registers''=== Lors d'un défaut de cache, la mémoire RAM est consultée pendant le défaut de cache, mais le cache est inutilisé. Un défaut de cache n'utilise pas le cache, ce n'est qu'un accès en mémoire RAM, sur le bus mémoire (ou un accès aux niveaux de cache inférieurs, peu importe). Le cache en lui-même est laissé libre, rien n’empêche d'y accéder, il est en réalité intrinsèquement non-bloquant. Les caches, bloquants comme non-bloquants, sont en réalité composés d'une mémoire cache proprement dite, entourée de circuits qui servent d'interface entre le processeur et le cache lui-même. Et parmi les circuits tout autour, certains gèrent l'accès au cache lors d'un défaut de cache. Ils sont regroupés sous le terme de '''''Miss Handling Architecture''''' (MHA). La différence entre un cache bloquant et un cache non-bloquant est en réalité liée à l'implémentation de la MHA. Les caches bloquants coupent volontairement l'accès au cache lors d'un défaut, car les défauts sont plus simples à gérer ainsi. Le défaut de cache rapatrie une donnée depuis la RAM, qui sera écrite dans le cache. Et il ne faut pas qu'une tentative d'accès à cette donnée ait lieu avant qu'elle ne soit chargée. Mais ce blocage est général et touche tout le cache, alors que seule une ligne de cache est concernée par le défaut de cache. L'idée derrière un cache non-bloquant est que seule la ligne de cache est bloquée, mais les autres sont accessibles. L'idée est alors de mémoriser les lignes de cache concernées par le défaut de cache, afin d'en bloquer l'accès. À chaque accès, on vérifie si la ligne de cache est déjà réservée par un défaut de cache. La lecture/écriture est alors bloquée si c'est le cas, mais elle accepte les accès sinon. Pour cela, la ''Miss Handling Architecture'' contient des registres qui mémorisent des informations sur les défauts de cache en cours. Ils portent le nom de '''''miss status handling registers''''', que j’appellerais dorénavant MSHR, qui sont aussi appelés des '''''miss buffer'''''. Le contenu des MSHR varie beaucoup suivant le processeur, mais ces derniers stockent au minimum les informations suivantes : * Le numéro de la ligne de cache dans laquelle les données sont chargées. * Un bit de validité qui indique si le MSHR est vide ou pas, qui est mis à 0 quand le défaut de cache est résolu. * Un ou plusieurs champs de lecture/écriture, qui contiennent des informations sur la lecture/écriture. ** Pour une lecture, elle contient des informations sur la destination de la donnée, à savoir qui prévenir quand le défaut de cache est terminé. C'est parfois un nom/numéro de registre (celui dans lequel charger la donnée), mais c'est souvent le numéro de l'entrée dans le ''load/store queue''. ** Pour les écritures, elle contient la donnée à écrire, ou éventuellement un numéro de ''load/store queue'' où se trouve la donnée à écrire. Il faut noter que le bus mémoire ne peut gérer qu'un seul défaut de cache à la fois. Aussi, il est intéressant de regarder ce qui se passe lorsqu'un second défaut de cache survient, pendant qu'un premier est en cours. Dans ce cas, il y a deux réponses qui correspondent à deux types de caches non-bloquants, qui portent les noms barbares de caches de type succès après défaut et défaut après défaut. Sur le premier type, il ne peut pas y avoir plusieurs défauts de cache simultanés. Dès qu'un second défaut de cache survient, le cache stoppe son activité et on ne peut plus démarrer de nouvelle lecture/écriture, tant que le premier défaut de cache n'est pas résolu. Le second type est plus souple et autorise la survenue de plusieurs défauts de cache simultanés. Du moins, jusqu'à une certaine limite, car le cache ne peut supporter qu'un nombre limité d'accès mémoires simultanés (pipelinés). ===Les accès simultanés à une même ligne de cache=== Il arrive que le processeur fasse plusieurs accès mémoire simultanés à la même ligne de cache. Si la ligne de cache en question n'a pas encore été chargée dans le cache, alors on a plusieurs défauts de cache consécutifs pour la même ligne de cache, et le cache non-bloquant doit gérer la situation. Pour la suite, il va falloir faire une petite distinction entre les défauts primaires et secondaires. Imaginons qu'un défaut de cache ait lieu et demande à charger une donnée dans la ligne de cache numéro N. Il s'agit du premier défaut impliquant cette ligne de cache précise, ce qui lui vaut le nom de '''défaut de cache primaire'''. Mais par la suite, d'autres accès mémoire à la même ligne de cache ont lieu, alors que la ligne de cache n'est pas encore disponible. Dans ce cas, il s'agit de '''défauts de cache secondaires'''. Pour l'unité d'accès mémoire, les défauts de cache primaire et secondaire sont différents (ils prennent tous une entrée dans la ''load/store queue''). Mais pour le cache, ils ne correspondent qu'à un seul accès au cache : celui qui demande de charger la ligne de cache demandée. Les défauts de cache primaire et secondaire à la même ligne de cache se voient attribuer un MSHR unique. La gestion des défauts de cache secondaires dépend du cache non-bloquant. La solution la plus simple ne permet pas les défauts de cache secondaires. Le cache ne permet pas deux défauts de cache simultanés pour la même ligne de cache. Les autres solutions le permettent, en fusionnant des accès simultanés à la même ligne de cache en un seul au niveau des MSHR. Dans tous les cas, détecter les défauts de cache secondaires sont un problème qu'il faut détecter. La ''Miss Handling Architecture'' doit détecter les défauts de cache secondaire. Pour cela, elle procède comme suit. Lors de chaque défaut de cache, la MHA récupère le numéro de la ligne de cache associé. Il vérifie alors chaque MSHR pour vérifier s'il contient le numéro en question. S'il n'y a aucune correspondance dans les MSHR, alors c'est signe que le défaut de cache est un défaut primaire. Mais s'il y en a une, alors c'est un défaut secondaire. Évidemment, cela signifie que lors d'un défaut de cache, le numéro de ligne de cache est envoyé à tous les MSHR, pour comparaison. Les MSHR sont donc regroupés dans une mémoire associative, une sorte de mini-cache, faciliter l'implémentation. Lors d'un défaut de cache primaire, l'accès à un cache non-bloquant se fait comme suit : le processeur envoie une adresse au cache, accède à celui-ci, et détecte la survenue d'un défaut de cache. Il en profite alors pour attribuer une ligne de cache dans laquelle sera chargée ce défaut. L'attribution est très simple dans le cas des caches ''direct mapped'', ou associatifs par voie, pour lesquels l'attribution se fait assez simplement. Il mémorise alors cette information dans les MSHR, après avoir vérifié que le défaut de cache n'était pas un défaut secondaire. Lors des accès ultérieurs à une adresse proche, censée être dans la même ligne de cache, le processeur va encore une fois rencontrer un défaut de cache. Il va alors déterminer le numéro de la ligne de cache associée à l'adresse, et comparer ce numéro avec les MSHRs. Si un MSHR contient ce numéro, c'est signe que le défaut de cache est un défaut secondaire. La MHA réagit alors différemment selon le processeur considéré. Une première solution n'autorise pas les défauts de cache secondaires. Si l'un d'entre eux survient, le processeur est gelé par un ''pipeline stall'', une bulle de pipeline. Une autre solution fusionne les défauts de cache secondaires avec le défaut de cache primaire : tout cela ne correspond qu'à un seul défaut de cache pour lui. ===Les MSHR simples=== Un cache non-bloquant à '''MSHR simple''' contient juste plusieurs MSHR qui mémorisent juste un numéro de ligne de cache, un bit de validité, le champ de lecture/écriture, et l'adresse exacte de lecture/écriture. Avec cette organisation, il est possible d'avoir plusieurs défauts de cache séparés, mais à la condition que chaque défaut accède à une ligne de cache différente. Deux accès simultanés à une même ligne de cache ne sont pas possibles, les défauts de cache secondaires ne sont pas autorisés. Ainsi, chaque défaut de cache se voit attribuer son propre MSHR, chacun contient un numéro de ligne de cache différent. Pour comprendre pourquoi c'est impossible de gérer les défauts secondaires, il faut regarder le champ de lecture/écriture. Si on veut effectuer plusieurs écritures consécutives à la même adresse, le MSHR n'aura pas de quoi mémoriser les deux données à écrire. Il pourra mémoriser la première donnée à écrire, pas la seconde. Même chose lors d'une lecture : le champ lecture/écriture peut mémorisr la destination de la première lecture, pas de la seconde. L'avantage est que la MHA n'a besoin que des MSHR et de quelques circuits annexes. Les autres solutions rajoutent des circuits annexes pour gérer les défauts de cache secondaires, qui utilisent beaucoup de circuits. Le cout en circuit est donc élevé, mais le gain en performance est là. Passons maintenant aux caches non-bloquants qui autorisent les défauts de cache secondaire. La solution la plus simple consiste à utiliser ===Les MSHR adressés implicitement=== Avec les '''MSHR adressés implicitement''', il est possible de fusionner plusieurs accès mémoire à une même ligne de cache, mais sous une condition très importante : ces accès lisent/écrivent des mots mémoire différents. Par exemple, imaginons qu'une ligne de cache contienne 8 mots mémoire de 64 bits. Si un premier accès mémoire lit le mot mémoire numéro 7 (dernier mot mémoire de la ligne), et le second accès le mot mémoire numéro 3, alors la fusion est possible. Mais si deux accès mémoire veulent lire/écrire le mot mémoire numéro 7, alors la fusion n'est pas possible et le processeur se bloque, un ''pipeline stall'' survient. Un MSHR adressé implicitement est un MSHR simple, qui contient naturellement un numéro de ligne de cache (tag) et un bit de validité, sauf que le champ de lecture/écriture est dupliqué. Les différents champs lecture/écriture d'un MSHR sont regroupés dans une mémoire RAM/cache qui contient autant d'entrées qu'il y a de mot mémoire dans une ligne de cache. Chaque entrée de la table est associée à un mot mémoire de la ligne de cache et stocke des informations sur celui-ci. Une entrée mémorise, au minimum : * Un champ de lecture/écriture qui contient soit la destination de la lecture, soit la donnée à écrire. * Un bit R/W permettant d'interpréter correctement le champ de lecture/écriture. * Un bit de validité pour chaque entrée de la table, qui dit si un défaut de cache antérieur accède déjà à ce mot mémoire. La fusion de deux défauts de cache est ainsi assez simple. Un défaut de cache primaire/secondaire configure l'entrée associée au mot mémoire lu/écrit. Si un défaut secondaire ultérieur lit/écrit un mot mémoire différent (dont le bit de validité de l'entrée dans la table est à zéro), pas de conflit : il configure une autre entrée, vide. Mais s'il lit/écrit un mot mémoire pour lequel l'entrée de la table est occupée, il y a conflit, le processeur est bloqué par un ''pipeline stall'', une bulle de pipeline. Avec cette organisation, le nombre de MSHR indique combien de lignes de cache peuvent être lues en même temps depuis la mémoire. Quant au nombre d'entrées par MSHR, il détermine combien d'accès mémoires qui ne se recouvrent pas peuvent avoir lieu en même temps. ===Les MSHR adressés explicitement=== Les '''MSHR adressés explicitement''' n'ont pas les contraintes des MSHR adressés implicitement. Avec eux, il est possible d'avoir plusieurs défauts de cache pointant vers la même ligne de cache, mais aussi vers le même mot mémoire. De tels défauts de cache apparaissent sur les processeurs à exécution dans le désordre, mais sont très rares, voire inexistants, sur les processeurs ''in-order''. L'idée est encore que chaque MSHR est associée à une table mémoire qui mémorise des entrées. Cette table n'est autre qu'une mémoire FIFO. Sauf que cette fois-ci, une entrée n'est pas associée à un mot mémoire. Une entrée est associée à un défaut de cache. Une entrée mémorise là encore la destination de la lecture ou la donnée de l'écriture suivi du bit R/W, ainsi que d'un bit de validité par entrée, mais aussi : la position du mot mémoire lu dans la ligne de cache. C'est cette dernière information qui n'était pas présente dans les MSHR adressés implicitement. De plus, le nombre d'entrée par MSHR n'est pas égal au nombre de mots mémoires dans une ligne de cache, mais peut être arbitrairement grand. L'avantage d'un tel cache est qu'il est capable de traité les défauts secondaires concernant un même mot mémoire, et ce, car les accès à la ligne de cache concernée se produisent dans l'ordre de génération des défauts (la table mémoire est une FIFO). ===Les MSHRs inversés=== Généralement, plus on veut supporter de défauts de cache, plus le nombre de MSHR et d'entrées augmente. Mais au-delà d'un certain nombre d'entrées et de MSHR, les MSHR adressés implicitement et explicitement ont tendance à bouffer un peu trop de circuits. Utiliser une organisation un peu moins gourmande en circuits est donc une nécessité. Cette organisation plus économe se base sur des MSHR inversés. Les MSHR inversés ne contiennent qu'une seule entrée, en quelque sorte : au lieu d’utiliser n MSHR de m entrées chacun, on va utiliser n × m MSHR inversés. La différence, c'est que plusieurs MSHR peuvent contenir un tag identique, contrairement aux MSHR adressés implicitement et explicitement. Lorsqu'un défaut de cache a lieu, chaque MSHR est vérifié. Si jamais aucun MSHR ne contient de tag identique à celui utilisé par le défaut, un MSHR vide est choisi pour stocker ce défaut, et une requête de lecture en mémoire est lancée. Dans le cas contraire, un MSHR est réservé au défaut de cache, mais la requête n'est pas lancée. Quand la donnée est disponible, les MSHR correspondant à la ligne qui vient d'être chargée vont être utilisés un par un pour résoudre les défauts de cache en attente. ===Les MSHR intégrés au cache=== Certains chercheurs ont remarqué que pendant qu'une ligne de cache est en train de subir un défaut de cache, celle-ci reste inutilisée, et son contenu est destiné à être perdu une fois le défaut de cache résolu. Ils se sont dit que, plutôt que d'utiliser des MSHR séparés, il vaudrait mieux utiliser la ligne de cache pour stocker les informations sur les défauts de cache en attente dans cette ligne de cache. Pour éviter tout problème, il faut rajouter un bit dans les bits de contrôle de la ligne de cache, qui sert à indiquer que la ligne de cache est occupée : un défaut de cache a eu lieu dans cette ligne, et elle stocke donc des informations pour résoudre les défauts de cache. ==L'entrelacement mémoire== Nous venons de voir comment une mémoire cache peut gérer plusieurs accès mémoire simultanés. Il se trouve que ce genre d'optimisation dépasse largement le cadre du cache. Les mémoires RAM ne sont pas en reste, elles aussi ! Il est possible de pipeliner l'accès à une mémoire RAM, en utilisant des techniques dites d''''entrelacement'''. Précisons cependant que cette optimisation n'est pas utilisée sur les mémoires SDRAM ou DDR modernes, du moins pas sans modifications majeures. Mais divers ordinateurs assez anciens, dont des superordinateurs, ont utilisé cette technique. La technique demande d'utiliser plusieurs mémoires séparées. Sur les anciens ordinateurs, les mémoires en question sont des chips mémoire, à savoir des circuits intégrés de DRAM. Pour faire plus général, nous allons utiliser le terme de '''banques''', ou encore de bancs mémoire. La différence est qu'il existe des mémoires multi-banque, qui regroupent plusieurs banques indépendantes dans un seul boitier, dans un seul chip mémoire. En clair, de telles mémoires regroupent plusieurs sous-mémoires dans un seul circuit intégré. Il est aussi possible d'utiliser plusieurs chips mémoire séparés, chacun ayant une banque. Reste à combiner ces banques pour former un pseudo-pipeline. ===Utiliser plusieurs banques sans entrelacement=== Sans optimisation particulière, les adresses sont réparties dans les banques comme indiqué ci-dessous. Il s'agit de ce qui s'appelle un arrangement horizontal, et nous avions vu celui-ci dans le chapiutre sur les mémoires SDRAM. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Avec cet arrangement, les bits de poids fort de l'adresse sont utilisées pour sélectionner la banque adéquate, et le reste de l'adresse est envoyé sur le bus d'adresse. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Adresse de banque !! Adresse dans la banque |- | Quelques bits de poids fort || Reste de l'adresse |} L'entrelacement change cette répartition, afin d'accélérer les accès mémoire. L'entrelacement classique vise à améliorer les accès à des adresses mémoires consécutives, mais des formes plus évoluées d'entrelacement visent à optimiser des accès mémoire arbitraires. Voyons-les dans l'ordre, du plus simple au plus complexe. ===L'entrelacement classique=== Avec l''''entrelacement classique''', des adresses consécutives sont réparties dans des banques différentes. [[File:Répartition des adresses dans une mémoire interleaved.png|centre|vignette|upright=1.5|Répartition des adresses dans une mémoire interleaved.]] L'entrelacement simple permet d'accélérer les accès mémoire qui se font à des adresses consécutives. Avec l'entrelacement, chaque accès mémoire tombe dans une banque mémoire différente, ce qui fait qu'on peut démarrer un nouvel accès mémoire à chaque cycle d'horloge. Pendant qu'une banque est occupée par un accès mémoire, on démarre le suivant dans une autre banque, et ainsi de suite. Le tout se termine soit quand on a épuisé toutes les banques libres, soit quand l'accès en rafales se termine. Pas besoin d'attendre que la mémoire ait fini sa lecture/écriture avant de démarrer la lecture/écriture suivante. [[File:Pipemining mémoire.png|centre|vignette|upright=2.5|Pipelining mémoire]] L'animation suivante illustre bien le processus, avec quatres banques. [[File:Interleaving.gif|centre|vignette|upright=2.5|Pipelining mémoire]] L'entrelacement simple utilise les bits de poids faible pour sélectionner la banque, et les bits de poids fort pour la case mémoire. [[File:Adresse mémoire d'une mémoire entrelacée.png|centre|vignette|upright=2|Adresse mémoire d'une mémoire entrelacée]] Précisons que le temps d'accès mémoire ne change pas beaucoup avec l'entrelacement. Par contre, on peut faire plus d'accès mémoire simultanés. On peut démarrer un accès mémoire par cycle d'horloge, mais l'accès en lui-même prend plusieurs cycles. Les mémoires à entrelacement ont donc un débit supérieur aux mémoires qui ne l'utilisent pas. L'entrelacement classique pourrait être utilisé pour accélérer les transferts entre mémoire RAM et cache, qui se font en blocs d'adresses consécutives. Mais de nos jours, il n'est pas utilisé dans cette optique. Il faut dire qu'il est redondant avec le mode rafale des mémoires DRAM. Les deux font globalement la même chose et on voit mal comment utiliser les deux en même temps. Le mode rafale étant plus simple à implémenter, et plus léger niveau utilisation du bus de commande mémoire, il est préféré à l'entrelacement. Par contre, l'entrelacement a eu son heure de gloire sur d'anciens processeurs qui n'avaient pas de mémoire cache, alors qu'ils disposaient d'un pipeline. L'exemple typique est celui des nombreux ''processeurs vectoriels'', que nous n'avons pas encore abordé à ce stade du cours. De tels processeurs avaient un pipeline, une unité de calcul fortement pipelinée, et parfois même de l'exécution dans le désordre et du renommage de registres ! ===L'entrelacement par décalage=== L'entrelacement simple est très utile pour les accès en rafle ou équivalents. Par contre, il existe de rares situations où il n'est pas optimal. Une de ces situations est celle des accès en enjambée (en ''stride''), où l'on accède en série à des adresses sont séparées par N mots mémoires. De tels accès surviennent quand un logiciel accède à des structures de données spécifiques, à savoir des tableaux de structures, des matrices, ou d'autres structures de données dans le genre. Un cas classique est celui du parcours d'une matrice colonne par colonne. Les matrices de nombres sont mémorisées ligne par ligne, ce qui fait que parcourir une colonne demande de passer d'une ligne à la suivante et de faire des grands sauts en mémoire RAM, mais tous espacés par la même distance. Avec l'entrelacement simple, les accès en enjambée sont moins performants que les accès à des adresses consécutives, mais cela ne fonctionne pas trop mal. Le pire cas est celui où l'on a N banques et où les données sont justement placées toutes les N adresses. Des accès consécutifs vont tous tomber dans la même banque, on ne peut plus accéder à des banques différentes en parallèle. Mais en dehors de ce cas, on voit une amélioration par rapport à la situation sans entrelacement. Pour obtenir des performances maximales pour les accès en enjambées, il faut répartir les mots mémoires dans la mémoire autrement. L'organisation idéale est la suivante. : Dans les explications qui vont suivre, la variable N représente le nombre de banques, qui sont numérotées de 0 à N-1. On commence par organiser les N premières adresses comme une mémoire entrelacée simple : l'adresse 0 correspond à la banque 0, l'adresse 1 à la banque 1, etc. Pour le bloc suivant, on décale tout d'une adresse, à savoir qu'on commence à remplir les banques à partir de la seconde, non de la première. Une fois la fin du bloc atteinte, on finit de remplir le bloc en repartant du début du bloc. Le troisième bloc d'adresse subit le même traitement, sauf qu'on commence à remplir à partir de la seconde banque. Et on poursuit l’assignation des adresses en décalant d'un cran en plus à chaque bloc. Ainsi, chaque bloc verra ses adresses décalées d'un cran en plus comparé au bloc précédent. Si jamais le décalage dépasse la fin d'un bloc, alors on reprend au début. [[File:Mémoire interleaved par décalage.png|centre|vignette|upright=1.5|Mémoire entrelacée par décalage.]] En faisant cela, on remarque que les banques situées à N adresses d'intervalle sont différentes. Dans l'exemple du dessus, nous avons ajouté un décalage de 1 à chaque nouveau bloc à remplir. Mais on aurait tout aussi bien pu prendre un décalage de 2, 3, etc. Dans tous les cas, on obtient un entrelacement par décalage. Ce décalage est appelé le pas d'entrelacement, noté P. Le calcul de l'adresse à envoyer à la banque, ainsi que la banque à sélectionner se fait en utilisant les formules suivantes : * adresse à envoyer à la banque = adresse totale / N ; * numéro de la banque = (adresse + décalage) modulo N, avec décalage = (adresse totale * P) mod N. Avec cet entrelacement par décalage, on peut prouver que la bande passante maximale est atteinte si le nombre de banques est un nombre premier. Seulement, utiliser un nombre de banques premier peut créer des trous dans la mémoire, des mots mémoires inadressables. Pour éviter cela, il faut faire en sorte que N et la taille d'une banque soient premiers entre eux : ils ne doivent pas avoir de diviseur commun. Dans ce cas, les formules se simplifient : * adresse à envoyer à la banque = adresse totale / taille de la banque ; * numéro de la banque = adresse modulo N. ===L'entrelacement pseudo-aléatoire=== Une dernière méthode de répartition consiste à répartir les adresses dans les banques de manière "pseudo-aléatoire". L'idée exacte est de faire passer l'adresse dans une '''fonction de hachage''', plus ou moins complexe. Pour rappel, une fonction de hachage prend une entrée de grande taille et fournit en sortie un résultat de petite taille. Idéalement, elles doivent donner des résultats aussi différents que possible pour des entrées similaires, histoire de simuler une sorte de pseudo-aléatoire. Ici, une adresse est transformée en un numéro de banque. Une fonction de hachage simple ne fait que permuter des bits de l'adresse pour obtenir son résultat. Elle ne fait qu'échanger des bits de place avant de couper l'adresse en deux morceaux : un pour la sélection de la banque, et un autre pour la sélection de l'adresse dans la banque. Cette permutation est fixe, et ne change pas suivant l'adresse. Des fonctions de hachage plus complexes font des XOR bit à bit entre certains bits de l'adresse. Il existe aussi d'autres techniques qui donnent le numéro de banque à partir d'un polynôme modulo N, appliqué sur l'adresse. ==Les contrôleurs mémoires SDRAM/DDR optimisés== Après avoir vu le cas des mémoires RAM, nous allons nous concentrer sur le cas particulier des mémoires SDRAM. Les mémoires SDRAM et DDR modernes sont capables de gérer plusieurs accès simultanés à la mémoire RAM. Et les contrôleurs mémoire intègrent des optimisations spécifiques afin d'en profiter au maximum. ===La mise en attente des accès mémoire=== Comme pour les mémoires caches, il existe des contrôleurs mémoire bloquants et non-bloquants. Les premiers n'acceptent qu'un seul accès mémoire à la fois, les seconds acceptent plusieurs accès mémoire simultanés. Pour simplifier, nous allons dire que le contrôleur mémoire n’exécute qu'un seul accès mémoire à la fois, et qu'ils met en attente les accès en trop. C'est une simplification assez irréaliste, vu que les mémoires modernes disposent de plusieurs banques accessibles en parallèle. Mais nous introduirons ce détail un peu plus tard. Avec un contrôleur mémoire de ce type, les accès mémoire sont mis en attente dans une mémoire FIFO, histoire de les exécuter dans leur ordre d'arrivée. Les accès mémoire quittent alors la mémoire dans leur ordre d'arrivée, les lectures sont renvoyées dans l'ordre demandé. En plus d'une mémoire FIFO pour les commandes mémoire, on trouve une mémoire FIFO pour les données à écrire. Elle permet d'accumuler plusieurs écritures, tout en permettant que la donnée adéquate soit envoyée à la RAM au bon moment. Il y a aussi une FIFO pour les données lues, qui sert au cas où le cache ne soit pas disponible quand on lui envoie la donnée lue. [[File:Module d'interface avec la mémoire.png|centre|vignette|upright=2.5|Contrôleur mémoire avec mise en attente des requêtes processeur et autres optimisations.]] Une optimisation regroupe plusieurs accès à des données consécutives en un seul accès en rafale. Le contrôleur mémoire analyse les requêtes mises en attente et détecte si certaines se font à des adresses consécutives. Si c'est le cas, il fusionne ces requêtes en une lecture/écriture en rafale. Une telle optimisation est appelée la '''combinaison de lecture''' pour les lectures, et la '''combinaison d'écriture''' pour les écritures. ===Le pipeline des mémoires SDRAM=== Les SDRAM ont une forme de pseudo-pipeline très limité. Elles sont parfois capables de démarrer une commande avant que la précédente soit terminée. Même si elle parait très limitée, cette possibilité est exploitée au mieux par la présence de plusieurs banques dans la mémoire SDRAM. Les chips mémoires de SDRAM sont composés de plusieurs banques, chacune étant une sorte de mini-mémoire miniature. Chaque banque a son propre décodeur, son propre tampon de ligne, ses propres multiplexeurs de colonne, sa logique de rafraichissement mémoire, etc. C'est comme si une SDRAM regroupait plusieurs mémoires séparées dans un même circuit intégré. Et cela permet de faire plusieurs accès mémoire en même temps, dans des banques différentes. Il est possible de lancer un accès mémoire dans deux banques en même temps, par exemple. Ou encore, on peut lancer un nouvel accès mémoire dans une banque si elle est inoccupée. La possibilité améliore grandement la performance de la SDRAM. Mais nous en reparlerons dans un chapitre ultérieur. {|class="wikitable" |+ Pipelining basique sur les SDRAM |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 |- ! Banque Numéro 1 | || || || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || || || |- ! Banque Numéro 2 | || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || |- ! Banque Numéro 3 | || || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || |} L'implémentation n'a rien de vraiment compliqué. Il suffit d'utiliser une mémoire FIFO par banque. Ainsi, les accès dans une même banque se feront en série, dans l'ordre d'arrivée, mais des accès à des banques différentes se feront dans le désordre. Cela ne pose pas de problème car les accès se font à des adresses différentes, ce qui fait qu'il n'y a pas de conflits majeurs. [[File:Gestion parallèle des banques.png|centre|vignette|upright=2.5|Gestion parallèle des banques.]] ===Le ré-ordonnancement des commandes mémoires=== Un contrôleur mémoire moderne est capable de changer l'ordre des requêtes mémoires, pour gagner en performances. En clair : il peut faire l'équivalent mémoire de l'exécution dans le désordre des processeurs haute performance. Une différence majeure est que le contrôleur mémoire ne remet pas les accès mémoire dans l'ordre avant de les envoyer au processeur. C'est le processeur qui remet en ordre les accès mémoire, dans sa ''load queue''. Pour cela, les requêtes mémoire sont "numérotées", à savoir qu'on leur attribue un identifiant binaire. Le contrôleur mémoire exécute les lectures/écritures dans le désordre, mais garde la trace de leur identifiant. Il sait dans quel ordre il exécute les accès mémoire et connait leur latence. Ce qui fait qu'il sait que la donnée lue à tel instant est associée à la requête mémoire numéro X. Pour les lectures, il renvoie la donnée lue avec l'identifiant. Le processeur sait alors à quelle lecture la donnée lue correspond et il se débrouille en interne pour gérer la situation. En clair : le processeur voit les accès mémoire dans le désordre, c'est lui qui les remet en ordre. Maintenant que ces précisions sont faites, posons cette question : quel est l'intérêt de faire les accès mémoire dans le désordre ? Accéder à des banques en parallèle est une réponse possible, mais le ré-ordonnancement est surtout utile si tous les accès mémoire atterrissent dans la même banque. Pour comprendre pourquoi, faisons un rappel rapide sur le tampon de ligne. Les mémoires SDRAM sont des mémoires à tampon de ligne. Elles font un accès mémoire en deux étapes. La première étape recopie une ligne de N * 64 bits dans un tampon interne à la SDRAM. La seconde étape sélectionne une colonne, à savoir une donnée de 64 bits, qui est soit envoyée sur le bus de données pour une lecture, soit modifiée par une écriture. [[File:Mémoire à tampon de ligne.png|centre|vignette|upright=2|Mémoire à tampon de ligne]] Les deux étapes correspondent à deux commandes séparées : une commande ACT qui précise l'adresse de la ligne, et une commande READ ou WRITE qui précise l'adresse de la colonne. [[File:Signaux RAS et CAS.png|centre|vignette|upright=2|Signaux RAS et CAS.]] Les SDRAM permettent de se passer de la première étape si des accès consécutifs se font dans la même ligne. Une fois activée, la ligne reste ouverte et on peut accéder plusieurs fois de suite dedans. On a alors juste à préciser l'adresse de colonne dedans. [[File:Sélection d'une ligne sur une mémoire FPM ou EDO.png|centre|vignette|upright=2|Sélection d'une ligne sur une mémoire FPM ou EDO.]] L'exécution dans le désordre des lectures/écritures SDRAM vise à faire en sorte que des accès consécutifs se fassent dans une même ligne. Elle marche si plusieurs accès à une même ligne ne sont pas consécutifs, et qu'il y a des accès entre les deux. Après ré-ordonnancement, ces accès mémoire deviennent consécutifs, ils sont exécutés l'un à la suite de l'autre. Pour rendre le tout plus concret, voici un exemple. Imaginez que l'on sait les 3 accès mémoire suivants : * Une lecture ligne A ; * Une écriture ligne B ; * Une lecture ligne A. Sans réordonnancent, on doit émettre deux commandes PRECHARGE : une quand on passe de la ligne A à la ligne B, et une autre pour le passage inverse. pour éviter cela, le contrôleur mémoire peut retarder l'écriture, ou au contraire avancer la seconde lecture. le résultat est alors le suivant : * Lecture ligne A ; * Lecture ligne A ; * Une écriture ligne B. On a donc deux accès consécutifs à la même ligne, suivi par une écriture dans une autre ligne. La technique marche parce que l'on a un mix adéquat de lectures et d'écritures. Mais il existe des possibilités de réorganisation autres qui ne sont pas valides. Il faut par exemple prendre garde à éviter d'intervertir lectures et écritures à une même adresse, sous peine de problèmes. Encore une fois, il faut faire attention aux dépendances mémoires. Le controleur mémoire teste les dépendances mémoires avant d'envoyer des commandes à la mémoire DDR/SDRAM. ===L'entrelacement sur les mémoires SDRAM=== L''''entrelacement''' fonctionne sur les mémoires SDRAM, mais il doit être fortement modifié. Concrètement, tout ce qui a étré dit plus est inapplicable pour les SDRAM, du fait de la présence du tampon de ligne. Des adresses consécutives atterrissent déjà dans la même ligne, ce qui fait qu'on n'a pas besoin d'entrelacement au niveau d'une ligne. Et ne parlons pas de ce la présence de rangées et de canaux mémoire ! Cependant, il peut y avoir un entrelacement lié à la présence des banques SDRAM. Pour gérer l'entrelacement, le contrôleur de SDRAM prend en entrée l'adresse envoyée par le processeur, et la découpe en plusieurs champs : un pour sélectionner la banque adéquate, un autre pour la ligne, un autre pour la colonne, un autre pour la rangée. Une solution simple désactive l'entrelacement. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de banque || Adresse de ligne || Adresse de colonne |} Si cette solution fonctionne bien si les accès mémoire sont assez aléatoires, en pratique, mieux vaut utiliser l'entrelacement. Une première méthode d'entrelacement est l''''entrelacement de ligne'''. Elle répartit deux lignes consécutives dans deux banques différentes. Avec N banques, la première ligne ira dans la première banque, la seconde ligne dans la seconde, et ainsi de suite jusqu'à la énième banque qui va dans la banque numéro N. Et la ligne N+1 repart dans la première banque. L'avantage est que passer d'une ligne à la suivante est assez rapide. Au lieu de devoir fermer la première ligne et ouvrir la suivante on ouvrira directement la ligne suivante dans une banque différente, ce qui sera plus rapide. La commande PRECHARGE pourra être envoyée à la seconde banque en avance. Pour cela, l'adresse est découpée comme suit. {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne (précise à l'octet) |} D'autres méthodes d'entrelacement sont possibles, mais elles sont plus complexes. Chaque contrôleur mémoire fait un peu à sa sauce. Ils permettent en général de choisir entre plusieurs entrelacements. Au minimum, ils permettent de désactiver l'entrelacement, d'activer un entrelacement de ligne, et d'autres entrelacements plus complexes, dont certains sont propriétaires et tenus secrets par le fabricant. L'usage du ''dual channel'' complique encore l'entrelacement. Là encore, il y a deux grandes solutions : avec et sans entrelacement des canaux mémoire. Rappelons le principe : deux barrettes de RAM sont accédées en parallèle. Et pour cela, il faut utiliser l'entrelacement. Typiquement, chaque barrette mémoire fournit 64 bits, ce qui fait que l'on peut accéder à 128 bits d'un seul coup, par exemple avec un accès en rafale. Une solution pour cela est de ne pas utiliser l'entrelacement. Avec deux barrettes, la première barrette regroupera la moitié haute de la RAM, la seconde barrette utilisera la moitié basse. Les possibilité d’accéder à deux données séparées de 64 bits en même temps est cependant très rare. Aussi, on préfère utiliser l'entrelacement. Pour cela, deux blocs de 64 bits sont placés dans des canaux mémoire séparés. Idem avec du triple ou quadruple canal, mais c'est alors trois ou quatre blocs de 64 bits qui sont dispersés dans des canaux consécutifs. {|class="wikitable" |+ Adresse mémoire |- ! colspan="6" | Sans entrelacement |- | Numéro de canal/barrette || Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne || Position de l'octet dans un bloc de 64 bits |- | colspan="6" | |- ! colspan="6" | Avec entrelacement |- | Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne || Numéro de canal/barrette || Position de l'octet dans un bloc de 64 bits |} Les contrôleurs de SDRAM précédents sont assez basiques et ne représentent pas les contrôleurs les plus évolués. Ils étaient utilisés sur les anciens PC, à une époque où ils étaient encore sur la carte mère du processeur. Mais de nos jours, le contrôleur mémoire est intégré au processeur. Et il incorpore de nombreuses optimisations afin de gagner en performances. Malheureusement, ces optimisations sont elles-mêmes dépendantes des optimisations intégrées dans le processeur, ce qui fait qu'on ne peut pas en parler ici. Elles demandent que le contrôleur mémoire reçoive plusieurs accès mémoire simultanés, ce qui n'a aucun sens à ce stade du cours. Aussi, nous allons les passer sous silence pour le moment. Nous les verrons dans le chapitre sur le parallélisme mémoire. L'entrelacement est géré juste avant le séquenceur mémoire. Le '''circuit d'entrelacement''' intervertit certains bits de l'adresse lors des accès mémoires. On trouve aussi des mémoires FIFOs pour mettre en attente les requêtes mémoire provenant du processeur. Elles permettent de recevoir plusieurs accès mémoire consécutifs. Si vous vous demandez comment plusieurs accès mémoire consécutifs peuvent avoir lieu, sachez que c'est une bonne question. Pour le moment, nous allons dire que ces accès proviennent du processeur ou de la mémoire cache, sans expliquer comment. Le contrôleur mémoire reçoit une série d'accès mémoire assez rapprochés, qu'il doit exécuter au mieux. [[File:Controleur mémoire d'une SDRAM avec entrelacement.png|centre|vignette|upright=2.5|Contrôleur mémoire d'une SDRAM avec entrelacement.]] <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=La désambiguïsation mémoire | prevText=La désambiguïsation mémoire | next=Les processeurs superscalaires | nextText=Les processeurs superscalaires }} </noinclude> su9tssz6qblr3pnp180qt4m4ptjgj65 764652 764651 2026-04-23T14:25:39Z Mewtow 31375 /* L'entrelacement sur les mémoires SDRAM */ 764652 wikitext text/x-wiki Dans ce chapitre, nous allons voir les techniques qui permettent de gérer plusieurs accès mémoire simultanés directement au niveau du cache ou de la mémoire RAM. Par plusieurs accès mémoire simultanés, vous pensez sans doute à l'usage de cache multiports, voire de mémoires RAM multiport. C'est en effet une solution, mais ce n'est clairement pas la seule. Par exemple, il est possible de pipeliner l'accès au cache, voire à la mémoire RAM. Il existe de nombreuses techniques de '''parallélisme mémoire''', et nous allons les voir dans ce chapitre. Pour donner un exemple, il est possible de pipeliner les accès mémoire. Il est possible de pipeliner l'accès au cache et/ou l'accès à la mémoire, et nous verrons comment faire dans ce chapitre. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, qu'ils aient ou non un pipeline dynamique. Par contre, pipeliner la mémoire est une technique ancienne, aujourd'hui peu utilisée. Elle était utilisée sur des très vieux ordinateurs, qui avaient un pipeline mais pas de mémoire cache. Mais de nos jours, les mémoires SDRAM et DDR ne sont pas adaptées pour ça, il est très difficile de les pipeliner correctement. Le parallélisme mémoire est utile aussi bien sur des processeurs à émission dans l'ordre que dans le désordre. Par exemple, un processeur ''in-order'' avec des lectures non-bloquantes peut e, profiter. Il peut techniquement lancer une seconde lecture, après avoir rencontré une première lecture non-bloquante. En général, le nombre de lectures consécutives est cependant limité à moins d'une dizaine. À l'opposé, les processeurs sans lectures non-bloquantes profitent pas du parallélisme mémoire. À la rigueur, ils peuvent en profiter s'ils intègrent des techniques de préchargement. Pendant que le processeur exécute une lecture/écriture, il peut précharger une autre donnée en parallèle. Inutile de dire que sans pipeline processeur, le parallélisme mémoire ne sert pas à grand-chose. ==Le ''line fill buffer'' et les techniques associées== Lors d'un défaut de cache, le processeur doit attendre que toute la ligne de cache soit chargée avant d'être utilisable. Or, la taille d'une ligne de cache est supérieure à la largeur du bus mémoire, ce qui fait qu'une ligne de cache est chargée en plusieurs fois, morceaux par morceaux, mot mémoire par mot mémoire. Le chargement peut se faire directement dans le cache, mais ce n'est pas une solution très pratique. À la place, beaucoup de processeurs ajoutent une mémoire tampon entre la RAM et le cache, appelée le '''tampon de remplissage de ligne''' (''line-fill buffer'') dans les processeurs modernes. Lors d'un défaut de cache, le processeur charge la donnée de la RAM dans le tampon de remplissage de ligne, mot mémoire par mot mémoire. Une fois plein, le tampon de remplissage de ligne est recopié dans la ligne de cache. Le tampon de remplissage de ligne contient une ligne de cache, si ce n'est que certains bits de contrôle ne sont pas présents. Le tampon de remplissage de ligne contient un bit de validité pour chaque mot mémoire de la ligne de cache, qui indiquent si le mot mémoire a été chargé. Par exemple, prenons un processeur 64 bits, qui gère donc des mots mémoire de 8 octets, avec des lignes de cache 256 octets/32 mots mémoire. Le tampon de remplissage de ligne contiendra 32 bits de validité. Si le processeur a chargé les 6 premiers mots mémoire, les 6 premiers bits de validité seront mis à 1, les autres seront encore à zéro. Il faut noter que la disponibilité de la ligne complète se détermine assez facilement en faisant un ET logique entre tous les bits de validité. Le processeur sait ainsi quand la ligne de cache est disponible entièrement et donc quand la transférer dans le cache. La même chose existe avec une hiérarchie de cache, sauf que l'on trouve une mémoire tampon entre chaque niveau de cache. Il y a un tampon de remplissage de ligne entre le cache L1 et le cache L2, entre le cache L2 et le L3, etc. Si le cache ne gère qu'un seul défaut de cache à la fois, le ''fill line buffer'' est une mémoire très simple, qui ne mémorise qu'une seule ligne de cache. Et cela vaut aussi bien pour un cache bloquant que non-bloquant. Mais sur les caches capables de gérer plusieurs défauts simultanés, le tampon de remplissage de ligne est une mémoire de type FIFO ou LIFO, capable de mémoriser plusieurs lignes de cache. Notons que le tampon de remplissage de ligne est très utile pour implémenter certaines techniques, par exemple le contournement du cache. Nous avions vu dans le chapitre sur le cache que certains accès mémoire doivent contourner le cache, pour des raisons de cohérence des caches. C'est notamment nécessaire pour accéder aux périphériques, mais c'est aussi utile pour des raisons de performances dans des cas très spécifiques. Les accès qui contournent le cache se font directement dans le tampon de remplissage de ligne : le processeur écrit ou lit les données depuis ce tampon de remplissage de ligne, sans accéder au cache. ===L'''early restart'' et le ''critical word load''=== La présence du ''line buffer'' permet une optimisation assez intéressante, qui permet de réduire la latence des défauts de cache. L'optimisation consiste à lire un mot mémoire dans le tampon de remplissage de ligne, même si la ligne de cache complète n'a pas encore été chargée. Il existe deux manières de faire cela, qui portent les noms d'''early restart'' et de ''critical word load''. La première est la version la plus simple, la seconde est plus complexe mais plus performante. Avec la technique d''''''early restart''''', la ligne de cache est chargée normalement, en partant de son premier mot mémoire. Dès que le mot mémoire lu/écrit par le processeur est copié dans le ''line fill buffer'', il est envoyé au processeur immédiatement. Illustrons le tout par un exemple, où une ligne de cache fait 16 mots mémoire. Le processeur effectue une lecture, qui lit le 5ème mot mémoire. Sans ''early restart'', le processeur doit charger les 16 mots mémoire avant de faire la lecture dans le cache. Avec ''early restart'', le processeur reçoit la donnée dès que le 5ème mot mémoire est disponible. Le processeur doit attendre que les 4 premiers mots mémoire soient chargés, puis le 5ème mot mémoire arrive et est envoyé directement au processeur, il est lu directement depuis le ''line fill buffer''. Les 11 mots mémoire suivants sont ensuite chargés dans le cache pendant que le processeur fait des calculs dans son coin. Le ''critical word load'' est une optimisation de la technique précédente où le chargement de la ligne de cache commence directement à la donnée demandée par le processeur. Pour reprendre l'exemple précédent, où le processeur demande le 5ème mot mémoire sur 16, le ''critical word load'' charge le 5ème mot mémoire en premier et l'envoie au processeur, ce qui fait qu'il est chargé très rapidement. Pas besoin d'attendre que le processeur charge les 4 mots mémoire précédents comme avec l'''early restart''. Dans le détail, le processeur charge le 5ème mot mémoire en premier, puis charge les 11 suivants, et termine par les 4 mots mémoire du début. En clair, le ''critical word load'' commence par charger le mot mémoire lu, puis les blocs suivants, avant de revenir au début du bloc pour charger les blocs restants. Ainsi, la donnée demandée par le processeur sera la première disponible. Pour cela, l'organisation du tampon de remplissage de ligne est modifiée de manière à rendre cela possible. Il a une taille égale à une ligne de cache complète, qui contient elle-même plusieurs mots mémoire. Dans le ''line-fill buffer'', chaque mot mémoire est stocké avec un tag, qui indique l'adresse du mot mémoire stocké dans le ''line-fill buffer''. Le ''line-fill buffer'' est donc un cache un peu particulier, qui fonctionne comme un cache du point de vue du processeur, comme une mémoire FIFO pour les transferts avec le cache. Ainsi, un processeur qui veut lire dans le cache après un défaut peut accéder à la donnée directement depuis le tampon de remplissage de ligne, alors que la ligne de cache n'a pas encore été totalement recopiée en mémoire. ==Les caches pipelinés== Il est possible de pipeliner l'accès au cache, ce qui demande juste de rajouter des registres au bon endroits et quelques circuits de contrôle. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, même ceux avec un pipeline dynamique. Et l'implémentation peut se faire de plusieurs manières différentes. Il faut dire que découper une mémoire cache en plusieurs étapes peut se faire de plusieurs manière. Commençons par la plus simple. Beaucoup de processeurs des années 2000 avaient une fréquence peu élevée comparée aux standards d'aujourd'hui, ce qui fait que leur cache avait bien un temps d'accès d'un cycle d'horloge. Mais l'accès au cache se faisait en deux cycles d'horloge : un cycle pour calculer une adresse et un cycle pour l'accès mémoire proprement dit. Et ces deux étapes étaient pipelinées, à savoir que deux micro-opérations mémoire peuvent s'exécuter en même temps : une dans l'étage de calcul d'adresse, une autre dans le cache. L'unité mémoire était donc pipelinée, alors que l'accès au cache ne l'était pas. Les processeurs qui implémentaient cette technique regroupent les micro-architectures K5 et K6 d'AMD, les processeurs Intel de micro-architecture P6 (Pentium 2 et 3) et quelques autres. Il est aussi possible de pipeliner l'accès au cache lui-même. Avec un cache pipeliné, l'accès au cache ne se fait pas en un seul cycle, mais en plusieurs. Cependant, on peut lancer un nouvel accès au cache à chaque cycle d'horloge, comme un processeur pipeliné lance une nouvelle instruction à chaque cycle. L'implémentation est assez simple : il suffit d'ajouter des registres dans le cache. Pour cela, on profite que le cache est composé de plusieurs composants séparés, qui échangent des données dans un ordre bien précis, d'un composant à un autre. De plus, le trajet des informations dans un cache est linéaire, ce qui les rend parfois pour l'usage d'un pipeline. ===Le ''pipelining'' des caches ''direct-mapped''=== Voici ce qui se passe avec un cache directement adressé. Pour rappel ce genre de cache est conçu en combinant une mémoire RAM, généralement une SRAM, avec quelques circuits de comparaison et un MUX. L'accès au cache se fait globalement en deux étapes : la première lit la donnée et le ''tag'' dans la SRAM, et on utilise les deux dans une seconde étape. Nous avions vu il y a quelques chapitre comment pipeliner des mémoires, dans le chapitre sur les mémoires évoluées. Et bien ces méthodes peuvent s'utiliser pour la mémoire RAM interne au cache !L'idée est d'insérer un registre entre la sortie de la RAM et la suite du cache, pour en faire un cache pipéliné, à deux étages. Le premier étage lit dans la SRAM, le second fait le reste. L'implémentation sur les caches associatifs à plusieurs voies est globalement la même à quelques détails près. [[File:Cache directement adressé pipeliné - deux étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - deux étages]] Avec le circuit précédent, il est possible d'aller plus loin, cette fois en pipelinant l'accès à la mémoire RAM interne au cache. Pour comprendre comment, rappelons qu'une mémoire SRAM est composée d'un plan mémoire et d'un décodeur. L'accès à la mémoire demande d'abord que le décodeur fasse son travail pour sélectionner la case mémoire adéquate, puis ensuite la lecture ou écriture a lieu dans cette case mémoire. L'accès se fait donc en deux étapes successives séparées, on a juste à mettre un registre entre les deux. Il suffit donc de mettre un gros registre entre le décodeur et le plan mémoire. [[File:Cache directement adressé pipeliné - trois étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - trois étages]] Et on peut aller encore plus loin en découpant le décodeur en deux circuits séparés. En effet, rappelez-vous le chapitre sur les circuits de sélection : nous avions vu qu'il est possible de créer des décodeurs en assemblant des décodeurs plus petits, contrôlés par un circuit de prédécodage. Et bien on peut encore une ajouter un registre entre ce circuit de prédécodage et les petits décodeurs. : Théoriquement, toute l'adresse est fournie d'un seul coup au cache, la quasi-totalité des processeurs présentent l'adresse complète à un cache pipeliné. Mais le Pentium 4 fait autrement. Il faut noter que les premiers étages manipulent l'indice dans la SRAM, qui est dans les bits de poids faible, alors que les étages ultérieurs manipulent le ''tag'' qui est dans les bits de poids fort. Les concepteurs du Pentium 4 ont alors décidé de présenter les bits de poids faible lors du premier cycle d'accès au cache, puis ceux de poids fort au second cycle. Pour cela, l'ALU fonctionnait à une fréquence double de celle du processeur, tout comme le cache L1. Il n'y avait pas de pipeline proprement dit, mais cela réduisait grandement la latence d'accès au cache. ===Le ''pipelining'' des caches splittés/sériels=== Il est aussi possible de pipeliner un cache dits splittés, aussi appelés à accès sériel. Pour rappel, les caches à accès sériel vérifient si il y a succès ou défaut de cache, avant d'accéder aux lignes de cache en cas de succès. Ils font donc différemment des autres caches, qui accèdent à une ligne de cache, avant de déterminer s'il y a succès ou défaut en lisant le tag de la ligne de cache. Les caches sériels disposent de deux SRAM : une pour les tags des lignes de cache et une pour les données. Ils accèdent à la SRAM pour les tags, avant d’accéder à la SRAM des données en cas de succès de cache. Vu que l'accès se fait en deux étapes, une vérification des tags suivie de la lecture/écriture des données, il est facile à pipeliner. Pipeliner le cache permet de régler le problème des accès au cache L1, et elle est tout le temps utilisé sur les processeurs modernes. Mais que faire en cas de défaut de cache ? ==Les caches non bloquants== Un '''cache bloquant''' est un cache auquel le processeur ne peut pas accéder pendant un défaut de cache. Il faut attendre que la lecture ou écriture en RAM soit terminée avant de pouvoir utiliser de nouveau le cache. Un '''cache non bloquant''' n'a pas ce problème : on peut l'utiliser pendant un défaut de cache. Les caches non bloquants permettent de démarrer une nouvelle lecture ou écriture alors qu'une autre est en cours, ce qui permet d'exécuter plusieurs lectures ou écritures en même temps. ===Les ''Miss Handling Status Registers''=== Lors d'un défaut de cache, la mémoire RAM est consultée pendant le défaut de cache, mais le cache est inutilisé. Un défaut de cache n'utilise pas le cache, ce n'est qu'un accès en mémoire RAM, sur le bus mémoire (ou un accès aux niveaux de cache inférieurs, peu importe). Le cache en lui-même est laissé libre, rien n’empêche d'y accéder, il est en réalité intrinsèquement non-bloquant. Les caches, bloquants comme non-bloquants, sont en réalité composés d'une mémoire cache proprement dite, entourée de circuits qui servent d'interface entre le processeur et le cache lui-même. Et parmi les circuits tout autour, certains gèrent l'accès au cache lors d'un défaut de cache. Ils sont regroupés sous le terme de '''''Miss Handling Architecture''''' (MHA). La différence entre un cache bloquant et un cache non-bloquant est en réalité liée à l'implémentation de la MHA. Les caches bloquants coupent volontairement l'accès au cache lors d'un défaut, car les défauts sont plus simples à gérer ainsi. Le défaut de cache rapatrie une donnée depuis la RAM, qui sera écrite dans le cache. Et il ne faut pas qu'une tentative d'accès à cette donnée ait lieu avant qu'elle ne soit chargée. Mais ce blocage est général et touche tout le cache, alors que seule une ligne de cache est concernée par le défaut de cache. L'idée derrière un cache non-bloquant est que seule la ligne de cache est bloquée, mais les autres sont accessibles. L'idée est alors de mémoriser les lignes de cache concernées par le défaut de cache, afin d'en bloquer l'accès. À chaque accès, on vérifie si la ligne de cache est déjà réservée par un défaut de cache. La lecture/écriture est alors bloquée si c'est le cas, mais elle accepte les accès sinon. Pour cela, la ''Miss Handling Architecture'' contient des registres qui mémorisent des informations sur les défauts de cache en cours. Ils portent le nom de '''''miss status handling registers''''', que j’appellerais dorénavant MSHR, qui sont aussi appelés des '''''miss buffer'''''. Le contenu des MSHR varie beaucoup suivant le processeur, mais ces derniers stockent au minimum les informations suivantes : * Le numéro de la ligne de cache dans laquelle les données sont chargées. * Un bit de validité qui indique si le MSHR est vide ou pas, qui est mis à 0 quand le défaut de cache est résolu. * Un ou plusieurs champs de lecture/écriture, qui contiennent des informations sur la lecture/écriture. ** Pour une lecture, elle contient des informations sur la destination de la donnée, à savoir qui prévenir quand le défaut de cache est terminé. C'est parfois un nom/numéro de registre (celui dans lequel charger la donnée), mais c'est souvent le numéro de l'entrée dans le ''load/store queue''. ** Pour les écritures, elle contient la donnée à écrire, ou éventuellement un numéro de ''load/store queue'' où se trouve la donnée à écrire. Il faut noter que le bus mémoire ne peut gérer qu'un seul défaut de cache à la fois. Aussi, il est intéressant de regarder ce qui se passe lorsqu'un second défaut de cache survient, pendant qu'un premier est en cours. Dans ce cas, il y a deux réponses qui correspondent à deux types de caches non-bloquants, qui portent les noms barbares de caches de type succès après défaut et défaut après défaut. Sur le premier type, il ne peut pas y avoir plusieurs défauts de cache simultanés. Dès qu'un second défaut de cache survient, le cache stoppe son activité et on ne peut plus démarrer de nouvelle lecture/écriture, tant que le premier défaut de cache n'est pas résolu. Le second type est plus souple et autorise la survenue de plusieurs défauts de cache simultanés. Du moins, jusqu'à une certaine limite, car le cache ne peut supporter qu'un nombre limité d'accès mémoires simultanés (pipelinés). ===Les accès simultanés à une même ligne de cache=== Il arrive que le processeur fasse plusieurs accès mémoire simultanés à la même ligne de cache. Si la ligne de cache en question n'a pas encore été chargée dans le cache, alors on a plusieurs défauts de cache consécutifs pour la même ligne de cache, et le cache non-bloquant doit gérer la situation. Pour la suite, il va falloir faire une petite distinction entre les défauts primaires et secondaires. Imaginons qu'un défaut de cache ait lieu et demande à charger une donnée dans la ligne de cache numéro N. Il s'agit du premier défaut impliquant cette ligne de cache précise, ce qui lui vaut le nom de '''défaut de cache primaire'''. Mais par la suite, d'autres accès mémoire à la même ligne de cache ont lieu, alors que la ligne de cache n'est pas encore disponible. Dans ce cas, il s'agit de '''défauts de cache secondaires'''. Pour l'unité d'accès mémoire, les défauts de cache primaire et secondaire sont différents (ils prennent tous une entrée dans la ''load/store queue''). Mais pour le cache, ils ne correspondent qu'à un seul accès au cache : celui qui demande de charger la ligne de cache demandée. Les défauts de cache primaire et secondaire à la même ligne de cache se voient attribuer un MSHR unique. La gestion des défauts de cache secondaires dépend du cache non-bloquant. La solution la plus simple ne permet pas les défauts de cache secondaires. Le cache ne permet pas deux défauts de cache simultanés pour la même ligne de cache. Les autres solutions le permettent, en fusionnant des accès simultanés à la même ligne de cache en un seul au niveau des MSHR. Dans tous les cas, détecter les défauts de cache secondaires sont un problème qu'il faut détecter. La ''Miss Handling Architecture'' doit détecter les défauts de cache secondaire. Pour cela, elle procède comme suit. Lors de chaque défaut de cache, la MHA récupère le numéro de la ligne de cache associé. Il vérifie alors chaque MSHR pour vérifier s'il contient le numéro en question. S'il n'y a aucune correspondance dans les MSHR, alors c'est signe que le défaut de cache est un défaut primaire. Mais s'il y en a une, alors c'est un défaut secondaire. Évidemment, cela signifie que lors d'un défaut de cache, le numéro de ligne de cache est envoyé à tous les MSHR, pour comparaison. Les MSHR sont donc regroupés dans une mémoire associative, une sorte de mini-cache, faciliter l'implémentation. Lors d'un défaut de cache primaire, l'accès à un cache non-bloquant se fait comme suit : le processeur envoie une adresse au cache, accède à celui-ci, et détecte la survenue d'un défaut de cache. Il en profite alors pour attribuer une ligne de cache dans laquelle sera chargée ce défaut. L'attribution est très simple dans le cas des caches ''direct mapped'', ou associatifs par voie, pour lesquels l'attribution se fait assez simplement. Il mémorise alors cette information dans les MSHR, après avoir vérifié que le défaut de cache n'était pas un défaut secondaire. Lors des accès ultérieurs à une adresse proche, censée être dans la même ligne de cache, le processeur va encore une fois rencontrer un défaut de cache. Il va alors déterminer le numéro de la ligne de cache associée à l'adresse, et comparer ce numéro avec les MSHRs. Si un MSHR contient ce numéro, c'est signe que le défaut de cache est un défaut secondaire. La MHA réagit alors différemment selon le processeur considéré. Une première solution n'autorise pas les défauts de cache secondaires. Si l'un d'entre eux survient, le processeur est gelé par un ''pipeline stall'', une bulle de pipeline. Une autre solution fusionne les défauts de cache secondaires avec le défaut de cache primaire : tout cela ne correspond qu'à un seul défaut de cache pour lui. ===Les MSHR simples=== Un cache non-bloquant à '''MSHR simple''' contient juste plusieurs MSHR qui mémorisent juste un numéro de ligne de cache, un bit de validité, le champ de lecture/écriture, et l'adresse exacte de lecture/écriture. Avec cette organisation, il est possible d'avoir plusieurs défauts de cache séparés, mais à la condition que chaque défaut accède à une ligne de cache différente. Deux accès simultanés à une même ligne de cache ne sont pas possibles, les défauts de cache secondaires ne sont pas autorisés. Ainsi, chaque défaut de cache se voit attribuer son propre MSHR, chacun contient un numéro de ligne de cache différent. Pour comprendre pourquoi c'est impossible de gérer les défauts secondaires, il faut regarder le champ de lecture/écriture. Si on veut effectuer plusieurs écritures consécutives à la même adresse, le MSHR n'aura pas de quoi mémoriser les deux données à écrire. Il pourra mémoriser la première donnée à écrire, pas la seconde. Même chose lors d'une lecture : le champ lecture/écriture peut mémorisr la destination de la première lecture, pas de la seconde. L'avantage est que la MHA n'a besoin que des MSHR et de quelques circuits annexes. Les autres solutions rajoutent des circuits annexes pour gérer les défauts de cache secondaires, qui utilisent beaucoup de circuits. Le cout en circuit est donc élevé, mais le gain en performance est là. Passons maintenant aux caches non-bloquants qui autorisent les défauts de cache secondaire. La solution la plus simple consiste à utiliser ===Les MSHR adressés implicitement=== Avec les '''MSHR adressés implicitement''', il est possible de fusionner plusieurs accès mémoire à une même ligne de cache, mais sous une condition très importante : ces accès lisent/écrivent des mots mémoire différents. Par exemple, imaginons qu'une ligne de cache contienne 8 mots mémoire de 64 bits. Si un premier accès mémoire lit le mot mémoire numéro 7 (dernier mot mémoire de la ligne), et le second accès le mot mémoire numéro 3, alors la fusion est possible. Mais si deux accès mémoire veulent lire/écrire le mot mémoire numéro 7, alors la fusion n'est pas possible et le processeur se bloque, un ''pipeline stall'' survient. Un MSHR adressé implicitement est un MSHR simple, qui contient naturellement un numéro de ligne de cache (tag) et un bit de validité, sauf que le champ de lecture/écriture est dupliqué. Les différents champs lecture/écriture d'un MSHR sont regroupés dans une mémoire RAM/cache qui contient autant d'entrées qu'il y a de mot mémoire dans une ligne de cache. Chaque entrée de la table est associée à un mot mémoire de la ligne de cache et stocke des informations sur celui-ci. Une entrée mémorise, au minimum : * Un champ de lecture/écriture qui contient soit la destination de la lecture, soit la donnée à écrire. * Un bit R/W permettant d'interpréter correctement le champ de lecture/écriture. * Un bit de validité pour chaque entrée de la table, qui dit si un défaut de cache antérieur accède déjà à ce mot mémoire. La fusion de deux défauts de cache est ainsi assez simple. Un défaut de cache primaire/secondaire configure l'entrée associée au mot mémoire lu/écrit. Si un défaut secondaire ultérieur lit/écrit un mot mémoire différent (dont le bit de validité de l'entrée dans la table est à zéro), pas de conflit : il configure une autre entrée, vide. Mais s'il lit/écrit un mot mémoire pour lequel l'entrée de la table est occupée, il y a conflit, le processeur est bloqué par un ''pipeline stall'', une bulle de pipeline. Avec cette organisation, le nombre de MSHR indique combien de lignes de cache peuvent être lues en même temps depuis la mémoire. Quant au nombre d'entrées par MSHR, il détermine combien d'accès mémoires qui ne se recouvrent pas peuvent avoir lieu en même temps. ===Les MSHR adressés explicitement=== Les '''MSHR adressés explicitement''' n'ont pas les contraintes des MSHR adressés implicitement. Avec eux, il est possible d'avoir plusieurs défauts de cache pointant vers la même ligne de cache, mais aussi vers le même mot mémoire. De tels défauts de cache apparaissent sur les processeurs à exécution dans le désordre, mais sont très rares, voire inexistants, sur les processeurs ''in-order''. L'idée est encore que chaque MSHR est associée à une table mémoire qui mémorise des entrées. Cette table n'est autre qu'une mémoire FIFO. Sauf que cette fois-ci, une entrée n'est pas associée à un mot mémoire. Une entrée est associée à un défaut de cache. Une entrée mémorise là encore la destination de la lecture ou la donnée de l'écriture suivi du bit R/W, ainsi que d'un bit de validité par entrée, mais aussi : la position du mot mémoire lu dans la ligne de cache. C'est cette dernière information qui n'était pas présente dans les MSHR adressés implicitement. De plus, le nombre d'entrée par MSHR n'est pas égal au nombre de mots mémoires dans une ligne de cache, mais peut être arbitrairement grand. L'avantage d'un tel cache est qu'il est capable de traité les défauts secondaires concernant un même mot mémoire, et ce, car les accès à la ligne de cache concernée se produisent dans l'ordre de génération des défauts (la table mémoire est une FIFO). ===Les MSHRs inversés=== Généralement, plus on veut supporter de défauts de cache, plus le nombre de MSHR et d'entrées augmente. Mais au-delà d'un certain nombre d'entrées et de MSHR, les MSHR adressés implicitement et explicitement ont tendance à bouffer un peu trop de circuits. Utiliser une organisation un peu moins gourmande en circuits est donc une nécessité. Cette organisation plus économe se base sur des MSHR inversés. Les MSHR inversés ne contiennent qu'une seule entrée, en quelque sorte : au lieu d’utiliser n MSHR de m entrées chacun, on va utiliser n × m MSHR inversés. La différence, c'est que plusieurs MSHR peuvent contenir un tag identique, contrairement aux MSHR adressés implicitement et explicitement. Lorsqu'un défaut de cache a lieu, chaque MSHR est vérifié. Si jamais aucun MSHR ne contient de tag identique à celui utilisé par le défaut, un MSHR vide est choisi pour stocker ce défaut, et une requête de lecture en mémoire est lancée. Dans le cas contraire, un MSHR est réservé au défaut de cache, mais la requête n'est pas lancée. Quand la donnée est disponible, les MSHR correspondant à la ligne qui vient d'être chargée vont être utilisés un par un pour résoudre les défauts de cache en attente. ===Les MSHR intégrés au cache=== Certains chercheurs ont remarqué que pendant qu'une ligne de cache est en train de subir un défaut de cache, celle-ci reste inutilisée, et son contenu est destiné à être perdu une fois le défaut de cache résolu. Ils se sont dit que, plutôt que d'utiliser des MSHR séparés, il vaudrait mieux utiliser la ligne de cache pour stocker les informations sur les défauts de cache en attente dans cette ligne de cache. Pour éviter tout problème, il faut rajouter un bit dans les bits de contrôle de la ligne de cache, qui sert à indiquer que la ligne de cache est occupée : un défaut de cache a eu lieu dans cette ligne, et elle stocke donc des informations pour résoudre les défauts de cache. ==L'entrelacement mémoire== Nous venons de voir comment une mémoire cache peut gérer plusieurs accès mémoire simultanés. Il se trouve que ce genre d'optimisation dépasse largement le cadre du cache. Les mémoires RAM ne sont pas en reste, elles aussi ! Il est possible de pipeliner l'accès à une mémoire RAM, en utilisant des techniques dites d''''entrelacement'''. Précisons cependant que cette optimisation n'est pas utilisée sur les mémoires SDRAM ou DDR modernes, du moins pas sans modifications majeures. Mais divers ordinateurs assez anciens, dont des superordinateurs, ont utilisé cette technique. La technique demande d'utiliser plusieurs mémoires séparées. Sur les anciens ordinateurs, les mémoires en question sont des chips mémoire, à savoir des circuits intégrés de DRAM. Pour faire plus général, nous allons utiliser le terme de '''banques''', ou encore de bancs mémoire. La différence est qu'il existe des mémoires multi-banque, qui regroupent plusieurs banques indépendantes dans un seul boitier, dans un seul chip mémoire. En clair, de telles mémoires regroupent plusieurs sous-mémoires dans un seul circuit intégré. Il est aussi possible d'utiliser plusieurs chips mémoire séparés, chacun ayant une banque. Reste à combiner ces banques pour former un pseudo-pipeline. ===Utiliser plusieurs banques sans entrelacement=== Sans optimisation particulière, les adresses sont réparties dans les banques comme indiqué ci-dessous. Il s'agit de ce qui s'appelle un arrangement horizontal, et nous avions vu celui-ci dans le chapiutre sur les mémoires SDRAM. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Avec cet arrangement, les bits de poids fort de l'adresse sont utilisées pour sélectionner la banque adéquate, et le reste de l'adresse est envoyé sur le bus d'adresse. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Adresse de banque !! Adresse dans la banque |- | Quelques bits de poids fort || Reste de l'adresse |} L'entrelacement change cette répartition, afin d'accélérer les accès mémoire. L'entrelacement classique vise à améliorer les accès à des adresses mémoires consécutives, mais des formes plus évoluées d'entrelacement visent à optimiser des accès mémoire arbitraires. Voyons-les dans l'ordre, du plus simple au plus complexe. ===L'entrelacement classique=== Avec l''''entrelacement classique''', des adresses consécutives sont réparties dans des banques différentes. [[File:Répartition des adresses dans une mémoire interleaved.png|centre|vignette|upright=1.5|Répartition des adresses dans une mémoire interleaved.]] L'entrelacement simple permet d'accélérer les accès mémoire qui se font à des adresses consécutives. Avec l'entrelacement, chaque accès mémoire tombe dans une banque mémoire différente, ce qui fait qu'on peut démarrer un nouvel accès mémoire à chaque cycle d'horloge. Pendant qu'une banque est occupée par un accès mémoire, on démarre le suivant dans une autre banque, et ainsi de suite. Le tout se termine soit quand on a épuisé toutes les banques libres, soit quand l'accès en rafales se termine. Pas besoin d'attendre que la mémoire ait fini sa lecture/écriture avant de démarrer la lecture/écriture suivante. [[File:Pipemining mémoire.png|centre|vignette|upright=2.5|Pipelining mémoire]] L'animation suivante illustre bien le processus, avec quatres banques. [[File:Interleaving.gif|centre|vignette|upright=2.5|Pipelining mémoire]] L'entrelacement simple utilise les bits de poids faible pour sélectionner la banque, et les bits de poids fort pour la case mémoire. [[File:Adresse mémoire d'une mémoire entrelacée.png|centre|vignette|upright=2|Adresse mémoire d'une mémoire entrelacée]] Précisons que le temps d'accès mémoire ne change pas beaucoup avec l'entrelacement. Par contre, on peut faire plus d'accès mémoire simultanés. On peut démarrer un accès mémoire par cycle d'horloge, mais l'accès en lui-même prend plusieurs cycles. Les mémoires à entrelacement ont donc un débit supérieur aux mémoires qui ne l'utilisent pas. L'entrelacement classique pourrait être utilisé pour accélérer les transferts entre mémoire RAM et cache, qui se font en blocs d'adresses consécutives. Mais de nos jours, il n'est pas utilisé dans cette optique. Il faut dire qu'il est redondant avec le mode rafale des mémoires DRAM. Les deux font globalement la même chose et on voit mal comment utiliser les deux en même temps. Le mode rafale étant plus simple à implémenter, et plus léger niveau utilisation du bus de commande mémoire, il est préféré à l'entrelacement. Par contre, l'entrelacement a eu son heure de gloire sur d'anciens processeurs qui n'avaient pas de mémoire cache, alors qu'ils disposaient d'un pipeline. L'exemple typique est celui des nombreux ''processeurs vectoriels'', que nous n'avons pas encore abordé à ce stade du cours. De tels processeurs avaient un pipeline, une unité de calcul fortement pipelinée, et parfois même de l'exécution dans le désordre et du renommage de registres ! ===L'entrelacement par décalage=== L'entrelacement simple est très utile pour les accès en rafle ou équivalents. Par contre, il existe de rares situations où il n'est pas optimal. Une de ces situations est celle des accès en enjambée (en ''stride''), où l'on accède en série à des adresses sont séparées par N mots mémoires. De tels accès surviennent quand un logiciel accède à des structures de données spécifiques, à savoir des tableaux de structures, des matrices, ou d'autres structures de données dans le genre. Un cas classique est celui du parcours d'une matrice colonne par colonne. Les matrices de nombres sont mémorisées ligne par ligne, ce qui fait que parcourir une colonne demande de passer d'une ligne à la suivante et de faire des grands sauts en mémoire RAM, mais tous espacés par la même distance. Avec l'entrelacement simple, les accès en enjambée sont moins performants que les accès à des adresses consécutives, mais cela ne fonctionne pas trop mal. Le pire cas est celui où l'on a N banques et où les données sont justement placées toutes les N adresses. Des accès consécutifs vont tous tomber dans la même banque, on ne peut plus accéder à des banques différentes en parallèle. Mais en dehors de ce cas, on voit une amélioration par rapport à la situation sans entrelacement. Pour obtenir des performances maximales pour les accès en enjambées, il faut répartir les mots mémoires dans la mémoire autrement. L'organisation idéale est la suivante. : Dans les explications qui vont suivre, la variable N représente le nombre de banques, qui sont numérotées de 0 à N-1. On commence par organiser les N premières adresses comme une mémoire entrelacée simple : l'adresse 0 correspond à la banque 0, l'adresse 1 à la banque 1, etc. Pour le bloc suivant, on décale tout d'une adresse, à savoir qu'on commence à remplir les banques à partir de la seconde, non de la première. Une fois la fin du bloc atteinte, on finit de remplir le bloc en repartant du début du bloc. Le troisième bloc d'adresse subit le même traitement, sauf qu'on commence à remplir à partir de la seconde banque. Et on poursuit l’assignation des adresses en décalant d'un cran en plus à chaque bloc. Ainsi, chaque bloc verra ses adresses décalées d'un cran en plus comparé au bloc précédent. Si jamais le décalage dépasse la fin d'un bloc, alors on reprend au début. [[File:Mémoire interleaved par décalage.png|centre|vignette|upright=1.5|Mémoire entrelacée par décalage.]] En faisant cela, on remarque que les banques situées à N adresses d'intervalle sont différentes. Dans l'exemple du dessus, nous avons ajouté un décalage de 1 à chaque nouveau bloc à remplir. Mais on aurait tout aussi bien pu prendre un décalage de 2, 3, etc. Dans tous les cas, on obtient un entrelacement par décalage. Ce décalage est appelé le pas d'entrelacement, noté P. Le calcul de l'adresse à envoyer à la banque, ainsi que la banque à sélectionner se fait en utilisant les formules suivantes : * adresse à envoyer à la banque = adresse totale / N ; * numéro de la banque = (adresse + décalage) modulo N, avec décalage = (adresse totale * P) mod N. Avec cet entrelacement par décalage, on peut prouver que la bande passante maximale est atteinte si le nombre de banques est un nombre premier. Seulement, utiliser un nombre de banques premier peut créer des trous dans la mémoire, des mots mémoires inadressables. Pour éviter cela, il faut faire en sorte que N et la taille d'une banque soient premiers entre eux : ils ne doivent pas avoir de diviseur commun. Dans ce cas, les formules se simplifient : * adresse à envoyer à la banque = adresse totale / taille de la banque ; * numéro de la banque = adresse modulo N. ===L'entrelacement pseudo-aléatoire=== Une dernière méthode de répartition consiste à répartir les adresses dans les banques de manière "pseudo-aléatoire". L'idée exacte est de faire passer l'adresse dans une '''fonction de hachage''', plus ou moins complexe. Pour rappel, une fonction de hachage prend une entrée de grande taille et fournit en sortie un résultat de petite taille. Idéalement, elles doivent donner des résultats aussi différents que possible pour des entrées similaires, histoire de simuler une sorte de pseudo-aléatoire. Ici, une adresse est transformée en un numéro de banque. Une fonction de hachage simple ne fait que permuter des bits de l'adresse pour obtenir son résultat. Elle ne fait qu'échanger des bits de place avant de couper l'adresse en deux morceaux : un pour la sélection de la banque, et un autre pour la sélection de l'adresse dans la banque. Cette permutation est fixe, et ne change pas suivant l'adresse. Des fonctions de hachage plus complexes font des XOR bit à bit entre certains bits de l'adresse. Il existe aussi d'autres techniques qui donnent le numéro de banque à partir d'un polynôme modulo N, appliqué sur l'adresse. ==Les contrôleurs mémoires SDRAM/DDR optimisés== Après avoir vu le cas des mémoires RAM, nous allons nous concentrer sur le cas particulier des mémoires SDRAM. Les mémoires SDRAM et DDR modernes sont capables de gérer plusieurs accès simultanés à la mémoire RAM. Et les contrôleurs mémoire intègrent des optimisations spécifiques afin d'en profiter au maximum. ===La mise en attente des accès mémoire=== Comme pour les mémoires caches, il existe des contrôleurs mémoire bloquants et non-bloquants. Les premiers n'acceptent qu'un seul accès mémoire à la fois, les seconds acceptent plusieurs accès mémoire simultanés. Pour simplifier, nous allons dire que le contrôleur mémoire n’exécute qu'un seul accès mémoire à la fois, et qu'ils met en attente les accès en trop. C'est une simplification assez irréaliste, vu que les mémoires modernes disposent de plusieurs banques accessibles en parallèle. Mais nous introduirons ce détail un peu plus tard. Avec un contrôleur mémoire de ce type, les accès mémoire sont mis en attente dans une mémoire FIFO, histoire de les exécuter dans leur ordre d'arrivée. Les accès mémoire quittent alors la mémoire dans leur ordre d'arrivée, les lectures sont renvoyées dans l'ordre demandé. En plus d'une mémoire FIFO pour les commandes mémoire, on trouve une mémoire FIFO pour les données à écrire. Elle permet d'accumuler plusieurs écritures, tout en permettant que la donnée adéquate soit envoyée à la RAM au bon moment. Il y a aussi une FIFO pour les données lues, qui sert au cas où le cache ne soit pas disponible quand on lui envoie la donnée lue. [[File:Module d'interface avec la mémoire.png|centre|vignette|upright=2.5|Contrôleur mémoire avec mise en attente des requêtes processeur et autres optimisations.]] Une optimisation regroupe plusieurs accès à des données consécutives en un seul accès en rafale. Le contrôleur mémoire analyse les requêtes mises en attente et détecte si certaines se font à des adresses consécutives. Si c'est le cas, il fusionne ces requêtes en une lecture/écriture en rafale. Une telle optimisation est appelée la '''combinaison de lecture''' pour les lectures, et la '''combinaison d'écriture''' pour les écritures. ===Le pipeline des mémoires SDRAM=== Les SDRAM ont une forme de pseudo-pipeline très limité. Elles sont parfois capables de démarrer une commande avant que la précédente soit terminée. Même si elle parait très limitée, cette possibilité est exploitée au mieux par la présence de plusieurs banques dans la mémoire SDRAM. Les chips mémoires de SDRAM sont composés de plusieurs banques, chacune étant une sorte de mini-mémoire miniature. Chaque banque a son propre décodeur, son propre tampon de ligne, ses propres multiplexeurs de colonne, sa logique de rafraichissement mémoire, etc. C'est comme si une SDRAM regroupait plusieurs mémoires séparées dans un même circuit intégré. Et cela permet de faire plusieurs accès mémoire en même temps, dans des banques différentes. Il est possible de lancer un accès mémoire dans deux banques en même temps, par exemple. Ou encore, on peut lancer un nouvel accès mémoire dans une banque si elle est inoccupée. La possibilité améliore grandement la performance de la SDRAM. Mais nous en reparlerons dans un chapitre ultérieur. {|class="wikitable" |+ Pipelining basique sur les SDRAM |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 |- ! Banque Numéro 1 | || || || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || || || |- ! Banque Numéro 2 | || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || |- ! Banque Numéro 3 | || || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || |} L'implémentation n'a rien de vraiment compliqué. Il suffit d'utiliser une mémoire FIFO par banque. Ainsi, les accès dans une même banque se feront en série, dans l'ordre d'arrivée, mais des accès à des banques différentes se feront dans le désordre. Cela ne pose pas de problème car les accès se font à des adresses différentes, ce qui fait qu'il n'y a pas de conflits majeurs. [[File:Gestion parallèle des banques.png|centre|vignette|upright=2.5|Gestion parallèle des banques.]] ===Le ré-ordonnancement des commandes mémoires=== Un contrôleur mémoire moderne est capable de changer l'ordre des requêtes mémoires, pour gagner en performances. En clair : il peut faire l'équivalent mémoire de l'exécution dans le désordre des processeurs haute performance. Une différence majeure est que le contrôleur mémoire ne remet pas les accès mémoire dans l'ordre avant de les envoyer au processeur. C'est le processeur qui remet en ordre les accès mémoire, dans sa ''load queue''. Pour cela, les requêtes mémoire sont "numérotées", à savoir qu'on leur attribue un identifiant binaire. Le contrôleur mémoire exécute les lectures/écritures dans le désordre, mais garde la trace de leur identifiant. Il sait dans quel ordre il exécute les accès mémoire et connait leur latence. Ce qui fait qu'il sait que la donnée lue à tel instant est associée à la requête mémoire numéro X. Pour les lectures, il renvoie la donnée lue avec l'identifiant. Le processeur sait alors à quelle lecture la donnée lue correspond et il se débrouille en interne pour gérer la situation. En clair : le processeur voit les accès mémoire dans le désordre, c'est lui qui les remet en ordre. Maintenant que ces précisions sont faites, posons cette question : quel est l'intérêt de faire les accès mémoire dans le désordre ? Accéder à des banques en parallèle est une réponse possible, mais le ré-ordonnancement est surtout utile si tous les accès mémoire atterrissent dans la même banque. Pour comprendre pourquoi, faisons un rappel rapide sur le tampon de ligne. Les mémoires SDRAM sont des mémoires à tampon de ligne. Elles font un accès mémoire en deux étapes. La première étape recopie une ligne de N * 64 bits dans un tampon interne à la SDRAM. La seconde étape sélectionne une colonne, à savoir une donnée de 64 bits, qui est soit envoyée sur le bus de données pour une lecture, soit modifiée par une écriture. [[File:Mémoire à tampon de ligne.png|centre|vignette|upright=2|Mémoire à tampon de ligne]] Les deux étapes correspondent à deux commandes séparées : une commande ACT qui précise l'adresse de la ligne, et une commande READ ou WRITE qui précise l'adresse de la colonne. [[File:Signaux RAS et CAS.png|centre|vignette|upright=2|Signaux RAS et CAS.]] Les SDRAM permettent de se passer de la première étape si des accès consécutifs se font dans la même ligne. Une fois activée, la ligne reste ouverte et on peut accéder plusieurs fois de suite dedans. On a alors juste à préciser l'adresse de colonne dedans. [[File:Sélection d'une ligne sur une mémoire FPM ou EDO.png|centre|vignette|upright=2|Sélection d'une ligne sur une mémoire FPM ou EDO.]] L'exécution dans le désordre des lectures/écritures SDRAM vise à faire en sorte que des accès consécutifs se fassent dans une même ligne. Elle marche si plusieurs accès à une même ligne ne sont pas consécutifs, et qu'il y a des accès entre les deux. Après ré-ordonnancement, ces accès mémoire deviennent consécutifs, ils sont exécutés l'un à la suite de l'autre. Pour rendre le tout plus concret, voici un exemple. Imaginez que l'on sait les 3 accès mémoire suivants : * Une lecture ligne A ; * Une écriture ligne B ; * Une lecture ligne A. Sans réordonnancent, on doit émettre deux commandes PRECHARGE : une quand on passe de la ligne A à la ligne B, et une autre pour le passage inverse. pour éviter cela, le contrôleur mémoire peut retarder l'écriture, ou au contraire avancer la seconde lecture. le résultat est alors le suivant : * Lecture ligne A ; * Lecture ligne A ; * Une écriture ligne B. On a donc deux accès consécutifs à la même ligne, suivi par une écriture dans une autre ligne. La technique marche parce que l'on a un mix adéquat de lectures et d'écritures. Mais il existe des possibilités de réorganisation autres qui ne sont pas valides. Il faut par exemple prendre garde à éviter d'intervertir lectures et écritures à une même adresse, sous peine de problèmes. Encore une fois, il faut faire attention aux dépendances mémoires. Le controleur mémoire teste les dépendances mémoires avant d'envoyer des commandes à la mémoire DDR/SDRAM. ===L'entrelacement sur les mémoires SDRAM=== L''''entrelacement''' fonctionne sur les mémoires SDRAM, mais il doit être fortement modifié. Concrètement, tout ce qui a étré dit plus est inapplicable pour les SDRAM, du fait de la présence du tampon de ligne. Des adresses consécutives atterrissent déjà dans la même ligne, ce qui fait qu'on n'a pas besoin d'entrelacement au niveau d'une ligne. Et ne parlons pas de ce la présence de rangées et de canaux mémoire ! Cependant, il peut y avoir un entrelacement lié à la présence des banques SDRAM. Pour gérer l'entrelacement, le contrôleur de SDRAM prend en entrée l'adresse envoyée par le processeur, et la découpe en plusieurs champs : un pour sélectionner la banque adéquate, un autre pour la ligne, un autre pour la colonne, un autre pour la rangée. Une solution simple désactive l'entrelacement. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de banque || Adresse de ligne || Adresse de colonne |} Si cette solution fonctionne bien si les accès mémoire sont assez aléatoires, en pratique, mieux vaut utiliser l'entrelacement. Une première méthode d'entrelacement est l''''entrelacement de ligne'''. Elle répartit deux lignes consécutives dans deux banques différentes. Pour comprendre l'idée, prenons un exemple. Imaginons une mémoire avec deux banques et 4 lignes. Imaginons qu'on parcoure/balaye la mémoire RAM en partant des adresses basses. Sans entrelacement de ligne, les accès se feront comme suit, de gauche à droite : {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Banque numéro 1 !! colspan="4" | Banque numéro 2 |- | Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 || Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 |} Avec l'entrelacement de ligne, les accès se feront comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 |- | Ligne 1 || Ligne 1 || Ligne 2 || Ligne 2 || Ligne 3 || Ligne 3 || Ligne 4 || Ligne 4 |} Avec N banques, la première ligne ira dans la première banque, la seconde ligne dans la seconde, et ainsi de suite jusqu'à la énième banque qui va dans la banque numéro N. Et la ligne N+1 repart dans la première banque. L'avantage est que passer d'une ligne à la suivante est assez rapide. Au lieu de devoir fermer la première ligne et ouvrir la suivante on ouvrira directement la ligne suivante dans une banque différente, ce qui sera plus rapide. La commande PRECHARGE pourra être envoyée à la seconde banque en avance. Pour cela, l'adresse est découpée comme suit. {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne (précise à l'octet) |} Il est aussi possible de faire la même chose, mais avec les rangées. Pour simplifier fortement, une rangée est simplement un chip mémoire. Pour faire comprendre l'entrelacement de rangée, le mieux est d'utiliser un exemple. Avec L'idée est que quand on a épuisé toutes les banques dans un chip mémoire, on change de rangée. L'idée est que quand on a épuisé toutes les banques dans une même rangée, on passe à la suivante. : Pour rappel, les rangées ne sont pas des chip mémoire, mais un ensemble de chips mémoire regroupés ensemble histoire d'atteindre les 64 bits du bus de données. Par exemple, une rangée peut combiner 8 chips mémoire avec un bus de données de 8 bits chacun pour obtenir les 64 bits du bus de données d'une SDRAM. Mais nous passons ce détail sous silence dans les explications précédentes, par souci de simplification. D'autres méthodes d'entrelacement sont possibles, mais elles sont plus complexes. Chaque contrôleur mémoire fait un peu à sa sauce. Ils permettent en général de choisir entre plusieurs entrelacements. Au minimum, ils permettent de désactiver l'entrelacement, d'activer un entrelacement de ligne, et d'autres entrelacements plus complexes, dont certains sont propriétaires et tenus secrets par le fabricant. L'usage du ''dual channel'' complique encore l'entrelacement. Là encore, il y a deux grandes solutions : avec et sans entrelacement des canaux mémoire. Rappelons le principe : deux barrettes de RAM sont accédées en parallèle. Et pour cela, il faut utiliser l'entrelacement. Typiquement, chaque barrette mémoire fournit 64 bits, ce qui fait que l'on peut accéder à 128 bits d'un seul coup, par exemple avec un accès en rafale. Une solution pour cela est de ne pas utiliser l'entrelacement. Avec deux barrettes, la première barrette regroupera la moitié haute de la RAM, la seconde barrette utilisera la moitié basse. Les possibilité d’accéder à deux données séparées de 64 bits en même temps est cependant très rare. Aussi, on préfère utiliser l'entrelacement. Pour cela, deux blocs de 64 bits sont placés dans des canaux mémoire séparés. Idem avec du triple ou quadruple canal, mais c'est alors trois ou quatre blocs de 64 bits qui sont dispersés dans des canaux consécutifs. {|class="wikitable" |+ Adresse mémoire |- ! colspan="6" | Sans entrelacement |- | Numéro de canal/barrette || Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne || Position de l'octet dans un bloc de 64 bits |- | colspan="6" | |- ! colspan="6" | Avec entrelacement |- | Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne || Numéro de canal/barrette || Position de l'octet dans un bloc de 64 bits |} Les contrôleurs de SDRAM précédents sont assez basiques et ne représentent pas les contrôleurs les plus évolués. Ils étaient utilisés sur les anciens PC, à une époque où ils étaient encore sur la carte mère du processeur. Mais de nos jours, le contrôleur mémoire est intégré au processeur. Et il incorpore de nombreuses optimisations afin de gagner en performances. Malheureusement, ces optimisations sont elles-mêmes dépendantes des optimisations intégrées dans le processeur, ce qui fait qu'on ne peut pas en parler ici. Elles demandent que le contrôleur mémoire reçoive plusieurs accès mémoire simultanés, ce qui n'a aucun sens à ce stade du cours. Aussi, nous allons les passer sous silence pour le moment. Nous les verrons dans le chapitre sur le parallélisme mémoire. L'entrelacement est géré juste avant le séquenceur mémoire. Le '''circuit d'entrelacement''' intervertit certains bits de l'adresse lors des accès mémoires. On trouve aussi des mémoires FIFOs pour mettre en attente les requêtes mémoire provenant du processeur. Elles permettent de recevoir plusieurs accès mémoire consécutifs. Si vous vous demandez comment plusieurs accès mémoire consécutifs peuvent avoir lieu, sachez que c'est une bonne question. Pour le moment, nous allons dire que ces accès proviennent du processeur ou de la mémoire cache, sans expliquer comment. Le contrôleur mémoire reçoit une série d'accès mémoire assez rapprochés, qu'il doit exécuter au mieux. [[File:Controleur mémoire d'une SDRAM avec entrelacement.png|centre|vignette|upright=2.5|Contrôleur mémoire d'une SDRAM avec entrelacement.]] <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=La désambiguïsation mémoire | prevText=La désambiguïsation mémoire | next=Les processeurs superscalaires | nextText=Les processeurs superscalaires }} </noinclude> 0jghmuirbcg196nd3t1zc995eh0f4ku 764653 764652 2026-04-23T14:29:35Z Mewtow 31375 /* L'entrelacement sur les mémoires SDRAM */ 764653 wikitext text/x-wiki Dans ce chapitre, nous allons voir les techniques qui permettent de gérer plusieurs accès mémoire simultanés directement au niveau du cache ou de la mémoire RAM. Par plusieurs accès mémoire simultanés, vous pensez sans doute à l'usage de cache multiports, voire de mémoires RAM multiport. C'est en effet une solution, mais ce n'est clairement pas la seule. Par exemple, il est possible de pipeliner l'accès au cache, voire à la mémoire RAM. Il existe de nombreuses techniques de '''parallélisme mémoire''', et nous allons les voir dans ce chapitre. Pour donner un exemple, il est possible de pipeliner les accès mémoire. Il est possible de pipeliner l'accès au cache et/ou l'accès à la mémoire, et nous verrons comment faire dans ce chapitre. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, qu'ils aient ou non un pipeline dynamique. Par contre, pipeliner la mémoire est une technique ancienne, aujourd'hui peu utilisée. Elle était utilisée sur des très vieux ordinateurs, qui avaient un pipeline mais pas de mémoire cache. Mais de nos jours, les mémoires SDRAM et DDR ne sont pas adaptées pour ça, il est très difficile de les pipeliner correctement. Le parallélisme mémoire est utile aussi bien sur des processeurs à émission dans l'ordre que dans le désordre. Par exemple, un processeur ''in-order'' avec des lectures non-bloquantes peut e, profiter. Il peut techniquement lancer une seconde lecture, après avoir rencontré une première lecture non-bloquante. En général, le nombre de lectures consécutives est cependant limité à moins d'une dizaine. À l'opposé, les processeurs sans lectures non-bloquantes profitent pas du parallélisme mémoire. À la rigueur, ils peuvent en profiter s'ils intègrent des techniques de préchargement. Pendant que le processeur exécute une lecture/écriture, il peut précharger une autre donnée en parallèle. Inutile de dire que sans pipeline processeur, le parallélisme mémoire ne sert pas à grand-chose. ==Le ''line fill buffer'' et les techniques associées== Lors d'un défaut de cache, le processeur doit attendre que toute la ligne de cache soit chargée avant d'être utilisable. Or, la taille d'une ligne de cache est supérieure à la largeur du bus mémoire, ce qui fait qu'une ligne de cache est chargée en plusieurs fois, morceaux par morceaux, mot mémoire par mot mémoire. Le chargement peut se faire directement dans le cache, mais ce n'est pas une solution très pratique. À la place, beaucoup de processeurs ajoutent une mémoire tampon entre la RAM et le cache, appelée le '''tampon de remplissage de ligne''' (''line-fill buffer'') dans les processeurs modernes. Lors d'un défaut de cache, le processeur charge la donnée de la RAM dans le tampon de remplissage de ligne, mot mémoire par mot mémoire. Une fois plein, le tampon de remplissage de ligne est recopié dans la ligne de cache. Le tampon de remplissage de ligne contient une ligne de cache, si ce n'est que certains bits de contrôle ne sont pas présents. Le tampon de remplissage de ligne contient un bit de validité pour chaque mot mémoire de la ligne de cache, qui indiquent si le mot mémoire a été chargé. Par exemple, prenons un processeur 64 bits, qui gère donc des mots mémoire de 8 octets, avec des lignes de cache 256 octets/32 mots mémoire. Le tampon de remplissage de ligne contiendra 32 bits de validité. Si le processeur a chargé les 6 premiers mots mémoire, les 6 premiers bits de validité seront mis à 1, les autres seront encore à zéro. Il faut noter que la disponibilité de la ligne complète se détermine assez facilement en faisant un ET logique entre tous les bits de validité. Le processeur sait ainsi quand la ligne de cache est disponible entièrement et donc quand la transférer dans le cache. La même chose existe avec une hiérarchie de cache, sauf que l'on trouve une mémoire tampon entre chaque niveau de cache. Il y a un tampon de remplissage de ligne entre le cache L1 et le cache L2, entre le cache L2 et le L3, etc. Si le cache ne gère qu'un seul défaut de cache à la fois, le ''fill line buffer'' est une mémoire très simple, qui ne mémorise qu'une seule ligne de cache. Et cela vaut aussi bien pour un cache bloquant que non-bloquant. Mais sur les caches capables de gérer plusieurs défauts simultanés, le tampon de remplissage de ligne est une mémoire de type FIFO ou LIFO, capable de mémoriser plusieurs lignes de cache. Notons que le tampon de remplissage de ligne est très utile pour implémenter certaines techniques, par exemple le contournement du cache. Nous avions vu dans le chapitre sur le cache que certains accès mémoire doivent contourner le cache, pour des raisons de cohérence des caches. C'est notamment nécessaire pour accéder aux périphériques, mais c'est aussi utile pour des raisons de performances dans des cas très spécifiques. Les accès qui contournent le cache se font directement dans le tampon de remplissage de ligne : le processeur écrit ou lit les données depuis ce tampon de remplissage de ligne, sans accéder au cache. ===L'''early restart'' et le ''critical word load''=== La présence du ''line buffer'' permet une optimisation assez intéressante, qui permet de réduire la latence des défauts de cache. L'optimisation consiste à lire un mot mémoire dans le tampon de remplissage de ligne, même si la ligne de cache complète n'a pas encore été chargée. Il existe deux manières de faire cela, qui portent les noms d'''early restart'' et de ''critical word load''. La première est la version la plus simple, la seconde est plus complexe mais plus performante. Avec la technique d''''''early restart''''', la ligne de cache est chargée normalement, en partant de son premier mot mémoire. Dès que le mot mémoire lu/écrit par le processeur est copié dans le ''line fill buffer'', il est envoyé au processeur immédiatement. Illustrons le tout par un exemple, où une ligne de cache fait 16 mots mémoire. Le processeur effectue une lecture, qui lit le 5ème mot mémoire. Sans ''early restart'', le processeur doit charger les 16 mots mémoire avant de faire la lecture dans le cache. Avec ''early restart'', le processeur reçoit la donnée dès que le 5ème mot mémoire est disponible. Le processeur doit attendre que les 4 premiers mots mémoire soient chargés, puis le 5ème mot mémoire arrive et est envoyé directement au processeur, il est lu directement depuis le ''line fill buffer''. Les 11 mots mémoire suivants sont ensuite chargés dans le cache pendant que le processeur fait des calculs dans son coin. Le ''critical word load'' est une optimisation de la technique précédente où le chargement de la ligne de cache commence directement à la donnée demandée par le processeur. Pour reprendre l'exemple précédent, où le processeur demande le 5ème mot mémoire sur 16, le ''critical word load'' charge le 5ème mot mémoire en premier et l'envoie au processeur, ce qui fait qu'il est chargé très rapidement. Pas besoin d'attendre que le processeur charge les 4 mots mémoire précédents comme avec l'''early restart''. Dans le détail, le processeur charge le 5ème mot mémoire en premier, puis charge les 11 suivants, et termine par les 4 mots mémoire du début. En clair, le ''critical word load'' commence par charger le mot mémoire lu, puis les blocs suivants, avant de revenir au début du bloc pour charger les blocs restants. Ainsi, la donnée demandée par le processeur sera la première disponible. Pour cela, l'organisation du tampon de remplissage de ligne est modifiée de manière à rendre cela possible. Il a une taille égale à une ligne de cache complète, qui contient elle-même plusieurs mots mémoire. Dans le ''line-fill buffer'', chaque mot mémoire est stocké avec un tag, qui indique l'adresse du mot mémoire stocké dans le ''line-fill buffer''. Le ''line-fill buffer'' est donc un cache un peu particulier, qui fonctionne comme un cache du point de vue du processeur, comme une mémoire FIFO pour les transferts avec le cache. Ainsi, un processeur qui veut lire dans le cache après un défaut peut accéder à la donnée directement depuis le tampon de remplissage de ligne, alors que la ligne de cache n'a pas encore été totalement recopiée en mémoire. ==Les caches pipelinés== Il est possible de pipeliner l'accès au cache, ce qui demande juste de rajouter des registres au bon endroits et quelques circuits de contrôle. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, même ceux avec un pipeline dynamique. Et l'implémentation peut se faire de plusieurs manières différentes. Il faut dire que découper une mémoire cache en plusieurs étapes peut se faire de plusieurs manière. Commençons par la plus simple. Beaucoup de processeurs des années 2000 avaient une fréquence peu élevée comparée aux standards d'aujourd'hui, ce qui fait que leur cache avait bien un temps d'accès d'un cycle d'horloge. Mais l'accès au cache se faisait en deux cycles d'horloge : un cycle pour calculer une adresse et un cycle pour l'accès mémoire proprement dit. Et ces deux étapes étaient pipelinées, à savoir que deux micro-opérations mémoire peuvent s'exécuter en même temps : une dans l'étage de calcul d'adresse, une autre dans le cache. L'unité mémoire était donc pipelinée, alors que l'accès au cache ne l'était pas. Les processeurs qui implémentaient cette technique regroupent les micro-architectures K5 et K6 d'AMD, les processeurs Intel de micro-architecture P6 (Pentium 2 et 3) et quelques autres. Il est aussi possible de pipeliner l'accès au cache lui-même. Avec un cache pipeliné, l'accès au cache ne se fait pas en un seul cycle, mais en plusieurs. Cependant, on peut lancer un nouvel accès au cache à chaque cycle d'horloge, comme un processeur pipeliné lance une nouvelle instruction à chaque cycle. L'implémentation est assez simple : il suffit d'ajouter des registres dans le cache. Pour cela, on profite que le cache est composé de plusieurs composants séparés, qui échangent des données dans un ordre bien précis, d'un composant à un autre. De plus, le trajet des informations dans un cache est linéaire, ce qui les rend parfois pour l'usage d'un pipeline. ===Le ''pipelining'' des caches ''direct-mapped''=== Voici ce qui se passe avec un cache directement adressé. Pour rappel ce genre de cache est conçu en combinant une mémoire RAM, généralement une SRAM, avec quelques circuits de comparaison et un MUX. L'accès au cache se fait globalement en deux étapes : la première lit la donnée et le ''tag'' dans la SRAM, et on utilise les deux dans une seconde étape. Nous avions vu il y a quelques chapitre comment pipeliner des mémoires, dans le chapitre sur les mémoires évoluées. Et bien ces méthodes peuvent s'utiliser pour la mémoire RAM interne au cache !L'idée est d'insérer un registre entre la sortie de la RAM et la suite du cache, pour en faire un cache pipéliné, à deux étages. Le premier étage lit dans la SRAM, le second fait le reste. L'implémentation sur les caches associatifs à plusieurs voies est globalement la même à quelques détails près. [[File:Cache directement adressé pipeliné - deux étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - deux étages]] Avec le circuit précédent, il est possible d'aller plus loin, cette fois en pipelinant l'accès à la mémoire RAM interne au cache. Pour comprendre comment, rappelons qu'une mémoire SRAM est composée d'un plan mémoire et d'un décodeur. L'accès à la mémoire demande d'abord que le décodeur fasse son travail pour sélectionner la case mémoire adéquate, puis ensuite la lecture ou écriture a lieu dans cette case mémoire. L'accès se fait donc en deux étapes successives séparées, on a juste à mettre un registre entre les deux. Il suffit donc de mettre un gros registre entre le décodeur et le plan mémoire. [[File:Cache directement adressé pipeliné - trois étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - trois étages]] Et on peut aller encore plus loin en découpant le décodeur en deux circuits séparés. En effet, rappelez-vous le chapitre sur les circuits de sélection : nous avions vu qu'il est possible de créer des décodeurs en assemblant des décodeurs plus petits, contrôlés par un circuit de prédécodage. Et bien on peut encore une ajouter un registre entre ce circuit de prédécodage et les petits décodeurs. : Théoriquement, toute l'adresse est fournie d'un seul coup au cache, la quasi-totalité des processeurs présentent l'adresse complète à un cache pipeliné. Mais le Pentium 4 fait autrement. Il faut noter que les premiers étages manipulent l'indice dans la SRAM, qui est dans les bits de poids faible, alors que les étages ultérieurs manipulent le ''tag'' qui est dans les bits de poids fort. Les concepteurs du Pentium 4 ont alors décidé de présenter les bits de poids faible lors du premier cycle d'accès au cache, puis ceux de poids fort au second cycle. Pour cela, l'ALU fonctionnait à une fréquence double de celle du processeur, tout comme le cache L1. Il n'y avait pas de pipeline proprement dit, mais cela réduisait grandement la latence d'accès au cache. ===Le ''pipelining'' des caches splittés/sériels=== Il est aussi possible de pipeliner un cache dits splittés, aussi appelés à accès sériel. Pour rappel, les caches à accès sériel vérifient si il y a succès ou défaut de cache, avant d'accéder aux lignes de cache en cas de succès. Ils font donc différemment des autres caches, qui accèdent à une ligne de cache, avant de déterminer s'il y a succès ou défaut en lisant le tag de la ligne de cache. Les caches sériels disposent de deux SRAM : une pour les tags des lignes de cache et une pour les données. Ils accèdent à la SRAM pour les tags, avant d’accéder à la SRAM des données en cas de succès de cache. Vu que l'accès se fait en deux étapes, une vérification des tags suivie de la lecture/écriture des données, il est facile à pipeliner. Pipeliner le cache permet de régler le problème des accès au cache L1, et elle est tout le temps utilisé sur les processeurs modernes. Mais que faire en cas de défaut de cache ? ==Les caches non bloquants== Un '''cache bloquant''' est un cache auquel le processeur ne peut pas accéder pendant un défaut de cache. Il faut attendre que la lecture ou écriture en RAM soit terminée avant de pouvoir utiliser de nouveau le cache. Un '''cache non bloquant''' n'a pas ce problème : on peut l'utiliser pendant un défaut de cache. Les caches non bloquants permettent de démarrer une nouvelle lecture ou écriture alors qu'une autre est en cours, ce qui permet d'exécuter plusieurs lectures ou écritures en même temps. ===Les ''Miss Handling Status Registers''=== Lors d'un défaut de cache, la mémoire RAM est consultée pendant le défaut de cache, mais le cache est inutilisé. Un défaut de cache n'utilise pas le cache, ce n'est qu'un accès en mémoire RAM, sur le bus mémoire (ou un accès aux niveaux de cache inférieurs, peu importe). Le cache en lui-même est laissé libre, rien n’empêche d'y accéder, il est en réalité intrinsèquement non-bloquant. Les caches, bloquants comme non-bloquants, sont en réalité composés d'une mémoire cache proprement dite, entourée de circuits qui servent d'interface entre le processeur et le cache lui-même. Et parmi les circuits tout autour, certains gèrent l'accès au cache lors d'un défaut de cache. Ils sont regroupés sous le terme de '''''Miss Handling Architecture''''' (MHA). La différence entre un cache bloquant et un cache non-bloquant est en réalité liée à l'implémentation de la MHA. Les caches bloquants coupent volontairement l'accès au cache lors d'un défaut, car les défauts sont plus simples à gérer ainsi. Le défaut de cache rapatrie une donnée depuis la RAM, qui sera écrite dans le cache. Et il ne faut pas qu'une tentative d'accès à cette donnée ait lieu avant qu'elle ne soit chargée. Mais ce blocage est général et touche tout le cache, alors que seule une ligne de cache est concernée par le défaut de cache. L'idée derrière un cache non-bloquant est que seule la ligne de cache est bloquée, mais les autres sont accessibles. L'idée est alors de mémoriser les lignes de cache concernées par le défaut de cache, afin d'en bloquer l'accès. À chaque accès, on vérifie si la ligne de cache est déjà réservée par un défaut de cache. La lecture/écriture est alors bloquée si c'est le cas, mais elle accepte les accès sinon. Pour cela, la ''Miss Handling Architecture'' contient des registres qui mémorisent des informations sur les défauts de cache en cours. Ils portent le nom de '''''miss status handling registers''''', que j’appellerais dorénavant MSHR, qui sont aussi appelés des '''''miss buffer'''''. Le contenu des MSHR varie beaucoup suivant le processeur, mais ces derniers stockent au minimum les informations suivantes : * Le numéro de la ligne de cache dans laquelle les données sont chargées. * Un bit de validité qui indique si le MSHR est vide ou pas, qui est mis à 0 quand le défaut de cache est résolu. * Un ou plusieurs champs de lecture/écriture, qui contiennent des informations sur la lecture/écriture. ** Pour une lecture, elle contient des informations sur la destination de la donnée, à savoir qui prévenir quand le défaut de cache est terminé. C'est parfois un nom/numéro de registre (celui dans lequel charger la donnée), mais c'est souvent le numéro de l'entrée dans le ''load/store queue''. ** Pour les écritures, elle contient la donnée à écrire, ou éventuellement un numéro de ''load/store queue'' où se trouve la donnée à écrire. Il faut noter que le bus mémoire ne peut gérer qu'un seul défaut de cache à la fois. Aussi, il est intéressant de regarder ce qui se passe lorsqu'un second défaut de cache survient, pendant qu'un premier est en cours. Dans ce cas, il y a deux réponses qui correspondent à deux types de caches non-bloquants, qui portent les noms barbares de caches de type succès après défaut et défaut après défaut. Sur le premier type, il ne peut pas y avoir plusieurs défauts de cache simultanés. Dès qu'un second défaut de cache survient, le cache stoppe son activité et on ne peut plus démarrer de nouvelle lecture/écriture, tant que le premier défaut de cache n'est pas résolu. Le second type est plus souple et autorise la survenue de plusieurs défauts de cache simultanés. Du moins, jusqu'à une certaine limite, car le cache ne peut supporter qu'un nombre limité d'accès mémoires simultanés (pipelinés). ===Les accès simultanés à une même ligne de cache=== Il arrive que le processeur fasse plusieurs accès mémoire simultanés à la même ligne de cache. Si la ligne de cache en question n'a pas encore été chargée dans le cache, alors on a plusieurs défauts de cache consécutifs pour la même ligne de cache, et le cache non-bloquant doit gérer la situation. Pour la suite, il va falloir faire une petite distinction entre les défauts primaires et secondaires. Imaginons qu'un défaut de cache ait lieu et demande à charger une donnée dans la ligne de cache numéro N. Il s'agit du premier défaut impliquant cette ligne de cache précise, ce qui lui vaut le nom de '''défaut de cache primaire'''. Mais par la suite, d'autres accès mémoire à la même ligne de cache ont lieu, alors que la ligne de cache n'est pas encore disponible. Dans ce cas, il s'agit de '''défauts de cache secondaires'''. Pour l'unité d'accès mémoire, les défauts de cache primaire et secondaire sont différents (ils prennent tous une entrée dans la ''load/store queue''). Mais pour le cache, ils ne correspondent qu'à un seul accès au cache : celui qui demande de charger la ligne de cache demandée. Les défauts de cache primaire et secondaire à la même ligne de cache se voient attribuer un MSHR unique. La gestion des défauts de cache secondaires dépend du cache non-bloquant. La solution la plus simple ne permet pas les défauts de cache secondaires. Le cache ne permet pas deux défauts de cache simultanés pour la même ligne de cache. Les autres solutions le permettent, en fusionnant des accès simultanés à la même ligne de cache en un seul au niveau des MSHR. Dans tous les cas, détecter les défauts de cache secondaires sont un problème qu'il faut détecter. La ''Miss Handling Architecture'' doit détecter les défauts de cache secondaire. Pour cela, elle procède comme suit. Lors de chaque défaut de cache, la MHA récupère le numéro de la ligne de cache associé. Il vérifie alors chaque MSHR pour vérifier s'il contient le numéro en question. S'il n'y a aucune correspondance dans les MSHR, alors c'est signe que le défaut de cache est un défaut primaire. Mais s'il y en a une, alors c'est un défaut secondaire. Évidemment, cela signifie que lors d'un défaut de cache, le numéro de ligne de cache est envoyé à tous les MSHR, pour comparaison. Les MSHR sont donc regroupés dans une mémoire associative, une sorte de mini-cache, faciliter l'implémentation. Lors d'un défaut de cache primaire, l'accès à un cache non-bloquant se fait comme suit : le processeur envoie une adresse au cache, accède à celui-ci, et détecte la survenue d'un défaut de cache. Il en profite alors pour attribuer une ligne de cache dans laquelle sera chargée ce défaut. L'attribution est très simple dans le cas des caches ''direct mapped'', ou associatifs par voie, pour lesquels l'attribution se fait assez simplement. Il mémorise alors cette information dans les MSHR, après avoir vérifié que le défaut de cache n'était pas un défaut secondaire. Lors des accès ultérieurs à une adresse proche, censée être dans la même ligne de cache, le processeur va encore une fois rencontrer un défaut de cache. Il va alors déterminer le numéro de la ligne de cache associée à l'adresse, et comparer ce numéro avec les MSHRs. Si un MSHR contient ce numéro, c'est signe que le défaut de cache est un défaut secondaire. La MHA réagit alors différemment selon le processeur considéré. Une première solution n'autorise pas les défauts de cache secondaires. Si l'un d'entre eux survient, le processeur est gelé par un ''pipeline stall'', une bulle de pipeline. Une autre solution fusionne les défauts de cache secondaires avec le défaut de cache primaire : tout cela ne correspond qu'à un seul défaut de cache pour lui. ===Les MSHR simples=== Un cache non-bloquant à '''MSHR simple''' contient juste plusieurs MSHR qui mémorisent juste un numéro de ligne de cache, un bit de validité, le champ de lecture/écriture, et l'adresse exacte de lecture/écriture. Avec cette organisation, il est possible d'avoir plusieurs défauts de cache séparés, mais à la condition que chaque défaut accède à une ligne de cache différente. Deux accès simultanés à une même ligne de cache ne sont pas possibles, les défauts de cache secondaires ne sont pas autorisés. Ainsi, chaque défaut de cache se voit attribuer son propre MSHR, chacun contient un numéro de ligne de cache différent. Pour comprendre pourquoi c'est impossible de gérer les défauts secondaires, il faut regarder le champ de lecture/écriture. Si on veut effectuer plusieurs écritures consécutives à la même adresse, le MSHR n'aura pas de quoi mémoriser les deux données à écrire. Il pourra mémoriser la première donnée à écrire, pas la seconde. Même chose lors d'une lecture : le champ lecture/écriture peut mémorisr la destination de la première lecture, pas de la seconde. L'avantage est que la MHA n'a besoin que des MSHR et de quelques circuits annexes. Les autres solutions rajoutent des circuits annexes pour gérer les défauts de cache secondaires, qui utilisent beaucoup de circuits. Le cout en circuit est donc élevé, mais le gain en performance est là. Passons maintenant aux caches non-bloquants qui autorisent les défauts de cache secondaire. La solution la plus simple consiste à utiliser ===Les MSHR adressés implicitement=== Avec les '''MSHR adressés implicitement''', il est possible de fusionner plusieurs accès mémoire à une même ligne de cache, mais sous une condition très importante : ces accès lisent/écrivent des mots mémoire différents. Par exemple, imaginons qu'une ligne de cache contienne 8 mots mémoire de 64 bits. Si un premier accès mémoire lit le mot mémoire numéro 7 (dernier mot mémoire de la ligne), et le second accès le mot mémoire numéro 3, alors la fusion est possible. Mais si deux accès mémoire veulent lire/écrire le mot mémoire numéro 7, alors la fusion n'est pas possible et le processeur se bloque, un ''pipeline stall'' survient. Un MSHR adressé implicitement est un MSHR simple, qui contient naturellement un numéro de ligne de cache (tag) et un bit de validité, sauf que le champ de lecture/écriture est dupliqué. Les différents champs lecture/écriture d'un MSHR sont regroupés dans une mémoire RAM/cache qui contient autant d'entrées qu'il y a de mot mémoire dans une ligne de cache. Chaque entrée de la table est associée à un mot mémoire de la ligne de cache et stocke des informations sur celui-ci. Une entrée mémorise, au minimum : * Un champ de lecture/écriture qui contient soit la destination de la lecture, soit la donnée à écrire. * Un bit R/W permettant d'interpréter correctement le champ de lecture/écriture. * Un bit de validité pour chaque entrée de la table, qui dit si un défaut de cache antérieur accède déjà à ce mot mémoire. La fusion de deux défauts de cache est ainsi assez simple. Un défaut de cache primaire/secondaire configure l'entrée associée au mot mémoire lu/écrit. Si un défaut secondaire ultérieur lit/écrit un mot mémoire différent (dont le bit de validité de l'entrée dans la table est à zéro), pas de conflit : il configure une autre entrée, vide. Mais s'il lit/écrit un mot mémoire pour lequel l'entrée de la table est occupée, il y a conflit, le processeur est bloqué par un ''pipeline stall'', une bulle de pipeline. Avec cette organisation, le nombre de MSHR indique combien de lignes de cache peuvent être lues en même temps depuis la mémoire. Quant au nombre d'entrées par MSHR, il détermine combien d'accès mémoires qui ne se recouvrent pas peuvent avoir lieu en même temps. ===Les MSHR adressés explicitement=== Les '''MSHR adressés explicitement''' n'ont pas les contraintes des MSHR adressés implicitement. Avec eux, il est possible d'avoir plusieurs défauts de cache pointant vers la même ligne de cache, mais aussi vers le même mot mémoire. De tels défauts de cache apparaissent sur les processeurs à exécution dans le désordre, mais sont très rares, voire inexistants, sur les processeurs ''in-order''. L'idée est encore que chaque MSHR est associée à une table mémoire qui mémorise des entrées. Cette table n'est autre qu'une mémoire FIFO. Sauf que cette fois-ci, une entrée n'est pas associée à un mot mémoire. Une entrée est associée à un défaut de cache. Une entrée mémorise là encore la destination de la lecture ou la donnée de l'écriture suivi du bit R/W, ainsi que d'un bit de validité par entrée, mais aussi : la position du mot mémoire lu dans la ligne de cache. C'est cette dernière information qui n'était pas présente dans les MSHR adressés implicitement. De plus, le nombre d'entrée par MSHR n'est pas égal au nombre de mots mémoires dans une ligne de cache, mais peut être arbitrairement grand. L'avantage d'un tel cache est qu'il est capable de traité les défauts secondaires concernant un même mot mémoire, et ce, car les accès à la ligne de cache concernée se produisent dans l'ordre de génération des défauts (la table mémoire est une FIFO). ===Les MSHRs inversés=== Généralement, plus on veut supporter de défauts de cache, plus le nombre de MSHR et d'entrées augmente. Mais au-delà d'un certain nombre d'entrées et de MSHR, les MSHR adressés implicitement et explicitement ont tendance à bouffer un peu trop de circuits. Utiliser une organisation un peu moins gourmande en circuits est donc une nécessité. Cette organisation plus économe se base sur des MSHR inversés. Les MSHR inversés ne contiennent qu'une seule entrée, en quelque sorte : au lieu d’utiliser n MSHR de m entrées chacun, on va utiliser n × m MSHR inversés. La différence, c'est que plusieurs MSHR peuvent contenir un tag identique, contrairement aux MSHR adressés implicitement et explicitement. Lorsqu'un défaut de cache a lieu, chaque MSHR est vérifié. Si jamais aucun MSHR ne contient de tag identique à celui utilisé par le défaut, un MSHR vide est choisi pour stocker ce défaut, et une requête de lecture en mémoire est lancée. Dans le cas contraire, un MSHR est réservé au défaut de cache, mais la requête n'est pas lancée. Quand la donnée est disponible, les MSHR correspondant à la ligne qui vient d'être chargée vont être utilisés un par un pour résoudre les défauts de cache en attente. ===Les MSHR intégrés au cache=== Certains chercheurs ont remarqué que pendant qu'une ligne de cache est en train de subir un défaut de cache, celle-ci reste inutilisée, et son contenu est destiné à être perdu une fois le défaut de cache résolu. Ils se sont dit que, plutôt que d'utiliser des MSHR séparés, il vaudrait mieux utiliser la ligne de cache pour stocker les informations sur les défauts de cache en attente dans cette ligne de cache. Pour éviter tout problème, il faut rajouter un bit dans les bits de contrôle de la ligne de cache, qui sert à indiquer que la ligne de cache est occupée : un défaut de cache a eu lieu dans cette ligne, et elle stocke donc des informations pour résoudre les défauts de cache. ==L'entrelacement mémoire== Nous venons de voir comment une mémoire cache peut gérer plusieurs accès mémoire simultanés. Il se trouve que ce genre d'optimisation dépasse largement le cadre du cache. Les mémoires RAM ne sont pas en reste, elles aussi ! Il est possible de pipeliner l'accès à une mémoire RAM, en utilisant des techniques dites d''''entrelacement'''. Précisons cependant que cette optimisation n'est pas utilisée sur les mémoires SDRAM ou DDR modernes, du moins pas sans modifications majeures. Mais divers ordinateurs assez anciens, dont des superordinateurs, ont utilisé cette technique. La technique demande d'utiliser plusieurs mémoires séparées. Sur les anciens ordinateurs, les mémoires en question sont des chips mémoire, à savoir des circuits intégrés de DRAM. Pour faire plus général, nous allons utiliser le terme de '''banques''', ou encore de bancs mémoire. La différence est qu'il existe des mémoires multi-banque, qui regroupent plusieurs banques indépendantes dans un seul boitier, dans un seul chip mémoire. En clair, de telles mémoires regroupent plusieurs sous-mémoires dans un seul circuit intégré. Il est aussi possible d'utiliser plusieurs chips mémoire séparés, chacun ayant une banque. Reste à combiner ces banques pour former un pseudo-pipeline. ===Utiliser plusieurs banques sans entrelacement=== Sans optimisation particulière, les adresses sont réparties dans les banques comme indiqué ci-dessous. Il s'agit de ce qui s'appelle un arrangement horizontal, et nous avions vu celui-ci dans le chapiutre sur les mémoires SDRAM. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Avec cet arrangement, les bits de poids fort de l'adresse sont utilisées pour sélectionner la banque adéquate, et le reste de l'adresse est envoyé sur le bus d'adresse. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Adresse de banque !! Adresse dans la banque |- | Quelques bits de poids fort || Reste de l'adresse |} L'entrelacement change cette répartition, afin d'accélérer les accès mémoire. L'entrelacement classique vise à améliorer les accès à des adresses mémoires consécutives, mais des formes plus évoluées d'entrelacement visent à optimiser des accès mémoire arbitraires. Voyons-les dans l'ordre, du plus simple au plus complexe. ===L'entrelacement classique=== Avec l''''entrelacement classique''', des adresses consécutives sont réparties dans des banques différentes. [[File:Répartition des adresses dans une mémoire interleaved.png|centre|vignette|upright=1.5|Répartition des adresses dans une mémoire interleaved.]] L'entrelacement simple permet d'accélérer les accès mémoire qui se font à des adresses consécutives. Avec l'entrelacement, chaque accès mémoire tombe dans une banque mémoire différente, ce qui fait qu'on peut démarrer un nouvel accès mémoire à chaque cycle d'horloge. Pendant qu'une banque est occupée par un accès mémoire, on démarre le suivant dans une autre banque, et ainsi de suite. Le tout se termine soit quand on a épuisé toutes les banques libres, soit quand l'accès en rafales se termine. Pas besoin d'attendre que la mémoire ait fini sa lecture/écriture avant de démarrer la lecture/écriture suivante. [[File:Pipemining mémoire.png|centre|vignette|upright=2.5|Pipelining mémoire]] L'animation suivante illustre bien le processus, avec quatres banques. [[File:Interleaving.gif|centre|vignette|upright=2.5|Pipelining mémoire]] L'entrelacement simple utilise les bits de poids faible pour sélectionner la banque, et les bits de poids fort pour la case mémoire. [[File:Adresse mémoire d'une mémoire entrelacée.png|centre|vignette|upright=2|Adresse mémoire d'une mémoire entrelacée]] Précisons que le temps d'accès mémoire ne change pas beaucoup avec l'entrelacement. Par contre, on peut faire plus d'accès mémoire simultanés. On peut démarrer un accès mémoire par cycle d'horloge, mais l'accès en lui-même prend plusieurs cycles. Les mémoires à entrelacement ont donc un débit supérieur aux mémoires qui ne l'utilisent pas. L'entrelacement classique pourrait être utilisé pour accélérer les transferts entre mémoire RAM et cache, qui se font en blocs d'adresses consécutives. Mais de nos jours, il n'est pas utilisé dans cette optique. Il faut dire qu'il est redondant avec le mode rafale des mémoires DRAM. Les deux font globalement la même chose et on voit mal comment utiliser les deux en même temps. Le mode rafale étant plus simple à implémenter, et plus léger niveau utilisation du bus de commande mémoire, il est préféré à l'entrelacement. Par contre, l'entrelacement a eu son heure de gloire sur d'anciens processeurs qui n'avaient pas de mémoire cache, alors qu'ils disposaient d'un pipeline. L'exemple typique est celui des nombreux ''processeurs vectoriels'', que nous n'avons pas encore abordé à ce stade du cours. De tels processeurs avaient un pipeline, une unité de calcul fortement pipelinée, et parfois même de l'exécution dans le désordre et du renommage de registres ! ===L'entrelacement par décalage=== L'entrelacement simple est très utile pour les accès en rafle ou équivalents. Par contre, il existe de rares situations où il n'est pas optimal. Une de ces situations est celle des accès en enjambée (en ''stride''), où l'on accède en série à des adresses sont séparées par N mots mémoires. De tels accès surviennent quand un logiciel accède à des structures de données spécifiques, à savoir des tableaux de structures, des matrices, ou d'autres structures de données dans le genre. Un cas classique est celui du parcours d'une matrice colonne par colonne. Les matrices de nombres sont mémorisées ligne par ligne, ce qui fait que parcourir une colonne demande de passer d'une ligne à la suivante et de faire des grands sauts en mémoire RAM, mais tous espacés par la même distance. Avec l'entrelacement simple, les accès en enjambée sont moins performants que les accès à des adresses consécutives, mais cela ne fonctionne pas trop mal. Le pire cas est celui où l'on a N banques et où les données sont justement placées toutes les N adresses. Des accès consécutifs vont tous tomber dans la même banque, on ne peut plus accéder à des banques différentes en parallèle. Mais en dehors de ce cas, on voit une amélioration par rapport à la situation sans entrelacement. Pour obtenir des performances maximales pour les accès en enjambées, il faut répartir les mots mémoires dans la mémoire autrement. L'organisation idéale est la suivante. : Dans les explications qui vont suivre, la variable N représente le nombre de banques, qui sont numérotées de 0 à N-1. On commence par organiser les N premières adresses comme une mémoire entrelacée simple : l'adresse 0 correspond à la banque 0, l'adresse 1 à la banque 1, etc. Pour le bloc suivant, on décale tout d'une adresse, à savoir qu'on commence à remplir les banques à partir de la seconde, non de la première. Une fois la fin du bloc atteinte, on finit de remplir le bloc en repartant du début du bloc. Le troisième bloc d'adresse subit le même traitement, sauf qu'on commence à remplir à partir de la seconde banque. Et on poursuit l’assignation des adresses en décalant d'un cran en plus à chaque bloc. Ainsi, chaque bloc verra ses adresses décalées d'un cran en plus comparé au bloc précédent. Si jamais le décalage dépasse la fin d'un bloc, alors on reprend au début. [[File:Mémoire interleaved par décalage.png|centre|vignette|upright=1.5|Mémoire entrelacée par décalage.]] En faisant cela, on remarque que les banques situées à N adresses d'intervalle sont différentes. Dans l'exemple du dessus, nous avons ajouté un décalage de 1 à chaque nouveau bloc à remplir. Mais on aurait tout aussi bien pu prendre un décalage de 2, 3, etc. Dans tous les cas, on obtient un entrelacement par décalage. Ce décalage est appelé le pas d'entrelacement, noté P. Le calcul de l'adresse à envoyer à la banque, ainsi que la banque à sélectionner se fait en utilisant les formules suivantes : * adresse à envoyer à la banque = adresse totale / N ; * numéro de la banque = (adresse + décalage) modulo N, avec décalage = (adresse totale * P) mod N. Avec cet entrelacement par décalage, on peut prouver que la bande passante maximale est atteinte si le nombre de banques est un nombre premier. Seulement, utiliser un nombre de banques premier peut créer des trous dans la mémoire, des mots mémoires inadressables. Pour éviter cela, il faut faire en sorte que N et la taille d'une banque soient premiers entre eux : ils ne doivent pas avoir de diviseur commun. Dans ce cas, les formules se simplifient : * adresse à envoyer à la banque = adresse totale / taille de la banque ; * numéro de la banque = adresse modulo N. ===L'entrelacement pseudo-aléatoire=== Une dernière méthode de répartition consiste à répartir les adresses dans les banques de manière "pseudo-aléatoire". L'idée exacte est de faire passer l'adresse dans une '''fonction de hachage''', plus ou moins complexe. Pour rappel, une fonction de hachage prend une entrée de grande taille et fournit en sortie un résultat de petite taille. Idéalement, elles doivent donner des résultats aussi différents que possible pour des entrées similaires, histoire de simuler une sorte de pseudo-aléatoire. Ici, une adresse est transformée en un numéro de banque. Une fonction de hachage simple ne fait que permuter des bits de l'adresse pour obtenir son résultat. Elle ne fait qu'échanger des bits de place avant de couper l'adresse en deux morceaux : un pour la sélection de la banque, et un autre pour la sélection de l'adresse dans la banque. Cette permutation est fixe, et ne change pas suivant l'adresse. Des fonctions de hachage plus complexes font des XOR bit à bit entre certains bits de l'adresse. Il existe aussi d'autres techniques qui donnent le numéro de banque à partir d'un polynôme modulo N, appliqué sur l'adresse. ==Les contrôleurs mémoires SDRAM/DDR optimisés== Après avoir vu le cas des mémoires RAM, nous allons nous concentrer sur le cas particulier des mémoires SDRAM. Les mémoires SDRAM et DDR modernes sont capables de gérer plusieurs accès simultanés à la mémoire RAM. Et les contrôleurs mémoire intègrent des optimisations spécifiques afin d'en profiter au maximum. ===La mise en attente des accès mémoire=== Comme pour les mémoires caches, il existe des contrôleurs mémoire bloquants et non-bloquants. Les premiers n'acceptent qu'un seul accès mémoire à la fois, les seconds acceptent plusieurs accès mémoire simultanés. Pour simplifier, nous allons dire que le contrôleur mémoire n’exécute qu'un seul accès mémoire à la fois, et qu'ils met en attente les accès en trop. C'est une simplification assez irréaliste, vu que les mémoires modernes disposent de plusieurs banques accessibles en parallèle. Mais nous introduirons ce détail un peu plus tard. Avec un contrôleur mémoire de ce type, les accès mémoire sont mis en attente dans une mémoire FIFO, histoire de les exécuter dans leur ordre d'arrivée. Les accès mémoire quittent alors la mémoire dans leur ordre d'arrivée, les lectures sont renvoyées dans l'ordre demandé. En plus d'une mémoire FIFO pour les commandes mémoire, on trouve une mémoire FIFO pour les données à écrire. Elle permet d'accumuler plusieurs écritures, tout en permettant que la donnée adéquate soit envoyée à la RAM au bon moment. Il y a aussi une FIFO pour les données lues, qui sert au cas où le cache ne soit pas disponible quand on lui envoie la donnée lue. [[File:Module d'interface avec la mémoire.png|centre|vignette|upright=2.5|Contrôleur mémoire avec mise en attente des requêtes processeur et autres optimisations.]] Une optimisation regroupe plusieurs accès à des données consécutives en un seul accès en rafale. Le contrôleur mémoire analyse les requêtes mises en attente et détecte si certaines se font à des adresses consécutives. Si c'est le cas, il fusionne ces requêtes en une lecture/écriture en rafale. Une telle optimisation est appelée la '''combinaison de lecture''' pour les lectures, et la '''combinaison d'écriture''' pour les écritures. ===Le pipeline des mémoires SDRAM=== Les SDRAM ont une forme de pseudo-pipeline très limité. Elles sont parfois capables de démarrer une commande avant que la précédente soit terminée. Même si elle parait très limitée, cette possibilité est exploitée au mieux par la présence de plusieurs banques dans la mémoire SDRAM. Les chips mémoires de SDRAM sont composés de plusieurs banques, chacune étant une sorte de mini-mémoire miniature. Chaque banque a son propre décodeur, son propre tampon de ligne, ses propres multiplexeurs de colonne, sa logique de rafraichissement mémoire, etc. C'est comme si une SDRAM regroupait plusieurs mémoires séparées dans un même circuit intégré. Et cela permet de faire plusieurs accès mémoire en même temps, dans des banques différentes. Il est possible de lancer un accès mémoire dans deux banques en même temps, par exemple. Ou encore, on peut lancer un nouvel accès mémoire dans une banque si elle est inoccupée. La possibilité améliore grandement la performance de la SDRAM. Mais nous en reparlerons dans un chapitre ultérieur. {|class="wikitable" |+ Pipelining basique sur les SDRAM |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 |- ! Banque Numéro 1 | || || || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || || || |- ! Banque Numéro 2 | || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || |- ! Banque Numéro 3 | || || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || |} L'implémentation n'a rien de vraiment compliqué. Il suffit d'utiliser une mémoire FIFO par banque. Ainsi, les accès dans une même banque se feront en série, dans l'ordre d'arrivée, mais des accès à des banques différentes se feront dans le désordre. Cela ne pose pas de problème car les accès se font à des adresses différentes, ce qui fait qu'il n'y a pas de conflits majeurs. [[File:Gestion parallèle des banques.png|centre|vignette|upright=2.5|Gestion parallèle des banques.]] ===Le ré-ordonnancement des commandes mémoires=== Un contrôleur mémoire moderne est capable de changer l'ordre des requêtes mémoires, pour gagner en performances. En clair : il peut faire l'équivalent mémoire de l'exécution dans le désordre des processeurs haute performance. Une différence majeure est que le contrôleur mémoire ne remet pas les accès mémoire dans l'ordre avant de les envoyer au processeur. C'est le processeur qui remet en ordre les accès mémoire, dans sa ''load queue''. Pour cela, les requêtes mémoire sont "numérotées", à savoir qu'on leur attribue un identifiant binaire. Le contrôleur mémoire exécute les lectures/écritures dans le désordre, mais garde la trace de leur identifiant. Il sait dans quel ordre il exécute les accès mémoire et connait leur latence. Ce qui fait qu'il sait que la donnée lue à tel instant est associée à la requête mémoire numéro X. Pour les lectures, il renvoie la donnée lue avec l'identifiant. Le processeur sait alors à quelle lecture la donnée lue correspond et il se débrouille en interne pour gérer la situation. En clair : le processeur voit les accès mémoire dans le désordre, c'est lui qui les remet en ordre. Maintenant que ces précisions sont faites, posons cette question : quel est l'intérêt de faire les accès mémoire dans le désordre ? Accéder à des banques en parallèle est une réponse possible, mais le ré-ordonnancement est surtout utile si tous les accès mémoire atterrissent dans la même banque. Pour comprendre pourquoi, faisons un rappel rapide sur le tampon de ligne. Les mémoires SDRAM sont des mémoires à tampon de ligne. Elles font un accès mémoire en deux étapes. La première étape recopie une ligne de N * 64 bits dans un tampon interne à la SDRAM. La seconde étape sélectionne une colonne, à savoir une donnée de 64 bits, qui est soit envoyée sur le bus de données pour une lecture, soit modifiée par une écriture. [[File:Mémoire à tampon de ligne.png|centre|vignette|upright=2|Mémoire à tampon de ligne]] Les deux étapes correspondent à deux commandes séparées : une commande ACT qui précise l'adresse de la ligne, et une commande READ ou WRITE qui précise l'adresse de la colonne. [[File:Signaux RAS et CAS.png|centre|vignette|upright=2|Signaux RAS et CAS.]] Les SDRAM permettent de se passer de la première étape si des accès consécutifs se font dans la même ligne. Une fois activée, la ligne reste ouverte et on peut accéder plusieurs fois de suite dedans. On a alors juste à préciser l'adresse de colonne dedans. [[File:Sélection d'une ligne sur une mémoire FPM ou EDO.png|centre|vignette|upright=2|Sélection d'une ligne sur une mémoire FPM ou EDO.]] L'exécution dans le désordre des lectures/écritures SDRAM vise à faire en sorte que des accès consécutifs se fassent dans une même ligne. Elle marche si plusieurs accès à une même ligne ne sont pas consécutifs, et qu'il y a des accès entre les deux. Après ré-ordonnancement, ces accès mémoire deviennent consécutifs, ils sont exécutés l'un à la suite de l'autre. Pour rendre le tout plus concret, voici un exemple. Imaginez que l'on sait les 3 accès mémoire suivants : * Une lecture ligne A ; * Une écriture ligne B ; * Une lecture ligne A. Sans réordonnancent, on doit émettre deux commandes PRECHARGE : une quand on passe de la ligne A à la ligne B, et une autre pour le passage inverse. pour éviter cela, le contrôleur mémoire peut retarder l'écriture, ou au contraire avancer la seconde lecture. le résultat est alors le suivant : * Lecture ligne A ; * Lecture ligne A ; * Une écriture ligne B. On a donc deux accès consécutifs à la même ligne, suivi par une écriture dans une autre ligne. La technique marche parce que l'on a un mix adéquat de lectures et d'écritures. Mais il existe des possibilités de réorganisation autres qui ne sont pas valides. Il faut par exemple prendre garde à éviter d'intervertir lectures et écritures à une même adresse, sous peine de problèmes. Encore une fois, il faut faire attention aux dépendances mémoires. Le controleur mémoire teste les dépendances mémoires avant d'envoyer des commandes à la mémoire DDR/SDRAM. ===L'entrelacement sur les mémoires SDRAM=== L''''entrelacement''' fonctionne sur les mémoires SDRAM, mais il doit être fortement modifié. Concrètement, tout ce qui a étré dit plus est inapplicable pour les SDRAM, du fait de la présence du tampon de ligne. Des adresses consécutives atterrissent déjà dans la même ligne, ce qui fait qu'on n'a pas besoin d'entrelacement au niveau d'une ligne. Et ne parlons pas de ce la présence de rangées et de canaux mémoire ! Cependant, il peut y avoir un entrelacement lié à la présence des banques SDRAM. Pour gérer l'entrelacement, le contrôleur de SDRAM prend en entrée l'adresse envoyée par le processeur, et la découpe en plusieurs champs : un pour sélectionner la banque adéquate, un autre pour la ligne, un autre pour la colonne, un autre pour la rangée. Une solution simple désactive l'entrelacement. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de banque || Adresse de ligne || Adresse de colonne |} Si cette solution fonctionne bien si les accès mémoire sont assez aléatoires, en pratique, mieux vaut utiliser l'entrelacement. Une première méthode d'entrelacement est l''''entrelacement de ligne'''. Elle répartit deux lignes consécutives dans deux banques différentes. Pour comprendre l'idée, prenons un exemple. Imaginons une mémoire avec deux banques et 4 lignes. Imaginons qu'on parcoure/balaye la mémoire RAM en partant des adresses basses. Sans entrelacement de ligne, les accès se feront comme suit, de gauche à droite : {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Banque numéro 1 !! colspan="4" | Banque numéro 2 |- | Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 || Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 |} Avec l'entrelacement de ligne, les accès se feront comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 |- | Ligne 1 || Ligne 1 || Ligne 2 || Ligne 2 || Ligne 3 || Ligne 3 || Ligne 4 || Ligne 4 |} Avec N banques, la première ligne ira dans la première banque, la seconde ligne dans la seconde, et ainsi de suite jusqu'à la énième banque qui va dans la banque numéro N. Et la ligne N+1 repart dans la première banque. L'avantage est que passer d'une ligne à la suivante est assez rapide. Au lieu de devoir fermer la première ligne et ouvrir la suivante on ouvrira directement la ligne suivante dans une banque différente, ce qui sera plus rapide. La commande PRECHARGE pourra être envoyée à la seconde banque en avance. Pour cela, l'adresse est découpée comme suit. {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne (précise à l'octet) |} Il est aussi possible de faire la même chose, mais avec les rangées. Pour simplifier fortement, une rangée est simplement un chip mémoire. En réalité, les rangées ne sont pas des chip mémoire, mais un ensemble de chips mémoire regroupés ensemble histoire d'atteindre les 64 bits du bus de données. Par exemple, une rangée peut combiner 8 chips mémoire avec un bus de données de 8 bits chacun pour obtenir les 64 bits du bus de données d'une SDRAM. Mais nous passons ce détail sous silence dans les explications qui vont suivre, par souci de simplification. Pour faire comprendre l'entrelacement de rangée, le mieux est d'utiliser un exemple, le même que précédemment. Sans entrelacement de rangée, on change de chip mémoire une fois qu'on a balayé toutes ses adresses. {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Rangée numéro 1 !! colspan="4" | Rangée numéro 2 |- | Banque 1 || Banque 2 || Banque 3 || Banque 4 || Banque 1 || Banque 2 || Banque 3 || Banque 4 |} Avec l'entrelacement de ligne, on change de rangée dès qu'on change de banque/ligne. {|class="wikitable" |+ Adresse mémoire |- ! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 |- | Banque 1 || Banque 1 || Banque 2 || Banque 2 || Banque 3 || Banque 3 || Banque 4 || Banque 4 |} En clair, quand on a épuisé toutes les banques dans une même rangée, on passe à la rangée suivante au lieu de rester dans la même rangée. Notons que dans l'exemple précédent, on a combiné l'entrelacement de rangée et de banque, mais on aurait pu utiliser l'entrelacement avec des lignes, non des banques. Mais ce n'est pas le cas le plus courant en pratique. Toujours est-(il qu'en combinant entrelacement de rangée et de ligne, le découpage de l'adresse est le suivant : {|class="wikitable" |+ Adresse mémoire |- | Adresse de ligne || Numéro de rangée || Adresse de banque || Adresse de colonne (précise à l'octet) |} D'autres méthodes d'entrelacement sont possibles, mais elles sont plus complexes. Chaque contrôleur mémoire fait un peu à sa sauce. Ils permettent en général de choisir entre plusieurs entrelacements. Au minimum, ils permettent de désactiver l'entrelacement, d'activer un entrelacement de ligne, et d'autres entrelacements plus complexes, dont certains sont propriétaires et tenus secrets par le fabricant. L'usage du ''dual channel'' complique encore l'entrelacement. Là encore, il y a deux grandes solutions : avec et sans entrelacement des canaux mémoire. Rappelons le principe : deux barrettes de RAM sont accédées en parallèle. Et pour cela, il faut utiliser l'entrelacement. Typiquement, chaque barrette mémoire fournit 64 bits, ce qui fait que l'on peut accéder à 128 bits d'un seul coup, par exemple avec un accès en rafale. Une solution pour cela est de ne pas utiliser l'entrelacement. Avec deux barrettes, la première barrette regroupera la moitié haute de la RAM, la seconde barrette utilisera la moitié basse. Les possibilité d’accéder à deux données séparées de 64 bits en même temps est cependant très rare. Aussi, on préfère utiliser l'entrelacement. Pour cela, deux blocs de 64 bits sont placés dans des canaux mémoire séparés. Idem avec du triple ou quadruple canal, mais c'est alors trois ou quatre blocs de 64 bits qui sont dispersés dans des canaux consécutifs. {|class="wikitable" |+ Adresse mémoire |- ! colspan="6" | Sans entrelacement |- | Numéro de canal/barrette || Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne || Position de l'octet dans un bloc de 64 bits |- | colspan="6" | |- ! colspan="6" | Avec entrelacement |- | Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne || Numéro de canal/barrette || Position de l'octet dans un bloc de 64 bits |} Les contrôleurs de SDRAM précédents sont assez basiques et ne représentent pas les contrôleurs les plus évolués. Ils étaient utilisés sur les anciens PC, à une époque où ils étaient encore sur la carte mère du processeur. Mais de nos jours, le contrôleur mémoire est intégré au processeur. Et il incorpore de nombreuses optimisations afin de gagner en performances. Malheureusement, ces optimisations sont elles-mêmes dépendantes des optimisations intégrées dans le processeur, ce qui fait qu'on ne peut pas en parler ici. Elles demandent que le contrôleur mémoire reçoive plusieurs accès mémoire simultanés, ce qui n'a aucun sens à ce stade du cours. Aussi, nous allons les passer sous silence pour le moment. Nous les verrons dans le chapitre sur le parallélisme mémoire. L'entrelacement est géré juste avant le séquenceur mémoire. Le '''circuit d'entrelacement''' intervertit certains bits de l'adresse lors des accès mémoires. On trouve aussi des mémoires FIFOs pour mettre en attente les requêtes mémoire provenant du processeur. Elles permettent de recevoir plusieurs accès mémoire consécutifs. Si vous vous demandez comment plusieurs accès mémoire consécutifs peuvent avoir lieu, sachez que c'est une bonne question. Pour le moment, nous allons dire que ces accès proviennent du processeur ou de la mémoire cache, sans expliquer comment. Le contrôleur mémoire reçoit une série d'accès mémoire assez rapprochés, qu'il doit exécuter au mieux. [[File:Controleur mémoire d'une SDRAM avec entrelacement.png|centre|vignette|upright=2.5|Contrôleur mémoire d'une SDRAM avec entrelacement.]] <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=La désambiguïsation mémoire | prevText=La désambiguïsation mémoire | next=Les processeurs superscalaires | nextText=Les processeurs superscalaires }} </noinclude> axyn81jgqkgzlci8r4afudfdi3i0o8r 764654 764653 2026-04-23T14:31:24Z Mewtow 31375 /* L'entrelacement sur les mémoires SDRAM */ 764654 wikitext text/x-wiki Dans ce chapitre, nous allons voir les techniques qui permettent de gérer plusieurs accès mémoire simultanés directement au niveau du cache ou de la mémoire RAM. Par plusieurs accès mémoire simultanés, vous pensez sans doute à l'usage de cache multiports, voire de mémoires RAM multiport. C'est en effet une solution, mais ce n'est clairement pas la seule. Par exemple, il est possible de pipeliner l'accès au cache, voire à la mémoire RAM. Il existe de nombreuses techniques de '''parallélisme mémoire''', et nous allons les voir dans ce chapitre. Pour donner un exemple, il est possible de pipeliner les accès mémoire. Il est possible de pipeliner l'accès au cache et/ou l'accès à la mémoire, et nous verrons comment faire dans ce chapitre. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, qu'ils aient ou non un pipeline dynamique. Par contre, pipeliner la mémoire est une technique ancienne, aujourd'hui peu utilisée. Elle était utilisée sur des très vieux ordinateurs, qui avaient un pipeline mais pas de mémoire cache. Mais de nos jours, les mémoires SDRAM et DDR ne sont pas adaptées pour ça, il est très difficile de les pipeliner correctement. Le parallélisme mémoire est utile aussi bien sur des processeurs à émission dans l'ordre que dans le désordre. Par exemple, un processeur ''in-order'' avec des lectures non-bloquantes peut e, profiter. Il peut techniquement lancer une seconde lecture, après avoir rencontré une première lecture non-bloquante. En général, le nombre de lectures consécutives est cependant limité à moins d'une dizaine. À l'opposé, les processeurs sans lectures non-bloquantes profitent pas du parallélisme mémoire. À la rigueur, ils peuvent en profiter s'ils intègrent des techniques de préchargement. Pendant que le processeur exécute une lecture/écriture, il peut précharger une autre donnée en parallèle. Inutile de dire que sans pipeline processeur, le parallélisme mémoire ne sert pas à grand-chose. ==Le ''line fill buffer'' et les techniques associées== Lors d'un défaut de cache, le processeur doit attendre que toute la ligne de cache soit chargée avant d'être utilisable. Or, la taille d'une ligne de cache est supérieure à la largeur du bus mémoire, ce qui fait qu'une ligne de cache est chargée en plusieurs fois, morceaux par morceaux, mot mémoire par mot mémoire. Le chargement peut se faire directement dans le cache, mais ce n'est pas une solution très pratique. À la place, beaucoup de processeurs ajoutent une mémoire tampon entre la RAM et le cache, appelée le '''tampon de remplissage de ligne''' (''line-fill buffer'') dans les processeurs modernes. Lors d'un défaut de cache, le processeur charge la donnée de la RAM dans le tampon de remplissage de ligne, mot mémoire par mot mémoire. Une fois plein, le tampon de remplissage de ligne est recopié dans la ligne de cache. Le tampon de remplissage de ligne contient une ligne de cache, si ce n'est que certains bits de contrôle ne sont pas présents. Le tampon de remplissage de ligne contient un bit de validité pour chaque mot mémoire de la ligne de cache, qui indiquent si le mot mémoire a été chargé. Par exemple, prenons un processeur 64 bits, qui gère donc des mots mémoire de 8 octets, avec des lignes de cache 256 octets/32 mots mémoire. Le tampon de remplissage de ligne contiendra 32 bits de validité. Si le processeur a chargé les 6 premiers mots mémoire, les 6 premiers bits de validité seront mis à 1, les autres seront encore à zéro. Il faut noter que la disponibilité de la ligne complète se détermine assez facilement en faisant un ET logique entre tous les bits de validité. Le processeur sait ainsi quand la ligne de cache est disponible entièrement et donc quand la transférer dans le cache. La même chose existe avec une hiérarchie de cache, sauf que l'on trouve une mémoire tampon entre chaque niveau de cache. Il y a un tampon de remplissage de ligne entre le cache L1 et le cache L2, entre le cache L2 et le L3, etc. Si le cache ne gère qu'un seul défaut de cache à la fois, le ''fill line buffer'' est une mémoire très simple, qui ne mémorise qu'une seule ligne de cache. Et cela vaut aussi bien pour un cache bloquant que non-bloquant. Mais sur les caches capables de gérer plusieurs défauts simultanés, le tampon de remplissage de ligne est une mémoire de type FIFO ou LIFO, capable de mémoriser plusieurs lignes de cache. Notons que le tampon de remplissage de ligne est très utile pour implémenter certaines techniques, par exemple le contournement du cache. Nous avions vu dans le chapitre sur le cache que certains accès mémoire doivent contourner le cache, pour des raisons de cohérence des caches. C'est notamment nécessaire pour accéder aux périphériques, mais c'est aussi utile pour des raisons de performances dans des cas très spécifiques. Les accès qui contournent le cache se font directement dans le tampon de remplissage de ligne : le processeur écrit ou lit les données depuis ce tampon de remplissage de ligne, sans accéder au cache. ===L'''early restart'' et le ''critical word load''=== La présence du ''line buffer'' permet une optimisation assez intéressante, qui permet de réduire la latence des défauts de cache. L'optimisation consiste à lire un mot mémoire dans le tampon de remplissage de ligne, même si la ligne de cache complète n'a pas encore été chargée. Il existe deux manières de faire cela, qui portent les noms d'''early restart'' et de ''critical word load''. La première est la version la plus simple, la seconde est plus complexe mais plus performante. Avec la technique d''''''early restart''''', la ligne de cache est chargée normalement, en partant de son premier mot mémoire. Dès que le mot mémoire lu/écrit par le processeur est copié dans le ''line fill buffer'', il est envoyé au processeur immédiatement. Illustrons le tout par un exemple, où une ligne de cache fait 16 mots mémoire. Le processeur effectue une lecture, qui lit le 5ème mot mémoire. Sans ''early restart'', le processeur doit charger les 16 mots mémoire avant de faire la lecture dans le cache. Avec ''early restart'', le processeur reçoit la donnée dès que le 5ème mot mémoire est disponible. Le processeur doit attendre que les 4 premiers mots mémoire soient chargés, puis le 5ème mot mémoire arrive et est envoyé directement au processeur, il est lu directement depuis le ''line fill buffer''. Les 11 mots mémoire suivants sont ensuite chargés dans le cache pendant que le processeur fait des calculs dans son coin. Le ''critical word load'' est une optimisation de la technique précédente où le chargement de la ligne de cache commence directement à la donnée demandée par le processeur. Pour reprendre l'exemple précédent, où le processeur demande le 5ème mot mémoire sur 16, le ''critical word load'' charge le 5ème mot mémoire en premier et l'envoie au processeur, ce qui fait qu'il est chargé très rapidement. Pas besoin d'attendre que le processeur charge les 4 mots mémoire précédents comme avec l'''early restart''. Dans le détail, le processeur charge le 5ème mot mémoire en premier, puis charge les 11 suivants, et termine par les 4 mots mémoire du début. En clair, le ''critical word load'' commence par charger le mot mémoire lu, puis les blocs suivants, avant de revenir au début du bloc pour charger les blocs restants. Ainsi, la donnée demandée par le processeur sera la première disponible. Pour cela, l'organisation du tampon de remplissage de ligne est modifiée de manière à rendre cela possible. Il a une taille égale à une ligne de cache complète, qui contient elle-même plusieurs mots mémoire. Dans le ''line-fill buffer'', chaque mot mémoire est stocké avec un tag, qui indique l'adresse du mot mémoire stocké dans le ''line-fill buffer''. Le ''line-fill buffer'' est donc un cache un peu particulier, qui fonctionne comme un cache du point de vue du processeur, comme une mémoire FIFO pour les transferts avec le cache. Ainsi, un processeur qui veut lire dans le cache après un défaut peut accéder à la donnée directement depuis le tampon de remplissage de ligne, alors que la ligne de cache n'a pas encore été totalement recopiée en mémoire. ==Les caches pipelinés== Il est possible de pipeliner l'accès au cache, ce qui demande juste de rajouter des registres au bon endroits et quelques circuits de contrôle. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, même ceux avec un pipeline dynamique. Et l'implémentation peut se faire de plusieurs manières différentes. Il faut dire que découper une mémoire cache en plusieurs étapes peut se faire de plusieurs manière. Commençons par la plus simple. Beaucoup de processeurs des années 2000 avaient une fréquence peu élevée comparée aux standards d'aujourd'hui, ce qui fait que leur cache avait bien un temps d'accès d'un cycle d'horloge. Mais l'accès au cache se faisait en deux cycles d'horloge : un cycle pour calculer une adresse et un cycle pour l'accès mémoire proprement dit. Et ces deux étapes étaient pipelinées, à savoir que deux micro-opérations mémoire peuvent s'exécuter en même temps : une dans l'étage de calcul d'adresse, une autre dans le cache. L'unité mémoire était donc pipelinée, alors que l'accès au cache ne l'était pas. Les processeurs qui implémentaient cette technique regroupent les micro-architectures K5 et K6 d'AMD, les processeurs Intel de micro-architecture P6 (Pentium 2 et 3) et quelques autres. Il est aussi possible de pipeliner l'accès au cache lui-même. Avec un cache pipeliné, l'accès au cache ne se fait pas en un seul cycle, mais en plusieurs. Cependant, on peut lancer un nouvel accès au cache à chaque cycle d'horloge, comme un processeur pipeliné lance une nouvelle instruction à chaque cycle. L'implémentation est assez simple : il suffit d'ajouter des registres dans le cache. Pour cela, on profite que le cache est composé de plusieurs composants séparés, qui échangent des données dans un ordre bien précis, d'un composant à un autre. De plus, le trajet des informations dans un cache est linéaire, ce qui les rend parfois pour l'usage d'un pipeline. ===Le ''pipelining'' des caches ''direct-mapped''=== Voici ce qui se passe avec un cache directement adressé. Pour rappel ce genre de cache est conçu en combinant une mémoire RAM, généralement une SRAM, avec quelques circuits de comparaison et un MUX. L'accès au cache se fait globalement en deux étapes : la première lit la donnée et le ''tag'' dans la SRAM, et on utilise les deux dans une seconde étape. Nous avions vu il y a quelques chapitre comment pipeliner des mémoires, dans le chapitre sur les mémoires évoluées. Et bien ces méthodes peuvent s'utiliser pour la mémoire RAM interne au cache !L'idée est d'insérer un registre entre la sortie de la RAM et la suite du cache, pour en faire un cache pipéliné, à deux étages. Le premier étage lit dans la SRAM, le second fait le reste. L'implémentation sur les caches associatifs à plusieurs voies est globalement la même à quelques détails près. [[File:Cache directement adressé pipeliné - deux étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - deux étages]] Avec le circuit précédent, il est possible d'aller plus loin, cette fois en pipelinant l'accès à la mémoire RAM interne au cache. Pour comprendre comment, rappelons qu'une mémoire SRAM est composée d'un plan mémoire et d'un décodeur. L'accès à la mémoire demande d'abord que le décodeur fasse son travail pour sélectionner la case mémoire adéquate, puis ensuite la lecture ou écriture a lieu dans cette case mémoire. L'accès se fait donc en deux étapes successives séparées, on a juste à mettre un registre entre les deux. Il suffit donc de mettre un gros registre entre le décodeur et le plan mémoire. [[File:Cache directement adressé pipeliné - trois étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - trois étages]] Et on peut aller encore plus loin en découpant le décodeur en deux circuits séparés. En effet, rappelez-vous le chapitre sur les circuits de sélection : nous avions vu qu'il est possible de créer des décodeurs en assemblant des décodeurs plus petits, contrôlés par un circuit de prédécodage. Et bien on peut encore une ajouter un registre entre ce circuit de prédécodage et les petits décodeurs. : Théoriquement, toute l'adresse est fournie d'un seul coup au cache, la quasi-totalité des processeurs présentent l'adresse complète à un cache pipeliné. Mais le Pentium 4 fait autrement. Il faut noter que les premiers étages manipulent l'indice dans la SRAM, qui est dans les bits de poids faible, alors que les étages ultérieurs manipulent le ''tag'' qui est dans les bits de poids fort. Les concepteurs du Pentium 4 ont alors décidé de présenter les bits de poids faible lors du premier cycle d'accès au cache, puis ceux de poids fort au second cycle. Pour cela, l'ALU fonctionnait à une fréquence double de celle du processeur, tout comme le cache L1. Il n'y avait pas de pipeline proprement dit, mais cela réduisait grandement la latence d'accès au cache. ===Le ''pipelining'' des caches splittés/sériels=== Il est aussi possible de pipeliner un cache dits splittés, aussi appelés à accès sériel. Pour rappel, les caches à accès sériel vérifient si il y a succès ou défaut de cache, avant d'accéder aux lignes de cache en cas de succès. Ils font donc différemment des autres caches, qui accèdent à une ligne de cache, avant de déterminer s'il y a succès ou défaut en lisant le tag de la ligne de cache. Les caches sériels disposent de deux SRAM : une pour les tags des lignes de cache et une pour les données. Ils accèdent à la SRAM pour les tags, avant d’accéder à la SRAM des données en cas de succès de cache. Vu que l'accès se fait en deux étapes, une vérification des tags suivie de la lecture/écriture des données, il est facile à pipeliner. Pipeliner le cache permet de régler le problème des accès au cache L1, et elle est tout le temps utilisé sur les processeurs modernes. Mais que faire en cas de défaut de cache ? ==Les caches non bloquants== Un '''cache bloquant''' est un cache auquel le processeur ne peut pas accéder pendant un défaut de cache. Il faut attendre que la lecture ou écriture en RAM soit terminée avant de pouvoir utiliser de nouveau le cache. Un '''cache non bloquant''' n'a pas ce problème : on peut l'utiliser pendant un défaut de cache. Les caches non bloquants permettent de démarrer une nouvelle lecture ou écriture alors qu'une autre est en cours, ce qui permet d'exécuter plusieurs lectures ou écritures en même temps. ===Les ''Miss Handling Status Registers''=== Lors d'un défaut de cache, la mémoire RAM est consultée pendant le défaut de cache, mais le cache est inutilisé. Un défaut de cache n'utilise pas le cache, ce n'est qu'un accès en mémoire RAM, sur le bus mémoire (ou un accès aux niveaux de cache inférieurs, peu importe). Le cache en lui-même est laissé libre, rien n’empêche d'y accéder, il est en réalité intrinsèquement non-bloquant. Les caches, bloquants comme non-bloquants, sont en réalité composés d'une mémoire cache proprement dite, entourée de circuits qui servent d'interface entre le processeur et le cache lui-même. Et parmi les circuits tout autour, certains gèrent l'accès au cache lors d'un défaut de cache. Ils sont regroupés sous le terme de '''''Miss Handling Architecture''''' (MHA). La différence entre un cache bloquant et un cache non-bloquant est en réalité liée à l'implémentation de la MHA. Les caches bloquants coupent volontairement l'accès au cache lors d'un défaut, car les défauts sont plus simples à gérer ainsi. Le défaut de cache rapatrie une donnée depuis la RAM, qui sera écrite dans le cache. Et il ne faut pas qu'une tentative d'accès à cette donnée ait lieu avant qu'elle ne soit chargée. Mais ce blocage est général et touche tout le cache, alors que seule une ligne de cache est concernée par le défaut de cache. L'idée derrière un cache non-bloquant est que seule la ligne de cache est bloquée, mais les autres sont accessibles. L'idée est alors de mémoriser les lignes de cache concernées par le défaut de cache, afin d'en bloquer l'accès. À chaque accès, on vérifie si la ligne de cache est déjà réservée par un défaut de cache. La lecture/écriture est alors bloquée si c'est le cas, mais elle accepte les accès sinon. Pour cela, la ''Miss Handling Architecture'' contient des registres qui mémorisent des informations sur les défauts de cache en cours. Ils portent le nom de '''''miss status handling registers''''', que j’appellerais dorénavant MSHR, qui sont aussi appelés des '''''miss buffer'''''. Le contenu des MSHR varie beaucoup suivant le processeur, mais ces derniers stockent au minimum les informations suivantes : * Le numéro de la ligne de cache dans laquelle les données sont chargées. * Un bit de validité qui indique si le MSHR est vide ou pas, qui est mis à 0 quand le défaut de cache est résolu. * Un ou plusieurs champs de lecture/écriture, qui contiennent des informations sur la lecture/écriture. ** Pour une lecture, elle contient des informations sur la destination de la donnée, à savoir qui prévenir quand le défaut de cache est terminé. C'est parfois un nom/numéro de registre (celui dans lequel charger la donnée), mais c'est souvent le numéro de l'entrée dans le ''load/store queue''. ** Pour les écritures, elle contient la donnée à écrire, ou éventuellement un numéro de ''load/store queue'' où se trouve la donnée à écrire. Il faut noter que le bus mémoire ne peut gérer qu'un seul défaut de cache à la fois. Aussi, il est intéressant de regarder ce qui se passe lorsqu'un second défaut de cache survient, pendant qu'un premier est en cours. Dans ce cas, il y a deux réponses qui correspondent à deux types de caches non-bloquants, qui portent les noms barbares de caches de type succès après défaut et défaut après défaut. Sur le premier type, il ne peut pas y avoir plusieurs défauts de cache simultanés. Dès qu'un second défaut de cache survient, le cache stoppe son activité et on ne peut plus démarrer de nouvelle lecture/écriture, tant que le premier défaut de cache n'est pas résolu. Le second type est plus souple et autorise la survenue de plusieurs défauts de cache simultanés. Du moins, jusqu'à une certaine limite, car le cache ne peut supporter qu'un nombre limité d'accès mémoires simultanés (pipelinés). ===Les accès simultanés à une même ligne de cache=== Il arrive que le processeur fasse plusieurs accès mémoire simultanés à la même ligne de cache. Si la ligne de cache en question n'a pas encore été chargée dans le cache, alors on a plusieurs défauts de cache consécutifs pour la même ligne de cache, et le cache non-bloquant doit gérer la situation. Pour la suite, il va falloir faire une petite distinction entre les défauts primaires et secondaires. Imaginons qu'un défaut de cache ait lieu et demande à charger une donnée dans la ligne de cache numéro N. Il s'agit du premier défaut impliquant cette ligne de cache précise, ce qui lui vaut le nom de '''défaut de cache primaire'''. Mais par la suite, d'autres accès mémoire à la même ligne de cache ont lieu, alors que la ligne de cache n'est pas encore disponible. Dans ce cas, il s'agit de '''défauts de cache secondaires'''. Pour l'unité d'accès mémoire, les défauts de cache primaire et secondaire sont différents (ils prennent tous une entrée dans la ''load/store queue''). Mais pour le cache, ils ne correspondent qu'à un seul accès au cache : celui qui demande de charger la ligne de cache demandée. Les défauts de cache primaire et secondaire à la même ligne de cache se voient attribuer un MSHR unique. La gestion des défauts de cache secondaires dépend du cache non-bloquant. La solution la plus simple ne permet pas les défauts de cache secondaires. Le cache ne permet pas deux défauts de cache simultanés pour la même ligne de cache. Les autres solutions le permettent, en fusionnant des accès simultanés à la même ligne de cache en un seul au niveau des MSHR. Dans tous les cas, détecter les défauts de cache secondaires sont un problème qu'il faut détecter. La ''Miss Handling Architecture'' doit détecter les défauts de cache secondaire. Pour cela, elle procède comme suit. Lors de chaque défaut de cache, la MHA récupère le numéro de la ligne de cache associé. Il vérifie alors chaque MSHR pour vérifier s'il contient le numéro en question. S'il n'y a aucune correspondance dans les MSHR, alors c'est signe que le défaut de cache est un défaut primaire. Mais s'il y en a une, alors c'est un défaut secondaire. Évidemment, cela signifie que lors d'un défaut de cache, le numéro de ligne de cache est envoyé à tous les MSHR, pour comparaison. Les MSHR sont donc regroupés dans une mémoire associative, une sorte de mini-cache, faciliter l'implémentation. Lors d'un défaut de cache primaire, l'accès à un cache non-bloquant se fait comme suit : le processeur envoie une adresse au cache, accède à celui-ci, et détecte la survenue d'un défaut de cache. Il en profite alors pour attribuer une ligne de cache dans laquelle sera chargée ce défaut. L'attribution est très simple dans le cas des caches ''direct mapped'', ou associatifs par voie, pour lesquels l'attribution se fait assez simplement. Il mémorise alors cette information dans les MSHR, après avoir vérifié que le défaut de cache n'était pas un défaut secondaire. Lors des accès ultérieurs à une adresse proche, censée être dans la même ligne de cache, le processeur va encore une fois rencontrer un défaut de cache. Il va alors déterminer le numéro de la ligne de cache associée à l'adresse, et comparer ce numéro avec les MSHRs. Si un MSHR contient ce numéro, c'est signe que le défaut de cache est un défaut secondaire. La MHA réagit alors différemment selon le processeur considéré. Une première solution n'autorise pas les défauts de cache secondaires. Si l'un d'entre eux survient, le processeur est gelé par un ''pipeline stall'', une bulle de pipeline. Une autre solution fusionne les défauts de cache secondaires avec le défaut de cache primaire : tout cela ne correspond qu'à un seul défaut de cache pour lui. ===Les MSHR simples=== Un cache non-bloquant à '''MSHR simple''' contient juste plusieurs MSHR qui mémorisent juste un numéro de ligne de cache, un bit de validité, le champ de lecture/écriture, et l'adresse exacte de lecture/écriture. Avec cette organisation, il est possible d'avoir plusieurs défauts de cache séparés, mais à la condition que chaque défaut accède à une ligne de cache différente. Deux accès simultanés à une même ligne de cache ne sont pas possibles, les défauts de cache secondaires ne sont pas autorisés. Ainsi, chaque défaut de cache se voit attribuer son propre MSHR, chacun contient un numéro de ligne de cache différent. Pour comprendre pourquoi c'est impossible de gérer les défauts secondaires, il faut regarder le champ de lecture/écriture. Si on veut effectuer plusieurs écritures consécutives à la même adresse, le MSHR n'aura pas de quoi mémoriser les deux données à écrire. Il pourra mémoriser la première donnée à écrire, pas la seconde. Même chose lors d'une lecture : le champ lecture/écriture peut mémorisr la destination de la première lecture, pas de la seconde. L'avantage est que la MHA n'a besoin que des MSHR et de quelques circuits annexes. Les autres solutions rajoutent des circuits annexes pour gérer les défauts de cache secondaires, qui utilisent beaucoup de circuits. Le cout en circuit est donc élevé, mais le gain en performance est là. Passons maintenant aux caches non-bloquants qui autorisent les défauts de cache secondaire. La solution la plus simple consiste à utiliser ===Les MSHR adressés implicitement=== Avec les '''MSHR adressés implicitement''', il est possible de fusionner plusieurs accès mémoire à une même ligne de cache, mais sous une condition très importante : ces accès lisent/écrivent des mots mémoire différents. Par exemple, imaginons qu'une ligne de cache contienne 8 mots mémoire de 64 bits. Si un premier accès mémoire lit le mot mémoire numéro 7 (dernier mot mémoire de la ligne), et le second accès le mot mémoire numéro 3, alors la fusion est possible. Mais si deux accès mémoire veulent lire/écrire le mot mémoire numéro 7, alors la fusion n'est pas possible et le processeur se bloque, un ''pipeline stall'' survient. Un MSHR adressé implicitement est un MSHR simple, qui contient naturellement un numéro de ligne de cache (tag) et un bit de validité, sauf que le champ de lecture/écriture est dupliqué. Les différents champs lecture/écriture d'un MSHR sont regroupés dans une mémoire RAM/cache qui contient autant d'entrées qu'il y a de mot mémoire dans une ligne de cache. Chaque entrée de la table est associée à un mot mémoire de la ligne de cache et stocke des informations sur celui-ci. Une entrée mémorise, au minimum : * Un champ de lecture/écriture qui contient soit la destination de la lecture, soit la donnée à écrire. * Un bit R/W permettant d'interpréter correctement le champ de lecture/écriture. * Un bit de validité pour chaque entrée de la table, qui dit si un défaut de cache antérieur accède déjà à ce mot mémoire. La fusion de deux défauts de cache est ainsi assez simple. Un défaut de cache primaire/secondaire configure l'entrée associée au mot mémoire lu/écrit. Si un défaut secondaire ultérieur lit/écrit un mot mémoire différent (dont le bit de validité de l'entrée dans la table est à zéro), pas de conflit : il configure une autre entrée, vide. Mais s'il lit/écrit un mot mémoire pour lequel l'entrée de la table est occupée, il y a conflit, le processeur est bloqué par un ''pipeline stall'', une bulle de pipeline. Avec cette organisation, le nombre de MSHR indique combien de lignes de cache peuvent être lues en même temps depuis la mémoire. Quant au nombre d'entrées par MSHR, il détermine combien d'accès mémoires qui ne se recouvrent pas peuvent avoir lieu en même temps. ===Les MSHR adressés explicitement=== Les '''MSHR adressés explicitement''' n'ont pas les contraintes des MSHR adressés implicitement. Avec eux, il est possible d'avoir plusieurs défauts de cache pointant vers la même ligne de cache, mais aussi vers le même mot mémoire. De tels défauts de cache apparaissent sur les processeurs à exécution dans le désordre, mais sont très rares, voire inexistants, sur les processeurs ''in-order''. L'idée est encore que chaque MSHR est associée à une table mémoire qui mémorise des entrées. Cette table n'est autre qu'une mémoire FIFO. Sauf que cette fois-ci, une entrée n'est pas associée à un mot mémoire. Une entrée est associée à un défaut de cache. Une entrée mémorise là encore la destination de la lecture ou la donnée de l'écriture suivi du bit R/W, ainsi que d'un bit de validité par entrée, mais aussi : la position du mot mémoire lu dans la ligne de cache. C'est cette dernière information qui n'était pas présente dans les MSHR adressés implicitement. De plus, le nombre d'entrée par MSHR n'est pas égal au nombre de mots mémoires dans une ligne de cache, mais peut être arbitrairement grand. L'avantage d'un tel cache est qu'il est capable de traité les défauts secondaires concernant un même mot mémoire, et ce, car les accès à la ligne de cache concernée se produisent dans l'ordre de génération des défauts (la table mémoire est une FIFO). ===Les MSHRs inversés=== Généralement, plus on veut supporter de défauts de cache, plus le nombre de MSHR et d'entrées augmente. Mais au-delà d'un certain nombre d'entrées et de MSHR, les MSHR adressés implicitement et explicitement ont tendance à bouffer un peu trop de circuits. Utiliser une organisation un peu moins gourmande en circuits est donc une nécessité. Cette organisation plus économe se base sur des MSHR inversés. Les MSHR inversés ne contiennent qu'une seule entrée, en quelque sorte : au lieu d’utiliser n MSHR de m entrées chacun, on va utiliser n × m MSHR inversés. La différence, c'est que plusieurs MSHR peuvent contenir un tag identique, contrairement aux MSHR adressés implicitement et explicitement. Lorsqu'un défaut de cache a lieu, chaque MSHR est vérifié. Si jamais aucun MSHR ne contient de tag identique à celui utilisé par le défaut, un MSHR vide est choisi pour stocker ce défaut, et une requête de lecture en mémoire est lancée. Dans le cas contraire, un MSHR est réservé au défaut de cache, mais la requête n'est pas lancée. Quand la donnée est disponible, les MSHR correspondant à la ligne qui vient d'être chargée vont être utilisés un par un pour résoudre les défauts de cache en attente. ===Les MSHR intégrés au cache=== Certains chercheurs ont remarqué que pendant qu'une ligne de cache est en train de subir un défaut de cache, celle-ci reste inutilisée, et son contenu est destiné à être perdu une fois le défaut de cache résolu. Ils se sont dit que, plutôt que d'utiliser des MSHR séparés, il vaudrait mieux utiliser la ligne de cache pour stocker les informations sur les défauts de cache en attente dans cette ligne de cache. Pour éviter tout problème, il faut rajouter un bit dans les bits de contrôle de la ligne de cache, qui sert à indiquer que la ligne de cache est occupée : un défaut de cache a eu lieu dans cette ligne, et elle stocke donc des informations pour résoudre les défauts de cache. ==L'entrelacement mémoire== Nous venons de voir comment une mémoire cache peut gérer plusieurs accès mémoire simultanés. Il se trouve que ce genre d'optimisation dépasse largement le cadre du cache. Les mémoires RAM ne sont pas en reste, elles aussi ! Il est possible de pipeliner l'accès à une mémoire RAM, en utilisant des techniques dites d''''entrelacement'''. Précisons cependant que cette optimisation n'est pas utilisée sur les mémoires SDRAM ou DDR modernes, du moins pas sans modifications majeures. Mais divers ordinateurs assez anciens, dont des superordinateurs, ont utilisé cette technique. La technique demande d'utiliser plusieurs mémoires séparées. Sur les anciens ordinateurs, les mémoires en question sont des chips mémoire, à savoir des circuits intégrés de DRAM. Pour faire plus général, nous allons utiliser le terme de '''banques''', ou encore de bancs mémoire. La différence est qu'il existe des mémoires multi-banque, qui regroupent plusieurs banques indépendantes dans un seul boitier, dans un seul chip mémoire. En clair, de telles mémoires regroupent plusieurs sous-mémoires dans un seul circuit intégré. Il est aussi possible d'utiliser plusieurs chips mémoire séparés, chacun ayant une banque. Reste à combiner ces banques pour former un pseudo-pipeline. ===Utiliser plusieurs banques sans entrelacement=== Sans optimisation particulière, les adresses sont réparties dans les banques comme indiqué ci-dessous. Il s'agit de ce qui s'appelle un arrangement horizontal, et nous avions vu celui-ci dans le chapiutre sur les mémoires SDRAM. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Avec cet arrangement, les bits de poids fort de l'adresse sont utilisées pour sélectionner la banque adéquate, et le reste de l'adresse est envoyé sur le bus d'adresse. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Adresse de banque !! Adresse dans la banque |- | Quelques bits de poids fort || Reste de l'adresse |} L'entrelacement change cette répartition, afin d'accélérer les accès mémoire. L'entrelacement classique vise à améliorer les accès à des adresses mémoires consécutives, mais des formes plus évoluées d'entrelacement visent à optimiser des accès mémoire arbitraires. Voyons-les dans l'ordre, du plus simple au plus complexe. ===L'entrelacement classique=== Avec l''''entrelacement classique''', des adresses consécutives sont réparties dans des banques différentes. [[File:Répartition des adresses dans une mémoire interleaved.png|centre|vignette|upright=1.5|Répartition des adresses dans une mémoire interleaved.]] L'entrelacement simple permet d'accélérer les accès mémoire qui se font à des adresses consécutives. Avec l'entrelacement, chaque accès mémoire tombe dans une banque mémoire différente, ce qui fait qu'on peut démarrer un nouvel accès mémoire à chaque cycle d'horloge. Pendant qu'une banque est occupée par un accès mémoire, on démarre le suivant dans une autre banque, et ainsi de suite. Le tout se termine soit quand on a épuisé toutes les banques libres, soit quand l'accès en rafales se termine. Pas besoin d'attendre que la mémoire ait fini sa lecture/écriture avant de démarrer la lecture/écriture suivante. [[File:Pipemining mémoire.png|centre|vignette|upright=2.5|Pipelining mémoire]] L'animation suivante illustre bien le processus, avec quatres banques. [[File:Interleaving.gif|centre|vignette|upright=2.5|Pipelining mémoire]] L'entrelacement simple utilise les bits de poids faible pour sélectionner la banque, et les bits de poids fort pour la case mémoire. [[File:Adresse mémoire d'une mémoire entrelacée.png|centre|vignette|upright=2|Adresse mémoire d'une mémoire entrelacée]] Précisons que le temps d'accès mémoire ne change pas beaucoup avec l'entrelacement. Par contre, on peut faire plus d'accès mémoire simultanés. On peut démarrer un accès mémoire par cycle d'horloge, mais l'accès en lui-même prend plusieurs cycles. Les mémoires à entrelacement ont donc un débit supérieur aux mémoires qui ne l'utilisent pas. L'entrelacement classique pourrait être utilisé pour accélérer les transferts entre mémoire RAM et cache, qui se font en blocs d'adresses consécutives. Mais de nos jours, il n'est pas utilisé dans cette optique. Il faut dire qu'il est redondant avec le mode rafale des mémoires DRAM. Les deux font globalement la même chose et on voit mal comment utiliser les deux en même temps. Le mode rafale étant plus simple à implémenter, et plus léger niveau utilisation du bus de commande mémoire, il est préféré à l'entrelacement. Par contre, l'entrelacement a eu son heure de gloire sur d'anciens processeurs qui n'avaient pas de mémoire cache, alors qu'ils disposaient d'un pipeline. L'exemple typique est celui des nombreux ''processeurs vectoriels'', que nous n'avons pas encore abordé à ce stade du cours. De tels processeurs avaient un pipeline, une unité de calcul fortement pipelinée, et parfois même de l'exécution dans le désordre et du renommage de registres ! ===L'entrelacement par décalage=== L'entrelacement simple est très utile pour les accès en rafle ou équivalents. Par contre, il existe de rares situations où il n'est pas optimal. Une de ces situations est celle des accès en enjambée (en ''stride''), où l'on accède en série à des adresses sont séparées par N mots mémoires. De tels accès surviennent quand un logiciel accède à des structures de données spécifiques, à savoir des tableaux de structures, des matrices, ou d'autres structures de données dans le genre. Un cas classique est celui du parcours d'une matrice colonne par colonne. Les matrices de nombres sont mémorisées ligne par ligne, ce qui fait que parcourir une colonne demande de passer d'une ligne à la suivante et de faire des grands sauts en mémoire RAM, mais tous espacés par la même distance. Avec l'entrelacement simple, les accès en enjambée sont moins performants que les accès à des adresses consécutives, mais cela ne fonctionne pas trop mal. Le pire cas est celui où l'on a N banques et où les données sont justement placées toutes les N adresses. Des accès consécutifs vont tous tomber dans la même banque, on ne peut plus accéder à des banques différentes en parallèle. Mais en dehors de ce cas, on voit une amélioration par rapport à la situation sans entrelacement. Pour obtenir des performances maximales pour les accès en enjambées, il faut répartir les mots mémoires dans la mémoire autrement. L'organisation idéale est la suivante. : Dans les explications qui vont suivre, la variable N représente le nombre de banques, qui sont numérotées de 0 à N-1. On commence par organiser les N premières adresses comme une mémoire entrelacée simple : l'adresse 0 correspond à la banque 0, l'adresse 1 à la banque 1, etc. Pour le bloc suivant, on décale tout d'une adresse, à savoir qu'on commence à remplir les banques à partir de la seconde, non de la première. Une fois la fin du bloc atteinte, on finit de remplir le bloc en repartant du début du bloc. Le troisième bloc d'adresse subit le même traitement, sauf qu'on commence à remplir à partir de la seconde banque. Et on poursuit l’assignation des adresses en décalant d'un cran en plus à chaque bloc. Ainsi, chaque bloc verra ses adresses décalées d'un cran en plus comparé au bloc précédent. Si jamais le décalage dépasse la fin d'un bloc, alors on reprend au début. [[File:Mémoire interleaved par décalage.png|centre|vignette|upright=1.5|Mémoire entrelacée par décalage.]] En faisant cela, on remarque que les banques situées à N adresses d'intervalle sont différentes. Dans l'exemple du dessus, nous avons ajouté un décalage de 1 à chaque nouveau bloc à remplir. Mais on aurait tout aussi bien pu prendre un décalage de 2, 3, etc. Dans tous les cas, on obtient un entrelacement par décalage. Ce décalage est appelé le pas d'entrelacement, noté P. Le calcul de l'adresse à envoyer à la banque, ainsi que la banque à sélectionner se fait en utilisant les formules suivantes : * adresse à envoyer à la banque = adresse totale / N ; * numéro de la banque = (adresse + décalage) modulo N, avec décalage = (adresse totale * P) mod N. Avec cet entrelacement par décalage, on peut prouver que la bande passante maximale est atteinte si le nombre de banques est un nombre premier. Seulement, utiliser un nombre de banques premier peut créer des trous dans la mémoire, des mots mémoires inadressables. Pour éviter cela, il faut faire en sorte que N et la taille d'une banque soient premiers entre eux : ils ne doivent pas avoir de diviseur commun. Dans ce cas, les formules se simplifient : * adresse à envoyer à la banque = adresse totale / taille de la banque ; * numéro de la banque = adresse modulo N. ===L'entrelacement pseudo-aléatoire=== Une dernière méthode de répartition consiste à répartir les adresses dans les banques de manière "pseudo-aléatoire". L'idée exacte est de faire passer l'adresse dans une '''fonction de hachage''', plus ou moins complexe. Pour rappel, une fonction de hachage prend une entrée de grande taille et fournit en sortie un résultat de petite taille. Idéalement, elles doivent donner des résultats aussi différents que possible pour des entrées similaires, histoire de simuler une sorte de pseudo-aléatoire. Ici, une adresse est transformée en un numéro de banque. Une fonction de hachage simple ne fait que permuter des bits de l'adresse pour obtenir son résultat. Elle ne fait qu'échanger des bits de place avant de couper l'adresse en deux morceaux : un pour la sélection de la banque, et un autre pour la sélection de l'adresse dans la banque. Cette permutation est fixe, et ne change pas suivant l'adresse. Des fonctions de hachage plus complexes font des XOR bit à bit entre certains bits de l'adresse. Il existe aussi d'autres techniques qui donnent le numéro de banque à partir d'un polynôme modulo N, appliqué sur l'adresse. ==Les contrôleurs mémoires SDRAM/DDR optimisés== Après avoir vu le cas des mémoires RAM, nous allons nous concentrer sur le cas particulier des mémoires SDRAM. Les mémoires SDRAM et DDR modernes sont capables de gérer plusieurs accès simultanés à la mémoire RAM. Et les contrôleurs mémoire intègrent des optimisations spécifiques afin d'en profiter au maximum. ===La mise en attente des accès mémoire=== Comme pour les mémoires caches, il existe des contrôleurs mémoire bloquants et non-bloquants. Les premiers n'acceptent qu'un seul accès mémoire à la fois, les seconds acceptent plusieurs accès mémoire simultanés. Pour simplifier, nous allons dire que le contrôleur mémoire n’exécute qu'un seul accès mémoire à la fois, et qu'ils met en attente les accès en trop. C'est une simplification assez irréaliste, vu que les mémoires modernes disposent de plusieurs banques accessibles en parallèle. Mais nous introduirons ce détail un peu plus tard. Avec un contrôleur mémoire de ce type, les accès mémoire sont mis en attente dans une mémoire FIFO, histoire de les exécuter dans leur ordre d'arrivée. Les accès mémoire quittent alors la mémoire dans leur ordre d'arrivée, les lectures sont renvoyées dans l'ordre demandé. En plus d'une mémoire FIFO pour les commandes mémoire, on trouve une mémoire FIFO pour les données à écrire. Elle permet d'accumuler plusieurs écritures, tout en permettant que la donnée adéquate soit envoyée à la RAM au bon moment. Il y a aussi une FIFO pour les données lues, qui sert au cas où le cache ne soit pas disponible quand on lui envoie la donnée lue. [[File:Module d'interface avec la mémoire.png|centre|vignette|upright=2.5|Contrôleur mémoire avec mise en attente des requêtes processeur et autres optimisations.]] Une optimisation regroupe plusieurs accès à des données consécutives en un seul accès en rafale. Le contrôleur mémoire analyse les requêtes mises en attente et détecte si certaines se font à des adresses consécutives. Si c'est le cas, il fusionne ces requêtes en une lecture/écriture en rafale. Une telle optimisation est appelée la '''combinaison de lecture''' pour les lectures, et la '''combinaison d'écriture''' pour les écritures. ===Le pipeline des mémoires SDRAM=== Les SDRAM ont une forme de pseudo-pipeline très limité. Elles sont parfois capables de démarrer une commande avant que la précédente soit terminée. Même si elle parait très limitée, cette possibilité est exploitée au mieux par la présence de plusieurs banques dans la mémoire SDRAM. Les chips mémoires de SDRAM sont composés de plusieurs banques, chacune étant une sorte de mini-mémoire miniature. Chaque banque a son propre décodeur, son propre tampon de ligne, ses propres multiplexeurs de colonne, sa logique de rafraichissement mémoire, etc. C'est comme si une SDRAM regroupait plusieurs mémoires séparées dans un même circuit intégré. Et cela permet de faire plusieurs accès mémoire en même temps, dans des banques différentes. Il est possible de lancer un accès mémoire dans deux banques en même temps, par exemple. Ou encore, on peut lancer un nouvel accès mémoire dans une banque si elle est inoccupée. La possibilité améliore grandement la performance de la SDRAM. Mais nous en reparlerons dans un chapitre ultérieur. {|class="wikitable" |+ Pipelining basique sur les SDRAM |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 |- ! Banque Numéro 1 | || || || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || || || |- ! Banque Numéro 2 | || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || |- ! Banque Numéro 3 | || || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || |} L'implémentation n'a rien de vraiment compliqué. Il suffit d'utiliser une mémoire FIFO par banque. Ainsi, les accès dans une même banque se feront en série, dans l'ordre d'arrivée, mais des accès à des banques différentes se feront dans le désordre. Cela ne pose pas de problème car les accès se font à des adresses différentes, ce qui fait qu'il n'y a pas de conflits majeurs. [[File:Gestion parallèle des banques.png|centre|vignette|upright=2.5|Gestion parallèle des banques.]] ===Le ré-ordonnancement des commandes mémoires=== Un contrôleur mémoire moderne est capable de changer l'ordre des requêtes mémoires, pour gagner en performances. En clair : il peut faire l'équivalent mémoire de l'exécution dans le désordre des processeurs haute performance. Une différence majeure est que le contrôleur mémoire ne remet pas les accès mémoire dans l'ordre avant de les envoyer au processeur. C'est le processeur qui remet en ordre les accès mémoire, dans sa ''load queue''. Pour cela, les requêtes mémoire sont "numérotées", à savoir qu'on leur attribue un identifiant binaire. Le contrôleur mémoire exécute les lectures/écritures dans le désordre, mais garde la trace de leur identifiant. Il sait dans quel ordre il exécute les accès mémoire et connait leur latence. Ce qui fait qu'il sait que la donnée lue à tel instant est associée à la requête mémoire numéro X. Pour les lectures, il renvoie la donnée lue avec l'identifiant. Le processeur sait alors à quelle lecture la donnée lue correspond et il se débrouille en interne pour gérer la situation. En clair : le processeur voit les accès mémoire dans le désordre, c'est lui qui les remet en ordre. Maintenant que ces précisions sont faites, posons cette question : quel est l'intérêt de faire les accès mémoire dans le désordre ? Accéder à des banques en parallèle est une réponse possible, mais le ré-ordonnancement est surtout utile si tous les accès mémoire atterrissent dans la même banque. Pour comprendre pourquoi, faisons un rappel rapide sur le tampon de ligne. Les mémoires SDRAM sont des mémoires à tampon de ligne. Elles font un accès mémoire en deux étapes. La première étape recopie une ligne de N * 64 bits dans un tampon interne à la SDRAM. La seconde étape sélectionne une colonne, à savoir une donnée de 64 bits, qui est soit envoyée sur le bus de données pour une lecture, soit modifiée par une écriture. [[File:Mémoire à tampon de ligne.png|centre|vignette|upright=2|Mémoire à tampon de ligne]] Les deux étapes correspondent à deux commandes séparées : une commande ACT qui précise l'adresse de la ligne, et une commande READ ou WRITE qui précise l'adresse de la colonne. [[File:Signaux RAS et CAS.png|centre|vignette|upright=2|Signaux RAS et CAS.]] Les SDRAM permettent de se passer de la première étape si des accès consécutifs se font dans la même ligne. Une fois activée, la ligne reste ouverte et on peut accéder plusieurs fois de suite dedans. On a alors juste à préciser l'adresse de colonne dedans. [[File:Sélection d'une ligne sur une mémoire FPM ou EDO.png|centre|vignette|upright=2|Sélection d'une ligne sur une mémoire FPM ou EDO.]] L'exécution dans le désordre des lectures/écritures SDRAM vise à faire en sorte que des accès consécutifs se fassent dans une même ligne. Elle marche si plusieurs accès à une même ligne ne sont pas consécutifs, et qu'il y a des accès entre les deux. Après ré-ordonnancement, ces accès mémoire deviennent consécutifs, ils sont exécutés l'un à la suite de l'autre. Pour rendre le tout plus concret, voici un exemple. Imaginez que l'on sait les 3 accès mémoire suivants : * Une lecture ligne A ; * Une écriture ligne B ; * Une lecture ligne A. Sans réordonnancent, on doit émettre deux commandes PRECHARGE : une quand on passe de la ligne A à la ligne B, et une autre pour le passage inverse. pour éviter cela, le contrôleur mémoire peut retarder l'écriture, ou au contraire avancer la seconde lecture. le résultat est alors le suivant : * Lecture ligne A ; * Lecture ligne A ; * Une écriture ligne B. On a donc deux accès consécutifs à la même ligne, suivi par une écriture dans une autre ligne. La technique marche parce que l'on a un mix adéquat de lectures et d'écritures. Mais il existe des possibilités de réorganisation autres qui ne sont pas valides. Il faut par exemple prendre garde à éviter d'intervertir lectures et écritures à une même adresse, sous peine de problèmes. Encore une fois, il faut faire attention aux dépendances mémoires. Le controleur mémoire teste les dépendances mémoires avant d'envoyer des commandes à la mémoire DDR/SDRAM. ==L'entrelacement sur les mémoires SDRAM== L''''entrelacement''' fonctionne sur les mémoires SDRAM, mais il doit être fortement modifié. Concrètement, tout ce qui a étré dit plus est inapplicable pour les SDRAM, du fait de la présence du tampon de ligne. Des adresses consécutives atterrissent déjà dans la même ligne, ce qui fait qu'on n'a pas besoin d'entrelacement au niveau d'une ligne. Et ne parlons pas de ce la présence de rangées et de canaux mémoire ! Cependant, il peut y avoir un entrelacement lié à la présence des banques SDRAM. L'entrelacement est géré juste avant le séquenceur mémoire. Le '''circuit d'entrelacement''' intervertit certains bits de l'adresse lors des accès mémoires. On trouve aussi des mémoires FIFOs pour mettre en attente les requêtes mémoire provenant du processeur. Elles permettent de recevoir plusieurs accès mémoire consécutifs. Si vous vous demandez comment plusieurs accès mémoire consécutifs peuvent avoir lieu, sachez que c'est une bonne question. Pour le moment, nous allons dire que ces accès proviennent du processeur ou de la mémoire cache, sans expliquer comment. Le contrôleur mémoire reçoit une série d'accès mémoire assez rapprochés, qu'il doit exécuter au mieux. [[File:Controleur mémoire d'une SDRAM avec entrelacement.png|centre|vignette|upright=2.5|Contrôleur mémoire d'une SDRAM avec entrelacement.]] Pour gérer l'entrelacement, le contrôleur de SDRAM prend en entrée l'adresse envoyée par le processeur, et la découpe en plusieurs champs : un pour sélectionner la banque adéquate, un autre pour la ligne, un autre pour la colonne, un autre pour la rangée. Une solution simple désactive l'entrelacement. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de banque || Adresse de ligne || Adresse de colonne |} Si cette solution fonctionne bien si les accès mémoire sont assez aléatoires, en pratique, mieux vaut utiliser l'entrelacement. ===L'entrelacement de banques/lignes=== Une première méthode d'entrelacement est l''''entrelacement de banques'''. Elle répartit deux lignes consécutives dans deux banques différentes. Pour comprendre l'idée, prenons un exemple. Imaginons une mémoire avec deux banques et 4 lignes. Imaginons qu'on parcoure/balaye la mémoire RAM en partant des adresses basses. Sans entrelacement de ligne, les accès se feront comme suit, de gauche à droite : {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Banque numéro 1 !! colspan="4" | Banque numéro 2 |- | Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 || Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 |} Avec l'entrelacement de banques, les accès se feront comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 |- | Ligne 1 || Ligne 1 || Ligne 2 || Ligne 2 || Ligne 3 || Ligne 3 || Ligne 4 || Ligne 4 |} Avec N banques, la première ligne ira dans la première banque, la seconde ligne dans la seconde, et ainsi de suite jusqu'à la énième banque qui va dans la banque numéro N. Et la ligne N+1 repart dans la première banque. L'avantage est que passer d'une ligne à la suivante est assez rapide. Au lieu de devoir fermer la première ligne et ouvrir la suivante on ouvrira directement la ligne suivante dans une banque différente, ce qui sera plus rapide. La commande PRECHARGE pourra être envoyée à la seconde banque en avance. Pour cela, l'adresse est découpée comme suit. {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne (précise à l'octet) |} ===L'entrelacement de rangées=== Il est aussi possible de faire la même chose, mais avec les rangées. Pour simplifier fortement, une rangée est simplement un chip mémoire. En réalité, les rangées ne sont pas des chip mémoire, mais un ensemble de chips mémoire regroupés ensemble histoire d'atteindre les 64 bits du bus de données. Par exemple, une rangée peut combiner 8 chips mémoire avec un bus de données de 8 bits chacun pour obtenir les 64 bits du bus de données d'une SDRAM. Mais nous passons ce détail sous silence dans les explications qui vont suivre, par souci de simplification. Pour faire comprendre l'entrelacement de rangée, le mieux est d'utiliser un exemple, le même que précédemment. Sans entrelacement de rangée, on change de chip mémoire une fois qu'on a balayé toutes ses adresses. {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Rangée numéro 1 !! colspan="4" | Rangée numéro 2 |- | Banque 1 || Banque 2 || Banque 3 || Banque 4 || Banque 1 || Banque 2 || Banque 3 || Banque 4 |} Avec l'entrelacement de ligne, on change de rangée dès qu'on change de banque/ligne. {|class="wikitable" |+ Adresse mémoire |- ! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 |- | Banque 1 || Banque 1 || Banque 2 || Banque 2 || Banque 3 || Banque 3 || Banque 4 || Banque 4 |} En clair, quand on a épuisé toutes les banques dans une même rangée, on passe à la rangée suivante au lieu de rester dans la même rangée. Notons que dans l'exemple précédent, on a combiné l'entrelacement de rangée et de banque, mais on aurait pu utiliser l'entrelacement de rangée seul. Mais ce n'est pas le cas le plus courant en pratique. Toujours est-il qu'en combinant entrelacement de rangée et de ligne, le découpage de l'adresse est le suivant : {|class="wikitable" |+ Adresse mémoire |- | Adresse de ligne || Numéro de rangée || Adresse de banque || Adresse de colonne (précise à l'octet) |} D'autres méthodes d'entrelacement sont possibles, mais elles sont plus complexes. Chaque contrôleur mémoire fait un peu à sa sauce. Ils permettent en général de choisir entre plusieurs entrelacements. Au minimum, ils permettent de désactiver l'entrelacement, d'activer un entrelacement de ligne, et d'autres entrelacements plus complexes, dont certains sont propriétaires et tenus secrets par le fabricant. ===L'entrelacement avec le ''dual channel''=== L'usage du ''dual channel'' complique encore l'entrelacement. Là encore, il y a deux grandes solutions : avec et sans entrelacement des canaux mémoire. Rappelons le principe : deux barrettes de RAM sont accédées en parallèle. Et pour cela, il faut utiliser l'entrelacement. Typiquement, chaque barrette mémoire fournit 64 bits, ce qui fait que l'on peut accéder à 128 bits d'un seul coup, par exemple avec un accès en rafale. Une solution pour cela est de ne pas utiliser l'entrelacement. Avec deux barrettes, la première barrette regroupera la moitié haute de la RAM, la seconde barrette utilisera la moitié basse. Les possibilité d’accéder à deux données séparées de 64 bits en même temps est cependant très rare. Aussi, on préfère utiliser l'entrelacement. Pour cela, deux blocs de 64 bits sont placés dans des canaux mémoire séparés. Idem avec du triple ou quadruple canal, mais c'est alors trois ou quatre blocs de 64 bits qui sont dispersés dans des canaux consécutifs. {|class="wikitable" |+ Adresse mémoire |- ! colspan="6" | Sans entrelacement |- | Numéro de canal/barrette || Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne || Position de l'octet dans un bloc de 64 bits |- | colspan="6" | |- ! colspan="6" | Avec entrelacement |- | Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne || Numéro de canal/barrette || Position de l'octet dans un bloc de 64 bits |} Les contrôleurs de SDRAM précédents sont assez basiques et ne représentent pas les contrôleurs les plus évolués. Ils étaient utilisés sur les anciens PC, à une époque où ils étaient encore sur la carte mère du processeur. Mais de nos jours, le contrôleur mémoire est intégré au processeur. Et il incorpore de nombreuses optimisations afin de gagner en performances. Malheureusement, ces optimisations sont elles-mêmes dépendantes des optimisations intégrées dans le processeur, ce qui fait qu'on ne peut pas en parler ici. Elles demandent que le contrôleur mémoire reçoive plusieurs accès mémoire simultanés, ce qui n'a aucun sens à ce stade du cours. Aussi, nous allons les passer sous silence pour le moment. Nous les verrons dans le chapitre sur le parallélisme mémoire. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=La désambiguïsation mémoire | prevText=La désambiguïsation mémoire | next=Les processeurs superscalaires | nextText=Les processeurs superscalaires }} </noinclude> jl33glpuqhhaybv9tci2chel8jo25fl 764655 764654 2026-04-23T14:36:23Z Mewtow 31375 /* L'entrelacement avec le dual channel */ 764655 wikitext text/x-wiki Dans ce chapitre, nous allons voir les techniques qui permettent de gérer plusieurs accès mémoire simultanés directement au niveau du cache ou de la mémoire RAM. Par plusieurs accès mémoire simultanés, vous pensez sans doute à l'usage de cache multiports, voire de mémoires RAM multiport. C'est en effet une solution, mais ce n'est clairement pas la seule. Par exemple, il est possible de pipeliner l'accès au cache, voire à la mémoire RAM. Il existe de nombreuses techniques de '''parallélisme mémoire''', et nous allons les voir dans ce chapitre. Pour donner un exemple, il est possible de pipeliner les accès mémoire. Il est possible de pipeliner l'accès au cache et/ou l'accès à la mémoire, et nous verrons comment faire dans ce chapitre. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, qu'ils aient ou non un pipeline dynamique. Par contre, pipeliner la mémoire est une technique ancienne, aujourd'hui peu utilisée. Elle était utilisée sur des très vieux ordinateurs, qui avaient un pipeline mais pas de mémoire cache. Mais de nos jours, les mémoires SDRAM et DDR ne sont pas adaptées pour ça, il est très difficile de les pipeliner correctement. Le parallélisme mémoire est utile aussi bien sur des processeurs à émission dans l'ordre que dans le désordre. Par exemple, un processeur ''in-order'' avec des lectures non-bloquantes peut e, profiter. Il peut techniquement lancer une seconde lecture, après avoir rencontré une première lecture non-bloquante. En général, le nombre de lectures consécutives est cependant limité à moins d'une dizaine. À l'opposé, les processeurs sans lectures non-bloquantes profitent pas du parallélisme mémoire. À la rigueur, ils peuvent en profiter s'ils intègrent des techniques de préchargement. Pendant que le processeur exécute une lecture/écriture, il peut précharger une autre donnée en parallèle. Inutile de dire que sans pipeline processeur, le parallélisme mémoire ne sert pas à grand-chose. ==Le ''line fill buffer'' et les techniques associées== Lors d'un défaut de cache, le processeur doit attendre que toute la ligne de cache soit chargée avant d'être utilisable. Or, la taille d'une ligne de cache est supérieure à la largeur du bus mémoire, ce qui fait qu'une ligne de cache est chargée en plusieurs fois, morceaux par morceaux, mot mémoire par mot mémoire. Le chargement peut se faire directement dans le cache, mais ce n'est pas une solution très pratique. À la place, beaucoup de processeurs ajoutent une mémoire tampon entre la RAM et le cache, appelée le '''tampon de remplissage de ligne''' (''line-fill buffer'') dans les processeurs modernes. Lors d'un défaut de cache, le processeur charge la donnée de la RAM dans le tampon de remplissage de ligne, mot mémoire par mot mémoire. Une fois plein, le tampon de remplissage de ligne est recopié dans la ligne de cache. Le tampon de remplissage de ligne contient une ligne de cache, si ce n'est que certains bits de contrôle ne sont pas présents. Le tampon de remplissage de ligne contient un bit de validité pour chaque mot mémoire de la ligne de cache, qui indiquent si le mot mémoire a été chargé. Par exemple, prenons un processeur 64 bits, qui gère donc des mots mémoire de 8 octets, avec des lignes de cache 256 octets/32 mots mémoire. Le tampon de remplissage de ligne contiendra 32 bits de validité. Si le processeur a chargé les 6 premiers mots mémoire, les 6 premiers bits de validité seront mis à 1, les autres seront encore à zéro. Il faut noter que la disponibilité de la ligne complète se détermine assez facilement en faisant un ET logique entre tous les bits de validité. Le processeur sait ainsi quand la ligne de cache est disponible entièrement et donc quand la transférer dans le cache. La même chose existe avec une hiérarchie de cache, sauf que l'on trouve une mémoire tampon entre chaque niveau de cache. Il y a un tampon de remplissage de ligne entre le cache L1 et le cache L2, entre le cache L2 et le L3, etc. Si le cache ne gère qu'un seul défaut de cache à la fois, le ''fill line buffer'' est une mémoire très simple, qui ne mémorise qu'une seule ligne de cache. Et cela vaut aussi bien pour un cache bloquant que non-bloquant. Mais sur les caches capables de gérer plusieurs défauts simultanés, le tampon de remplissage de ligne est une mémoire de type FIFO ou LIFO, capable de mémoriser plusieurs lignes de cache. Notons que le tampon de remplissage de ligne est très utile pour implémenter certaines techniques, par exemple le contournement du cache. Nous avions vu dans le chapitre sur le cache que certains accès mémoire doivent contourner le cache, pour des raisons de cohérence des caches. C'est notamment nécessaire pour accéder aux périphériques, mais c'est aussi utile pour des raisons de performances dans des cas très spécifiques. Les accès qui contournent le cache se font directement dans le tampon de remplissage de ligne : le processeur écrit ou lit les données depuis ce tampon de remplissage de ligne, sans accéder au cache. ===L'''early restart'' et le ''critical word load''=== La présence du ''line buffer'' permet une optimisation assez intéressante, qui permet de réduire la latence des défauts de cache. L'optimisation consiste à lire un mot mémoire dans le tampon de remplissage de ligne, même si la ligne de cache complète n'a pas encore été chargée. Il existe deux manières de faire cela, qui portent les noms d'''early restart'' et de ''critical word load''. La première est la version la plus simple, la seconde est plus complexe mais plus performante. Avec la technique d''''''early restart''''', la ligne de cache est chargée normalement, en partant de son premier mot mémoire. Dès que le mot mémoire lu/écrit par le processeur est copié dans le ''line fill buffer'', il est envoyé au processeur immédiatement. Illustrons le tout par un exemple, où une ligne de cache fait 16 mots mémoire. Le processeur effectue une lecture, qui lit le 5ème mot mémoire. Sans ''early restart'', le processeur doit charger les 16 mots mémoire avant de faire la lecture dans le cache. Avec ''early restart'', le processeur reçoit la donnée dès que le 5ème mot mémoire est disponible. Le processeur doit attendre que les 4 premiers mots mémoire soient chargés, puis le 5ème mot mémoire arrive et est envoyé directement au processeur, il est lu directement depuis le ''line fill buffer''. Les 11 mots mémoire suivants sont ensuite chargés dans le cache pendant que le processeur fait des calculs dans son coin. Le ''critical word load'' est une optimisation de la technique précédente où le chargement de la ligne de cache commence directement à la donnée demandée par le processeur. Pour reprendre l'exemple précédent, où le processeur demande le 5ème mot mémoire sur 16, le ''critical word load'' charge le 5ème mot mémoire en premier et l'envoie au processeur, ce qui fait qu'il est chargé très rapidement. Pas besoin d'attendre que le processeur charge les 4 mots mémoire précédents comme avec l'''early restart''. Dans le détail, le processeur charge le 5ème mot mémoire en premier, puis charge les 11 suivants, et termine par les 4 mots mémoire du début. En clair, le ''critical word load'' commence par charger le mot mémoire lu, puis les blocs suivants, avant de revenir au début du bloc pour charger les blocs restants. Ainsi, la donnée demandée par le processeur sera la première disponible. Pour cela, l'organisation du tampon de remplissage de ligne est modifiée de manière à rendre cela possible. Il a une taille égale à une ligne de cache complète, qui contient elle-même plusieurs mots mémoire. Dans le ''line-fill buffer'', chaque mot mémoire est stocké avec un tag, qui indique l'adresse du mot mémoire stocké dans le ''line-fill buffer''. Le ''line-fill buffer'' est donc un cache un peu particulier, qui fonctionne comme un cache du point de vue du processeur, comme une mémoire FIFO pour les transferts avec le cache. Ainsi, un processeur qui veut lire dans le cache après un défaut peut accéder à la donnée directement depuis le tampon de remplissage de ligne, alors que la ligne de cache n'a pas encore été totalement recopiée en mémoire. ==Les caches pipelinés== Il est possible de pipeliner l'accès au cache, ce qui demande juste de rajouter des registres au bon endroits et quelques circuits de contrôle. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, même ceux avec un pipeline dynamique. Et l'implémentation peut se faire de plusieurs manières différentes. Il faut dire que découper une mémoire cache en plusieurs étapes peut se faire de plusieurs manière. Commençons par la plus simple. Beaucoup de processeurs des années 2000 avaient une fréquence peu élevée comparée aux standards d'aujourd'hui, ce qui fait que leur cache avait bien un temps d'accès d'un cycle d'horloge. Mais l'accès au cache se faisait en deux cycles d'horloge : un cycle pour calculer une adresse et un cycle pour l'accès mémoire proprement dit. Et ces deux étapes étaient pipelinées, à savoir que deux micro-opérations mémoire peuvent s'exécuter en même temps : une dans l'étage de calcul d'adresse, une autre dans le cache. L'unité mémoire était donc pipelinée, alors que l'accès au cache ne l'était pas. Les processeurs qui implémentaient cette technique regroupent les micro-architectures K5 et K6 d'AMD, les processeurs Intel de micro-architecture P6 (Pentium 2 et 3) et quelques autres. Il est aussi possible de pipeliner l'accès au cache lui-même. Avec un cache pipeliné, l'accès au cache ne se fait pas en un seul cycle, mais en plusieurs. Cependant, on peut lancer un nouvel accès au cache à chaque cycle d'horloge, comme un processeur pipeliné lance une nouvelle instruction à chaque cycle. L'implémentation est assez simple : il suffit d'ajouter des registres dans le cache. Pour cela, on profite que le cache est composé de plusieurs composants séparés, qui échangent des données dans un ordre bien précis, d'un composant à un autre. De plus, le trajet des informations dans un cache est linéaire, ce qui les rend parfois pour l'usage d'un pipeline. ===Le ''pipelining'' des caches ''direct-mapped''=== Voici ce qui se passe avec un cache directement adressé. Pour rappel ce genre de cache est conçu en combinant une mémoire RAM, généralement une SRAM, avec quelques circuits de comparaison et un MUX. L'accès au cache se fait globalement en deux étapes : la première lit la donnée et le ''tag'' dans la SRAM, et on utilise les deux dans une seconde étape. Nous avions vu il y a quelques chapitre comment pipeliner des mémoires, dans le chapitre sur les mémoires évoluées. Et bien ces méthodes peuvent s'utiliser pour la mémoire RAM interne au cache !L'idée est d'insérer un registre entre la sortie de la RAM et la suite du cache, pour en faire un cache pipéliné, à deux étages. Le premier étage lit dans la SRAM, le second fait le reste. L'implémentation sur les caches associatifs à plusieurs voies est globalement la même à quelques détails près. [[File:Cache directement adressé pipeliné - deux étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - deux étages]] Avec le circuit précédent, il est possible d'aller plus loin, cette fois en pipelinant l'accès à la mémoire RAM interne au cache. Pour comprendre comment, rappelons qu'une mémoire SRAM est composée d'un plan mémoire et d'un décodeur. L'accès à la mémoire demande d'abord que le décodeur fasse son travail pour sélectionner la case mémoire adéquate, puis ensuite la lecture ou écriture a lieu dans cette case mémoire. L'accès se fait donc en deux étapes successives séparées, on a juste à mettre un registre entre les deux. Il suffit donc de mettre un gros registre entre le décodeur et le plan mémoire. [[File:Cache directement adressé pipeliné - trois étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - trois étages]] Et on peut aller encore plus loin en découpant le décodeur en deux circuits séparés. En effet, rappelez-vous le chapitre sur les circuits de sélection : nous avions vu qu'il est possible de créer des décodeurs en assemblant des décodeurs plus petits, contrôlés par un circuit de prédécodage. Et bien on peut encore une ajouter un registre entre ce circuit de prédécodage et les petits décodeurs. : Théoriquement, toute l'adresse est fournie d'un seul coup au cache, la quasi-totalité des processeurs présentent l'adresse complète à un cache pipeliné. Mais le Pentium 4 fait autrement. Il faut noter que les premiers étages manipulent l'indice dans la SRAM, qui est dans les bits de poids faible, alors que les étages ultérieurs manipulent le ''tag'' qui est dans les bits de poids fort. Les concepteurs du Pentium 4 ont alors décidé de présenter les bits de poids faible lors du premier cycle d'accès au cache, puis ceux de poids fort au second cycle. Pour cela, l'ALU fonctionnait à une fréquence double de celle du processeur, tout comme le cache L1. Il n'y avait pas de pipeline proprement dit, mais cela réduisait grandement la latence d'accès au cache. ===Le ''pipelining'' des caches splittés/sériels=== Il est aussi possible de pipeliner un cache dits splittés, aussi appelés à accès sériel. Pour rappel, les caches à accès sériel vérifient si il y a succès ou défaut de cache, avant d'accéder aux lignes de cache en cas de succès. Ils font donc différemment des autres caches, qui accèdent à une ligne de cache, avant de déterminer s'il y a succès ou défaut en lisant le tag de la ligne de cache. Les caches sériels disposent de deux SRAM : une pour les tags des lignes de cache et une pour les données. Ils accèdent à la SRAM pour les tags, avant d’accéder à la SRAM des données en cas de succès de cache. Vu que l'accès se fait en deux étapes, une vérification des tags suivie de la lecture/écriture des données, il est facile à pipeliner. Pipeliner le cache permet de régler le problème des accès au cache L1, et elle est tout le temps utilisé sur les processeurs modernes. Mais que faire en cas de défaut de cache ? ==Les caches non bloquants== Un '''cache bloquant''' est un cache auquel le processeur ne peut pas accéder pendant un défaut de cache. Il faut attendre que la lecture ou écriture en RAM soit terminée avant de pouvoir utiliser de nouveau le cache. Un '''cache non bloquant''' n'a pas ce problème : on peut l'utiliser pendant un défaut de cache. Les caches non bloquants permettent de démarrer une nouvelle lecture ou écriture alors qu'une autre est en cours, ce qui permet d'exécuter plusieurs lectures ou écritures en même temps. ===Les ''Miss Handling Status Registers''=== Lors d'un défaut de cache, la mémoire RAM est consultée pendant le défaut de cache, mais le cache est inutilisé. Un défaut de cache n'utilise pas le cache, ce n'est qu'un accès en mémoire RAM, sur le bus mémoire (ou un accès aux niveaux de cache inférieurs, peu importe). Le cache en lui-même est laissé libre, rien n’empêche d'y accéder, il est en réalité intrinsèquement non-bloquant. Les caches, bloquants comme non-bloquants, sont en réalité composés d'une mémoire cache proprement dite, entourée de circuits qui servent d'interface entre le processeur et le cache lui-même. Et parmi les circuits tout autour, certains gèrent l'accès au cache lors d'un défaut de cache. Ils sont regroupés sous le terme de '''''Miss Handling Architecture''''' (MHA). La différence entre un cache bloquant et un cache non-bloquant est en réalité liée à l'implémentation de la MHA. Les caches bloquants coupent volontairement l'accès au cache lors d'un défaut, car les défauts sont plus simples à gérer ainsi. Le défaut de cache rapatrie une donnée depuis la RAM, qui sera écrite dans le cache. Et il ne faut pas qu'une tentative d'accès à cette donnée ait lieu avant qu'elle ne soit chargée. Mais ce blocage est général et touche tout le cache, alors que seule une ligne de cache est concernée par le défaut de cache. L'idée derrière un cache non-bloquant est que seule la ligne de cache est bloquée, mais les autres sont accessibles. L'idée est alors de mémoriser les lignes de cache concernées par le défaut de cache, afin d'en bloquer l'accès. À chaque accès, on vérifie si la ligne de cache est déjà réservée par un défaut de cache. La lecture/écriture est alors bloquée si c'est le cas, mais elle accepte les accès sinon. Pour cela, la ''Miss Handling Architecture'' contient des registres qui mémorisent des informations sur les défauts de cache en cours. Ils portent le nom de '''''miss status handling registers''''', que j’appellerais dorénavant MSHR, qui sont aussi appelés des '''''miss buffer'''''. Le contenu des MSHR varie beaucoup suivant le processeur, mais ces derniers stockent au minimum les informations suivantes : * Le numéro de la ligne de cache dans laquelle les données sont chargées. * Un bit de validité qui indique si le MSHR est vide ou pas, qui est mis à 0 quand le défaut de cache est résolu. * Un ou plusieurs champs de lecture/écriture, qui contiennent des informations sur la lecture/écriture. ** Pour une lecture, elle contient des informations sur la destination de la donnée, à savoir qui prévenir quand le défaut de cache est terminé. C'est parfois un nom/numéro de registre (celui dans lequel charger la donnée), mais c'est souvent le numéro de l'entrée dans le ''load/store queue''. ** Pour les écritures, elle contient la donnée à écrire, ou éventuellement un numéro de ''load/store queue'' où se trouve la donnée à écrire. Il faut noter que le bus mémoire ne peut gérer qu'un seul défaut de cache à la fois. Aussi, il est intéressant de regarder ce qui se passe lorsqu'un second défaut de cache survient, pendant qu'un premier est en cours. Dans ce cas, il y a deux réponses qui correspondent à deux types de caches non-bloquants, qui portent les noms barbares de caches de type succès après défaut et défaut après défaut. Sur le premier type, il ne peut pas y avoir plusieurs défauts de cache simultanés. Dès qu'un second défaut de cache survient, le cache stoppe son activité et on ne peut plus démarrer de nouvelle lecture/écriture, tant que le premier défaut de cache n'est pas résolu. Le second type est plus souple et autorise la survenue de plusieurs défauts de cache simultanés. Du moins, jusqu'à une certaine limite, car le cache ne peut supporter qu'un nombre limité d'accès mémoires simultanés (pipelinés). ===Les accès simultanés à une même ligne de cache=== Il arrive que le processeur fasse plusieurs accès mémoire simultanés à la même ligne de cache. Si la ligne de cache en question n'a pas encore été chargée dans le cache, alors on a plusieurs défauts de cache consécutifs pour la même ligne de cache, et le cache non-bloquant doit gérer la situation. Pour la suite, il va falloir faire une petite distinction entre les défauts primaires et secondaires. Imaginons qu'un défaut de cache ait lieu et demande à charger une donnée dans la ligne de cache numéro N. Il s'agit du premier défaut impliquant cette ligne de cache précise, ce qui lui vaut le nom de '''défaut de cache primaire'''. Mais par la suite, d'autres accès mémoire à la même ligne de cache ont lieu, alors que la ligne de cache n'est pas encore disponible. Dans ce cas, il s'agit de '''défauts de cache secondaires'''. Pour l'unité d'accès mémoire, les défauts de cache primaire et secondaire sont différents (ils prennent tous une entrée dans la ''load/store queue''). Mais pour le cache, ils ne correspondent qu'à un seul accès au cache : celui qui demande de charger la ligne de cache demandée. Les défauts de cache primaire et secondaire à la même ligne de cache se voient attribuer un MSHR unique. La gestion des défauts de cache secondaires dépend du cache non-bloquant. La solution la plus simple ne permet pas les défauts de cache secondaires. Le cache ne permet pas deux défauts de cache simultanés pour la même ligne de cache. Les autres solutions le permettent, en fusionnant des accès simultanés à la même ligne de cache en un seul au niveau des MSHR. Dans tous les cas, détecter les défauts de cache secondaires sont un problème qu'il faut détecter. La ''Miss Handling Architecture'' doit détecter les défauts de cache secondaire. Pour cela, elle procède comme suit. Lors de chaque défaut de cache, la MHA récupère le numéro de la ligne de cache associé. Il vérifie alors chaque MSHR pour vérifier s'il contient le numéro en question. S'il n'y a aucune correspondance dans les MSHR, alors c'est signe que le défaut de cache est un défaut primaire. Mais s'il y en a une, alors c'est un défaut secondaire. Évidemment, cela signifie que lors d'un défaut de cache, le numéro de ligne de cache est envoyé à tous les MSHR, pour comparaison. Les MSHR sont donc regroupés dans une mémoire associative, une sorte de mini-cache, faciliter l'implémentation. Lors d'un défaut de cache primaire, l'accès à un cache non-bloquant se fait comme suit : le processeur envoie une adresse au cache, accède à celui-ci, et détecte la survenue d'un défaut de cache. Il en profite alors pour attribuer une ligne de cache dans laquelle sera chargée ce défaut. L'attribution est très simple dans le cas des caches ''direct mapped'', ou associatifs par voie, pour lesquels l'attribution se fait assez simplement. Il mémorise alors cette information dans les MSHR, après avoir vérifié que le défaut de cache n'était pas un défaut secondaire. Lors des accès ultérieurs à une adresse proche, censée être dans la même ligne de cache, le processeur va encore une fois rencontrer un défaut de cache. Il va alors déterminer le numéro de la ligne de cache associée à l'adresse, et comparer ce numéro avec les MSHRs. Si un MSHR contient ce numéro, c'est signe que le défaut de cache est un défaut secondaire. La MHA réagit alors différemment selon le processeur considéré. Une première solution n'autorise pas les défauts de cache secondaires. Si l'un d'entre eux survient, le processeur est gelé par un ''pipeline stall'', une bulle de pipeline. Une autre solution fusionne les défauts de cache secondaires avec le défaut de cache primaire : tout cela ne correspond qu'à un seul défaut de cache pour lui. ===Les MSHR simples=== Un cache non-bloquant à '''MSHR simple''' contient juste plusieurs MSHR qui mémorisent juste un numéro de ligne de cache, un bit de validité, le champ de lecture/écriture, et l'adresse exacte de lecture/écriture. Avec cette organisation, il est possible d'avoir plusieurs défauts de cache séparés, mais à la condition que chaque défaut accède à une ligne de cache différente. Deux accès simultanés à une même ligne de cache ne sont pas possibles, les défauts de cache secondaires ne sont pas autorisés. Ainsi, chaque défaut de cache se voit attribuer son propre MSHR, chacun contient un numéro de ligne de cache différent. Pour comprendre pourquoi c'est impossible de gérer les défauts secondaires, il faut regarder le champ de lecture/écriture. Si on veut effectuer plusieurs écritures consécutives à la même adresse, le MSHR n'aura pas de quoi mémoriser les deux données à écrire. Il pourra mémoriser la première donnée à écrire, pas la seconde. Même chose lors d'une lecture : le champ lecture/écriture peut mémorisr la destination de la première lecture, pas de la seconde. L'avantage est que la MHA n'a besoin que des MSHR et de quelques circuits annexes. Les autres solutions rajoutent des circuits annexes pour gérer les défauts de cache secondaires, qui utilisent beaucoup de circuits. Le cout en circuit est donc élevé, mais le gain en performance est là. Passons maintenant aux caches non-bloquants qui autorisent les défauts de cache secondaire. La solution la plus simple consiste à utiliser ===Les MSHR adressés implicitement=== Avec les '''MSHR adressés implicitement''', il est possible de fusionner plusieurs accès mémoire à une même ligne de cache, mais sous une condition très importante : ces accès lisent/écrivent des mots mémoire différents. Par exemple, imaginons qu'une ligne de cache contienne 8 mots mémoire de 64 bits. Si un premier accès mémoire lit le mot mémoire numéro 7 (dernier mot mémoire de la ligne), et le second accès le mot mémoire numéro 3, alors la fusion est possible. Mais si deux accès mémoire veulent lire/écrire le mot mémoire numéro 7, alors la fusion n'est pas possible et le processeur se bloque, un ''pipeline stall'' survient. Un MSHR adressé implicitement est un MSHR simple, qui contient naturellement un numéro de ligne de cache (tag) et un bit de validité, sauf que le champ de lecture/écriture est dupliqué. Les différents champs lecture/écriture d'un MSHR sont regroupés dans une mémoire RAM/cache qui contient autant d'entrées qu'il y a de mot mémoire dans une ligne de cache. Chaque entrée de la table est associée à un mot mémoire de la ligne de cache et stocke des informations sur celui-ci. Une entrée mémorise, au minimum : * Un champ de lecture/écriture qui contient soit la destination de la lecture, soit la donnée à écrire. * Un bit R/W permettant d'interpréter correctement le champ de lecture/écriture. * Un bit de validité pour chaque entrée de la table, qui dit si un défaut de cache antérieur accède déjà à ce mot mémoire. La fusion de deux défauts de cache est ainsi assez simple. Un défaut de cache primaire/secondaire configure l'entrée associée au mot mémoire lu/écrit. Si un défaut secondaire ultérieur lit/écrit un mot mémoire différent (dont le bit de validité de l'entrée dans la table est à zéro), pas de conflit : il configure une autre entrée, vide. Mais s'il lit/écrit un mot mémoire pour lequel l'entrée de la table est occupée, il y a conflit, le processeur est bloqué par un ''pipeline stall'', une bulle de pipeline. Avec cette organisation, le nombre de MSHR indique combien de lignes de cache peuvent être lues en même temps depuis la mémoire. Quant au nombre d'entrées par MSHR, il détermine combien d'accès mémoires qui ne se recouvrent pas peuvent avoir lieu en même temps. ===Les MSHR adressés explicitement=== Les '''MSHR adressés explicitement''' n'ont pas les contraintes des MSHR adressés implicitement. Avec eux, il est possible d'avoir plusieurs défauts de cache pointant vers la même ligne de cache, mais aussi vers le même mot mémoire. De tels défauts de cache apparaissent sur les processeurs à exécution dans le désordre, mais sont très rares, voire inexistants, sur les processeurs ''in-order''. L'idée est encore que chaque MSHR est associée à une table mémoire qui mémorise des entrées. Cette table n'est autre qu'une mémoire FIFO. Sauf que cette fois-ci, une entrée n'est pas associée à un mot mémoire. Une entrée est associée à un défaut de cache. Une entrée mémorise là encore la destination de la lecture ou la donnée de l'écriture suivi du bit R/W, ainsi que d'un bit de validité par entrée, mais aussi : la position du mot mémoire lu dans la ligne de cache. C'est cette dernière information qui n'était pas présente dans les MSHR adressés implicitement. De plus, le nombre d'entrée par MSHR n'est pas égal au nombre de mots mémoires dans une ligne de cache, mais peut être arbitrairement grand. L'avantage d'un tel cache est qu'il est capable de traité les défauts secondaires concernant un même mot mémoire, et ce, car les accès à la ligne de cache concernée se produisent dans l'ordre de génération des défauts (la table mémoire est une FIFO). ===Les MSHRs inversés=== Généralement, plus on veut supporter de défauts de cache, plus le nombre de MSHR et d'entrées augmente. Mais au-delà d'un certain nombre d'entrées et de MSHR, les MSHR adressés implicitement et explicitement ont tendance à bouffer un peu trop de circuits. Utiliser une organisation un peu moins gourmande en circuits est donc une nécessité. Cette organisation plus économe se base sur des MSHR inversés. Les MSHR inversés ne contiennent qu'une seule entrée, en quelque sorte : au lieu d’utiliser n MSHR de m entrées chacun, on va utiliser n × m MSHR inversés. La différence, c'est que plusieurs MSHR peuvent contenir un tag identique, contrairement aux MSHR adressés implicitement et explicitement. Lorsqu'un défaut de cache a lieu, chaque MSHR est vérifié. Si jamais aucun MSHR ne contient de tag identique à celui utilisé par le défaut, un MSHR vide est choisi pour stocker ce défaut, et une requête de lecture en mémoire est lancée. Dans le cas contraire, un MSHR est réservé au défaut de cache, mais la requête n'est pas lancée. Quand la donnée est disponible, les MSHR correspondant à la ligne qui vient d'être chargée vont être utilisés un par un pour résoudre les défauts de cache en attente. ===Les MSHR intégrés au cache=== Certains chercheurs ont remarqué que pendant qu'une ligne de cache est en train de subir un défaut de cache, celle-ci reste inutilisée, et son contenu est destiné à être perdu une fois le défaut de cache résolu. Ils se sont dit que, plutôt que d'utiliser des MSHR séparés, il vaudrait mieux utiliser la ligne de cache pour stocker les informations sur les défauts de cache en attente dans cette ligne de cache. Pour éviter tout problème, il faut rajouter un bit dans les bits de contrôle de la ligne de cache, qui sert à indiquer que la ligne de cache est occupée : un défaut de cache a eu lieu dans cette ligne, et elle stocke donc des informations pour résoudre les défauts de cache. ==L'entrelacement mémoire== Nous venons de voir comment une mémoire cache peut gérer plusieurs accès mémoire simultanés. Il se trouve que ce genre d'optimisation dépasse largement le cadre du cache. Les mémoires RAM ne sont pas en reste, elles aussi ! Il est possible de pipeliner l'accès à une mémoire RAM, en utilisant des techniques dites d''''entrelacement'''. Précisons cependant que cette optimisation n'est pas utilisée sur les mémoires SDRAM ou DDR modernes, du moins pas sans modifications majeures. Mais divers ordinateurs assez anciens, dont des superordinateurs, ont utilisé cette technique. La technique demande d'utiliser plusieurs mémoires séparées. Sur les anciens ordinateurs, les mémoires en question sont des chips mémoire, à savoir des circuits intégrés de DRAM. Pour faire plus général, nous allons utiliser le terme de '''banques''', ou encore de bancs mémoire. La différence est qu'il existe des mémoires multi-banque, qui regroupent plusieurs banques indépendantes dans un seul boitier, dans un seul chip mémoire. En clair, de telles mémoires regroupent plusieurs sous-mémoires dans un seul circuit intégré. Il est aussi possible d'utiliser plusieurs chips mémoire séparés, chacun ayant une banque. Reste à combiner ces banques pour former un pseudo-pipeline. ===Utiliser plusieurs banques sans entrelacement=== Sans optimisation particulière, les adresses sont réparties dans les banques comme indiqué ci-dessous. Il s'agit de ce qui s'appelle un arrangement horizontal, et nous avions vu celui-ci dans le chapiutre sur les mémoires SDRAM. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Avec cet arrangement, les bits de poids fort de l'adresse sont utilisées pour sélectionner la banque adéquate, et le reste de l'adresse est envoyé sur le bus d'adresse. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Adresse de banque !! Adresse dans la banque |- | Quelques bits de poids fort || Reste de l'adresse |} L'entrelacement change cette répartition, afin d'accélérer les accès mémoire. L'entrelacement classique vise à améliorer les accès à des adresses mémoires consécutives, mais des formes plus évoluées d'entrelacement visent à optimiser des accès mémoire arbitraires. Voyons-les dans l'ordre, du plus simple au plus complexe. ===L'entrelacement classique=== Avec l''''entrelacement classique''', des adresses consécutives sont réparties dans des banques différentes. [[File:Répartition des adresses dans une mémoire interleaved.png|centre|vignette|upright=1.5|Répartition des adresses dans une mémoire interleaved.]] L'entrelacement simple permet d'accélérer les accès mémoire qui se font à des adresses consécutives. Avec l'entrelacement, chaque accès mémoire tombe dans une banque mémoire différente, ce qui fait qu'on peut démarrer un nouvel accès mémoire à chaque cycle d'horloge. Pendant qu'une banque est occupée par un accès mémoire, on démarre le suivant dans une autre banque, et ainsi de suite. Le tout se termine soit quand on a épuisé toutes les banques libres, soit quand l'accès en rafales se termine. Pas besoin d'attendre que la mémoire ait fini sa lecture/écriture avant de démarrer la lecture/écriture suivante. [[File:Pipemining mémoire.png|centre|vignette|upright=2.5|Pipelining mémoire]] L'animation suivante illustre bien le processus, avec quatres banques. [[File:Interleaving.gif|centre|vignette|upright=2.5|Pipelining mémoire]] L'entrelacement simple utilise les bits de poids faible pour sélectionner la banque, et les bits de poids fort pour la case mémoire. [[File:Adresse mémoire d'une mémoire entrelacée.png|centre|vignette|upright=2|Adresse mémoire d'une mémoire entrelacée]] Précisons que le temps d'accès mémoire ne change pas beaucoup avec l'entrelacement. Par contre, on peut faire plus d'accès mémoire simultanés. On peut démarrer un accès mémoire par cycle d'horloge, mais l'accès en lui-même prend plusieurs cycles. Les mémoires à entrelacement ont donc un débit supérieur aux mémoires qui ne l'utilisent pas. L'entrelacement classique pourrait être utilisé pour accélérer les transferts entre mémoire RAM et cache, qui se font en blocs d'adresses consécutives. Mais de nos jours, il n'est pas utilisé dans cette optique. Il faut dire qu'il est redondant avec le mode rafale des mémoires DRAM. Les deux font globalement la même chose et on voit mal comment utiliser les deux en même temps. Le mode rafale étant plus simple à implémenter, et plus léger niveau utilisation du bus de commande mémoire, il est préféré à l'entrelacement. Par contre, l'entrelacement a eu son heure de gloire sur d'anciens processeurs qui n'avaient pas de mémoire cache, alors qu'ils disposaient d'un pipeline. L'exemple typique est celui des nombreux ''processeurs vectoriels'', que nous n'avons pas encore abordé à ce stade du cours. De tels processeurs avaient un pipeline, une unité de calcul fortement pipelinée, et parfois même de l'exécution dans le désordre et du renommage de registres ! ===L'entrelacement par décalage=== L'entrelacement simple est très utile pour les accès en rafle ou équivalents. Par contre, il existe de rares situations où il n'est pas optimal. Une de ces situations est celle des accès en enjambée (en ''stride''), où l'on accède en série à des adresses sont séparées par N mots mémoires. De tels accès surviennent quand un logiciel accède à des structures de données spécifiques, à savoir des tableaux de structures, des matrices, ou d'autres structures de données dans le genre. Un cas classique est celui du parcours d'une matrice colonne par colonne. Les matrices de nombres sont mémorisées ligne par ligne, ce qui fait que parcourir une colonne demande de passer d'une ligne à la suivante et de faire des grands sauts en mémoire RAM, mais tous espacés par la même distance. Avec l'entrelacement simple, les accès en enjambée sont moins performants que les accès à des adresses consécutives, mais cela ne fonctionne pas trop mal. Le pire cas est celui où l'on a N banques et où les données sont justement placées toutes les N adresses. Des accès consécutifs vont tous tomber dans la même banque, on ne peut plus accéder à des banques différentes en parallèle. Mais en dehors de ce cas, on voit une amélioration par rapport à la situation sans entrelacement. Pour obtenir des performances maximales pour les accès en enjambées, il faut répartir les mots mémoires dans la mémoire autrement. L'organisation idéale est la suivante. : Dans les explications qui vont suivre, la variable N représente le nombre de banques, qui sont numérotées de 0 à N-1. On commence par organiser les N premières adresses comme une mémoire entrelacée simple : l'adresse 0 correspond à la banque 0, l'adresse 1 à la banque 1, etc. Pour le bloc suivant, on décale tout d'une adresse, à savoir qu'on commence à remplir les banques à partir de la seconde, non de la première. Une fois la fin du bloc atteinte, on finit de remplir le bloc en repartant du début du bloc. Le troisième bloc d'adresse subit le même traitement, sauf qu'on commence à remplir à partir de la seconde banque. Et on poursuit l’assignation des adresses en décalant d'un cran en plus à chaque bloc. Ainsi, chaque bloc verra ses adresses décalées d'un cran en plus comparé au bloc précédent. Si jamais le décalage dépasse la fin d'un bloc, alors on reprend au début. [[File:Mémoire interleaved par décalage.png|centre|vignette|upright=1.5|Mémoire entrelacée par décalage.]] En faisant cela, on remarque que les banques situées à N adresses d'intervalle sont différentes. Dans l'exemple du dessus, nous avons ajouté un décalage de 1 à chaque nouveau bloc à remplir. Mais on aurait tout aussi bien pu prendre un décalage de 2, 3, etc. Dans tous les cas, on obtient un entrelacement par décalage. Ce décalage est appelé le pas d'entrelacement, noté P. Le calcul de l'adresse à envoyer à la banque, ainsi que la banque à sélectionner se fait en utilisant les formules suivantes : * adresse à envoyer à la banque = adresse totale / N ; * numéro de la banque = (adresse + décalage) modulo N, avec décalage = (adresse totale * P) mod N. Avec cet entrelacement par décalage, on peut prouver que la bande passante maximale est atteinte si le nombre de banques est un nombre premier. Seulement, utiliser un nombre de banques premier peut créer des trous dans la mémoire, des mots mémoires inadressables. Pour éviter cela, il faut faire en sorte que N et la taille d'une banque soient premiers entre eux : ils ne doivent pas avoir de diviseur commun. Dans ce cas, les formules se simplifient : * adresse à envoyer à la banque = adresse totale / taille de la banque ; * numéro de la banque = adresse modulo N. ===L'entrelacement pseudo-aléatoire=== Une dernière méthode de répartition consiste à répartir les adresses dans les banques de manière "pseudo-aléatoire". L'idée exacte est de faire passer l'adresse dans une '''fonction de hachage''', plus ou moins complexe. Pour rappel, une fonction de hachage prend une entrée de grande taille et fournit en sortie un résultat de petite taille. Idéalement, elles doivent donner des résultats aussi différents que possible pour des entrées similaires, histoire de simuler une sorte de pseudo-aléatoire. Ici, une adresse est transformée en un numéro de banque. Une fonction de hachage simple ne fait que permuter des bits de l'adresse pour obtenir son résultat. Elle ne fait qu'échanger des bits de place avant de couper l'adresse en deux morceaux : un pour la sélection de la banque, et un autre pour la sélection de l'adresse dans la banque. Cette permutation est fixe, et ne change pas suivant l'adresse. Des fonctions de hachage plus complexes font des XOR bit à bit entre certains bits de l'adresse. Il existe aussi d'autres techniques qui donnent le numéro de banque à partir d'un polynôme modulo N, appliqué sur l'adresse. ==Les contrôleurs mémoires SDRAM/DDR optimisés== Après avoir vu le cas des mémoires RAM, nous allons nous concentrer sur le cas particulier des mémoires SDRAM. Les mémoires SDRAM et DDR modernes sont capables de gérer plusieurs accès simultanés à la mémoire RAM. Et les contrôleurs mémoire intègrent des optimisations spécifiques afin d'en profiter au maximum. ===La mise en attente des accès mémoire=== Comme pour les mémoires caches, il existe des contrôleurs mémoire bloquants et non-bloquants. Les premiers n'acceptent qu'un seul accès mémoire à la fois, les seconds acceptent plusieurs accès mémoire simultanés. Pour simplifier, nous allons dire que le contrôleur mémoire n’exécute qu'un seul accès mémoire à la fois, et qu'ils met en attente les accès en trop. C'est une simplification assez irréaliste, vu que les mémoires modernes disposent de plusieurs banques accessibles en parallèle. Mais nous introduirons ce détail un peu plus tard. Avec un contrôleur mémoire de ce type, les accès mémoire sont mis en attente dans une mémoire FIFO, histoire de les exécuter dans leur ordre d'arrivée. Les accès mémoire quittent alors la mémoire dans leur ordre d'arrivée, les lectures sont renvoyées dans l'ordre demandé. En plus d'une mémoire FIFO pour les commandes mémoire, on trouve une mémoire FIFO pour les données à écrire. Elle permet d'accumuler plusieurs écritures, tout en permettant que la donnée adéquate soit envoyée à la RAM au bon moment. Il y a aussi une FIFO pour les données lues, qui sert au cas où le cache ne soit pas disponible quand on lui envoie la donnée lue. [[File:Module d'interface avec la mémoire.png|centre|vignette|upright=2.5|Contrôleur mémoire avec mise en attente des requêtes processeur et autres optimisations.]] Une optimisation regroupe plusieurs accès à des données consécutives en un seul accès en rafale. Le contrôleur mémoire analyse les requêtes mises en attente et détecte si certaines se font à des adresses consécutives. Si c'est le cas, il fusionne ces requêtes en une lecture/écriture en rafale. Une telle optimisation est appelée la '''combinaison de lecture''' pour les lectures, et la '''combinaison d'écriture''' pour les écritures. ===Le pipeline des mémoires SDRAM=== Les SDRAM ont une forme de pseudo-pipeline très limité. Elles sont parfois capables de démarrer une commande avant que la précédente soit terminée. Même si elle parait très limitée, cette possibilité est exploitée au mieux par la présence de plusieurs banques dans la mémoire SDRAM. Les chips mémoires de SDRAM sont composés de plusieurs banques, chacune étant une sorte de mini-mémoire miniature. Chaque banque a son propre décodeur, son propre tampon de ligne, ses propres multiplexeurs de colonne, sa logique de rafraichissement mémoire, etc. C'est comme si une SDRAM regroupait plusieurs mémoires séparées dans un même circuit intégré. Et cela permet de faire plusieurs accès mémoire en même temps, dans des banques différentes. Il est possible de lancer un accès mémoire dans deux banques en même temps, par exemple. Ou encore, on peut lancer un nouvel accès mémoire dans une banque si elle est inoccupée. La possibilité améliore grandement la performance de la SDRAM. Mais nous en reparlerons dans un chapitre ultérieur. {|class="wikitable" |+ Pipelining basique sur les SDRAM |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 |- ! Banque Numéro 1 | || || || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || || || |- ! Banque Numéro 2 | || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || |- ! Banque Numéro 3 | || || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || |} L'implémentation n'a rien de vraiment compliqué. Il suffit d'utiliser une mémoire FIFO par banque. Ainsi, les accès dans une même banque se feront en série, dans l'ordre d'arrivée, mais des accès à des banques différentes se feront dans le désordre. Cela ne pose pas de problème car les accès se font à des adresses différentes, ce qui fait qu'il n'y a pas de conflits majeurs. [[File:Gestion parallèle des banques.png|centre|vignette|upright=2.5|Gestion parallèle des banques.]] ===Le ré-ordonnancement des commandes mémoires=== Un contrôleur mémoire moderne est capable de changer l'ordre des requêtes mémoires, pour gagner en performances. En clair : il peut faire l'équivalent mémoire de l'exécution dans le désordre des processeurs haute performance. Une différence majeure est que le contrôleur mémoire ne remet pas les accès mémoire dans l'ordre avant de les envoyer au processeur. C'est le processeur qui remet en ordre les accès mémoire, dans sa ''load queue''. Pour cela, les requêtes mémoire sont "numérotées", à savoir qu'on leur attribue un identifiant binaire. Le contrôleur mémoire exécute les lectures/écritures dans le désordre, mais garde la trace de leur identifiant. Il sait dans quel ordre il exécute les accès mémoire et connait leur latence. Ce qui fait qu'il sait que la donnée lue à tel instant est associée à la requête mémoire numéro X. Pour les lectures, il renvoie la donnée lue avec l'identifiant. Le processeur sait alors à quelle lecture la donnée lue correspond et il se débrouille en interne pour gérer la situation. En clair : le processeur voit les accès mémoire dans le désordre, c'est lui qui les remet en ordre. Maintenant que ces précisions sont faites, posons cette question : quel est l'intérêt de faire les accès mémoire dans le désordre ? Accéder à des banques en parallèle est une réponse possible, mais le ré-ordonnancement est surtout utile si tous les accès mémoire atterrissent dans la même banque. Pour comprendre pourquoi, faisons un rappel rapide sur le tampon de ligne. Les mémoires SDRAM sont des mémoires à tampon de ligne. Elles font un accès mémoire en deux étapes. La première étape recopie une ligne de N * 64 bits dans un tampon interne à la SDRAM. La seconde étape sélectionne une colonne, à savoir une donnée de 64 bits, qui est soit envoyée sur le bus de données pour une lecture, soit modifiée par une écriture. [[File:Mémoire à tampon de ligne.png|centre|vignette|upright=2|Mémoire à tampon de ligne]] Les deux étapes correspondent à deux commandes séparées : une commande ACT qui précise l'adresse de la ligne, et une commande READ ou WRITE qui précise l'adresse de la colonne. [[File:Signaux RAS et CAS.png|centre|vignette|upright=2|Signaux RAS et CAS.]] Les SDRAM permettent de se passer de la première étape si des accès consécutifs se font dans la même ligne. Une fois activée, la ligne reste ouverte et on peut accéder plusieurs fois de suite dedans. On a alors juste à préciser l'adresse de colonne dedans. [[File:Sélection d'une ligne sur une mémoire FPM ou EDO.png|centre|vignette|upright=2|Sélection d'une ligne sur une mémoire FPM ou EDO.]] L'exécution dans le désordre des lectures/écritures SDRAM vise à faire en sorte que des accès consécutifs se fassent dans une même ligne. Elle marche si plusieurs accès à une même ligne ne sont pas consécutifs, et qu'il y a des accès entre les deux. Après ré-ordonnancement, ces accès mémoire deviennent consécutifs, ils sont exécutés l'un à la suite de l'autre. Pour rendre le tout plus concret, voici un exemple. Imaginez que l'on sait les 3 accès mémoire suivants : * Une lecture ligne A ; * Une écriture ligne B ; * Une lecture ligne A. Sans réordonnancent, on doit émettre deux commandes PRECHARGE : une quand on passe de la ligne A à la ligne B, et une autre pour le passage inverse. pour éviter cela, le contrôleur mémoire peut retarder l'écriture, ou au contraire avancer la seconde lecture. le résultat est alors le suivant : * Lecture ligne A ; * Lecture ligne A ; * Une écriture ligne B. On a donc deux accès consécutifs à la même ligne, suivi par une écriture dans une autre ligne. La technique marche parce que l'on a un mix adéquat de lectures et d'écritures. Mais il existe des possibilités de réorganisation autres qui ne sont pas valides. Il faut par exemple prendre garde à éviter d'intervertir lectures et écritures à une même adresse, sous peine de problèmes. Encore une fois, il faut faire attention aux dépendances mémoires. Le controleur mémoire teste les dépendances mémoires avant d'envoyer des commandes à la mémoire DDR/SDRAM. ==L'entrelacement sur les mémoires SDRAM== L''''entrelacement''' fonctionne sur les mémoires SDRAM, mais il doit être fortement modifié. Concrètement, tout ce qui a étré dit plus est inapplicable pour les SDRAM, du fait de la présence du tampon de ligne. Des adresses consécutives atterrissent déjà dans la même ligne, ce qui fait qu'on n'a pas besoin d'entrelacement au niveau d'une ligne. Et ne parlons pas de ce la présence de rangées et de canaux mémoire ! Cependant, il peut y avoir un entrelacement lié à la présence des banques SDRAM. L'entrelacement est géré juste avant le séquenceur mémoire. Le '''circuit d'entrelacement''' intervertit certains bits de l'adresse lors des accès mémoires. On trouve aussi des mémoires FIFOs pour mettre en attente les requêtes mémoire provenant du processeur. Elles permettent de recevoir plusieurs accès mémoire consécutifs. Si vous vous demandez comment plusieurs accès mémoire consécutifs peuvent avoir lieu, sachez que c'est une bonne question. Pour le moment, nous allons dire que ces accès proviennent du processeur ou de la mémoire cache, sans expliquer comment. Le contrôleur mémoire reçoit une série d'accès mémoire assez rapprochés, qu'il doit exécuter au mieux. [[File:Controleur mémoire d'une SDRAM avec entrelacement.png|centre|vignette|upright=2.5|Contrôleur mémoire d'une SDRAM avec entrelacement.]] Pour gérer l'entrelacement, le contrôleur de SDRAM prend en entrée l'adresse envoyée par le processeur, et la découpe en plusieurs champs : un pour sélectionner la banque adéquate, un autre pour la ligne, un autre pour la colonne, un autre pour la rangée. Une solution simple désactive l'entrelacement. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de banque || Adresse de ligne || Adresse de colonne |} Si cette solution fonctionne bien si les accès mémoire sont assez aléatoires, en pratique, mieux vaut utiliser l'entrelacement. ===L'entrelacement de banques/lignes=== Une première méthode d'entrelacement est l''''entrelacement de banques'''. Elle répartit deux lignes consécutives dans deux banques différentes. Pour comprendre l'idée, prenons un exemple. Imaginons une mémoire avec deux banques et 4 lignes. Imaginons qu'on parcoure/balaye la mémoire RAM en partant des adresses basses. Sans entrelacement de ligne, les accès se feront comme suit, de gauche à droite : {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Banque numéro 1 !! colspan="4" | Banque numéro 2 |- | Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 || Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 |} Avec l'entrelacement de banques, les accès se feront comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 |- | Ligne 1 || Ligne 1 || Ligne 2 || Ligne 2 || Ligne 3 || Ligne 3 || Ligne 4 || Ligne 4 |} Avec N banques, la première ligne ira dans la première banque, la seconde ligne dans la seconde, et ainsi de suite jusqu'à la énième banque qui va dans la banque numéro N. Et la ligne N+1 repart dans la première banque. L'avantage est que passer d'une ligne à la suivante est assez rapide. Au lieu de devoir fermer la première ligne et ouvrir la suivante on ouvrira directement la ligne suivante dans une banque différente, ce qui sera plus rapide. La commande PRECHARGE pourra être envoyée à la seconde banque en avance. Pour cela, l'adresse est découpée comme suit. {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne (précise à l'octet) |} ===L'entrelacement de rangées=== Il est aussi possible de faire la même chose, mais avec les rangées. Pour simplifier fortement, une rangée est simplement un chip mémoire. En réalité, les rangées ne sont pas des chip mémoire, mais un ensemble de chips mémoire regroupés ensemble histoire d'atteindre les 64 bits du bus de données. Par exemple, une rangée peut combiner 8 chips mémoire avec un bus de données de 8 bits chacun pour obtenir les 64 bits du bus de données d'une SDRAM. Mais nous passons ce détail sous silence dans les explications qui vont suivre, par souci de simplification. Pour faire comprendre l'entrelacement de rangée, le mieux est d'utiliser un exemple, le même que précédemment. Sans entrelacement de rangée, on change de chip mémoire une fois qu'on a balayé toutes ses adresses. {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Rangée numéro 1 !! colspan="4" | Rangée numéro 2 |- | Banque 1 || Banque 2 || Banque 3 || Banque 4 || Banque 1 || Banque 2 || Banque 3 || Banque 4 |} Avec l'entrelacement de ligne, on change de rangée dès qu'on change de banque/ligne. {|class="wikitable" |+ Adresse mémoire |- ! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 |- | Banque 1 || Banque 1 || Banque 2 || Banque 2 || Banque 3 || Banque 3 || Banque 4 || Banque 4 |} En clair, quand on a épuisé toutes les banques dans une même rangée, on passe à la rangée suivante au lieu de rester dans la même rangée. Notons que dans l'exemple précédent, on a combiné l'entrelacement de rangée et de banque, mais on aurait pu utiliser l'entrelacement de rangée seul. Mais ce n'est pas le cas le plus courant en pratique. Toujours est-il qu'en combinant entrelacement de rangée et de ligne, le découpage de l'adresse est le suivant : {|class="wikitable" |+ Adresse mémoire |- | Adresse de ligne || Numéro de rangée || Adresse de banque || Adresse de colonne (précise à l'octet) |} D'autres méthodes d'entrelacement sont possibles, mais elles sont plus complexes. Chaque contrôleur mémoire fait un peu à sa sauce. Ils permettent en général de choisir entre plusieurs entrelacements. Au minimum, ils permettent de désactiver l'entrelacement, d'activer un entrelacement de ligne, et d'autres entrelacements plus complexes, dont certains sont propriétaires et tenus secrets par le fabricant. ===L'entrelacement avec le ''dual channel''=== [[File:Dual channel slots.jpg|centre|vignette|Slots mémoires avec ''dual channel''.]] L'usage du ''dual channel'' complique encore l'entrelacement. Là encore, il y a deux grandes solutions : avec et sans entrelacement des canaux mémoire. Rappelons le principe : deux barrettes de RAM sont accédées en parallèle. Et pour cela, il faut utiliser l'entrelacement. Typiquement, chaque barrette mémoire fournit 64 bits, ce qui fait que l'on peut accéder à 128 bits d'un seul coup, par exemple avec un accès en rafale. Une solution pour cela est de ne pas utiliser l'entrelacement. Avec deux barrettes, la première barrette regroupera la moitié haute de la RAM, la seconde barrette utilisera la moitié basse. Les possibilité d’accéder à deux données séparées de 64 bits en même temps est cependant très rare. Aussi, on préfère utiliser l'entrelacement. Pour cela, deux blocs de 64 bits sont placés dans des canaux mémoire séparés. Idem avec du triple ou quadruple canal, mais c'est alors trois ou quatre blocs de 64 bits qui sont dispersés dans des canaux consécutifs. {|class="wikitable" |+ Adresse mémoire |- ! colspan="6" | Sans entrelacement |- | Numéro de canal/barrette || Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne || Position de l'octet dans un bloc de 64 bits |- | colspan="6" | |- ! colspan="6" | Avec entrelacement |- | Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne || Numéro de canal/barrette || Position de l'octet dans un bloc de 64 bits |} Les contrôleurs de SDRAM précédents sont assez basiques et ne représentent pas les contrôleurs les plus évolués. Ils étaient utilisés sur les anciens PC, à une époque où ils étaient encore sur la carte mère du processeur. Mais de nos jours, le contrôleur mémoire est intégré au processeur. Et il incorpore de nombreuses optimisations afin de gagner en performances. Malheureusement, ces optimisations sont elles-mêmes dépendantes des optimisations intégrées dans le processeur, ce qui fait qu'on ne peut pas en parler ici. Elles demandent que le contrôleur mémoire reçoive plusieurs accès mémoire simultanés, ce qui n'a aucun sens à ce stade du cours. Aussi, nous allons les passer sous silence pour le moment. Nous les verrons dans le chapitre sur le parallélisme mémoire. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=La désambiguïsation mémoire | prevText=La désambiguïsation mémoire | next=Les processeurs superscalaires | nextText=Les processeurs superscalaires }} </noinclude> cavs02q7icn3tjrprlavwejd6llxnlr 764656 764655 2026-04-23T14:36:28Z Mewtow 31375 /* L'entrelacement avec le dual channel */ 764656 wikitext text/x-wiki Dans ce chapitre, nous allons voir les techniques qui permettent de gérer plusieurs accès mémoire simultanés directement au niveau du cache ou de la mémoire RAM. Par plusieurs accès mémoire simultanés, vous pensez sans doute à l'usage de cache multiports, voire de mémoires RAM multiport. C'est en effet une solution, mais ce n'est clairement pas la seule. Par exemple, il est possible de pipeliner l'accès au cache, voire à la mémoire RAM. Il existe de nombreuses techniques de '''parallélisme mémoire''', et nous allons les voir dans ce chapitre. Pour donner un exemple, il est possible de pipeliner les accès mémoire. Il est possible de pipeliner l'accès au cache et/ou l'accès à la mémoire, et nous verrons comment faire dans ce chapitre. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, qu'ils aient ou non un pipeline dynamique. Par contre, pipeliner la mémoire est une technique ancienne, aujourd'hui peu utilisée. Elle était utilisée sur des très vieux ordinateurs, qui avaient un pipeline mais pas de mémoire cache. Mais de nos jours, les mémoires SDRAM et DDR ne sont pas adaptées pour ça, il est très difficile de les pipeliner correctement. Le parallélisme mémoire est utile aussi bien sur des processeurs à émission dans l'ordre que dans le désordre. Par exemple, un processeur ''in-order'' avec des lectures non-bloquantes peut e, profiter. Il peut techniquement lancer une seconde lecture, après avoir rencontré une première lecture non-bloquante. En général, le nombre de lectures consécutives est cependant limité à moins d'une dizaine. À l'opposé, les processeurs sans lectures non-bloquantes profitent pas du parallélisme mémoire. À la rigueur, ils peuvent en profiter s'ils intègrent des techniques de préchargement. Pendant que le processeur exécute une lecture/écriture, il peut précharger une autre donnée en parallèle. Inutile de dire que sans pipeline processeur, le parallélisme mémoire ne sert pas à grand-chose. ==Le ''line fill buffer'' et les techniques associées== Lors d'un défaut de cache, le processeur doit attendre que toute la ligne de cache soit chargée avant d'être utilisable. Or, la taille d'une ligne de cache est supérieure à la largeur du bus mémoire, ce qui fait qu'une ligne de cache est chargée en plusieurs fois, morceaux par morceaux, mot mémoire par mot mémoire. Le chargement peut se faire directement dans le cache, mais ce n'est pas une solution très pratique. À la place, beaucoup de processeurs ajoutent une mémoire tampon entre la RAM et le cache, appelée le '''tampon de remplissage de ligne''' (''line-fill buffer'') dans les processeurs modernes. Lors d'un défaut de cache, le processeur charge la donnée de la RAM dans le tampon de remplissage de ligne, mot mémoire par mot mémoire. Une fois plein, le tampon de remplissage de ligne est recopié dans la ligne de cache. Le tampon de remplissage de ligne contient une ligne de cache, si ce n'est que certains bits de contrôle ne sont pas présents. Le tampon de remplissage de ligne contient un bit de validité pour chaque mot mémoire de la ligne de cache, qui indiquent si le mot mémoire a été chargé. Par exemple, prenons un processeur 64 bits, qui gère donc des mots mémoire de 8 octets, avec des lignes de cache 256 octets/32 mots mémoire. Le tampon de remplissage de ligne contiendra 32 bits de validité. Si le processeur a chargé les 6 premiers mots mémoire, les 6 premiers bits de validité seront mis à 1, les autres seront encore à zéro. Il faut noter que la disponibilité de la ligne complète se détermine assez facilement en faisant un ET logique entre tous les bits de validité. Le processeur sait ainsi quand la ligne de cache est disponible entièrement et donc quand la transférer dans le cache. La même chose existe avec une hiérarchie de cache, sauf que l'on trouve une mémoire tampon entre chaque niveau de cache. Il y a un tampon de remplissage de ligne entre le cache L1 et le cache L2, entre le cache L2 et le L3, etc. Si le cache ne gère qu'un seul défaut de cache à la fois, le ''fill line buffer'' est une mémoire très simple, qui ne mémorise qu'une seule ligne de cache. Et cela vaut aussi bien pour un cache bloquant que non-bloquant. Mais sur les caches capables de gérer plusieurs défauts simultanés, le tampon de remplissage de ligne est une mémoire de type FIFO ou LIFO, capable de mémoriser plusieurs lignes de cache. Notons que le tampon de remplissage de ligne est très utile pour implémenter certaines techniques, par exemple le contournement du cache. Nous avions vu dans le chapitre sur le cache que certains accès mémoire doivent contourner le cache, pour des raisons de cohérence des caches. C'est notamment nécessaire pour accéder aux périphériques, mais c'est aussi utile pour des raisons de performances dans des cas très spécifiques. Les accès qui contournent le cache se font directement dans le tampon de remplissage de ligne : le processeur écrit ou lit les données depuis ce tampon de remplissage de ligne, sans accéder au cache. ===L'''early restart'' et le ''critical word load''=== La présence du ''line buffer'' permet une optimisation assez intéressante, qui permet de réduire la latence des défauts de cache. L'optimisation consiste à lire un mot mémoire dans le tampon de remplissage de ligne, même si la ligne de cache complète n'a pas encore été chargée. Il existe deux manières de faire cela, qui portent les noms d'''early restart'' et de ''critical word load''. La première est la version la plus simple, la seconde est plus complexe mais plus performante. Avec la technique d''''''early restart''''', la ligne de cache est chargée normalement, en partant de son premier mot mémoire. Dès que le mot mémoire lu/écrit par le processeur est copié dans le ''line fill buffer'', il est envoyé au processeur immédiatement. Illustrons le tout par un exemple, où une ligne de cache fait 16 mots mémoire. Le processeur effectue une lecture, qui lit le 5ème mot mémoire. Sans ''early restart'', le processeur doit charger les 16 mots mémoire avant de faire la lecture dans le cache. Avec ''early restart'', le processeur reçoit la donnée dès que le 5ème mot mémoire est disponible. Le processeur doit attendre que les 4 premiers mots mémoire soient chargés, puis le 5ème mot mémoire arrive et est envoyé directement au processeur, il est lu directement depuis le ''line fill buffer''. Les 11 mots mémoire suivants sont ensuite chargés dans le cache pendant que le processeur fait des calculs dans son coin. Le ''critical word load'' est une optimisation de la technique précédente où le chargement de la ligne de cache commence directement à la donnée demandée par le processeur. Pour reprendre l'exemple précédent, où le processeur demande le 5ème mot mémoire sur 16, le ''critical word load'' charge le 5ème mot mémoire en premier et l'envoie au processeur, ce qui fait qu'il est chargé très rapidement. Pas besoin d'attendre que le processeur charge les 4 mots mémoire précédents comme avec l'''early restart''. Dans le détail, le processeur charge le 5ème mot mémoire en premier, puis charge les 11 suivants, et termine par les 4 mots mémoire du début. En clair, le ''critical word load'' commence par charger le mot mémoire lu, puis les blocs suivants, avant de revenir au début du bloc pour charger les blocs restants. Ainsi, la donnée demandée par le processeur sera la première disponible. Pour cela, l'organisation du tampon de remplissage de ligne est modifiée de manière à rendre cela possible. Il a une taille égale à une ligne de cache complète, qui contient elle-même plusieurs mots mémoire. Dans le ''line-fill buffer'', chaque mot mémoire est stocké avec un tag, qui indique l'adresse du mot mémoire stocké dans le ''line-fill buffer''. Le ''line-fill buffer'' est donc un cache un peu particulier, qui fonctionne comme un cache du point de vue du processeur, comme une mémoire FIFO pour les transferts avec le cache. Ainsi, un processeur qui veut lire dans le cache après un défaut peut accéder à la donnée directement depuis le tampon de remplissage de ligne, alors que la ligne de cache n'a pas encore été totalement recopiée en mémoire. ==Les caches pipelinés== Il est possible de pipeliner l'accès au cache, ce qui demande juste de rajouter des registres au bon endroits et quelques circuits de contrôle. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, même ceux avec un pipeline dynamique. Et l'implémentation peut se faire de plusieurs manières différentes. Il faut dire que découper une mémoire cache en plusieurs étapes peut se faire de plusieurs manière. Commençons par la plus simple. Beaucoup de processeurs des années 2000 avaient une fréquence peu élevée comparée aux standards d'aujourd'hui, ce qui fait que leur cache avait bien un temps d'accès d'un cycle d'horloge. Mais l'accès au cache se faisait en deux cycles d'horloge : un cycle pour calculer une adresse et un cycle pour l'accès mémoire proprement dit. Et ces deux étapes étaient pipelinées, à savoir que deux micro-opérations mémoire peuvent s'exécuter en même temps : une dans l'étage de calcul d'adresse, une autre dans le cache. L'unité mémoire était donc pipelinée, alors que l'accès au cache ne l'était pas. Les processeurs qui implémentaient cette technique regroupent les micro-architectures K5 et K6 d'AMD, les processeurs Intel de micro-architecture P6 (Pentium 2 et 3) et quelques autres. Il est aussi possible de pipeliner l'accès au cache lui-même. Avec un cache pipeliné, l'accès au cache ne se fait pas en un seul cycle, mais en plusieurs. Cependant, on peut lancer un nouvel accès au cache à chaque cycle d'horloge, comme un processeur pipeliné lance une nouvelle instruction à chaque cycle. L'implémentation est assez simple : il suffit d'ajouter des registres dans le cache. Pour cela, on profite que le cache est composé de plusieurs composants séparés, qui échangent des données dans un ordre bien précis, d'un composant à un autre. De plus, le trajet des informations dans un cache est linéaire, ce qui les rend parfois pour l'usage d'un pipeline. ===Le ''pipelining'' des caches ''direct-mapped''=== Voici ce qui se passe avec un cache directement adressé. Pour rappel ce genre de cache est conçu en combinant une mémoire RAM, généralement une SRAM, avec quelques circuits de comparaison et un MUX. L'accès au cache se fait globalement en deux étapes : la première lit la donnée et le ''tag'' dans la SRAM, et on utilise les deux dans une seconde étape. Nous avions vu il y a quelques chapitre comment pipeliner des mémoires, dans le chapitre sur les mémoires évoluées. Et bien ces méthodes peuvent s'utiliser pour la mémoire RAM interne au cache !L'idée est d'insérer un registre entre la sortie de la RAM et la suite du cache, pour en faire un cache pipéliné, à deux étages. Le premier étage lit dans la SRAM, le second fait le reste. L'implémentation sur les caches associatifs à plusieurs voies est globalement la même à quelques détails près. [[File:Cache directement adressé pipeliné - deux étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - deux étages]] Avec le circuit précédent, il est possible d'aller plus loin, cette fois en pipelinant l'accès à la mémoire RAM interne au cache. Pour comprendre comment, rappelons qu'une mémoire SRAM est composée d'un plan mémoire et d'un décodeur. L'accès à la mémoire demande d'abord que le décodeur fasse son travail pour sélectionner la case mémoire adéquate, puis ensuite la lecture ou écriture a lieu dans cette case mémoire. L'accès se fait donc en deux étapes successives séparées, on a juste à mettre un registre entre les deux. Il suffit donc de mettre un gros registre entre le décodeur et le plan mémoire. [[File:Cache directement adressé pipeliné - trois étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - trois étages]] Et on peut aller encore plus loin en découpant le décodeur en deux circuits séparés. En effet, rappelez-vous le chapitre sur les circuits de sélection : nous avions vu qu'il est possible de créer des décodeurs en assemblant des décodeurs plus petits, contrôlés par un circuit de prédécodage. Et bien on peut encore une ajouter un registre entre ce circuit de prédécodage et les petits décodeurs. : Théoriquement, toute l'adresse est fournie d'un seul coup au cache, la quasi-totalité des processeurs présentent l'adresse complète à un cache pipeliné. Mais le Pentium 4 fait autrement. Il faut noter que les premiers étages manipulent l'indice dans la SRAM, qui est dans les bits de poids faible, alors que les étages ultérieurs manipulent le ''tag'' qui est dans les bits de poids fort. Les concepteurs du Pentium 4 ont alors décidé de présenter les bits de poids faible lors du premier cycle d'accès au cache, puis ceux de poids fort au second cycle. Pour cela, l'ALU fonctionnait à une fréquence double de celle du processeur, tout comme le cache L1. Il n'y avait pas de pipeline proprement dit, mais cela réduisait grandement la latence d'accès au cache. ===Le ''pipelining'' des caches splittés/sériels=== Il est aussi possible de pipeliner un cache dits splittés, aussi appelés à accès sériel. Pour rappel, les caches à accès sériel vérifient si il y a succès ou défaut de cache, avant d'accéder aux lignes de cache en cas de succès. Ils font donc différemment des autres caches, qui accèdent à une ligne de cache, avant de déterminer s'il y a succès ou défaut en lisant le tag de la ligne de cache. Les caches sériels disposent de deux SRAM : une pour les tags des lignes de cache et une pour les données. Ils accèdent à la SRAM pour les tags, avant d’accéder à la SRAM des données en cas de succès de cache. Vu que l'accès se fait en deux étapes, une vérification des tags suivie de la lecture/écriture des données, il est facile à pipeliner. Pipeliner le cache permet de régler le problème des accès au cache L1, et elle est tout le temps utilisé sur les processeurs modernes. Mais que faire en cas de défaut de cache ? ==Les caches non bloquants== Un '''cache bloquant''' est un cache auquel le processeur ne peut pas accéder pendant un défaut de cache. Il faut attendre que la lecture ou écriture en RAM soit terminée avant de pouvoir utiliser de nouveau le cache. Un '''cache non bloquant''' n'a pas ce problème : on peut l'utiliser pendant un défaut de cache. Les caches non bloquants permettent de démarrer une nouvelle lecture ou écriture alors qu'une autre est en cours, ce qui permet d'exécuter plusieurs lectures ou écritures en même temps. ===Les ''Miss Handling Status Registers''=== Lors d'un défaut de cache, la mémoire RAM est consultée pendant le défaut de cache, mais le cache est inutilisé. Un défaut de cache n'utilise pas le cache, ce n'est qu'un accès en mémoire RAM, sur le bus mémoire (ou un accès aux niveaux de cache inférieurs, peu importe). Le cache en lui-même est laissé libre, rien n’empêche d'y accéder, il est en réalité intrinsèquement non-bloquant. Les caches, bloquants comme non-bloquants, sont en réalité composés d'une mémoire cache proprement dite, entourée de circuits qui servent d'interface entre le processeur et le cache lui-même. Et parmi les circuits tout autour, certains gèrent l'accès au cache lors d'un défaut de cache. Ils sont regroupés sous le terme de '''''Miss Handling Architecture''''' (MHA). La différence entre un cache bloquant et un cache non-bloquant est en réalité liée à l'implémentation de la MHA. Les caches bloquants coupent volontairement l'accès au cache lors d'un défaut, car les défauts sont plus simples à gérer ainsi. Le défaut de cache rapatrie une donnée depuis la RAM, qui sera écrite dans le cache. Et il ne faut pas qu'une tentative d'accès à cette donnée ait lieu avant qu'elle ne soit chargée. Mais ce blocage est général et touche tout le cache, alors que seule une ligne de cache est concernée par le défaut de cache. L'idée derrière un cache non-bloquant est que seule la ligne de cache est bloquée, mais les autres sont accessibles. L'idée est alors de mémoriser les lignes de cache concernées par le défaut de cache, afin d'en bloquer l'accès. À chaque accès, on vérifie si la ligne de cache est déjà réservée par un défaut de cache. La lecture/écriture est alors bloquée si c'est le cas, mais elle accepte les accès sinon. Pour cela, la ''Miss Handling Architecture'' contient des registres qui mémorisent des informations sur les défauts de cache en cours. Ils portent le nom de '''''miss status handling registers''''', que j’appellerais dorénavant MSHR, qui sont aussi appelés des '''''miss buffer'''''. Le contenu des MSHR varie beaucoup suivant le processeur, mais ces derniers stockent au minimum les informations suivantes : * Le numéro de la ligne de cache dans laquelle les données sont chargées. * Un bit de validité qui indique si le MSHR est vide ou pas, qui est mis à 0 quand le défaut de cache est résolu. * Un ou plusieurs champs de lecture/écriture, qui contiennent des informations sur la lecture/écriture. ** Pour une lecture, elle contient des informations sur la destination de la donnée, à savoir qui prévenir quand le défaut de cache est terminé. C'est parfois un nom/numéro de registre (celui dans lequel charger la donnée), mais c'est souvent le numéro de l'entrée dans le ''load/store queue''. ** Pour les écritures, elle contient la donnée à écrire, ou éventuellement un numéro de ''load/store queue'' où se trouve la donnée à écrire. Il faut noter que le bus mémoire ne peut gérer qu'un seul défaut de cache à la fois. Aussi, il est intéressant de regarder ce qui se passe lorsqu'un second défaut de cache survient, pendant qu'un premier est en cours. Dans ce cas, il y a deux réponses qui correspondent à deux types de caches non-bloquants, qui portent les noms barbares de caches de type succès après défaut et défaut après défaut. Sur le premier type, il ne peut pas y avoir plusieurs défauts de cache simultanés. Dès qu'un second défaut de cache survient, le cache stoppe son activité et on ne peut plus démarrer de nouvelle lecture/écriture, tant que le premier défaut de cache n'est pas résolu. Le second type est plus souple et autorise la survenue de plusieurs défauts de cache simultanés. Du moins, jusqu'à une certaine limite, car le cache ne peut supporter qu'un nombre limité d'accès mémoires simultanés (pipelinés). ===Les accès simultanés à une même ligne de cache=== Il arrive que le processeur fasse plusieurs accès mémoire simultanés à la même ligne de cache. Si la ligne de cache en question n'a pas encore été chargée dans le cache, alors on a plusieurs défauts de cache consécutifs pour la même ligne de cache, et le cache non-bloquant doit gérer la situation. Pour la suite, il va falloir faire une petite distinction entre les défauts primaires et secondaires. Imaginons qu'un défaut de cache ait lieu et demande à charger une donnée dans la ligne de cache numéro N. Il s'agit du premier défaut impliquant cette ligne de cache précise, ce qui lui vaut le nom de '''défaut de cache primaire'''. Mais par la suite, d'autres accès mémoire à la même ligne de cache ont lieu, alors que la ligne de cache n'est pas encore disponible. Dans ce cas, il s'agit de '''défauts de cache secondaires'''. Pour l'unité d'accès mémoire, les défauts de cache primaire et secondaire sont différents (ils prennent tous une entrée dans la ''load/store queue''). Mais pour le cache, ils ne correspondent qu'à un seul accès au cache : celui qui demande de charger la ligne de cache demandée. Les défauts de cache primaire et secondaire à la même ligne de cache se voient attribuer un MSHR unique. La gestion des défauts de cache secondaires dépend du cache non-bloquant. La solution la plus simple ne permet pas les défauts de cache secondaires. Le cache ne permet pas deux défauts de cache simultanés pour la même ligne de cache. Les autres solutions le permettent, en fusionnant des accès simultanés à la même ligne de cache en un seul au niveau des MSHR. Dans tous les cas, détecter les défauts de cache secondaires sont un problème qu'il faut détecter. La ''Miss Handling Architecture'' doit détecter les défauts de cache secondaire. Pour cela, elle procède comme suit. Lors de chaque défaut de cache, la MHA récupère le numéro de la ligne de cache associé. Il vérifie alors chaque MSHR pour vérifier s'il contient le numéro en question. S'il n'y a aucune correspondance dans les MSHR, alors c'est signe que le défaut de cache est un défaut primaire. Mais s'il y en a une, alors c'est un défaut secondaire. Évidemment, cela signifie que lors d'un défaut de cache, le numéro de ligne de cache est envoyé à tous les MSHR, pour comparaison. Les MSHR sont donc regroupés dans une mémoire associative, une sorte de mini-cache, faciliter l'implémentation. Lors d'un défaut de cache primaire, l'accès à un cache non-bloquant se fait comme suit : le processeur envoie une adresse au cache, accède à celui-ci, et détecte la survenue d'un défaut de cache. Il en profite alors pour attribuer une ligne de cache dans laquelle sera chargée ce défaut. L'attribution est très simple dans le cas des caches ''direct mapped'', ou associatifs par voie, pour lesquels l'attribution se fait assez simplement. Il mémorise alors cette information dans les MSHR, après avoir vérifié que le défaut de cache n'était pas un défaut secondaire. Lors des accès ultérieurs à une adresse proche, censée être dans la même ligne de cache, le processeur va encore une fois rencontrer un défaut de cache. Il va alors déterminer le numéro de la ligne de cache associée à l'adresse, et comparer ce numéro avec les MSHRs. Si un MSHR contient ce numéro, c'est signe que le défaut de cache est un défaut secondaire. La MHA réagit alors différemment selon le processeur considéré. Une première solution n'autorise pas les défauts de cache secondaires. Si l'un d'entre eux survient, le processeur est gelé par un ''pipeline stall'', une bulle de pipeline. Une autre solution fusionne les défauts de cache secondaires avec le défaut de cache primaire : tout cela ne correspond qu'à un seul défaut de cache pour lui. ===Les MSHR simples=== Un cache non-bloquant à '''MSHR simple''' contient juste plusieurs MSHR qui mémorisent juste un numéro de ligne de cache, un bit de validité, le champ de lecture/écriture, et l'adresse exacte de lecture/écriture. Avec cette organisation, il est possible d'avoir plusieurs défauts de cache séparés, mais à la condition que chaque défaut accède à une ligne de cache différente. Deux accès simultanés à une même ligne de cache ne sont pas possibles, les défauts de cache secondaires ne sont pas autorisés. Ainsi, chaque défaut de cache se voit attribuer son propre MSHR, chacun contient un numéro de ligne de cache différent. Pour comprendre pourquoi c'est impossible de gérer les défauts secondaires, il faut regarder le champ de lecture/écriture. Si on veut effectuer plusieurs écritures consécutives à la même adresse, le MSHR n'aura pas de quoi mémoriser les deux données à écrire. Il pourra mémoriser la première donnée à écrire, pas la seconde. Même chose lors d'une lecture : le champ lecture/écriture peut mémorisr la destination de la première lecture, pas de la seconde. L'avantage est que la MHA n'a besoin que des MSHR et de quelques circuits annexes. Les autres solutions rajoutent des circuits annexes pour gérer les défauts de cache secondaires, qui utilisent beaucoup de circuits. Le cout en circuit est donc élevé, mais le gain en performance est là. Passons maintenant aux caches non-bloquants qui autorisent les défauts de cache secondaire. La solution la plus simple consiste à utiliser ===Les MSHR adressés implicitement=== Avec les '''MSHR adressés implicitement''', il est possible de fusionner plusieurs accès mémoire à une même ligne de cache, mais sous une condition très importante : ces accès lisent/écrivent des mots mémoire différents. Par exemple, imaginons qu'une ligne de cache contienne 8 mots mémoire de 64 bits. Si un premier accès mémoire lit le mot mémoire numéro 7 (dernier mot mémoire de la ligne), et le second accès le mot mémoire numéro 3, alors la fusion est possible. Mais si deux accès mémoire veulent lire/écrire le mot mémoire numéro 7, alors la fusion n'est pas possible et le processeur se bloque, un ''pipeline stall'' survient. Un MSHR adressé implicitement est un MSHR simple, qui contient naturellement un numéro de ligne de cache (tag) et un bit de validité, sauf que le champ de lecture/écriture est dupliqué. Les différents champs lecture/écriture d'un MSHR sont regroupés dans une mémoire RAM/cache qui contient autant d'entrées qu'il y a de mot mémoire dans une ligne de cache. Chaque entrée de la table est associée à un mot mémoire de la ligne de cache et stocke des informations sur celui-ci. Une entrée mémorise, au minimum : * Un champ de lecture/écriture qui contient soit la destination de la lecture, soit la donnée à écrire. * Un bit R/W permettant d'interpréter correctement le champ de lecture/écriture. * Un bit de validité pour chaque entrée de la table, qui dit si un défaut de cache antérieur accède déjà à ce mot mémoire. La fusion de deux défauts de cache est ainsi assez simple. Un défaut de cache primaire/secondaire configure l'entrée associée au mot mémoire lu/écrit. Si un défaut secondaire ultérieur lit/écrit un mot mémoire différent (dont le bit de validité de l'entrée dans la table est à zéro), pas de conflit : il configure une autre entrée, vide. Mais s'il lit/écrit un mot mémoire pour lequel l'entrée de la table est occupée, il y a conflit, le processeur est bloqué par un ''pipeline stall'', une bulle de pipeline. Avec cette organisation, le nombre de MSHR indique combien de lignes de cache peuvent être lues en même temps depuis la mémoire. Quant au nombre d'entrées par MSHR, il détermine combien d'accès mémoires qui ne se recouvrent pas peuvent avoir lieu en même temps. ===Les MSHR adressés explicitement=== Les '''MSHR adressés explicitement''' n'ont pas les contraintes des MSHR adressés implicitement. Avec eux, il est possible d'avoir plusieurs défauts de cache pointant vers la même ligne de cache, mais aussi vers le même mot mémoire. De tels défauts de cache apparaissent sur les processeurs à exécution dans le désordre, mais sont très rares, voire inexistants, sur les processeurs ''in-order''. L'idée est encore que chaque MSHR est associée à une table mémoire qui mémorise des entrées. Cette table n'est autre qu'une mémoire FIFO. Sauf que cette fois-ci, une entrée n'est pas associée à un mot mémoire. Une entrée est associée à un défaut de cache. Une entrée mémorise là encore la destination de la lecture ou la donnée de l'écriture suivi du bit R/W, ainsi que d'un bit de validité par entrée, mais aussi : la position du mot mémoire lu dans la ligne de cache. C'est cette dernière information qui n'était pas présente dans les MSHR adressés implicitement. De plus, le nombre d'entrée par MSHR n'est pas égal au nombre de mots mémoires dans une ligne de cache, mais peut être arbitrairement grand. L'avantage d'un tel cache est qu'il est capable de traité les défauts secondaires concernant un même mot mémoire, et ce, car les accès à la ligne de cache concernée se produisent dans l'ordre de génération des défauts (la table mémoire est une FIFO). ===Les MSHRs inversés=== Généralement, plus on veut supporter de défauts de cache, plus le nombre de MSHR et d'entrées augmente. Mais au-delà d'un certain nombre d'entrées et de MSHR, les MSHR adressés implicitement et explicitement ont tendance à bouffer un peu trop de circuits. Utiliser une organisation un peu moins gourmande en circuits est donc une nécessité. Cette organisation plus économe se base sur des MSHR inversés. Les MSHR inversés ne contiennent qu'une seule entrée, en quelque sorte : au lieu d’utiliser n MSHR de m entrées chacun, on va utiliser n × m MSHR inversés. La différence, c'est que plusieurs MSHR peuvent contenir un tag identique, contrairement aux MSHR adressés implicitement et explicitement. Lorsqu'un défaut de cache a lieu, chaque MSHR est vérifié. Si jamais aucun MSHR ne contient de tag identique à celui utilisé par le défaut, un MSHR vide est choisi pour stocker ce défaut, et une requête de lecture en mémoire est lancée. Dans le cas contraire, un MSHR est réservé au défaut de cache, mais la requête n'est pas lancée. Quand la donnée est disponible, les MSHR correspondant à la ligne qui vient d'être chargée vont être utilisés un par un pour résoudre les défauts de cache en attente. ===Les MSHR intégrés au cache=== Certains chercheurs ont remarqué que pendant qu'une ligne de cache est en train de subir un défaut de cache, celle-ci reste inutilisée, et son contenu est destiné à être perdu une fois le défaut de cache résolu. Ils se sont dit que, plutôt que d'utiliser des MSHR séparés, il vaudrait mieux utiliser la ligne de cache pour stocker les informations sur les défauts de cache en attente dans cette ligne de cache. Pour éviter tout problème, il faut rajouter un bit dans les bits de contrôle de la ligne de cache, qui sert à indiquer que la ligne de cache est occupée : un défaut de cache a eu lieu dans cette ligne, et elle stocke donc des informations pour résoudre les défauts de cache. ==L'entrelacement mémoire== Nous venons de voir comment une mémoire cache peut gérer plusieurs accès mémoire simultanés. Il se trouve que ce genre d'optimisation dépasse largement le cadre du cache. Les mémoires RAM ne sont pas en reste, elles aussi ! Il est possible de pipeliner l'accès à une mémoire RAM, en utilisant des techniques dites d''''entrelacement'''. Précisons cependant que cette optimisation n'est pas utilisée sur les mémoires SDRAM ou DDR modernes, du moins pas sans modifications majeures. Mais divers ordinateurs assez anciens, dont des superordinateurs, ont utilisé cette technique. La technique demande d'utiliser plusieurs mémoires séparées. Sur les anciens ordinateurs, les mémoires en question sont des chips mémoire, à savoir des circuits intégrés de DRAM. Pour faire plus général, nous allons utiliser le terme de '''banques''', ou encore de bancs mémoire. La différence est qu'il existe des mémoires multi-banque, qui regroupent plusieurs banques indépendantes dans un seul boitier, dans un seul chip mémoire. En clair, de telles mémoires regroupent plusieurs sous-mémoires dans un seul circuit intégré. Il est aussi possible d'utiliser plusieurs chips mémoire séparés, chacun ayant une banque. Reste à combiner ces banques pour former un pseudo-pipeline. ===Utiliser plusieurs banques sans entrelacement=== Sans optimisation particulière, les adresses sont réparties dans les banques comme indiqué ci-dessous. Il s'agit de ce qui s'appelle un arrangement horizontal, et nous avions vu celui-ci dans le chapiutre sur les mémoires SDRAM. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Avec cet arrangement, les bits de poids fort de l'adresse sont utilisées pour sélectionner la banque adéquate, et le reste de l'adresse est envoyé sur le bus d'adresse. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Adresse de banque !! Adresse dans la banque |- | Quelques bits de poids fort || Reste de l'adresse |} L'entrelacement change cette répartition, afin d'accélérer les accès mémoire. L'entrelacement classique vise à améliorer les accès à des adresses mémoires consécutives, mais des formes plus évoluées d'entrelacement visent à optimiser des accès mémoire arbitraires. Voyons-les dans l'ordre, du plus simple au plus complexe. ===L'entrelacement classique=== Avec l''''entrelacement classique''', des adresses consécutives sont réparties dans des banques différentes. [[File:Répartition des adresses dans une mémoire interleaved.png|centre|vignette|upright=1.5|Répartition des adresses dans une mémoire interleaved.]] L'entrelacement simple permet d'accélérer les accès mémoire qui se font à des adresses consécutives. Avec l'entrelacement, chaque accès mémoire tombe dans une banque mémoire différente, ce qui fait qu'on peut démarrer un nouvel accès mémoire à chaque cycle d'horloge. Pendant qu'une banque est occupée par un accès mémoire, on démarre le suivant dans une autre banque, et ainsi de suite. Le tout se termine soit quand on a épuisé toutes les banques libres, soit quand l'accès en rafales se termine. Pas besoin d'attendre que la mémoire ait fini sa lecture/écriture avant de démarrer la lecture/écriture suivante. [[File:Pipemining mémoire.png|centre|vignette|upright=2.5|Pipelining mémoire]] L'animation suivante illustre bien le processus, avec quatres banques. [[File:Interleaving.gif|centre|vignette|upright=2.5|Pipelining mémoire]] L'entrelacement simple utilise les bits de poids faible pour sélectionner la banque, et les bits de poids fort pour la case mémoire. [[File:Adresse mémoire d'une mémoire entrelacée.png|centre|vignette|upright=2|Adresse mémoire d'une mémoire entrelacée]] Précisons que le temps d'accès mémoire ne change pas beaucoup avec l'entrelacement. Par contre, on peut faire plus d'accès mémoire simultanés. On peut démarrer un accès mémoire par cycle d'horloge, mais l'accès en lui-même prend plusieurs cycles. Les mémoires à entrelacement ont donc un débit supérieur aux mémoires qui ne l'utilisent pas. L'entrelacement classique pourrait être utilisé pour accélérer les transferts entre mémoire RAM et cache, qui se font en blocs d'adresses consécutives. Mais de nos jours, il n'est pas utilisé dans cette optique. Il faut dire qu'il est redondant avec le mode rafale des mémoires DRAM. Les deux font globalement la même chose et on voit mal comment utiliser les deux en même temps. Le mode rafale étant plus simple à implémenter, et plus léger niveau utilisation du bus de commande mémoire, il est préféré à l'entrelacement. Par contre, l'entrelacement a eu son heure de gloire sur d'anciens processeurs qui n'avaient pas de mémoire cache, alors qu'ils disposaient d'un pipeline. L'exemple typique est celui des nombreux ''processeurs vectoriels'', que nous n'avons pas encore abordé à ce stade du cours. De tels processeurs avaient un pipeline, une unité de calcul fortement pipelinée, et parfois même de l'exécution dans le désordre et du renommage de registres ! ===L'entrelacement par décalage=== L'entrelacement simple est très utile pour les accès en rafle ou équivalents. Par contre, il existe de rares situations où il n'est pas optimal. Une de ces situations est celle des accès en enjambée (en ''stride''), où l'on accède en série à des adresses sont séparées par N mots mémoires. De tels accès surviennent quand un logiciel accède à des structures de données spécifiques, à savoir des tableaux de structures, des matrices, ou d'autres structures de données dans le genre. Un cas classique est celui du parcours d'une matrice colonne par colonne. Les matrices de nombres sont mémorisées ligne par ligne, ce qui fait que parcourir une colonne demande de passer d'une ligne à la suivante et de faire des grands sauts en mémoire RAM, mais tous espacés par la même distance. Avec l'entrelacement simple, les accès en enjambée sont moins performants que les accès à des adresses consécutives, mais cela ne fonctionne pas trop mal. Le pire cas est celui où l'on a N banques et où les données sont justement placées toutes les N adresses. Des accès consécutifs vont tous tomber dans la même banque, on ne peut plus accéder à des banques différentes en parallèle. Mais en dehors de ce cas, on voit une amélioration par rapport à la situation sans entrelacement. Pour obtenir des performances maximales pour les accès en enjambées, il faut répartir les mots mémoires dans la mémoire autrement. L'organisation idéale est la suivante. : Dans les explications qui vont suivre, la variable N représente le nombre de banques, qui sont numérotées de 0 à N-1. On commence par organiser les N premières adresses comme une mémoire entrelacée simple : l'adresse 0 correspond à la banque 0, l'adresse 1 à la banque 1, etc. Pour le bloc suivant, on décale tout d'une adresse, à savoir qu'on commence à remplir les banques à partir de la seconde, non de la première. Une fois la fin du bloc atteinte, on finit de remplir le bloc en repartant du début du bloc. Le troisième bloc d'adresse subit le même traitement, sauf qu'on commence à remplir à partir de la seconde banque. Et on poursuit l’assignation des adresses en décalant d'un cran en plus à chaque bloc. Ainsi, chaque bloc verra ses adresses décalées d'un cran en plus comparé au bloc précédent. Si jamais le décalage dépasse la fin d'un bloc, alors on reprend au début. [[File:Mémoire interleaved par décalage.png|centre|vignette|upright=1.5|Mémoire entrelacée par décalage.]] En faisant cela, on remarque que les banques situées à N adresses d'intervalle sont différentes. Dans l'exemple du dessus, nous avons ajouté un décalage de 1 à chaque nouveau bloc à remplir. Mais on aurait tout aussi bien pu prendre un décalage de 2, 3, etc. Dans tous les cas, on obtient un entrelacement par décalage. Ce décalage est appelé le pas d'entrelacement, noté P. Le calcul de l'adresse à envoyer à la banque, ainsi que la banque à sélectionner se fait en utilisant les formules suivantes : * adresse à envoyer à la banque = adresse totale / N ; * numéro de la banque = (adresse + décalage) modulo N, avec décalage = (adresse totale * P) mod N. Avec cet entrelacement par décalage, on peut prouver que la bande passante maximale est atteinte si le nombre de banques est un nombre premier. Seulement, utiliser un nombre de banques premier peut créer des trous dans la mémoire, des mots mémoires inadressables. Pour éviter cela, il faut faire en sorte que N et la taille d'une banque soient premiers entre eux : ils ne doivent pas avoir de diviseur commun. Dans ce cas, les formules se simplifient : * adresse à envoyer à la banque = adresse totale / taille de la banque ; * numéro de la banque = adresse modulo N. ===L'entrelacement pseudo-aléatoire=== Une dernière méthode de répartition consiste à répartir les adresses dans les banques de manière "pseudo-aléatoire". L'idée exacte est de faire passer l'adresse dans une '''fonction de hachage''', plus ou moins complexe. Pour rappel, une fonction de hachage prend une entrée de grande taille et fournit en sortie un résultat de petite taille. Idéalement, elles doivent donner des résultats aussi différents que possible pour des entrées similaires, histoire de simuler une sorte de pseudo-aléatoire. Ici, une adresse est transformée en un numéro de banque. Une fonction de hachage simple ne fait que permuter des bits de l'adresse pour obtenir son résultat. Elle ne fait qu'échanger des bits de place avant de couper l'adresse en deux morceaux : un pour la sélection de la banque, et un autre pour la sélection de l'adresse dans la banque. Cette permutation est fixe, et ne change pas suivant l'adresse. Des fonctions de hachage plus complexes font des XOR bit à bit entre certains bits de l'adresse. Il existe aussi d'autres techniques qui donnent le numéro de banque à partir d'un polynôme modulo N, appliqué sur l'adresse. ==Les contrôleurs mémoires SDRAM/DDR optimisés== Après avoir vu le cas des mémoires RAM, nous allons nous concentrer sur le cas particulier des mémoires SDRAM. Les mémoires SDRAM et DDR modernes sont capables de gérer plusieurs accès simultanés à la mémoire RAM. Et les contrôleurs mémoire intègrent des optimisations spécifiques afin d'en profiter au maximum. ===La mise en attente des accès mémoire=== Comme pour les mémoires caches, il existe des contrôleurs mémoire bloquants et non-bloquants. Les premiers n'acceptent qu'un seul accès mémoire à la fois, les seconds acceptent plusieurs accès mémoire simultanés. Pour simplifier, nous allons dire que le contrôleur mémoire n’exécute qu'un seul accès mémoire à la fois, et qu'ils met en attente les accès en trop. C'est une simplification assez irréaliste, vu que les mémoires modernes disposent de plusieurs banques accessibles en parallèle. Mais nous introduirons ce détail un peu plus tard. Avec un contrôleur mémoire de ce type, les accès mémoire sont mis en attente dans une mémoire FIFO, histoire de les exécuter dans leur ordre d'arrivée. Les accès mémoire quittent alors la mémoire dans leur ordre d'arrivée, les lectures sont renvoyées dans l'ordre demandé. En plus d'une mémoire FIFO pour les commandes mémoire, on trouve une mémoire FIFO pour les données à écrire. Elle permet d'accumuler plusieurs écritures, tout en permettant que la donnée adéquate soit envoyée à la RAM au bon moment. Il y a aussi une FIFO pour les données lues, qui sert au cas où le cache ne soit pas disponible quand on lui envoie la donnée lue. [[File:Module d'interface avec la mémoire.png|centre|vignette|upright=2.5|Contrôleur mémoire avec mise en attente des requêtes processeur et autres optimisations.]] Une optimisation regroupe plusieurs accès à des données consécutives en un seul accès en rafale. Le contrôleur mémoire analyse les requêtes mises en attente et détecte si certaines se font à des adresses consécutives. Si c'est le cas, il fusionne ces requêtes en une lecture/écriture en rafale. Une telle optimisation est appelée la '''combinaison de lecture''' pour les lectures, et la '''combinaison d'écriture''' pour les écritures. ===Le pipeline des mémoires SDRAM=== Les SDRAM ont une forme de pseudo-pipeline très limité. Elles sont parfois capables de démarrer une commande avant que la précédente soit terminée. Même si elle parait très limitée, cette possibilité est exploitée au mieux par la présence de plusieurs banques dans la mémoire SDRAM. Les chips mémoires de SDRAM sont composés de plusieurs banques, chacune étant une sorte de mini-mémoire miniature. Chaque banque a son propre décodeur, son propre tampon de ligne, ses propres multiplexeurs de colonne, sa logique de rafraichissement mémoire, etc. C'est comme si une SDRAM regroupait plusieurs mémoires séparées dans un même circuit intégré. Et cela permet de faire plusieurs accès mémoire en même temps, dans des banques différentes. Il est possible de lancer un accès mémoire dans deux banques en même temps, par exemple. Ou encore, on peut lancer un nouvel accès mémoire dans une banque si elle est inoccupée. La possibilité améliore grandement la performance de la SDRAM. Mais nous en reparlerons dans un chapitre ultérieur. {|class="wikitable" |+ Pipelining basique sur les SDRAM |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 |- ! Banque Numéro 1 | || || || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || || || |- ! Banque Numéro 2 | || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || |- ! Banque Numéro 3 | || || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || |} L'implémentation n'a rien de vraiment compliqué. Il suffit d'utiliser une mémoire FIFO par banque. Ainsi, les accès dans une même banque se feront en série, dans l'ordre d'arrivée, mais des accès à des banques différentes se feront dans le désordre. Cela ne pose pas de problème car les accès se font à des adresses différentes, ce qui fait qu'il n'y a pas de conflits majeurs. [[File:Gestion parallèle des banques.png|centre|vignette|upright=2.5|Gestion parallèle des banques.]] ===Le ré-ordonnancement des commandes mémoires=== Un contrôleur mémoire moderne est capable de changer l'ordre des requêtes mémoires, pour gagner en performances. En clair : il peut faire l'équivalent mémoire de l'exécution dans le désordre des processeurs haute performance. Une différence majeure est que le contrôleur mémoire ne remet pas les accès mémoire dans l'ordre avant de les envoyer au processeur. C'est le processeur qui remet en ordre les accès mémoire, dans sa ''load queue''. Pour cela, les requêtes mémoire sont "numérotées", à savoir qu'on leur attribue un identifiant binaire. Le contrôleur mémoire exécute les lectures/écritures dans le désordre, mais garde la trace de leur identifiant. Il sait dans quel ordre il exécute les accès mémoire et connait leur latence. Ce qui fait qu'il sait que la donnée lue à tel instant est associée à la requête mémoire numéro X. Pour les lectures, il renvoie la donnée lue avec l'identifiant. Le processeur sait alors à quelle lecture la donnée lue correspond et il se débrouille en interne pour gérer la situation. En clair : le processeur voit les accès mémoire dans le désordre, c'est lui qui les remet en ordre. Maintenant que ces précisions sont faites, posons cette question : quel est l'intérêt de faire les accès mémoire dans le désordre ? Accéder à des banques en parallèle est une réponse possible, mais le ré-ordonnancement est surtout utile si tous les accès mémoire atterrissent dans la même banque. Pour comprendre pourquoi, faisons un rappel rapide sur le tampon de ligne. Les mémoires SDRAM sont des mémoires à tampon de ligne. Elles font un accès mémoire en deux étapes. La première étape recopie une ligne de N * 64 bits dans un tampon interne à la SDRAM. La seconde étape sélectionne une colonne, à savoir une donnée de 64 bits, qui est soit envoyée sur le bus de données pour une lecture, soit modifiée par une écriture. [[File:Mémoire à tampon de ligne.png|centre|vignette|upright=2|Mémoire à tampon de ligne]] Les deux étapes correspondent à deux commandes séparées : une commande ACT qui précise l'adresse de la ligne, et une commande READ ou WRITE qui précise l'adresse de la colonne. [[File:Signaux RAS et CAS.png|centre|vignette|upright=2|Signaux RAS et CAS.]] Les SDRAM permettent de se passer de la première étape si des accès consécutifs se font dans la même ligne. Une fois activée, la ligne reste ouverte et on peut accéder plusieurs fois de suite dedans. On a alors juste à préciser l'adresse de colonne dedans. [[File:Sélection d'une ligne sur une mémoire FPM ou EDO.png|centre|vignette|upright=2|Sélection d'une ligne sur une mémoire FPM ou EDO.]] L'exécution dans le désordre des lectures/écritures SDRAM vise à faire en sorte que des accès consécutifs se fassent dans une même ligne. Elle marche si plusieurs accès à une même ligne ne sont pas consécutifs, et qu'il y a des accès entre les deux. Après ré-ordonnancement, ces accès mémoire deviennent consécutifs, ils sont exécutés l'un à la suite de l'autre. Pour rendre le tout plus concret, voici un exemple. Imaginez que l'on sait les 3 accès mémoire suivants : * Une lecture ligne A ; * Une écriture ligne B ; * Une lecture ligne A. Sans réordonnancent, on doit émettre deux commandes PRECHARGE : une quand on passe de la ligne A à la ligne B, et une autre pour le passage inverse. pour éviter cela, le contrôleur mémoire peut retarder l'écriture, ou au contraire avancer la seconde lecture. le résultat est alors le suivant : * Lecture ligne A ; * Lecture ligne A ; * Une écriture ligne B. On a donc deux accès consécutifs à la même ligne, suivi par une écriture dans une autre ligne. La technique marche parce que l'on a un mix adéquat de lectures et d'écritures. Mais il existe des possibilités de réorganisation autres qui ne sont pas valides. Il faut par exemple prendre garde à éviter d'intervertir lectures et écritures à une même adresse, sous peine de problèmes. Encore une fois, il faut faire attention aux dépendances mémoires. Le controleur mémoire teste les dépendances mémoires avant d'envoyer des commandes à la mémoire DDR/SDRAM. ==L'entrelacement sur les mémoires SDRAM== L''''entrelacement''' fonctionne sur les mémoires SDRAM, mais il doit être fortement modifié. Concrètement, tout ce qui a étré dit plus est inapplicable pour les SDRAM, du fait de la présence du tampon de ligne. Des adresses consécutives atterrissent déjà dans la même ligne, ce qui fait qu'on n'a pas besoin d'entrelacement au niveau d'une ligne. Et ne parlons pas de ce la présence de rangées et de canaux mémoire ! Cependant, il peut y avoir un entrelacement lié à la présence des banques SDRAM. L'entrelacement est géré juste avant le séquenceur mémoire. Le '''circuit d'entrelacement''' intervertit certains bits de l'adresse lors des accès mémoires. On trouve aussi des mémoires FIFOs pour mettre en attente les requêtes mémoire provenant du processeur. Elles permettent de recevoir plusieurs accès mémoire consécutifs. Si vous vous demandez comment plusieurs accès mémoire consécutifs peuvent avoir lieu, sachez que c'est une bonne question. Pour le moment, nous allons dire que ces accès proviennent du processeur ou de la mémoire cache, sans expliquer comment. Le contrôleur mémoire reçoit une série d'accès mémoire assez rapprochés, qu'il doit exécuter au mieux. [[File:Controleur mémoire d'une SDRAM avec entrelacement.png|centre|vignette|upright=2.5|Contrôleur mémoire d'une SDRAM avec entrelacement.]] Pour gérer l'entrelacement, le contrôleur de SDRAM prend en entrée l'adresse envoyée par le processeur, et la découpe en plusieurs champs : un pour sélectionner la banque adéquate, un autre pour la ligne, un autre pour la colonne, un autre pour la rangée. Une solution simple désactive l'entrelacement. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de banque || Adresse de ligne || Adresse de colonne |} Si cette solution fonctionne bien si les accès mémoire sont assez aléatoires, en pratique, mieux vaut utiliser l'entrelacement. ===L'entrelacement de banques/lignes=== Une première méthode d'entrelacement est l''''entrelacement de banques'''. Elle répartit deux lignes consécutives dans deux banques différentes. Pour comprendre l'idée, prenons un exemple. Imaginons une mémoire avec deux banques et 4 lignes. Imaginons qu'on parcoure/balaye la mémoire RAM en partant des adresses basses. Sans entrelacement de ligne, les accès se feront comme suit, de gauche à droite : {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Banque numéro 1 !! colspan="4" | Banque numéro 2 |- | Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 || Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 |} Avec l'entrelacement de banques, les accès se feront comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 |- | Ligne 1 || Ligne 1 || Ligne 2 || Ligne 2 || Ligne 3 || Ligne 3 || Ligne 4 || Ligne 4 |} Avec N banques, la première ligne ira dans la première banque, la seconde ligne dans la seconde, et ainsi de suite jusqu'à la énième banque qui va dans la banque numéro N. Et la ligne N+1 repart dans la première banque. L'avantage est que passer d'une ligne à la suivante est assez rapide. Au lieu de devoir fermer la première ligne et ouvrir la suivante on ouvrira directement la ligne suivante dans une banque différente, ce qui sera plus rapide. La commande PRECHARGE pourra être envoyée à la seconde banque en avance. Pour cela, l'adresse est découpée comme suit. {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne (précise à l'octet) |} ===L'entrelacement de rangées=== Il est aussi possible de faire la même chose, mais avec les rangées. Pour simplifier fortement, une rangée est simplement un chip mémoire. En réalité, les rangées ne sont pas des chip mémoire, mais un ensemble de chips mémoire regroupés ensemble histoire d'atteindre les 64 bits du bus de données. Par exemple, une rangée peut combiner 8 chips mémoire avec un bus de données de 8 bits chacun pour obtenir les 64 bits du bus de données d'une SDRAM. Mais nous passons ce détail sous silence dans les explications qui vont suivre, par souci de simplification. Pour faire comprendre l'entrelacement de rangée, le mieux est d'utiliser un exemple, le même que précédemment. Sans entrelacement de rangée, on change de chip mémoire une fois qu'on a balayé toutes ses adresses. {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Rangée numéro 1 !! colspan="4" | Rangée numéro 2 |- | Banque 1 || Banque 2 || Banque 3 || Banque 4 || Banque 1 || Banque 2 || Banque 3 || Banque 4 |} Avec l'entrelacement de ligne, on change de rangée dès qu'on change de banque/ligne. {|class="wikitable" |+ Adresse mémoire |- ! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 |- | Banque 1 || Banque 1 || Banque 2 || Banque 2 || Banque 3 || Banque 3 || Banque 4 || Banque 4 |} En clair, quand on a épuisé toutes les banques dans une même rangée, on passe à la rangée suivante au lieu de rester dans la même rangée. Notons que dans l'exemple précédent, on a combiné l'entrelacement de rangée et de banque, mais on aurait pu utiliser l'entrelacement de rangée seul. Mais ce n'est pas le cas le plus courant en pratique. Toujours est-il qu'en combinant entrelacement de rangée et de ligne, le découpage de l'adresse est le suivant : {|class="wikitable" |+ Adresse mémoire |- | Adresse de ligne || Numéro de rangée || Adresse de banque || Adresse de colonne (précise à l'octet) |} D'autres méthodes d'entrelacement sont possibles, mais elles sont plus complexes. Chaque contrôleur mémoire fait un peu à sa sauce. Ils permettent en général de choisir entre plusieurs entrelacements. Au minimum, ils permettent de désactiver l'entrelacement, d'activer un entrelacement de ligne, et d'autres entrelacements plus complexes, dont certains sont propriétaires et tenus secrets par le fabricant. ===L'entrelacement avec le ''dual channel''=== [[File:Dual channel slots.jpg|vignette|Slots mémoires avec ''dual channel''.]] L'usage du ''dual channel'' complique encore l'entrelacement. Là encore, il y a deux grandes solutions : avec et sans entrelacement des canaux mémoire. Rappelons le principe : deux barrettes de RAM sont accédées en parallèle. Et pour cela, il faut utiliser l'entrelacement. Typiquement, chaque barrette mémoire fournit 64 bits, ce qui fait que l'on peut accéder à 128 bits d'un seul coup, par exemple avec un accès en rafale. Une solution pour cela est de ne pas utiliser l'entrelacement. Avec deux barrettes, la première barrette regroupera la moitié haute de la RAM, la seconde barrette utilisera la moitié basse. Les possibilité d’accéder à deux données séparées de 64 bits en même temps est cependant très rare. Aussi, on préfère utiliser l'entrelacement. Pour cela, deux blocs de 64 bits sont placés dans des canaux mémoire séparés. Idem avec du triple ou quadruple canal, mais c'est alors trois ou quatre blocs de 64 bits qui sont dispersés dans des canaux consécutifs. {|class="wikitable" |+ Adresse mémoire |- ! colspan="6" | Sans entrelacement |- | Numéro de canal/barrette || Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne || Position de l'octet dans un bloc de 64 bits |- | colspan="6" | |- ! colspan="6" | Avec entrelacement |- | Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne || Numéro de canal/barrette || Position de l'octet dans un bloc de 64 bits |} Les contrôleurs de SDRAM précédents sont assez basiques et ne représentent pas les contrôleurs les plus évolués. Ils étaient utilisés sur les anciens PC, à une époque où ils étaient encore sur la carte mère du processeur. Mais de nos jours, le contrôleur mémoire est intégré au processeur. Et il incorpore de nombreuses optimisations afin de gagner en performances. Malheureusement, ces optimisations sont elles-mêmes dépendantes des optimisations intégrées dans le processeur, ce qui fait qu'on ne peut pas en parler ici. Elles demandent que le contrôleur mémoire reçoive plusieurs accès mémoire simultanés, ce qui n'a aucun sens à ce stade du cours. Aussi, nous allons les passer sous silence pour le moment. Nous les verrons dans le chapitre sur le parallélisme mémoire. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=La désambiguïsation mémoire | prevText=La désambiguïsation mémoire | next=Les processeurs superscalaires | nextText=Les processeurs superscalaires }} </noinclude> 40dfswitw2a0w6v6mwndq8nc720l64p 764657 764656 2026-04-23T14:41:05Z Mewtow 31375 /* L'entrelacement avec le dual channel */ 764657 wikitext text/x-wiki Dans ce chapitre, nous allons voir les techniques qui permettent de gérer plusieurs accès mémoire simultanés directement au niveau du cache ou de la mémoire RAM. Par plusieurs accès mémoire simultanés, vous pensez sans doute à l'usage de cache multiports, voire de mémoires RAM multiport. C'est en effet une solution, mais ce n'est clairement pas la seule. Par exemple, il est possible de pipeliner l'accès au cache, voire à la mémoire RAM. Il existe de nombreuses techniques de '''parallélisme mémoire''', et nous allons les voir dans ce chapitre. Pour donner un exemple, il est possible de pipeliner les accès mémoire. Il est possible de pipeliner l'accès au cache et/ou l'accès à la mémoire, et nous verrons comment faire dans ce chapitre. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, qu'ils aient ou non un pipeline dynamique. Par contre, pipeliner la mémoire est une technique ancienne, aujourd'hui peu utilisée. Elle était utilisée sur des très vieux ordinateurs, qui avaient un pipeline mais pas de mémoire cache. Mais de nos jours, les mémoires SDRAM et DDR ne sont pas adaptées pour ça, il est très difficile de les pipeliner correctement. Le parallélisme mémoire est utile aussi bien sur des processeurs à émission dans l'ordre que dans le désordre. Par exemple, un processeur ''in-order'' avec des lectures non-bloquantes peut e, profiter. Il peut techniquement lancer une seconde lecture, après avoir rencontré une première lecture non-bloquante. En général, le nombre de lectures consécutives est cependant limité à moins d'une dizaine. À l'opposé, les processeurs sans lectures non-bloquantes profitent pas du parallélisme mémoire. À la rigueur, ils peuvent en profiter s'ils intègrent des techniques de préchargement. Pendant que le processeur exécute une lecture/écriture, il peut précharger une autre donnée en parallèle. Inutile de dire que sans pipeline processeur, le parallélisme mémoire ne sert pas à grand-chose. ==Le ''line fill buffer'' et les techniques associées== Lors d'un défaut de cache, le processeur doit attendre que toute la ligne de cache soit chargée avant d'être utilisable. Or, la taille d'une ligne de cache est supérieure à la largeur du bus mémoire, ce qui fait qu'une ligne de cache est chargée en plusieurs fois, morceaux par morceaux, mot mémoire par mot mémoire. Le chargement peut se faire directement dans le cache, mais ce n'est pas une solution très pratique. À la place, beaucoup de processeurs ajoutent une mémoire tampon entre la RAM et le cache, appelée le '''tampon de remplissage de ligne''' (''line-fill buffer'') dans les processeurs modernes. Lors d'un défaut de cache, le processeur charge la donnée de la RAM dans le tampon de remplissage de ligne, mot mémoire par mot mémoire. Une fois plein, le tampon de remplissage de ligne est recopié dans la ligne de cache. Le tampon de remplissage de ligne contient une ligne de cache, si ce n'est que certains bits de contrôle ne sont pas présents. Le tampon de remplissage de ligne contient un bit de validité pour chaque mot mémoire de la ligne de cache, qui indiquent si le mot mémoire a été chargé. Par exemple, prenons un processeur 64 bits, qui gère donc des mots mémoire de 8 octets, avec des lignes de cache 256 octets/32 mots mémoire. Le tampon de remplissage de ligne contiendra 32 bits de validité. Si le processeur a chargé les 6 premiers mots mémoire, les 6 premiers bits de validité seront mis à 1, les autres seront encore à zéro. Il faut noter que la disponibilité de la ligne complète se détermine assez facilement en faisant un ET logique entre tous les bits de validité. Le processeur sait ainsi quand la ligne de cache est disponible entièrement et donc quand la transférer dans le cache. La même chose existe avec une hiérarchie de cache, sauf que l'on trouve une mémoire tampon entre chaque niveau de cache. Il y a un tampon de remplissage de ligne entre le cache L1 et le cache L2, entre le cache L2 et le L3, etc. Si le cache ne gère qu'un seul défaut de cache à la fois, le ''fill line buffer'' est une mémoire très simple, qui ne mémorise qu'une seule ligne de cache. Et cela vaut aussi bien pour un cache bloquant que non-bloquant. Mais sur les caches capables de gérer plusieurs défauts simultanés, le tampon de remplissage de ligne est une mémoire de type FIFO ou LIFO, capable de mémoriser plusieurs lignes de cache. Notons que le tampon de remplissage de ligne est très utile pour implémenter certaines techniques, par exemple le contournement du cache. Nous avions vu dans le chapitre sur le cache que certains accès mémoire doivent contourner le cache, pour des raisons de cohérence des caches. C'est notamment nécessaire pour accéder aux périphériques, mais c'est aussi utile pour des raisons de performances dans des cas très spécifiques. Les accès qui contournent le cache se font directement dans le tampon de remplissage de ligne : le processeur écrit ou lit les données depuis ce tampon de remplissage de ligne, sans accéder au cache. ===L'''early restart'' et le ''critical word load''=== La présence du ''line buffer'' permet une optimisation assez intéressante, qui permet de réduire la latence des défauts de cache. L'optimisation consiste à lire un mot mémoire dans le tampon de remplissage de ligne, même si la ligne de cache complète n'a pas encore été chargée. Il existe deux manières de faire cela, qui portent les noms d'''early restart'' et de ''critical word load''. La première est la version la plus simple, la seconde est plus complexe mais plus performante. Avec la technique d''''''early restart''''', la ligne de cache est chargée normalement, en partant de son premier mot mémoire. Dès que le mot mémoire lu/écrit par le processeur est copié dans le ''line fill buffer'', il est envoyé au processeur immédiatement. Illustrons le tout par un exemple, où une ligne de cache fait 16 mots mémoire. Le processeur effectue une lecture, qui lit le 5ème mot mémoire. Sans ''early restart'', le processeur doit charger les 16 mots mémoire avant de faire la lecture dans le cache. Avec ''early restart'', le processeur reçoit la donnée dès que le 5ème mot mémoire est disponible. Le processeur doit attendre que les 4 premiers mots mémoire soient chargés, puis le 5ème mot mémoire arrive et est envoyé directement au processeur, il est lu directement depuis le ''line fill buffer''. Les 11 mots mémoire suivants sont ensuite chargés dans le cache pendant que le processeur fait des calculs dans son coin. Le ''critical word load'' est une optimisation de la technique précédente où le chargement de la ligne de cache commence directement à la donnée demandée par le processeur. Pour reprendre l'exemple précédent, où le processeur demande le 5ème mot mémoire sur 16, le ''critical word load'' charge le 5ème mot mémoire en premier et l'envoie au processeur, ce qui fait qu'il est chargé très rapidement. Pas besoin d'attendre que le processeur charge les 4 mots mémoire précédents comme avec l'''early restart''. Dans le détail, le processeur charge le 5ème mot mémoire en premier, puis charge les 11 suivants, et termine par les 4 mots mémoire du début. En clair, le ''critical word load'' commence par charger le mot mémoire lu, puis les blocs suivants, avant de revenir au début du bloc pour charger les blocs restants. Ainsi, la donnée demandée par le processeur sera la première disponible. Pour cela, l'organisation du tampon de remplissage de ligne est modifiée de manière à rendre cela possible. Il a une taille égale à une ligne de cache complète, qui contient elle-même plusieurs mots mémoire. Dans le ''line-fill buffer'', chaque mot mémoire est stocké avec un tag, qui indique l'adresse du mot mémoire stocké dans le ''line-fill buffer''. Le ''line-fill buffer'' est donc un cache un peu particulier, qui fonctionne comme un cache du point de vue du processeur, comme une mémoire FIFO pour les transferts avec le cache. Ainsi, un processeur qui veut lire dans le cache après un défaut peut accéder à la donnée directement depuis le tampon de remplissage de ligne, alors que la ligne de cache n'a pas encore été totalement recopiée en mémoire. ==Les caches pipelinés== Il est possible de pipeliner l'accès au cache, ce qui demande juste de rajouter des registres au bon endroits et quelques circuits de contrôle. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, même ceux avec un pipeline dynamique. Et l'implémentation peut se faire de plusieurs manières différentes. Il faut dire que découper une mémoire cache en plusieurs étapes peut se faire de plusieurs manière. Commençons par la plus simple. Beaucoup de processeurs des années 2000 avaient une fréquence peu élevée comparée aux standards d'aujourd'hui, ce qui fait que leur cache avait bien un temps d'accès d'un cycle d'horloge. Mais l'accès au cache se faisait en deux cycles d'horloge : un cycle pour calculer une adresse et un cycle pour l'accès mémoire proprement dit. Et ces deux étapes étaient pipelinées, à savoir que deux micro-opérations mémoire peuvent s'exécuter en même temps : une dans l'étage de calcul d'adresse, une autre dans le cache. L'unité mémoire était donc pipelinée, alors que l'accès au cache ne l'était pas. Les processeurs qui implémentaient cette technique regroupent les micro-architectures K5 et K6 d'AMD, les processeurs Intel de micro-architecture P6 (Pentium 2 et 3) et quelques autres. Il est aussi possible de pipeliner l'accès au cache lui-même. Avec un cache pipeliné, l'accès au cache ne se fait pas en un seul cycle, mais en plusieurs. Cependant, on peut lancer un nouvel accès au cache à chaque cycle d'horloge, comme un processeur pipeliné lance une nouvelle instruction à chaque cycle. L'implémentation est assez simple : il suffit d'ajouter des registres dans le cache. Pour cela, on profite que le cache est composé de plusieurs composants séparés, qui échangent des données dans un ordre bien précis, d'un composant à un autre. De plus, le trajet des informations dans un cache est linéaire, ce qui les rend parfois pour l'usage d'un pipeline. ===Le ''pipelining'' des caches ''direct-mapped''=== Voici ce qui se passe avec un cache directement adressé. Pour rappel ce genre de cache est conçu en combinant une mémoire RAM, généralement une SRAM, avec quelques circuits de comparaison et un MUX. L'accès au cache se fait globalement en deux étapes : la première lit la donnée et le ''tag'' dans la SRAM, et on utilise les deux dans une seconde étape. Nous avions vu il y a quelques chapitre comment pipeliner des mémoires, dans le chapitre sur les mémoires évoluées. Et bien ces méthodes peuvent s'utiliser pour la mémoire RAM interne au cache !L'idée est d'insérer un registre entre la sortie de la RAM et la suite du cache, pour en faire un cache pipéliné, à deux étages. Le premier étage lit dans la SRAM, le second fait le reste. L'implémentation sur les caches associatifs à plusieurs voies est globalement la même à quelques détails près. [[File:Cache directement adressé pipeliné - deux étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - deux étages]] Avec le circuit précédent, il est possible d'aller plus loin, cette fois en pipelinant l'accès à la mémoire RAM interne au cache. Pour comprendre comment, rappelons qu'une mémoire SRAM est composée d'un plan mémoire et d'un décodeur. L'accès à la mémoire demande d'abord que le décodeur fasse son travail pour sélectionner la case mémoire adéquate, puis ensuite la lecture ou écriture a lieu dans cette case mémoire. L'accès se fait donc en deux étapes successives séparées, on a juste à mettre un registre entre les deux. Il suffit donc de mettre un gros registre entre le décodeur et le plan mémoire. [[File:Cache directement adressé pipeliné - trois étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - trois étages]] Et on peut aller encore plus loin en découpant le décodeur en deux circuits séparés. En effet, rappelez-vous le chapitre sur les circuits de sélection : nous avions vu qu'il est possible de créer des décodeurs en assemblant des décodeurs plus petits, contrôlés par un circuit de prédécodage. Et bien on peut encore une ajouter un registre entre ce circuit de prédécodage et les petits décodeurs. : Théoriquement, toute l'adresse est fournie d'un seul coup au cache, la quasi-totalité des processeurs présentent l'adresse complète à un cache pipeliné. Mais le Pentium 4 fait autrement. Il faut noter que les premiers étages manipulent l'indice dans la SRAM, qui est dans les bits de poids faible, alors que les étages ultérieurs manipulent le ''tag'' qui est dans les bits de poids fort. Les concepteurs du Pentium 4 ont alors décidé de présenter les bits de poids faible lors du premier cycle d'accès au cache, puis ceux de poids fort au second cycle. Pour cela, l'ALU fonctionnait à une fréquence double de celle du processeur, tout comme le cache L1. Il n'y avait pas de pipeline proprement dit, mais cela réduisait grandement la latence d'accès au cache. ===Le ''pipelining'' des caches splittés/sériels=== Il est aussi possible de pipeliner un cache dits splittés, aussi appelés à accès sériel. Pour rappel, les caches à accès sériel vérifient si il y a succès ou défaut de cache, avant d'accéder aux lignes de cache en cas de succès. Ils font donc différemment des autres caches, qui accèdent à une ligne de cache, avant de déterminer s'il y a succès ou défaut en lisant le tag de la ligne de cache. Les caches sériels disposent de deux SRAM : une pour les tags des lignes de cache et une pour les données. Ils accèdent à la SRAM pour les tags, avant d’accéder à la SRAM des données en cas de succès de cache. Vu que l'accès se fait en deux étapes, une vérification des tags suivie de la lecture/écriture des données, il est facile à pipeliner. Pipeliner le cache permet de régler le problème des accès au cache L1, et elle est tout le temps utilisé sur les processeurs modernes. Mais que faire en cas de défaut de cache ? ==Les caches non bloquants== Un '''cache bloquant''' est un cache auquel le processeur ne peut pas accéder pendant un défaut de cache. Il faut attendre que la lecture ou écriture en RAM soit terminée avant de pouvoir utiliser de nouveau le cache. Un '''cache non bloquant''' n'a pas ce problème : on peut l'utiliser pendant un défaut de cache. Les caches non bloquants permettent de démarrer une nouvelle lecture ou écriture alors qu'une autre est en cours, ce qui permet d'exécuter plusieurs lectures ou écritures en même temps. ===Les ''Miss Handling Status Registers''=== Lors d'un défaut de cache, la mémoire RAM est consultée pendant le défaut de cache, mais le cache est inutilisé. Un défaut de cache n'utilise pas le cache, ce n'est qu'un accès en mémoire RAM, sur le bus mémoire (ou un accès aux niveaux de cache inférieurs, peu importe). Le cache en lui-même est laissé libre, rien n’empêche d'y accéder, il est en réalité intrinsèquement non-bloquant. Les caches, bloquants comme non-bloquants, sont en réalité composés d'une mémoire cache proprement dite, entourée de circuits qui servent d'interface entre le processeur et le cache lui-même. Et parmi les circuits tout autour, certains gèrent l'accès au cache lors d'un défaut de cache. Ils sont regroupés sous le terme de '''''Miss Handling Architecture''''' (MHA). La différence entre un cache bloquant et un cache non-bloquant est en réalité liée à l'implémentation de la MHA. Les caches bloquants coupent volontairement l'accès au cache lors d'un défaut, car les défauts sont plus simples à gérer ainsi. Le défaut de cache rapatrie une donnée depuis la RAM, qui sera écrite dans le cache. Et il ne faut pas qu'une tentative d'accès à cette donnée ait lieu avant qu'elle ne soit chargée. Mais ce blocage est général et touche tout le cache, alors que seule une ligne de cache est concernée par le défaut de cache. L'idée derrière un cache non-bloquant est que seule la ligne de cache est bloquée, mais les autres sont accessibles. L'idée est alors de mémoriser les lignes de cache concernées par le défaut de cache, afin d'en bloquer l'accès. À chaque accès, on vérifie si la ligne de cache est déjà réservée par un défaut de cache. La lecture/écriture est alors bloquée si c'est le cas, mais elle accepte les accès sinon. Pour cela, la ''Miss Handling Architecture'' contient des registres qui mémorisent des informations sur les défauts de cache en cours. Ils portent le nom de '''''miss status handling registers''''', que j’appellerais dorénavant MSHR, qui sont aussi appelés des '''''miss buffer'''''. Le contenu des MSHR varie beaucoup suivant le processeur, mais ces derniers stockent au minimum les informations suivantes : * Le numéro de la ligne de cache dans laquelle les données sont chargées. * Un bit de validité qui indique si le MSHR est vide ou pas, qui est mis à 0 quand le défaut de cache est résolu. * Un ou plusieurs champs de lecture/écriture, qui contiennent des informations sur la lecture/écriture. ** Pour une lecture, elle contient des informations sur la destination de la donnée, à savoir qui prévenir quand le défaut de cache est terminé. C'est parfois un nom/numéro de registre (celui dans lequel charger la donnée), mais c'est souvent le numéro de l'entrée dans le ''load/store queue''. ** Pour les écritures, elle contient la donnée à écrire, ou éventuellement un numéro de ''load/store queue'' où se trouve la donnée à écrire. Il faut noter que le bus mémoire ne peut gérer qu'un seul défaut de cache à la fois. Aussi, il est intéressant de regarder ce qui se passe lorsqu'un second défaut de cache survient, pendant qu'un premier est en cours. Dans ce cas, il y a deux réponses qui correspondent à deux types de caches non-bloquants, qui portent les noms barbares de caches de type succès après défaut et défaut après défaut. Sur le premier type, il ne peut pas y avoir plusieurs défauts de cache simultanés. Dès qu'un second défaut de cache survient, le cache stoppe son activité et on ne peut plus démarrer de nouvelle lecture/écriture, tant que le premier défaut de cache n'est pas résolu. Le second type est plus souple et autorise la survenue de plusieurs défauts de cache simultanés. Du moins, jusqu'à une certaine limite, car le cache ne peut supporter qu'un nombre limité d'accès mémoires simultanés (pipelinés). ===Les accès simultanés à une même ligne de cache=== Il arrive que le processeur fasse plusieurs accès mémoire simultanés à la même ligne de cache. Si la ligne de cache en question n'a pas encore été chargée dans le cache, alors on a plusieurs défauts de cache consécutifs pour la même ligne de cache, et le cache non-bloquant doit gérer la situation. Pour la suite, il va falloir faire une petite distinction entre les défauts primaires et secondaires. Imaginons qu'un défaut de cache ait lieu et demande à charger une donnée dans la ligne de cache numéro N. Il s'agit du premier défaut impliquant cette ligne de cache précise, ce qui lui vaut le nom de '''défaut de cache primaire'''. Mais par la suite, d'autres accès mémoire à la même ligne de cache ont lieu, alors que la ligne de cache n'est pas encore disponible. Dans ce cas, il s'agit de '''défauts de cache secondaires'''. Pour l'unité d'accès mémoire, les défauts de cache primaire et secondaire sont différents (ils prennent tous une entrée dans la ''load/store queue''). Mais pour le cache, ils ne correspondent qu'à un seul accès au cache : celui qui demande de charger la ligne de cache demandée. Les défauts de cache primaire et secondaire à la même ligne de cache se voient attribuer un MSHR unique. La gestion des défauts de cache secondaires dépend du cache non-bloquant. La solution la plus simple ne permet pas les défauts de cache secondaires. Le cache ne permet pas deux défauts de cache simultanés pour la même ligne de cache. Les autres solutions le permettent, en fusionnant des accès simultanés à la même ligne de cache en un seul au niveau des MSHR. Dans tous les cas, détecter les défauts de cache secondaires sont un problème qu'il faut détecter. La ''Miss Handling Architecture'' doit détecter les défauts de cache secondaire. Pour cela, elle procède comme suit. Lors de chaque défaut de cache, la MHA récupère le numéro de la ligne de cache associé. Il vérifie alors chaque MSHR pour vérifier s'il contient le numéro en question. S'il n'y a aucune correspondance dans les MSHR, alors c'est signe que le défaut de cache est un défaut primaire. Mais s'il y en a une, alors c'est un défaut secondaire. Évidemment, cela signifie que lors d'un défaut de cache, le numéro de ligne de cache est envoyé à tous les MSHR, pour comparaison. Les MSHR sont donc regroupés dans une mémoire associative, une sorte de mini-cache, faciliter l'implémentation. Lors d'un défaut de cache primaire, l'accès à un cache non-bloquant se fait comme suit : le processeur envoie une adresse au cache, accède à celui-ci, et détecte la survenue d'un défaut de cache. Il en profite alors pour attribuer une ligne de cache dans laquelle sera chargée ce défaut. L'attribution est très simple dans le cas des caches ''direct mapped'', ou associatifs par voie, pour lesquels l'attribution se fait assez simplement. Il mémorise alors cette information dans les MSHR, après avoir vérifié que le défaut de cache n'était pas un défaut secondaire. Lors des accès ultérieurs à une adresse proche, censée être dans la même ligne de cache, le processeur va encore une fois rencontrer un défaut de cache. Il va alors déterminer le numéro de la ligne de cache associée à l'adresse, et comparer ce numéro avec les MSHRs. Si un MSHR contient ce numéro, c'est signe que le défaut de cache est un défaut secondaire. La MHA réagit alors différemment selon le processeur considéré. Une première solution n'autorise pas les défauts de cache secondaires. Si l'un d'entre eux survient, le processeur est gelé par un ''pipeline stall'', une bulle de pipeline. Une autre solution fusionne les défauts de cache secondaires avec le défaut de cache primaire : tout cela ne correspond qu'à un seul défaut de cache pour lui. ===Les MSHR simples=== Un cache non-bloquant à '''MSHR simple''' contient juste plusieurs MSHR qui mémorisent juste un numéro de ligne de cache, un bit de validité, le champ de lecture/écriture, et l'adresse exacte de lecture/écriture. Avec cette organisation, il est possible d'avoir plusieurs défauts de cache séparés, mais à la condition que chaque défaut accède à une ligne de cache différente. Deux accès simultanés à une même ligne de cache ne sont pas possibles, les défauts de cache secondaires ne sont pas autorisés. Ainsi, chaque défaut de cache se voit attribuer son propre MSHR, chacun contient un numéro de ligne de cache différent. Pour comprendre pourquoi c'est impossible de gérer les défauts secondaires, il faut regarder le champ de lecture/écriture. Si on veut effectuer plusieurs écritures consécutives à la même adresse, le MSHR n'aura pas de quoi mémoriser les deux données à écrire. Il pourra mémoriser la première donnée à écrire, pas la seconde. Même chose lors d'une lecture : le champ lecture/écriture peut mémorisr la destination de la première lecture, pas de la seconde. L'avantage est que la MHA n'a besoin que des MSHR et de quelques circuits annexes. Les autres solutions rajoutent des circuits annexes pour gérer les défauts de cache secondaires, qui utilisent beaucoup de circuits. Le cout en circuit est donc élevé, mais le gain en performance est là. Passons maintenant aux caches non-bloquants qui autorisent les défauts de cache secondaire. La solution la plus simple consiste à utiliser ===Les MSHR adressés implicitement=== Avec les '''MSHR adressés implicitement''', il est possible de fusionner plusieurs accès mémoire à une même ligne de cache, mais sous une condition très importante : ces accès lisent/écrivent des mots mémoire différents. Par exemple, imaginons qu'une ligne de cache contienne 8 mots mémoire de 64 bits. Si un premier accès mémoire lit le mot mémoire numéro 7 (dernier mot mémoire de la ligne), et le second accès le mot mémoire numéro 3, alors la fusion est possible. Mais si deux accès mémoire veulent lire/écrire le mot mémoire numéro 7, alors la fusion n'est pas possible et le processeur se bloque, un ''pipeline stall'' survient. Un MSHR adressé implicitement est un MSHR simple, qui contient naturellement un numéro de ligne de cache (tag) et un bit de validité, sauf que le champ de lecture/écriture est dupliqué. Les différents champs lecture/écriture d'un MSHR sont regroupés dans une mémoire RAM/cache qui contient autant d'entrées qu'il y a de mot mémoire dans une ligne de cache. Chaque entrée de la table est associée à un mot mémoire de la ligne de cache et stocke des informations sur celui-ci. Une entrée mémorise, au minimum : * Un champ de lecture/écriture qui contient soit la destination de la lecture, soit la donnée à écrire. * Un bit R/W permettant d'interpréter correctement le champ de lecture/écriture. * Un bit de validité pour chaque entrée de la table, qui dit si un défaut de cache antérieur accède déjà à ce mot mémoire. La fusion de deux défauts de cache est ainsi assez simple. Un défaut de cache primaire/secondaire configure l'entrée associée au mot mémoire lu/écrit. Si un défaut secondaire ultérieur lit/écrit un mot mémoire différent (dont le bit de validité de l'entrée dans la table est à zéro), pas de conflit : il configure une autre entrée, vide. Mais s'il lit/écrit un mot mémoire pour lequel l'entrée de la table est occupée, il y a conflit, le processeur est bloqué par un ''pipeline stall'', une bulle de pipeline. Avec cette organisation, le nombre de MSHR indique combien de lignes de cache peuvent être lues en même temps depuis la mémoire. Quant au nombre d'entrées par MSHR, il détermine combien d'accès mémoires qui ne se recouvrent pas peuvent avoir lieu en même temps. ===Les MSHR adressés explicitement=== Les '''MSHR adressés explicitement''' n'ont pas les contraintes des MSHR adressés implicitement. Avec eux, il est possible d'avoir plusieurs défauts de cache pointant vers la même ligne de cache, mais aussi vers le même mot mémoire. De tels défauts de cache apparaissent sur les processeurs à exécution dans le désordre, mais sont très rares, voire inexistants, sur les processeurs ''in-order''. L'idée est encore que chaque MSHR est associée à une table mémoire qui mémorise des entrées. Cette table n'est autre qu'une mémoire FIFO. Sauf que cette fois-ci, une entrée n'est pas associée à un mot mémoire. Une entrée est associée à un défaut de cache. Une entrée mémorise là encore la destination de la lecture ou la donnée de l'écriture suivi du bit R/W, ainsi que d'un bit de validité par entrée, mais aussi : la position du mot mémoire lu dans la ligne de cache. C'est cette dernière information qui n'était pas présente dans les MSHR adressés implicitement. De plus, le nombre d'entrée par MSHR n'est pas égal au nombre de mots mémoires dans une ligne de cache, mais peut être arbitrairement grand. L'avantage d'un tel cache est qu'il est capable de traité les défauts secondaires concernant un même mot mémoire, et ce, car les accès à la ligne de cache concernée se produisent dans l'ordre de génération des défauts (la table mémoire est une FIFO). ===Les MSHRs inversés=== Généralement, plus on veut supporter de défauts de cache, plus le nombre de MSHR et d'entrées augmente. Mais au-delà d'un certain nombre d'entrées et de MSHR, les MSHR adressés implicitement et explicitement ont tendance à bouffer un peu trop de circuits. Utiliser une organisation un peu moins gourmande en circuits est donc une nécessité. Cette organisation plus économe se base sur des MSHR inversés. Les MSHR inversés ne contiennent qu'une seule entrée, en quelque sorte : au lieu d’utiliser n MSHR de m entrées chacun, on va utiliser n × m MSHR inversés. La différence, c'est que plusieurs MSHR peuvent contenir un tag identique, contrairement aux MSHR adressés implicitement et explicitement. Lorsqu'un défaut de cache a lieu, chaque MSHR est vérifié. Si jamais aucun MSHR ne contient de tag identique à celui utilisé par le défaut, un MSHR vide est choisi pour stocker ce défaut, et une requête de lecture en mémoire est lancée. Dans le cas contraire, un MSHR est réservé au défaut de cache, mais la requête n'est pas lancée. Quand la donnée est disponible, les MSHR correspondant à la ligne qui vient d'être chargée vont être utilisés un par un pour résoudre les défauts de cache en attente. ===Les MSHR intégrés au cache=== Certains chercheurs ont remarqué que pendant qu'une ligne de cache est en train de subir un défaut de cache, celle-ci reste inutilisée, et son contenu est destiné à être perdu une fois le défaut de cache résolu. Ils se sont dit que, plutôt que d'utiliser des MSHR séparés, il vaudrait mieux utiliser la ligne de cache pour stocker les informations sur les défauts de cache en attente dans cette ligne de cache. Pour éviter tout problème, il faut rajouter un bit dans les bits de contrôle de la ligne de cache, qui sert à indiquer que la ligne de cache est occupée : un défaut de cache a eu lieu dans cette ligne, et elle stocke donc des informations pour résoudre les défauts de cache. ==L'entrelacement mémoire== Nous venons de voir comment une mémoire cache peut gérer plusieurs accès mémoire simultanés. Il se trouve que ce genre d'optimisation dépasse largement le cadre du cache. Les mémoires RAM ne sont pas en reste, elles aussi ! Il est possible de pipeliner l'accès à une mémoire RAM, en utilisant des techniques dites d''''entrelacement'''. Précisons cependant que cette optimisation n'est pas utilisée sur les mémoires SDRAM ou DDR modernes, du moins pas sans modifications majeures. Mais divers ordinateurs assez anciens, dont des superordinateurs, ont utilisé cette technique. La technique demande d'utiliser plusieurs mémoires séparées. Sur les anciens ordinateurs, les mémoires en question sont des chips mémoire, à savoir des circuits intégrés de DRAM. Pour faire plus général, nous allons utiliser le terme de '''banques''', ou encore de bancs mémoire. La différence est qu'il existe des mémoires multi-banque, qui regroupent plusieurs banques indépendantes dans un seul boitier, dans un seul chip mémoire. En clair, de telles mémoires regroupent plusieurs sous-mémoires dans un seul circuit intégré. Il est aussi possible d'utiliser plusieurs chips mémoire séparés, chacun ayant une banque. Reste à combiner ces banques pour former un pseudo-pipeline. ===Utiliser plusieurs banques sans entrelacement=== Sans optimisation particulière, les adresses sont réparties dans les banques comme indiqué ci-dessous. Il s'agit de ce qui s'appelle un arrangement horizontal, et nous avions vu celui-ci dans le chapiutre sur les mémoires SDRAM. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Avec cet arrangement, les bits de poids fort de l'adresse sont utilisées pour sélectionner la banque adéquate, et le reste de l'adresse est envoyé sur le bus d'adresse. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Adresse de banque !! Adresse dans la banque |- | Quelques bits de poids fort || Reste de l'adresse |} L'entrelacement change cette répartition, afin d'accélérer les accès mémoire. L'entrelacement classique vise à améliorer les accès à des adresses mémoires consécutives, mais des formes plus évoluées d'entrelacement visent à optimiser des accès mémoire arbitraires. Voyons-les dans l'ordre, du plus simple au plus complexe. ===L'entrelacement classique=== Avec l''''entrelacement classique''', des adresses consécutives sont réparties dans des banques différentes. [[File:Répartition des adresses dans une mémoire interleaved.png|centre|vignette|upright=1.5|Répartition des adresses dans une mémoire interleaved.]] L'entrelacement simple permet d'accélérer les accès mémoire qui se font à des adresses consécutives. Avec l'entrelacement, chaque accès mémoire tombe dans une banque mémoire différente, ce qui fait qu'on peut démarrer un nouvel accès mémoire à chaque cycle d'horloge. Pendant qu'une banque est occupée par un accès mémoire, on démarre le suivant dans une autre banque, et ainsi de suite. Le tout se termine soit quand on a épuisé toutes les banques libres, soit quand l'accès en rafales se termine. Pas besoin d'attendre que la mémoire ait fini sa lecture/écriture avant de démarrer la lecture/écriture suivante. [[File:Pipemining mémoire.png|centre|vignette|upright=2.5|Pipelining mémoire]] L'animation suivante illustre bien le processus, avec quatres banques. [[File:Interleaving.gif|centre|vignette|upright=2.5|Pipelining mémoire]] L'entrelacement simple utilise les bits de poids faible pour sélectionner la banque, et les bits de poids fort pour la case mémoire. [[File:Adresse mémoire d'une mémoire entrelacée.png|centre|vignette|upright=2|Adresse mémoire d'une mémoire entrelacée]] Précisons que le temps d'accès mémoire ne change pas beaucoup avec l'entrelacement. Par contre, on peut faire plus d'accès mémoire simultanés. On peut démarrer un accès mémoire par cycle d'horloge, mais l'accès en lui-même prend plusieurs cycles. Les mémoires à entrelacement ont donc un débit supérieur aux mémoires qui ne l'utilisent pas. L'entrelacement classique pourrait être utilisé pour accélérer les transferts entre mémoire RAM et cache, qui se font en blocs d'adresses consécutives. Mais de nos jours, il n'est pas utilisé dans cette optique. Il faut dire qu'il est redondant avec le mode rafale des mémoires DRAM. Les deux font globalement la même chose et on voit mal comment utiliser les deux en même temps. Le mode rafale étant plus simple à implémenter, et plus léger niveau utilisation du bus de commande mémoire, il est préféré à l'entrelacement. Par contre, l'entrelacement a eu son heure de gloire sur d'anciens processeurs qui n'avaient pas de mémoire cache, alors qu'ils disposaient d'un pipeline. L'exemple typique est celui des nombreux ''processeurs vectoriels'', que nous n'avons pas encore abordé à ce stade du cours. De tels processeurs avaient un pipeline, une unité de calcul fortement pipelinée, et parfois même de l'exécution dans le désordre et du renommage de registres ! ===L'entrelacement par décalage=== L'entrelacement simple est très utile pour les accès en rafle ou équivalents. Par contre, il existe de rares situations où il n'est pas optimal. Une de ces situations est celle des accès en enjambée (en ''stride''), où l'on accède en série à des adresses sont séparées par N mots mémoires. De tels accès surviennent quand un logiciel accède à des structures de données spécifiques, à savoir des tableaux de structures, des matrices, ou d'autres structures de données dans le genre. Un cas classique est celui du parcours d'une matrice colonne par colonne. Les matrices de nombres sont mémorisées ligne par ligne, ce qui fait que parcourir une colonne demande de passer d'une ligne à la suivante et de faire des grands sauts en mémoire RAM, mais tous espacés par la même distance. Avec l'entrelacement simple, les accès en enjambée sont moins performants que les accès à des adresses consécutives, mais cela ne fonctionne pas trop mal. Le pire cas est celui où l'on a N banques et où les données sont justement placées toutes les N adresses. Des accès consécutifs vont tous tomber dans la même banque, on ne peut plus accéder à des banques différentes en parallèle. Mais en dehors de ce cas, on voit une amélioration par rapport à la situation sans entrelacement. Pour obtenir des performances maximales pour les accès en enjambées, il faut répartir les mots mémoires dans la mémoire autrement. L'organisation idéale est la suivante. : Dans les explications qui vont suivre, la variable N représente le nombre de banques, qui sont numérotées de 0 à N-1. On commence par organiser les N premières adresses comme une mémoire entrelacée simple : l'adresse 0 correspond à la banque 0, l'adresse 1 à la banque 1, etc. Pour le bloc suivant, on décale tout d'une adresse, à savoir qu'on commence à remplir les banques à partir de la seconde, non de la première. Une fois la fin du bloc atteinte, on finit de remplir le bloc en repartant du début du bloc. Le troisième bloc d'adresse subit le même traitement, sauf qu'on commence à remplir à partir de la seconde banque. Et on poursuit l’assignation des adresses en décalant d'un cran en plus à chaque bloc. Ainsi, chaque bloc verra ses adresses décalées d'un cran en plus comparé au bloc précédent. Si jamais le décalage dépasse la fin d'un bloc, alors on reprend au début. [[File:Mémoire interleaved par décalage.png|centre|vignette|upright=1.5|Mémoire entrelacée par décalage.]] En faisant cela, on remarque que les banques situées à N adresses d'intervalle sont différentes. Dans l'exemple du dessus, nous avons ajouté un décalage de 1 à chaque nouveau bloc à remplir. Mais on aurait tout aussi bien pu prendre un décalage de 2, 3, etc. Dans tous les cas, on obtient un entrelacement par décalage. Ce décalage est appelé le pas d'entrelacement, noté P. Le calcul de l'adresse à envoyer à la banque, ainsi que la banque à sélectionner se fait en utilisant les formules suivantes : * adresse à envoyer à la banque = adresse totale / N ; * numéro de la banque = (adresse + décalage) modulo N, avec décalage = (adresse totale * P) mod N. Avec cet entrelacement par décalage, on peut prouver que la bande passante maximale est atteinte si le nombre de banques est un nombre premier. Seulement, utiliser un nombre de banques premier peut créer des trous dans la mémoire, des mots mémoires inadressables. Pour éviter cela, il faut faire en sorte que N et la taille d'une banque soient premiers entre eux : ils ne doivent pas avoir de diviseur commun. Dans ce cas, les formules se simplifient : * adresse à envoyer à la banque = adresse totale / taille de la banque ; * numéro de la banque = adresse modulo N. ===L'entrelacement pseudo-aléatoire=== Une dernière méthode de répartition consiste à répartir les adresses dans les banques de manière "pseudo-aléatoire". L'idée exacte est de faire passer l'adresse dans une '''fonction de hachage''', plus ou moins complexe. Pour rappel, une fonction de hachage prend une entrée de grande taille et fournit en sortie un résultat de petite taille. Idéalement, elles doivent donner des résultats aussi différents que possible pour des entrées similaires, histoire de simuler une sorte de pseudo-aléatoire. Ici, une adresse est transformée en un numéro de banque. Une fonction de hachage simple ne fait que permuter des bits de l'adresse pour obtenir son résultat. Elle ne fait qu'échanger des bits de place avant de couper l'adresse en deux morceaux : un pour la sélection de la banque, et un autre pour la sélection de l'adresse dans la banque. Cette permutation est fixe, et ne change pas suivant l'adresse. Des fonctions de hachage plus complexes font des XOR bit à bit entre certains bits de l'adresse. Il existe aussi d'autres techniques qui donnent le numéro de banque à partir d'un polynôme modulo N, appliqué sur l'adresse. ==Les contrôleurs mémoires SDRAM/DDR optimisés== Après avoir vu le cas des mémoires RAM, nous allons nous concentrer sur le cas particulier des mémoires SDRAM. Les mémoires SDRAM et DDR modernes sont capables de gérer plusieurs accès simultanés à la mémoire RAM. Et les contrôleurs mémoire intègrent des optimisations spécifiques afin d'en profiter au maximum. ===La mise en attente des accès mémoire=== Comme pour les mémoires caches, il existe des contrôleurs mémoire bloquants et non-bloquants. Les premiers n'acceptent qu'un seul accès mémoire à la fois, les seconds acceptent plusieurs accès mémoire simultanés. Pour simplifier, nous allons dire que le contrôleur mémoire n’exécute qu'un seul accès mémoire à la fois, et qu'ils met en attente les accès en trop. C'est une simplification assez irréaliste, vu que les mémoires modernes disposent de plusieurs banques accessibles en parallèle. Mais nous introduirons ce détail un peu plus tard. Avec un contrôleur mémoire de ce type, les accès mémoire sont mis en attente dans une mémoire FIFO, histoire de les exécuter dans leur ordre d'arrivée. Les accès mémoire quittent alors la mémoire dans leur ordre d'arrivée, les lectures sont renvoyées dans l'ordre demandé. En plus d'une mémoire FIFO pour les commandes mémoire, on trouve une mémoire FIFO pour les données à écrire. Elle permet d'accumuler plusieurs écritures, tout en permettant que la donnée adéquate soit envoyée à la RAM au bon moment. Il y a aussi une FIFO pour les données lues, qui sert au cas où le cache ne soit pas disponible quand on lui envoie la donnée lue. [[File:Module d'interface avec la mémoire.png|centre|vignette|upright=2.5|Contrôleur mémoire avec mise en attente des requêtes processeur et autres optimisations.]] Une optimisation regroupe plusieurs accès à des données consécutives en un seul accès en rafale. Le contrôleur mémoire analyse les requêtes mises en attente et détecte si certaines se font à des adresses consécutives. Si c'est le cas, il fusionne ces requêtes en une lecture/écriture en rafale. Une telle optimisation est appelée la '''combinaison de lecture''' pour les lectures, et la '''combinaison d'écriture''' pour les écritures. ===Le pipeline des mémoires SDRAM=== Les SDRAM ont une forme de pseudo-pipeline très limité. Elles sont parfois capables de démarrer une commande avant que la précédente soit terminée. Même si elle parait très limitée, cette possibilité est exploitée au mieux par la présence de plusieurs banques dans la mémoire SDRAM. Les chips mémoires de SDRAM sont composés de plusieurs banques, chacune étant une sorte de mini-mémoire miniature. Chaque banque a son propre décodeur, son propre tampon de ligne, ses propres multiplexeurs de colonne, sa logique de rafraichissement mémoire, etc. C'est comme si une SDRAM regroupait plusieurs mémoires séparées dans un même circuit intégré. Et cela permet de faire plusieurs accès mémoire en même temps, dans des banques différentes. Il est possible de lancer un accès mémoire dans deux banques en même temps, par exemple. Ou encore, on peut lancer un nouvel accès mémoire dans une banque si elle est inoccupée. La possibilité améliore grandement la performance de la SDRAM. Mais nous en reparlerons dans un chapitre ultérieur. {|class="wikitable" |+ Pipelining basique sur les SDRAM |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 |- ! Banque Numéro 1 | || || || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || || || |- ! Banque Numéro 2 | || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || |- ! Banque Numéro 3 | || || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || |} L'implémentation n'a rien de vraiment compliqué. Il suffit d'utiliser une mémoire FIFO par banque. Ainsi, les accès dans une même banque se feront en série, dans l'ordre d'arrivée, mais des accès à des banques différentes se feront dans le désordre. Cela ne pose pas de problème car les accès se font à des adresses différentes, ce qui fait qu'il n'y a pas de conflits majeurs. [[File:Gestion parallèle des banques.png|centre|vignette|upright=2.5|Gestion parallèle des banques.]] ===Le ré-ordonnancement des commandes mémoires=== Un contrôleur mémoire moderne est capable de changer l'ordre des requêtes mémoires, pour gagner en performances. En clair : il peut faire l'équivalent mémoire de l'exécution dans le désordre des processeurs haute performance. Une différence majeure est que le contrôleur mémoire ne remet pas les accès mémoire dans l'ordre avant de les envoyer au processeur. C'est le processeur qui remet en ordre les accès mémoire, dans sa ''load queue''. Pour cela, les requêtes mémoire sont "numérotées", à savoir qu'on leur attribue un identifiant binaire. Le contrôleur mémoire exécute les lectures/écritures dans le désordre, mais garde la trace de leur identifiant. Il sait dans quel ordre il exécute les accès mémoire et connait leur latence. Ce qui fait qu'il sait que la donnée lue à tel instant est associée à la requête mémoire numéro X. Pour les lectures, il renvoie la donnée lue avec l'identifiant. Le processeur sait alors à quelle lecture la donnée lue correspond et il se débrouille en interne pour gérer la situation. En clair : le processeur voit les accès mémoire dans le désordre, c'est lui qui les remet en ordre. Maintenant que ces précisions sont faites, posons cette question : quel est l'intérêt de faire les accès mémoire dans le désordre ? Accéder à des banques en parallèle est une réponse possible, mais le ré-ordonnancement est surtout utile si tous les accès mémoire atterrissent dans la même banque. Pour comprendre pourquoi, faisons un rappel rapide sur le tampon de ligne. Les mémoires SDRAM sont des mémoires à tampon de ligne. Elles font un accès mémoire en deux étapes. La première étape recopie une ligne de N * 64 bits dans un tampon interne à la SDRAM. La seconde étape sélectionne une colonne, à savoir une donnée de 64 bits, qui est soit envoyée sur le bus de données pour une lecture, soit modifiée par une écriture. [[File:Mémoire à tampon de ligne.png|centre|vignette|upright=2|Mémoire à tampon de ligne]] Les deux étapes correspondent à deux commandes séparées : une commande ACT qui précise l'adresse de la ligne, et une commande READ ou WRITE qui précise l'adresse de la colonne. [[File:Signaux RAS et CAS.png|centre|vignette|upright=2|Signaux RAS et CAS.]] Les SDRAM permettent de se passer de la première étape si des accès consécutifs se font dans la même ligne. Une fois activée, la ligne reste ouverte et on peut accéder plusieurs fois de suite dedans. On a alors juste à préciser l'adresse de colonne dedans. [[File:Sélection d'une ligne sur une mémoire FPM ou EDO.png|centre|vignette|upright=2|Sélection d'une ligne sur une mémoire FPM ou EDO.]] L'exécution dans le désordre des lectures/écritures SDRAM vise à faire en sorte que des accès consécutifs se fassent dans une même ligne. Elle marche si plusieurs accès à une même ligne ne sont pas consécutifs, et qu'il y a des accès entre les deux. Après ré-ordonnancement, ces accès mémoire deviennent consécutifs, ils sont exécutés l'un à la suite de l'autre. Pour rendre le tout plus concret, voici un exemple. Imaginez que l'on sait les 3 accès mémoire suivants : * Une lecture ligne A ; * Une écriture ligne B ; * Une lecture ligne A. Sans réordonnancent, on doit émettre deux commandes PRECHARGE : une quand on passe de la ligne A à la ligne B, et une autre pour le passage inverse. pour éviter cela, le contrôleur mémoire peut retarder l'écriture, ou au contraire avancer la seconde lecture. le résultat est alors le suivant : * Lecture ligne A ; * Lecture ligne A ; * Une écriture ligne B. On a donc deux accès consécutifs à la même ligne, suivi par une écriture dans une autre ligne. La technique marche parce que l'on a un mix adéquat de lectures et d'écritures. Mais il existe des possibilités de réorganisation autres qui ne sont pas valides. Il faut par exemple prendre garde à éviter d'intervertir lectures et écritures à une même adresse, sous peine de problèmes. Encore une fois, il faut faire attention aux dépendances mémoires. Le controleur mémoire teste les dépendances mémoires avant d'envoyer des commandes à la mémoire DDR/SDRAM. ==L'entrelacement sur les mémoires SDRAM== L''''entrelacement''' fonctionne sur les mémoires SDRAM, mais il doit être fortement modifié. Concrètement, tout ce qui a étré dit plus est inapplicable pour les SDRAM, du fait de la présence du tampon de ligne. Des adresses consécutives atterrissent déjà dans la même ligne, ce qui fait qu'on n'a pas besoin d'entrelacement au niveau d'une ligne. Et ne parlons pas de ce la présence de rangées et de canaux mémoire ! Cependant, il peut y avoir un entrelacement lié à la présence des banques SDRAM. L'entrelacement est géré juste avant le séquenceur mémoire. Le '''circuit d'entrelacement''' intervertit certains bits de l'adresse lors des accès mémoires. On trouve aussi des mémoires FIFOs pour mettre en attente les requêtes mémoire provenant du processeur. Elles permettent de recevoir plusieurs accès mémoire consécutifs. Si vous vous demandez comment plusieurs accès mémoire consécutifs peuvent avoir lieu, sachez que c'est une bonne question. Pour le moment, nous allons dire que ces accès proviennent du processeur ou de la mémoire cache, sans expliquer comment. Le contrôleur mémoire reçoit une série d'accès mémoire assez rapprochés, qu'il doit exécuter au mieux. [[File:Controleur mémoire d'une SDRAM avec entrelacement.png|centre|vignette|upright=2.5|Contrôleur mémoire d'une SDRAM avec entrelacement.]] Pour gérer l'entrelacement, le contrôleur de SDRAM prend en entrée l'adresse envoyée par le processeur, et la découpe en plusieurs champs : un pour sélectionner la banque adéquate, un autre pour la ligne, un autre pour la colonne, un autre pour la rangée. Une solution simple désactive l'entrelacement. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de banque || Adresse de ligne || Adresse de colonne |} Si cette solution fonctionne bien si les accès mémoire sont assez aléatoires, en pratique, mieux vaut utiliser l'entrelacement. ===L'entrelacement de banques/lignes=== Une première méthode d'entrelacement est l''''entrelacement de banques'''. Elle répartit deux lignes consécutives dans deux banques différentes. Pour comprendre l'idée, prenons un exemple. Imaginons une mémoire avec deux banques et 4 lignes. Imaginons qu'on parcoure/balaye la mémoire RAM en partant des adresses basses. Sans entrelacement de ligne, les accès se feront comme suit, de gauche à droite : {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Banque numéro 1 !! colspan="4" | Banque numéro 2 |- | Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 || Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 |} Avec l'entrelacement de banques, les accès se feront comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 |- | Ligne 1 || Ligne 1 || Ligne 2 || Ligne 2 || Ligne 3 || Ligne 3 || Ligne 4 || Ligne 4 |} Avec N banques, la première ligne ira dans la première banque, la seconde ligne dans la seconde, et ainsi de suite jusqu'à la énième banque qui va dans la banque numéro N. Et la ligne N+1 repart dans la première banque. L'avantage est que passer d'une ligne à la suivante est assez rapide. Au lieu de devoir fermer la première ligne et ouvrir la suivante on ouvrira directement la ligne suivante dans une banque différente, ce qui sera plus rapide. La commande PRECHARGE pourra être envoyée à la seconde banque en avance. Pour cela, l'adresse est découpée comme suit. {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne (précise à l'octet) |} ===L'entrelacement de rangées=== Il est aussi possible de faire la même chose, mais avec les rangées. Pour simplifier fortement, une rangée est simplement un chip mémoire. En réalité, les rangées ne sont pas des chip mémoire, mais un ensemble de chips mémoire regroupés ensemble histoire d'atteindre les 64 bits du bus de données. Par exemple, une rangée peut combiner 8 chips mémoire avec un bus de données de 8 bits chacun pour obtenir les 64 bits du bus de données d'une SDRAM. Mais nous passons ce détail sous silence dans les explications qui vont suivre, par souci de simplification. Pour faire comprendre l'entrelacement de rangée, le mieux est d'utiliser un exemple, le même que précédemment. Sans entrelacement de rangée, on change de chip mémoire une fois qu'on a balayé toutes ses adresses. {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Rangée numéro 1 !! colspan="4" | Rangée numéro 2 |- | Banque 1 || Banque 2 || Banque 3 || Banque 4 || Banque 1 || Banque 2 || Banque 3 || Banque 4 |} Avec l'entrelacement de ligne, on change de rangée dès qu'on change de banque/ligne. {|class="wikitable" |+ Adresse mémoire |- ! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 |- | Banque 1 || Banque 1 || Banque 2 || Banque 2 || Banque 3 || Banque 3 || Banque 4 || Banque 4 |} En clair, quand on a épuisé toutes les banques dans une même rangée, on passe à la rangée suivante au lieu de rester dans la même rangée. Notons que dans l'exemple précédent, on a combiné l'entrelacement de rangée et de banque, mais on aurait pu utiliser l'entrelacement de rangée seul. Mais ce n'est pas le cas le plus courant en pratique. Toujours est-il qu'en combinant entrelacement de rangée et de ligne, le découpage de l'adresse est le suivant : {|class="wikitable" |+ Adresse mémoire |- | Adresse de ligne || Numéro de rangée || Adresse de banque || Adresse de colonne (précise à l'octet) |} D'autres méthodes d'entrelacement sont possibles, mais elles sont plus complexes. Chaque contrôleur mémoire fait un peu à sa sauce. Ils permettent en général de choisir entre plusieurs entrelacements. Au minimum, ils permettent de désactiver l'entrelacement, d'activer un entrelacement de ligne, et d'autres entrelacements plus complexes, dont certains sont propriétaires et tenus secrets par le fabricant. ===L'entrelacement avec le ''dual channel''=== [[File:Dual channel slots.jpg|vignette|Slots mémoires avec ''dual channel''.]] L'usage du ''dual channel'' complique encore l'entrelacement. Là encore, il y a deux grandes solutions : avec et sans entrelacement des canaux mémoire. Rappelons le principe : deux barrettes de RAM sont accédées en parallèle. Et pour cela, il faut utiliser l'entrelacement. Typiquement, chaque barrette mémoire fournit 64 bits, ce qui fait que l'on peut accéder à 128 bits d'un seul coup, par exemple avec un accès en rafale. Sans ''dual channel'', la première barrette correspond à la moitié haute de la RAM, la seconde barrette correspond à la moitié basse. Avec ''dual channel'', une forme spécifique d'entrelacement est activée. Concrétement, deux blocs de 64 bits sont placés dans des canaux mémoire séparés. Idem avec du triple ou quadruple canal, mais c'est alors trois ou quatre blocs de 64 bits qui sont dispersés dans des canaux consécutifs. Le découpage de l'adresse est alors le suivant : {|class="wikitable" |+ Adresse mémoire |- ! colspan="6" | Sans entrelacement |- | Numéro de canal/barrette || Reste de l'adresse || Position de l'octet dans un bloc de 64 bits |- | colspan="6" | |- ! colspan="6" | Avec entrelacement |- | Reste de l'adresse || Numéro de canal/barrette || Position de l'octet dans un bloc de 64 bits |} Les contrôleurs de SDRAM précédents sont assez basiques et ne représentent pas les contrôleurs les plus évolués. Ils étaient utilisés sur les anciens PC, à une époque où ils étaient encore sur la carte mère du processeur. Mais de nos jours, le contrôleur mémoire est intégré au processeur. Et il incorpore de nombreuses optimisations afin de gagner en performances. Malheureusement, ces optimisations sont elles-mêmes dépendantes des optimisations intégrées dans le processeur, ce qui fait qu'on ne peut pas en parler ici. Elles demandent que le contrôleur mémoire reçoive plusieurs accès mémoire simultanés, ce qui n'a aucun sens à ce stade du cours. Aussi, nous allons les passer sous silence pour le moment. Nous les verrons dans le chapitre sur le parallélisme mémoire. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=La désambiguïsation mémoire | prevText=La désambiguïsation mémoire | next=Les processeurs superscalaires | nextText=Les processeurs superscalaires }} </noinclude> 341qeta4djajebp0gxzmlmqodqrol09 764660 764657 2026-04-23T14:55:04Z Mewtow 31375 /* L'entrelacement par décalage */ 764660 wikitext text/x-wiki Dans ce chapitre, nous allons voir les techniques qui permettent de gérer plusieurs accès mémoire simultanés directement au niveau du cache ou de la mémoire RAM. Par plusieurs accès mémoire simultanés, vous pensez sans doute à l'usage de cache multiports, voire de mémoires RAM multiport. C'est en effet une solution, mais ce n'est clairement pas la seule. Par exemple, il est possible de pipeliner l'accès au cache, voire à la mémoire RAM. Il existe de nombreuses techniques de '''parallélisme mémoire''', et nous allons les voir dans ce chapitre. Pour donner un exemple, il est possible de pipeliner les accès mémoire. Il est possible de pipeliner l'accès au cache et/ou l'accès à la mémoire, et nous verrons comment faire dans ce chapitre. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, qu'ils aient ou non un pipeline dynamique. Par contre, pipeliner la mémoire est une technique ancienne, aujourd'hui peu utilisée. Elle était utilisée sur des très vieux ordinateurs, qui avaient un pipeline mais pas de mémoire cache. Mais de nos jours, les mémoires SDRAM et DDR ne sont pas adaptées pour ça, il est très difficile de les pipeliner correctement. Le parallélisme mémoire est utile aussi bien sur des processeurs à émission dans l'ordre que dans le désordre. Par exemple, un processeur ''in-order'' avec des lectures non-bloquantes peut e, profiter. Il peut techniquement lancer une seconde lecture, après avoir rencontré une première lecture non-bloquante. En général, le nombre de lectures consécutives est cependant limité à moins d'une dizaine. À l'opposé, les processeurs sans lectures non-bloquantes profitent pas du parallélisme mémoire. À la rigueur, ils peuvent en profiter s'ils intègrent des techniques de préchargement. Pendant que le processeur exécute une lecture/écriture, il peut précharger une autre donnée en parallèle. Inutile de dire que sans pipeline processeur, le parallélisme mémoire ne sert pas à grand-chose. ==Le ''line fill buffer'' et les techniques associées== Lors d'un défaut de cache, le processeur doit attendre que toute la ligne de cache soit chargée avant d'être utilisable. Or, la taille d'une ligne de cache est supérieure à la largeur du bus mémoire, ce qui fait qu'une ligne de cache est chargée en plusieurs fois, morceaux par morceaux, mot mémoire par mot mémoire. Le chargement peut se faire directement dans le cache, mais ce n'est pas une solution très pratique. À la place, beaucoup de processeurs ajoutent une mémoire tampon entre la RAM et le cache, appelée le '''tampon de remplissage de ligne''' (''line-fill buffer'') dans les processeurs modernes. Lors d'un défaut de cache, le processeur charge la donnée de la RAM dans le tampon de remplissage de ligne, mot mémoire par mot mémoire. Une fois plein, le tampon de remplissage de ligne est recopié dans la ligne de cache. Le tampon de remplissage de ligne contient une ligne de cache, si ce n'est que certains bits de contrôle ne sont pas présents. Le tampon de remplissage de ligne contient un bit de validité pour chaque mot mémoire de la ligne de cache, qui indiquent si le mot mémoire a été chargé. Par exemple, prenons un processeur 64 bits, qui gère donc des mots mémoire de 8 octets, avec des lignes de cache 256 octets/32 mots mémoire. Le tampon de remplissage de ligne contiendra 32 bits de validité. Si le processeur a chargé les 6 premiers mots mémoire, les 6 premiers bits de validité seront mis à 1, les autres seront encore à zéro. Il faut noter que la disponibilité de la ligne complète se détermine assez facilement en faisant un ET logique entre tous les bits de validité. Le processeur sait ainsi quand la ligne de cache est disponible entièrement et donc quand la transférer dans le cache. La même chose existe avec une hiérarchie de cache, sauf que l'on trouve une mémoire tampon entre chaque niveau de cache. Il y a un tampon de remplissage de ligne entre le cache L1 et le cache L2, entre le cache L2 et le L3, etc. Si le cache ne gère qu'un seul défaut de cache à la fois, le ''fill line buffer'' est une mémoire très simple, qui ne mémorise qu'une seule ligne de cache. Et cela vaut aussi bien pour un cache bloquant que non-bloquant. Mais sur les caches capables de gérer plusieurs défauts simultanés, le tampon de remplissage de ligne est une mémoire de type FIFO ou LIFO, capable de mémoriser plusieurs lignes de cache. Notons que le tampon de remplissage de ligne est très utile pour implémenter certaines techniques, par exemple le contournement du cache. Nous avions vu dans le chapitre sur le cache que certains accès mémoire doivent contourner le cache, pour des raisons de cohérence des caches. C'est notamment nécessaire pour accéder aux périphériques, mais c'est aussi utile pour des raisons de performances dans des cas très spécifiques. Les accès qui contournent le cache se font directement dans le tampon de remplissage de ligne : le processeur écrit ou lit les données depuis ce tampon de remplissage de ligne, sans accéder au cache. ===L'''early restart'' et le ''critical word load''=== La présence du ''line buffer'' permet une optimisation assez intéressante, qui permet de réduire la latence des défauts de cache. L'optimisation consiste à lire un mot mémoire dans le tampon de remplissage de ligne, même si la ligne de cache complète n'a pas encore été chargée. Il existe deux manières de faire cela, qui portent les noms d'''early restart'' et de ''critical word load''. La première est la version la plus simple, la seconde est plus complexe mais plus performante. Avec la technique d''''''early restart''''', la ligne de cache est chargée normalement, en partant de son premier mot mémoire. Dès que le mot mémoire lu/écrit par le processeur est copié dans le ''line fill buffer'', il est envoyé au processeur immédiatement. Illustrons le tout par un exemple, où une ligne de cache fait 16 mots mémoire. Le processeur effectue une lecture, qui lit le 5ème mot mémoire. Sans ''early restart'', le processeur doit charger les 16 mots mémoire avant de faire la lecture dans le cache. Avec ''early restart'', le processeur reçoit la donnée dès que le 5ème mot mémoire est disponible. Le processeur doit attendre que les 4 premiers mots mémoire soient chargés, puis le 5ème mot mémoire arrive et est envoyé directement au processeur, il est lu directement depuis le ''line fill buffer''. Les 11 mots mémoire suivants sont ensuite chargés dans le cache pendant que le processeur fait des calculs dans son coin. Le ''critical word load'' est une optimisation de la technique précédente où le chargement de la ligne de cache commence directement à la donnée demandée par le processeur. Pour reprendre l'exemple précédent, où le processeur demande le 5ème mot mémoire sur 16, le ''critical word load'' charge le 5ème mot mémoire en premier et l'envoie au processeur, ce qui fait qu'il est chargé très rapidement. Pas besoin d'attendre que le processeur charge les 4 mots mémoire précédents comme avec l'''early restart''. Dans le détail, le processeur charge le 5ème mot mémoire en premier, puis charge les 11 suivants, et termine par les 4 mots mémoire du début. En clair, le ''critical word load'' commence par charger le mot mémoire lu, puis les blocs suivants, avant de revenir au début du bloc pour charger les blocs restants. Ainsi, la donnée demandée par le processeur sera la première disponible. Pour cela, l'organisation du tampon de remplissage de ligne est modifiée de manière à rendre cela possible. Il a une taille égale à une ligne de cache complète, qui contient elle-même plusieurs mots mémoire. Dans le ''line-fill buffer'', chaque mot mémoire est stocké avec un tag, qui indique l'adresse du mot mémoire stocké dans le ''line-fill buffer''. Le ''line-fill buffer'' est donc un cache un peu particulier, qui fonctionne comme un cache du point de vue du processeur, comme une mémoire FIFO pour les transferts avec le cache. Ainsi, un processeur qui veut lire dans le cache après un défaut peut accéder à la donnée directement depuis le tampon de remplissage de ligne, alors que la ligne de cache n'a pas encore été totalement recopiée en mémoire. ==Les caches pipelinés== Il est possible de pipeliner l'accès au cache, ce qui demande juste de rajouter des registres au bon endroits et quelques circuits de contrôle. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, même ceux avec un pipeline dynamique. Et l'implémentation peut se faire de plusieurs manières différentes. Il faut dire que découper une mémoire cache en plusieurs étapes peut se faire de plusieurs manière. Commençons par la plus simple. Beaucoup de processeurs des années 2000 avaient une fréquence peu élevée comparée aux standards d'aujourd'hui, ce qui fait que leur cache avait bien un temps d'accès d'un cycle d'horloge. Mais l'accès au cache se faisait en deux cycles d'horloge : un cycle pour calculer une adresse et un cycle pour l'accès mémoire proprement dit. Et ces deux étapes étaient pipelinées, à savoir que deux micro-opérations mémoire peuvent s'exécuter en même temps : une dans l'étage de calcul d'adresse, une autre dans le cache. L'unité mémoire était donc pipelinée, alors que l'accès au cache ne l'était pas. Les processeurs qui implémentaient cette technique regroupent les micro-architectures K5 et K6 d'AMD, les processeurs Intel de micro-architecture P6 (Pentium 2 et 3) et quelques autres. Il est aussi possible de pipeliner l'accès au cache lui-même. Avec un cache pipeliné, l'accès au cache ne se fait pas en un seul cycle, mais en plusieurs. Cependant, on peut lancer un nouvel accès au cache à chaque cycle d'horloge, comme un processeur pipeliné lance une nouvelle instruction à chaque cycle. L'implémentation est assez simple : il suffit d'ajouter des registres dans le cache. Pour cela, on profite que le cache est composé de plusieurs composants séparés, qui échangent des données dans un ordre bien précis, d'un composant à un autre. De plus, le trajet des informations dans un cache est linéaire, ce qui les rend parfois pour l'usage d'un pipeline. ===Le ''pipelining'' des caches ''direct-mapped''=== Voici ce qui se passe avec un cache directement adressé. Pour rappel ce genre de cache est conçu en combinant une mémoire RAM, généralement une SRAM, avec quelques circuits de comparaison et un MUX. L'accès au cache se fait globalement en deux étapes : la première lit la donnée et le ''tag'' dans la SRAM, et on utilise les deux dans une seconde étape. Nous avions vu il y a quelques chapitre comment pipeliner des mémoires, dans le chapitre sur les mémoires évoluées. Et bien ces méthodes peuvent s'utiliser pour la mémoire RAM interne au cache !L'idée est d'insérer un registre entre la sortie de la RAM et la suite du cache, pour en faire un cache pipéliné, à deux étages. Le premier étage lit dans la SRAM, le second fait le reste. L'implémentation sur les caches associatifs à plusieurs voies est globalement la même à quelques détails près. [[File:Cache directement adressé pipeliné - deux étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - deux étages]] Avec le circuit précédent, il est possible d'aller plus loin, cette fois en pipelinant l'accès à la mémoire RAM interne au cache. Pour comprendre comment, rappelons qu'une mémoire SRAM est composée d'un plan mémoire et d'un décodeur. L'accès à la mémoire demande d'abord que le décodeur fasse son travail pour sélectionner la case mémoire adéquate, puis ensuite la lecture ou écriture a lieu dans cette case mémoire. L'accès se fait donc en deux étapes successives séparées, on a juste à mettre un registre entre les deux. Il suffit donc de mettre un gros registre entre le décodeur et le plan mémoire. [[File:Cache directement adressé pipeliné - trois étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - trois étages]] Et on peut aller encore plus loin en découpant le décodeur en deux circuits séparés. En effet, rappelez-vous le chapitre sur les circuits de sélection : nous avions vu qu'il est possible de créer des décodeurs en assemblant des décodeurs plus petits, contrôlés par un circuit de prédécodage. Et bien on peut encore une ajouter un registre entre ce circuit de prédécodage et les petits décodeurs. : Théoriquement, toute l'adresse est fournie d'un seul coup au cache, la quasi-totalité des processeurs présentent l'adresse complète à un cache pipeliné. Mais le Pentium 4 fait autrement. Il faut noter que les premiers étages manipulent l'indice dans la SRAM, qui est dans les bits de poids faible, alors que les étages ultérieurs manipulent le ''tag'' qui est dans les bits de poids fort. Les concepteurs du Pentium 4 ont alors décidé de présenter les bits de poids faible lors du premier cycle d'accès au cache, puis ceux de poids fort au second cycle. Pour cela, l'ALU fonctionnait à une fréquence double de celle du processeur, tout comme le cache L1. Il n'y avait pas de pipeline proprement dit, mais cela réduisait grandement la latence d'accès au cache. ===Le ''pipelining'' des caches splittés/sériels=== Il est aussi possible de pipeliner un cache dits splittés, aussi appelés à accès sériel. Pour rappel, les caches à accès sériel vérifient si il y a succès ou défaut de cache, avant d'accéder aux lignes de cache en cas de succès. Ils font donc différemment des autres caches, qui accèdent à une ligne de cache, avant de déterminer s'il y a succès ou défaut en lisant le tag de la ligne de cache. Les caches sériels disposent de deux SRAM : une pour les tags des lignes de cache et une pour les données. Ils accèdent à la SRAM pour les tags, avant d’accéder à la SRAM des données en cas de succès de cache. Vu que l'accès se fait en deux étapes, une vérification des tags suivie de la lecture/écriture des données, il est facile à pipeliner. Pipeliner le cache permet de régler le problème des accès au cache L1, et elle est tout le temps utilisé sur les processeurs modernes. Mais que faire en cas de défaut de cache ? ==Les caches non bloquants== Un '''cache bloquant''' est un cache auquel le processeur ne peut pas accéder pendant un défaut de cache. Il faut attendre que la lecture ou écriture en RAM soit terminée avant de pouvoir utiliser de nouveau le cache. Un '''cache non bloquant''' n'a pas ce problème : on peut l'utiliser pendant un défaut de cache. Les caches non bloquants permettent de démarrer une nouvelle lecture ou écriture alors qu'une autre est en cours, ce qui permet d'exécuter plusieurs lectures ou écritures en même temps. ===Les ''Miss Handling Status Registers''=== Lors d'un défaut de cache, la mémoire RAM est consultée pendant le défaut de cache, mais le cache est inutilisé. Un défaut de cache n'utilise pas le cache, ce n'est qu'un accès en mémoire RAM, sur le bus mémoire (ou un accès aux niveaux de cache inférieurs, peu importe). Le cache en lui-même est laissé libre, rien n’empêche d'y accéder, il est en réalité intrinsèquement non-bloquant. Les caches, bloquants comme non-bloquants, sont en réalité composés d'une mémoire cache proprement dite, entourée de circuits qui servent d'interface entre le processeur et le cache lui-même. Et parmi les circuits tout autour, certains gèrent l'accès au cache lors d'un défaut de cache. Ils sont regroupés sous le terme de '''''Miss Handling Architecture''''' (MHA). La différence entre un cache bloquant et un cache non-bloquant est en réalité liée à l'implémentation de la MHA. Les caches bloquants coupent volontairement l'accès au cache lors d'un défaut, car les défauts sont plus simples à gérer ainsi. Le défaut de cache rapatrie une donnée depuis la RAM, qui sera écrite dans le cache. Et il ne faut pas qu'une tentative d'accès à cette donnée ait lieu avant qu'elle ne soit chargée. Mais ce blocage est général et touche tout le cache, alors que seule une ligne de cache est concernée par le défaut de cache. L'idée derrière un cache non-bloquant est que seule la ligne de cache est bloquée, mais les autres sont accessibles. L'idée est alors de mémoriser les lignes de cache concernées par le défaut de cache, afin d'en bloquer l'accès. À chaque accès, on vérifie si la ligne de cache est déjà réservée par un défaut de cache. La lecture/écriture est alors bloquée si c'est le cas, mais elle accepte les accès sinon. Pour cela, la ''Miss Handling Architecture'' contient des registres qui mémorisent des informations sur les défauts de cache en cours. Ils portent le nom de '''''miss status handling registers''''', que j’appellerais dorénavant MSHR, qui sont aussi appelés des '''''miss buffer'''''. Le contenu des MSHR varie beaucoup suivant le processeur, mais ces derniers stockent au minimum les informations suivantes : * Le numéro de la ligne de cache dans laquelle les données sont chargées. * Un bit de validité qui indique si le MSHR est vide ou pas, qui est mis à 0 quand le défaut de cache est résolu. * Un ou plusieurs champs de lecture/écriture, qui contiennent des informations sur la lecture/écriture. ** Pour une lecture, elle contient des informations sur la destination de la donnée, à savoir qui prévenir quand le défaut de cache est terminé. C'est parfois un nom/numéro de registre (celui dans lequel charger la donnée), mais c'est souvent le numéro de l'entrée dans le ''load/store queue''. ** Pour les écritures, elle contient la donnée à écrire, ou éventuellement un numéro de ''load/store queue'' où se trouve la donnée à écrire. Il faut noter que le bus mémoire ne peut gérer qu'un seul défaut de cache à la fois. Aussi, il est intéressant de regarder ce qui se passe lorsqu'un second défaut de cache survient, pendant qu'un premier est en cours. Dans ce cas, il y a deux réponses qui correspondent à deux types de caches non-bloquants, qui portent les noms barbares de caches de type succès après défaut et défaut après défaut. Sur le premier type, il ne peut pas y avoir plusieurs défauts de cache simultanés. Dès qu'un second défaut de cache survient, le cache stoppe son activité et on ne peut plus démarrer de nouvelle lecture/écriture, tant que le premier défaut de cache n'est pas résolu. Le second type est plus souple et autorise la survenue de plusieurs défauts de cache simultanés. Du moins, jusqu'à une certaine limite, car le cache ne peut supporter qu'un nombre limité d'accès mémoires simultanés (pipelinés). ===Les accès simultanés à une même ligne de cache=== Il arrive que le processeur fasse plusieurs accès mémoire simultanés à la même ligne de cache. Si la ligne de cache en question n'a pas encore été chargée dans le cache, alors on a plusieurs défauts de cache consécutifs pour la même ligne de cache, et le cache non-bloquant doit gérer la situation. Pour la suite, il va falloir faire une petite distinction entre les défauts primaires et secondaires. Imaginons qu'un défaut de cache ait lieu et demande à charger une donnée dans la ligne de cache numéro N. Il s'agit du premier défaut impliquant cette ligne de cache précise, ce qui lui vaut le nom de '''défaut de cache primaire'''. Mais par la suite, d'autres accès mémoire à la même ligne de cache ont lieu, alors que la ligne de cache n'est pas encore disponible. Dans ce cas, il s'agit de '''défauts de cache secondaires'''. Pour l'unité d'accès mémoire, les défauts de cache primaire et secondaire sont différents (ils prennent tous une entrée dans la ''load/store queue''). Mais pour le cache, ils ne correspondent qu'à un seul accès au cache : celui qui demande de charger la ligne de cache demandée. Les défauts de cache primaire et secondaire à la même ligne de cache se voient attribuer un MSHR unique. La gestion des défauts de cache secondaires dépend du cache non-bloquant. La solution la plus simple ne permet pas les défauts de cache secondaires. Le cache ne permet pas deux défauts de cache simultanés pour la même ligne de cache. Les autres solutions le permettent, en fusionnant des accès simultanés à la même ligne de cache en un seul au niveau des MSHR. Dans tous les cas, détecter les défauts de cache secondaires sont un problème qu'il faut détecter. La ''Miss Handling Architecture'' doit détecter les défauts de cache secondaire. Pour cela, elle procède comme suit. Lors de chaque défaut de cache, la MHA récupère le numéro de la ligne de cache associé. Il vérifie alors chaque MSHR pour vérifier s'il contient le numéro en question. S'il n'y a aucune correspondance dans les MSHR, alors c'est signe que le défaut de cache est un défaut primaire. Mais s'il y en a une, alors c'est un défaut secondaire. Évidemment, cela signifie que lors d'un défaut de cache, le numéro de ligne de cache est envoyé à tous les MSHR, pour comparaison. Les MSHR sont donc regroupés dans une mémoire associative, une sorte de mini-cache, faciliter l'implémentation. Lors d'un défaut de cache primaire, l'accès à un cache non-bloquant se fait comme suit : le processeur envoie une adresse au cache, accède à celui-ci, et détecte la survenue d'un défaut de cache. Il en profite alors pour attribuer une ligne de cache dans laquelle sera chargée ce défaut. L'attribution est très simple dans le cas des caches ''direct mapped'', ou associatifs par voie, pour lesquels l'attribution se fait assez simplement. Il mémorise alors cette information dans les MSHR, après avoir vérifié que le défaut de cache n'était pas un défaut secondaire. Lors des accès ultérieurs à une adresse proche, censée être dans la même ligne de cache, le processeur va encore une fois rencontrer un défaut de cache. Il va alors déterminer le numéro de la ligne de cache associée à l'adresse, et comparer ce numéro avec les MSHRs. Si un MSHR contient ce numéro, c'est signe que le défaut de cache est un défaut secondaire. La MHA réagit alors différemment selon le processeur considéré. Une première solution n'autorise pas les défauts de cache secondaires. Si l'un d'entre eux survient, le processeur est gelé par un ''pipeline stall'', une bulle de pipeline. Une autre solution fusionne les défauts de cache secondaires avec le défaut de cache primaire : tout cela ne correspond qu'à un seul défaut de cache pour lui. ===Les MSHR simples=== Un cache non-bloquant à '''MSHR simple''' contient juste plusieurs MSHR qui mémorisent juste un numéro de ligne de cache, un bit de validité, le champ de lecture/écriture, et l'adresse exacte de lecture/écriture. Avec cette organisation, il est possible d'avoir plusieurs défauts de cache séparés, mais à la condition que chaque défaut accède à une ligne de cache différente. Deux accès simultanés à une même ligne de cache ne sont pas possibles, les défauts de cache secondaires ne sont pas autorisés. Ainsi, chaque défaut de cache se voit attribuer son propre MSHR, chacun contient un numéro de ligne de cache différent. Pour comprendre pourquoi c'est impossible de gérer les défauts secondaires, il faut regarder le champ de lecture/écriture. Si on veut effectuer plusieurs écritures consécutives à la même adresse, le MSHR n'aura pas de quoi mémoriser les deux données à écrire. Il pourra mémoriser la première donnée à écrire, pas la seconde. Même chose lors d'une lecture : le champ lecture/écriture peut mémorisr la destination de la première lecture, pas de la seconde. L'avantage est que la MHA n'a besoin que des MSHR et de quelques circuits annexes. Les autres solutions rajoutent des circuits annexes pour gérer les défauts de cache secondaires, qui utilisent beaucoup de circuits. Le cout en circuit est donc élevé, mais le gain en performance est là. Passons maintenant aux caches non-bloquants qui autorisent les défauts de cache secondaire. La solution la plus simple consiste à utiliser ===Les MSHR adressés implicitement=== Avec les '''MSHR adressés implicitement''', il est possible de fusionner plusieurs accès mémoire à une même ligne de cache, mais sous une condition très importante : ces accès lisent/écrivent des mots mémoire différents. Par exemple, imaginons qu'une ligne de cache contienne 8 mots mémoire de 64 bits. Si un premier accès mémoire lit le mot mémoire numéro 7 (dernier mot mémoire de la ligne), et le second accès le mot mémoire numéro 3, alors la fusion est possible. Mais si deux accès mémoire veulent lire/écrire le mot mémoire numéro 7, alors la fusion n'est pas possible et le processeur se bloque, un ''pipeline stall'' survient. Un MSHR adressé implicitement est un MSHR simple, qui contient naturellement un numéro de ligne de cache (tag) et un bit de validité, sauf que le champ de lecture/écriture est dupliqué. Les différents champs lecture/écriture d'un MSHR sont regroupés dans une mémoire RAM/cache qui contient autant d'entrées qu'il y a de mot mémoire dans une ligne de cache. Chaque entrée de la table est associée à un mot mémoire de la ligne de cache et stocke des informations sur celui-ci. Une entrée mémorise, au minimum : * Un champ de lecture/écriture qui contient soit la destination de la lecture, soit la donnée à écrire. * Un bit R/W permettant d'interpréter correctement le champ de lecture/écriture. * Un bit de validité pour chaque entrée de la table, qui dit si un défaut de cache antérieur accède déjà à ce mot mémoire. La fusion de deux défauts de cache est ainsi assez simple. Un défaut de cache primaire/secondaire configure l'entrée associée au mot mémoire lu/écrit. Si un défaut secondaire ultérieur lit/écrit un mot mémoire différent (dont le bit de validité de l'entrée dans la table est à zéro), pas de conflit : il configure une autre entrée, vide. Mais s'il lit/écrit un mot mémoire pour lequel l'entrée de la table est occupée, il y a conflit, le processeur est bloqué par un ''pipeline stall'', une bulle de pipeline. Avec cette organisation, le nombre de MSHR indique combien de lignes de cache peuvent être lues en même temps depuis la mémoire. Quant au nombre d'entrées par MSHR, il détermine combien d'accès mémoires qui ne se recouvrent pas peuvent avoir lieu en même temps. ===Les MSHR adressés explicitement=== Les '''MSHR adressés explicitement''' n'ont pas les contraintes des MSHR adressés implicitement. Avec eux, il est possible d'avoir plusieurs défauts de cache pointant vers la même ligne de cache, mais aussi vers le même mot mémoire. De tels défauts de cache apparaissent sur les processeurs à exécution dans le désordre, mais sont très rares, voire inexistants, sur les processeurs ''in-order''. L'idée est encore que chaque MSHR est associée à une table mémoire qui mémorise des entrées. Cette table n'est autre qu'une mémoire FIFO. Sauf que cette fois-ci, une entrée n'est pas associée à un mot mémoire. Une entrée est associée à un défaut de cache. Une entrée mémorise là encore la destination de la lecture ou la donnée de l'écriture suivi du bit R/W, ainsi que d'un bit de validité par entrée, mais aussi : la position du mot mémoire lu dans la ligne de cache. C'est cette dernière information qui n'était pas présente dans les MSHR adressés implicitement. De plus, le nombre d'entrée par MSHR n'est pas égal au nombre de mots mémoires dans une ligne de cache, mais peut être arbitrairement grand. L'avantage d'un tel cache est qu'il est capable de traité les défauts secondaires concernant un même mot mémoire, et ce, car les accès à la ligne de cache concernée se produisent dans l'ordre de génération des défauts (la table mémoire est une FIFO). ===Les MSHRs inversés=== Généralement, plus on veut supporter de défauts de cache, plus le nombre de MSHR et d'entrées augmente. Mais au-delà d'un certain nombre d'entrées et de MSHR, les MSHR adressés implicitement et explicitement ont tendance à bouffer un peu trop de circuits. Utiliser une organisation un peu moins gourmande en circuits est donc une nécessité. Cette organisation plus économe se base sur des MSHR inversés. Les MSHR inversés ne contiennent qu'une seule entrée, en quelque sorte : au lieu d’utiliser n MSHR de m entrées chacun, on va utiliser n × m MSHR inversés. La différence, c'est que plusieurs MSHR peuvent contenir un tag identique, contrairement aux MSHR adressés implicitement et explicitement. Lorsqu'un défaut de cache a lieu, chaque MSHR est vérifié. Si jamais aucun MSHR ne contient de tag identique à celui utilisé par le défaut, un MSHR vide est choisi pour stocker ce défaut, et une requête de lecture en mémoire est lancée. Dans le cas contraire, un MSHR est réservé au défaut de cache, mais la requête n'est pas lancée. Quand la donnée est disponible, les MSHR correspondant à la ligne qui vient d'être chargée vont être utilisés un par un pour résoudre les défauts de cache en attente. ===Les MSHR intégrés au cache=== Certains chercheurs ont remarqué que pendant qu'une ligne de cache est en train de subir un défaut de cache, celle-ci reste inutilisée, et son contenu est destiné à être perdu une fois le défaut de cache résolu. Ils se sont dit que, plutôt que d'utiliser des MSHR séparés, il vaudrait mieux utiliser la ligne de cache pour stocker les informations sur les défauts de cache en attente dans cette ligne de cache. Pour éviter tout problème, il faut rajouter un bit dans les bits de contrôle de la ligne de cache, qui sert à indiquer que la ligne de cache est occupée : un défaut de cache a eu lieu dans cette ligne, et elle stocke donc des informations pour résoudre les défauts de cache. ==L'entrelacement mémoire== Nous venons de voir comment une mémoire cache peut gérer plusieurs accès mémoire simultanés. Il se trouve que ce genre d'optimisation dépasse largement le cadre du cache. Les mémoires RAM ne sont pas en reste, elles aussi ! Il est possible de pipeliner l'accès à une mémoire RAM, en utilisant des techniques dites d''''entrelacement'''. Précisons cependant que cette optimisation n'est pas utilisée sur les mémoires SDRAM ou DDR modernes, du moins pas sans modifications majeures. Mais divers ordinateurs assez anciens, dont des superordinateurs, ont utilisé cette technique. La technique demande d'utiliser plusieurs mémoires séparées. Sur les anciens ordinateurs, les mémoires en question sont des chips mémoire, à savoir des circuits intégrés de DRAM. Pour faire plus général, nous allons utiliser le terme de '''banques''', ou encore de bancs mémoire. La différence est qu'il existe des mémoires multi-banque, qui regroupent plusieurs banques indépendantes dans un seul boitier, dans un seul chip mémoire. En clair, de telles mémoires regroupent plusieurs sous-mémoires dans un seul circuit intégré. Il est aussi possible d'utiliser plusieurs chips mémoire séparés, chacun ayant une banque. Reste à combiner ces banques pour former un pseudo-pipeline. ===Utiliser plusieurs banques sans entrelacement=== Sans optimisation particulière, les adresses sont réparties dans les banques comme indiqué ci-dessous. Il s'agit de ce qui s'appelle un arrangement horizontal, et nous avions vu celui-ci dans le chapiutre sur les mémoires SDRAM. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Avec cet arrangement, les bits de poids fort de l'adresse sont utilisées pour sélectionner la banque adéquate, et le reste de l'adresse est envoyé sur le bus d'adresse. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Adresse de banque !! Adresse dans la banque |- | Quelques bits de poids fort || Reste de l'adresse |} L'entrelacement change cette répartition, afin d'accélérer les accès mémoire. L'entrelacement classique vise à améliorer les accès à des adresses mémoires consécutives, mais des formes plus évoluées d'entrelacement visent à optimiser des accès mémoire arbitraires. Voyons-les dans l'ordre, du plus simple au plus complexe. ===L'entrelacement classique=== Avec l''''entrelacement classique''', des adresses consécutives sont réparties dans des banques différentes. [[File:Répartition des adresses dans une mémoire interleaved.png|centre|vignette|upright=1.5|Répartition des adresses dans une mémoire interleaved.]] L'entrelacement simple permet d'accélérer les accès mémoire qui se font à des adresses consécutives. Avec l'entrelacement, chaque accès mémoire tombe dans une banque mémoire différente, ce qui fait qu'on peut démarrer un nouvel accès mémoire à chaque cycle d'horloge. Pendant qu'une banque est occupée par un accès mémoire, on démarre le suivant dans une autre banque, et ainsi de suite. Le tout se termine soit quand on a épuisé toutes les banques libres, soit quand l'accès en rafales se termine. Pas besoin d'attendre que la mémoire ait fini sa lecture/écriture avant de démarrer la lecture/écriture suivante. [[File:Pipemining mémoire.png|centre|vignette|upright=2.5|Pipelining mémoire]] L'animation suivante illustre bien le processus, avec quatres banques. [[File:Interleaving.gif|centre|vignette|upright=2.5|Pipelining mémoire]] L'entrelacement simple utilise les bits de poids faible pour sélectionner la banque, et les bits de poids fort pour la case mémoire. [[File:Adresse mémoire d'une mémoire entrelacée.png|centre|vignette|upright=2|Adresse mémoire d'une mémoire entrelacée]] Précisons que le temps d'accès mémoire ne change pas beaucoup avec l'entrelacement. Par contre, on peut faire plus d'accès mémoire simultanés. On peut démarrer un accès mémoire par cycle d'horloge, mais l'accès en lui-même prend plusieurs cycles. Les mémoires à entrelacement ont donc un débit supérieur aux mémoires qui ne l'utilisent pas. L'entrelacement classique pourrait être utilisé pour accélérer les transferts entre mémoire RAM et cache, qui se font en blocs d'adresses consécutives. Mais de nos jours, il n'est pas utilisé dans cette optique. Il faut dire qu'il est redondant avec le mode rafale des mémoires DRAM. Les deux font globalement la même chose et on voit mal comment utiliser les deux en même temps. Le mode rafale étant plus simple à implémenter, et plus léger niveau utilisation du bus de commande mémoire, il est préféré à l'entrelacement. Par contre, l'entrelacement a eu son heure de gloire sur d'anciens processeurs qui n'avaient pas de mémoire cache, alors qu'ils disposaient d'un pipeline. L'exemple typique est celui des nombreux ''processeurs vectoriels'', que nous n'avons pas encore abordé à ce stade du cours. De tels processeurs avaient un pipeline, une unité de calcul fortement pipelinée, et parfois même de l'exécution dans le désordre et du renommage de registres ! ===L'entrelacement par décalage=== L'entrelacement simple est très utile pour les accès en rafle ou équivalents. Par contre, il existe de rares situations où il n'est pas optimal. Une de ces situations est celle des accès en enjambée (en ''stride''), où l'on accède en série à des adresses sont séparées par N mots mémoires. [[File:Accès par enjambées.png|centre|vignette|upright=2|Accès par enjambées.]] De tels accès surviennent quand un logiciel accède à des structures de données spécifiques, à savoir des tableaux de structures, des matrices, ou d'autres structures de données dans le genre. Un cas classique est celui du parcours d'une matrice colonne par colonne. Les matrices de nombres sont mémorisées ligne par ligne, ce qui fait que parcourir une colonne demande de passer d'une ligne à la suivante et de faire des grands sauts en mémoire RAM, mais tous espacés par la même distance. Avec l'entrelacement simple, les accès en enjambée sont moins performants que les accès à des adresses consécutives, mais cela ne fonctionne pas trop mal. Le pire cas est celui où l'on a N banques et où les données sont justement placées toutes les N adresses. Des accès consécutifs vont tous tomber dans la même banque, on ne peut plus accéder à des banques différentes en parallèle. Mais en dehors de ce cas, on voit une amélioration par rapport à la situation sans entrelacement. Pour obtenir des performances maximales pour les accès en enjambées, il faut répartir les mots mémoires dans la mémoire autrement. L'organisation idéale est la suivante. : Dans les explications qui vont suivre, la variable N représente le nombre de banques, qui sont numérotées de 0 à N-1. On commence par organiser les N premières adresses comme une mémoire entrelacée simple : l'adresse 0 correspond à la banque 0, l'adresse 1 à la banque 1, etc. Pour le bloc suivant, on décale tout d'une adresse, à savoir qu'on commence à remplir les banques à partir de la seconde, non de la première. Une fois la fin du bloc atteinte, on finit de remplir le bloc en repartant du début du bloc. Le troisième bloc d'adresse subit le même traitement, sauf qu'on commence à remplir à partir de la seconde banque. Et on poursuit l’assignation des adresses en décalant d'un cran en plus à chaque bloc. Ainsi, chaque bloc verra ses adresses décalées d'un cran en plus comparé au bloc précédent. Si jamais le décalage dépasse la fin d'un bloc, alors on reprend au début. [[File:Mémoire interleaved par décalage.png|centre|vignette|upright=1.5|Mémoire entrelacée par décalage.]] En faisant cela, on remarque que les banques situées à N adresses d'intervalle sont différentes. Dans l'exemple du dessus, nous avons ajouté un décalage de 1 à chaque nouveau bloc à remplir. Mais on aurait tout aussi bien pu prendre un décalage de 2, 3, etc. Dans tous les cas, on obtient un entrelacement par décalage. Ce décalage est appelé le pas d'entrelacement, noté P. Le calcul de l'adresse à envoyer à la banque, ainsi que la banque à sélectionner se fait en utilisant les formules suivantes : * adresse à envoyer à la banque = adresse totale / N ; * numéro de la banque = (adresse + décalage) modulo N, avec décalage = (adresse totale * P) mod N. Avec cet entrelacement par décalage, on peut prouver que la bande passante maximale est atteinte si le nombre de banques est un nombre premier. Seulement, utiliser un nombre de banques premier peut créer des trous dans la mémoire, des mots mémoires inadressables. Pour éviter cela, il faut faire en sorte que N et la taille d'une banque soient premiers entre eux : ils ne doivent pas avoir de diviseur commun. Dans ce cas, les formules se simplifient : * adresse à envoyer à la banque = adresse totale / taille de la banque ; * numéro de la banque = adresse modulo N. ===L'entrelacement pseudo-aléatoire=== Une dernière méthode de répartition consiste à répartir les adresses dans les banques de manière "pseudo-aléatoire". L'idée exacte est de faire passer l'adresse dans une '''fonction de hachage''', plus ou moins complexe. Pour rappel, une fonction de hachage prend une entrée de grande taille et fournit en sortie un résultat de petite taille. Idéalement, elles doivent donner des résultats aussi différents que possible pour des entrées similaires, histoire de simuler une sorte de pseudo-aléatoire. Ici, une adresse est transformée en un numéro de banque. Une fonction de hachage simple ne fait que permuter des bits de l'adresse pour obtenir son résultat. Elle ne fait qu'échanger des bits de place avant de couper l'adresse en deux morceaux : un pour la sélection de la banque, et un autre pour la sélection de l'adresse dans la banque. Cette permutation est fixe, et ne change pas suivant l'adresse. Des fonctions de hachage plus complexes font des XOR bit à bit entre certains bits de l'adresse. Il existe aussi d'autres techniques qui donnent le numéro de banque à partir d'un polynôme modulo N, appliqué sur l'adresse. ==Les contrôleurs mémoires SDRAM/DDR optimisés== Après avoir vu le cas des mémoires RAM, nous allons nous concentrer sur le cas particulier des mémoires SDRAM. Les mémoires SDRAM et DDR modernes sont capables de gérer plusieurs accès simultanés à la mémoire RAM. Et les contrôleurs mémoire intègrent des optimisations spécifiques afin d'en profiter au maximum. ===La mise en attente des accès mémoire=== Comme pour les mémoires caches, il existe des contrôleurs mémoire bloquants et non-bloquants. Les premiers n'acceptent qu'un seul accès mémoire à la fois, les seconds acceptent plusieurs accès mémoire simultanés. Pour simplifier, nous allons dire que le contrôleur mémoire n’exécute qu'un seul accès mémoire à la fois, et qu'ils met en attente les accès en trop. C'est une simplification assez irréaliste, vu que les mémoires modernes disposent de plusieurs banques accessibles en parallèle. Mais nous introduirons ce détail un peu plus tard. Avec un contrôleur mémoire de ce type, les accès mémoire sont mis en attente dans une mémoire FIFO, histoire de les exécuter dans leur ordre d'arrivée. Les accès mémoire quittent alors la mémoire dans leur ordre d'arrivée, les lectures sont renvoyées dans l'ordre demandé. En plus d'une mémoire FIFO pour les commandes mémoire, on trouve une mémoire FIFO pour les données à écrire. Elle permet d'accumuler plusieurs écritures, tout en permettant que la donnée adéquate soit envoyée à la RAM au bon moment. Il y a aussi une FIFO pour les données lues, qui sert au cas où le cache ne soit pas disponible quand on lui envoie la donnée lue. [[File:Module d'interface avec la mémoire.png|centre|vignette|upright=2.5|Contrôleur mémoire avec mise en attente des requêtes processeur et autres optimisations.]] Une optimisation regroupe plusieurs accès à des données consécutives en un seul accès en rafale. Le contrôleur mémoire analyse les requêtes mises en attente et détecte si certaines se font à des adresses consécutives. Si c'est le cas, il fusionne ces requêtes en une lecture/écriture en rafale. Une telle optimisation est appelée la '''combinaison de lecture''' pour les lectures, et la '''combinaison d'écriture''' pour les écritures. ===Le pipeline des mémoires SDRAM=== Les SDRAM ont une forme de pseudo-pipeline très limité. Elles sont parfois capables de démarrer une commande avant que la précédente soit terminée. Même si elle parait très limitée, cette possibilité est exploitée au mieux par la présence de plusieurs banques dans la mémoire SDRAM. Les chips mémoires de SDRAM sont composés de plusieurs banques, chacune étant une sorte de mini-mémoire miniature. Chaque banque a son propre décodeur, son propre tampon de ligne, ses propres multiplexeurs de colonne, sa logique de rafraichissement mémoire, etc. C'est comme si une SDRAM regroupait plusieurs mémoires séparées dans un même circuit intégré. Et cela permet de faire plusieurs accès mémoire en même temps, dans des banques différentes. Il est possible de lancer un accès mémoire dans deux banques en même temps, par exemple. Ou encore, on peut lancer un nouvel accès mémoire dans une banque si elle est inoccupée. La possibilité améliore grandement la performance de la SDRAM. Mais nous en reparlerons dans un chapitre ultérieur. {|class="wikitable" |+ Pipelining basique sur les SDRAM |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 |- ! Banque Numéro 1 | || || || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || || || |- ! Banque Numéro 2 | || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || |- ! Banque Numéro 3 | || || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || |} L'implémentation n'a rien de vraiment compliqué. Il suffit d'utiliser une mémoire FIFO par banque. Ainsi, les accès dans une même banque se feront en série, dans l'ordre d'arrivée, mais des accès à des banques différentes se feront dans le désordre. Cela ne pose pas de problème car les accès se font à des adresses différentes, ce qui fait qu'il n'y a pas de conflits majeurs. [[File:Gestion parallèle des banques.png|centre|vignette|upright=2.5|Gestion parallèle des banques.]] ===Le ré-ordonnancement des commandes mémoires=== Un contrôleur mémoire moderne est capable de changer l'ordre des requêtes mémoires, pour gagner en performances. En clair : il peut faire l'équivalent mémoire de l'exécution dans le désordre des processeurs haute performance. Une différence majeure est que le contrôleur mémoire ne remet pas les accès mémoire dans l'ordre avant de les envoyer au processeur. C'est le processeur qui remet en ordre les accès mémoire, dans sa ''load queue''. Pour cela, les requêtes mémoire sont "numérotées", à savoir qu'on leur attribue un identifiant binaire. Le contrôleur mémoire exécute les lectures/écritures dans le désordre, mais garde la trace de leur identifiant. Il sait dans quel ordre il exécute les accès mémoire et connait leur latence. Ce qui fait qu'il sait que la donnée lue à tel instant est associée à la requête mémoire numéro X. Pour les lectures, il renvoie la donnée lue avec l'identifiant. Le processeur sait alors à quelle lecture la donnée lue correspond et il se débrouille en interne pour gérer la situation. En clair : le processeur voit les accès mémoire dans le désordre, c'est lui qui les remet en ordre. Maintenant que ces précisions sont faites, posons cette question : quel est l'intérêt de faire les accès mémoire dans le désordre ? Accéder à des banques en parallèle est une réponse possible, mais le ré-ordonnancement est surtout utile si tous les accès mémoire atterrissent dans la même banque. Pour comprendre pourquoi, faisons un rappel rapide sur le tampon de ligne. Les mémoires SDRAM sont des mémoires à tampon de ligne. Elles font un accès mémoire en deux étapes. La première étape recopie une ligne de N * 64 bits dans un tampon interne à la SDRAM. La seconde étape sélectionne une colonne, à savoir une donnée de 64 bits, qui est soit envoyée sur le bus de données pour une lecture, soit modifiée par une écriture. [[File:Mémoire à tampon de ligne.png|centre|vignette|upright=2|Mémoire à tampon de ligne]] Les deux étapes correspondent à deux commandes séparées : une commande ACT qui précise l'adresse de la ligne, et une commande READ ou WRITE qui précise l'adresse de la colonne. [[File:Signaux RAS et CAS.png|centre|vignette|upright=2|Signaux RAS et CAS.]] Les SDRAM permettent de se passer de la première étape si des accès consécutifs se font dans la même ligne. Une fois activée, la ligne reste ouverte et on peut accéder plusieurs fois de suite dedans. On a alors juste à préciser l'adresse de colonne dedans. [[File:Sélection d'une ligne sur une mémoire FPM ou EDO.png|centre|vignette|upright=2|Sélection d'une ligne sur une mémoire FPM ou EDO.]] L'exécution dans le désordre des lectures/écritures SDRAM vise à faire en sorte que des accès consécutifs se fassent dans une même ligne. Elle marche si plusieurs accès à une même ligne ne sont pas consécutifs, et qu'il y a des accès entre les deux. Après ré-ordonnancement, ces accès mémoire deviennent consécutifs, ils sont exécutés l'un à la suite de l'autre. Pour rendre le tout plus concret, voici un exemple. Imaginez que l'on sait les 3 accès mémoire suivants : * Une lecture ligne A ; * Une écriture ligne B ; * Une lecture ligne A. Sans réordonnancent, on doit émettre deux commandes PRECHARGE : une quand on passe de la ligne A à la ligne B, et une autre pour le passage inverse. pour éviter cela, le contrôleur mémoire peut retarder l'écriture, ou au contraire avancer la seconde lecture. le résultat est alors le suivant : * Lecture ligne A ; * Lecture ligne A ; * Une écriture ligne B. On a donc deux accès consécutifs à la même ligne, suivi par une écriture dans une autre ligne. La technique marche parce que l'on a un mix adéquat de lectures et d'écritures. Mais il existe des possibilités de réorganisation autres qui ne sont pas valides. Il faut par exemple prendre garde à éviter d'intervertir lectures et écritures à une même adresse, sous peine de problèmes. Encore une fois, il faut faire attention aux dépendances mémoires. Le controleur mémoire teste les dépendances mémoires avant d'envoyer des commandes à la mémoire DDR/SDRAM. ==L'entrelacement sur les mémoires SDRAM== L''''entrelacement''' fonctionne sur les mémoires SDRAM, mais il doit être fortement modifié. Concrètement, tout ce qui a étré dit plus est inapplicable pour les SDRAM, du fait de la présence du tampon de ligne. Des adresses consécutives atterrissent déjà dans la même ligne, ce qui fait qu'on n'a pas besoin d'entrelacement au niveau d'une ligne. Et ne parlons pas de ce la présence de rangées et de canaux mémoire ! Cependant, il peut y avoir un entrelacement lié à la présence des banques SDRAM. L'entrelacement est géré juste avant le séquenceur mémoire. Le '''circuit d'entrelacement''' intervertit certains bits de l'adresse lors des accès mémoires. On trouve aussi des mémoires FIFOs pour mettre en attente les requêtes mémoire provenant du processeur. Elles permettent de recevoir plusieurs accès mémoire consécutifs. Si vous vous demandez comment plusieurs accès mémoire consécutifs peuvent avoir lieu, sachez que c'est une bonne question. Pour le moment, nous allons dire que ces accès proviennent du processeur ou de la mémoire cache, sans expliquer comment. Le contrôleur mémoire reçoit une série d'accès mémoire assez rapprochés, qu'il doit exécuter au mieux. [[File:Controleur mémoire d'une SDRAM avec entrelacement.png|centre|vignette|upright=2.5|Contrôleur mémoire d'une SDRAM avec entrelacement.]] Pour gérer l'entrelacement, le contrôleur de SDRAM prend en entrée l'adresse envoyée par le processeur, et la découpe en plusieurs champs : un pour sélectionner la banque adéquate, un autre pour la ligne, un autre pour la colonne, un autre pour la rangée. Une solution simple désactive l'entrelacement. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de banque || Adresse de ligne || Adresse de colonne |} Si cette solution fonctionne bien si les accès mémoire sont assez aléatoires, en pratique, mieux vaut utiliser l'entrelacement. ===L'entrelacement de banques/lignes=== Une première méthode d'entrelacement est l''''entrelacement de banques'''. Elle répartit deux lignes consécutives dans deux banques différentes. Pour comprendre l'idée, prenons un exemple. Imaginons une mémoire avec deux banques et 4 lignes. Imaginons qu'on parcoure/balaye la mémoire RAM en partant des adresses basses. Sans entrelacement de ligne, les accès se feront comme suit, de gauche à droite : {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Banque numéro 1 !! colspan="4" | Banque numéro 2 |- | Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 || Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 |} Avec l'entrelacement de banques, les accès se feront comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 |- | Ligne 1 || Ligne 1 || Ligne 2 || Ligne 2 || Ligne 3 || Ligne 3 || Ligne 4 || Ligne 4 |} Avec N banques, la première ligne ira dans la première banque, la seconde ligne dans la seconde, et ainsi de suite jusqu'à la énième banque qui va dans la banque numéro N. Et la ligne N+1 repart dans la première banque. L'avantage est que passer d'une ligne à la suivante est assez rapide. Au lieu de devoir fermer la première ligne et ouvrir la suivante on ouvrira directement la ligne suivante dans une banque différente, ce qui sera plus rapide. La commande PRECHARGE pourra être envoyée à la seconde banque en avance. Pour cela, l'adresse est découpée comme suit. {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne (précise à l'octet) |} ===L'entrelacement de rangées=== Il est aussi possible de faire la même chose, mais avec les rangées. Pour simplifier fortement, une rangée est simplement un chip mémoire. En réalité, les rangées ne sont pas des chip mémoire, mais un ensemble de chips mémoire regroupés ensemble histoire d'atteindre les 64 bits du bus de données. Par exemple, une rangée peut combiner 8 chips mémoire avec un bus de données de 8 bits chacun pour obtenir les 64 bits du bus de données d'une SDRAM. Mais nous passons ce détail sous silence dans les explications qui vont suivre, par souci de simplification. Pour faire comprendre l'entrelacement de rangée, le mieux est d'utiliser un exemple, le même que précédemment. Sans entrelacement de rangée, on change de chip mémoire une fois qu'on a balayé toutes ses adresses. {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Rangée numéro 1 !! colspan="4" | Rangée numéro 2 |- | Banque 1 || Banque 2 || Banque 3 || Banque 4 || Banque 1 || Banque 2 || Banque 3 || Banque 4 |} Avec l'entrelacement de ligne, on change de rangée dès qu'on change de banque/ligne. {|class="wikitable" |+ Adresse mémoire |- ! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 |- | Banque 1 || Banque 1 || Banque 2 || Banque 2 || Banque 3 || Banque 3 || Banque 4 || Banque 4 |} En clair, quand on a épuisé toutes les banques dans une même rangée, on passe à la rangée suivante au lieu de rester dans la même rangée. Notons que dans l'exemple précédent, on a combiné l'entrelacement de rangée et de banque, mais on aurait pu utiliser l'entrelacement de rangée seul. Mais ce n'est pas le cas le plus courant en pratique. Toujours est-il qu'en combinant entrelacement de rangée et de ligne, le découpage de l'adresse est le suivant : {|class="wikitable" |+ Adresse mémoire |- | Adresse de ligne || Numéro de rangée || Adresse de banque || Adresse de colonne (précise à l'octet) |} D'autres méthodes d'entrelacement sont possibles, mais elles sont plus complexes. Chaque contrôleur mémoire fait un peu à sa sauce. Ils permettent en général de choisir entre plusieurs entrelacements. Au minimum, ils permettent de désactiver l'entrelacement, d'activer un entrelacement de ligne, et d'autres entrelacements plus complexes, dont certains sont propriétaires et tenus secrets par le fabricant. ===L'entrelacement avec le ''dual channel''=== [[File:Dual channel slots.jpg|vignette|Slots mémoires avec ''dual channel''.]] L'usage du ''dual channel'' complique encore l'entrelacement. Là encore, il y a deux grandes solutions : avec et sans entrelacement des canaux mémoire. Rappelons le principe : deux barrettes de RAM sont accédées en parallèle. Et pour cela, il faut utiliser l'entrelacement. Typiquement, chaque barrette mémoire fournit 64 bits, ce qui fait que l'on peut accéder à 128 bits d'un seul coup, par exemple avec un accès en rafale. Sans ''dual channel'', la première barrette correspond à la moitié haute de la RAM, la seconde barrette correspond à la moitié basse. Avec ''dual channel'', une forme spécifique d'entrelacement est activée. Concrétement, deux blocs de 64 bits sont placés dans des canaux mémoire séparés. Idem avec du triple ou quadruple canal, mais c'est alors trois ou quatre blocs de 64 bits qui sont dispersés dans des canaux consécutifs. Le découpage de l'adresse est alors le suivant : {|class="wikitable" |+ Adresse mémoire |- ! colspan="6" | Sans entrelacement |- | Numéro de canal/barrette || Reste de l'adresse || Position de l'octet dans un bloc de 64 bits |- | colspan="6" | |- ! colspan="6" | Avec entrelacement |- | Reste de l'adresse || Numéro de canal/barrette || Position de l'octet dans un bloc de 64 bits |} Les contrôleurs de SDRAM précédents sont assez basiques et ne représentent pas les contrôleurs les plus évolués. Ils étaient utilisés sur les anciens PC, à une époque où ils étaient encore sur la carte mère du processeur. Mais de nos jours, le contrôleur mémoire est intégré au processeur. Et il incorpore de nombreuses optimisations afin de gagner en performances. Malheureusement, ces optimisations sont elles-mêmes dépendantes des optimisations intégrées dans le processeur, ce qui fait qu'on ne peut pas en parler ici. Elles demandent que le contrôleur mémoire reçoive plusieurs accès mémoire simultanés, ce qui n'a aucun sens à ce stade du cours. Aussi, nous allons les passer sous silence pour le moment. Nous les verrons dans le chapitre sur le parallélisme mémoire. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=La désambiguïsation mémoire | prevText=La désambiguïsation mémoire | next=Les processeurs superscalaires | nextText=Les processeurs superscalaires }} </noinclude> 7l1y2um1eh2diav0gtqc4k5f2umvurl 764661 764660 2026-04-23T14:56:30Z Mewtow 31375 /* L'entrelacement par décalage */ 764661 wikitext text/x-wiki Dans ce chapitre, nous allons voir les techniques qui permettent de gérer plusieurs accès mémoire simultanés directement au niveau du cache ou de la mémoire RAM. Par plusieurs accès mémoire simultanés, vous pensez sans doute à l'usage de cache multiports, voire de mémoires RAM multiport. C'est en effet une solution, mais ce n'est clairement pas la seule. Par exemple, il est possible de pipeliner l'accès au cache, voire à la mémoire RAM. Il existe de nombreuses techniques de '''parallélisme mémoire''', et nous allons les voir dans ce chapitre. Pour donner un exemple, il est possible de pipeliner les accès mémoire. Il est possible de pipeliner l'accès au cache et/ou l'accès à la mémoire, et nous verrons comment faire dans ce chapitre. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, qu'ils aient ou non un pipeline dynamique. Par contre, pipeliner la mémoire est une technique ancienne, aujourd'hui peu utilisée. Elle était utilisée sur des très vieux ordinateurs, qui avaient un pipeline mais pas de mémoire cache. Mais de nos jours, les mémoires SDRAM et DDR ne sont pas adaptées pour ça, il est très difficile de les pipeliner correctement. Le parallélisme mémoire est utile aussi bien sur des processeurs à émission dans l'ordre que dans le désordre. Par exemple, un processeur ''in-order'' avec des lectures non-bloquantes peut e, profiter. Il peut techniquement lancer une seconde lecture, après avoir rencontré une première lecture non-bloquante. En général, le nombre de lectures consécutives est cependant limité à moins d'une dizaine. À l'opposé, les processeurs sans lectures non-bloquantes profitent pas du parallélisme mémoire. À la rigueur, ils peuvent en profiter s'ils intègrent des techniques de préchargement. Pendant que le processeur exécute une lecture/écriture, il peut précharger une autre donnée en parallèle. Inutile de dire que sans pipeline processeur, le parallélisme mémoire ne sert pas à grand-chose. ==Le ''line fill buffer'' et les techniques associées== Lors d'un défaut de cache, le processeur doit attendre que toute la ligne de cache soit chargée avant d'être utilisable. Or, la taille d'une ligne de cache est supérieure à la largeur du bus mémoire, ce qui fait qu'une ligne de cache est chargée en plusieurs fois, morceaux par morceaux, mot mémoire par mot mémoire. Le chargement peut se faire directement dans le cache, mais ce n'est pas une solution très pratique. À la place, beaucoup de processeurs ajoutent une mémoire tampon entre la RAM et le cache, appelée le '''tampon de remplissage de ligne''' (''line-fill buffer'') dans les processeurs modernes. Lors d'un défaut de cache, le processeur charge la donnée de la RAM dans le tampon de remplissage de ligne, mot mémoire par mot mémoire. Une fois plein, le tampon de remplissage de ligne est recopié dans la ligne de cache. Le tampon de remplissage de ligne contient une ligne de cache, si ce n'est que certains bits de contrôle ne sont pas présents. Le tampon de remplissage de ligne contient un bit de validité pour chaque mot mémoire de la ligne de cache, qui indiquent si le mot mémoire a été chargé. Par exemple, prenons un processeur 64 bits, qui gère donc des mots mémoire de 8 octets, avec des lignes de cache 256 octets/32 mots mémoire. Le tampon de remplissage de ligne contiendra 32 bits de validité. Si le processeur a chargé les 6 premiers mots mémoire, les 6 premiers bits de validité seront mis à 1, les autres seront encore à zéro. Il faut noter que la disponibilité de la ligne complète se détermine assez facilement en faisant un ET logique entre tous les bits de validité. Le processeur sait ainsi quand la ligne de cache est disponible entièrement et donc quand la transférer dans le cache. La même chose existe avec une hiérarchie de cache, sauf que l'on trouve une mémoire tampon entre chaque niveau de cache. Il y a un tampon de remplissage de ligne entre le cache L1 et le cache L2, entre le cache L2 et le L3, etc. Si le cache ne gère qu'un seul défaut de cache à la fois, le ''fill line buffer'' est une mémoire très simple, qui ne mémorise qu'une seule ligne de cache. Et cela vaut aussi bien pour un cache bloquant que non-bloquant. Mais sur les caches capables de gérer plusieurs défauts simultanés, le tampon de remplissage de ligne est une mémoire de type FIFO ou LIFO, capable de mémoriser plusieurs lignes de cache. Notons que le tampon de remplissage de ligne est très utile pour implémenter certaines techniques, par exemple le contournement du cache. Nous avions vu dans le chapitre sur le cache que certains accès mémoire doivent contourner le cache, pour des raisons de cohérence des caches. C'est notamment nécessaire pour accéder aux périphériques, mais c'est aussi utile pour des raisons de performances dans des cas très spécifiques. Les accès qui contournent le cache se font directement dans le tampon de remplissage de ligne : le processeur écrit ou lit les données depuis ce tampon de remplissage de ligne, sans accéder au cache. ===L'''early restart'' et le ''critical word load''=== La présence du ''line buffer'' permet une optimisation assez intéressante, qui permet de réduire la latence des défauts de cache. L'optimisation consiste à lire un mot mémoire dans le tampon de remplissage de ligne, même si la ligne de cache complète n'a pas encore été chargée. Il existe deux manières de faire cela, qui portent les noms d'''early restart'' et de ''critical word load''. La première est la version la plus simple, la seconde est plus complexe mais plus performante. Avec la technique d''''''early restart''''', la ligne de cache est chargée normalement, en partant de son premier mot mémoire. Dès que le mot mémoire lu/écrit par le processeur est copié dans le ''line fill buffer'', il est envoyé au processeur immédiatement. Illustrons le tout par un exemple, où une ligne de cache fait 16 mots mémoire. Le processeur effectue une lecture, qui lit le 5ème mot mémoire. Sans ''early restart'', le processeur doit charger les 16 mots mémoire avant de faire la lecture dans le cache. Avec ''early restart'', le processeur reçoit la donnée dès que le 5ème mot mémoire est disponible. Le processeur doit attendre que les 4 premiers mots mémoire soient chargés, puis le 5ème mot mémoire arrive et est envoyé directement au processeur, il est lu directement depuis le ''line fill buffer''. Les 11 mots mémoire suivants sont ensuite chargés dans le cache pendant que le processeur fait des calculs dans son coin. Le ''critical word load'' est une optimisation de la technique précédente où le chargement de la ligne de cache commence directement à la donnée demandée par le processeur. Pour reprendre l'exemple précédent, où le processeur demande le 5ème mot mémoire sur 16, le ''critical word load'' charge le 5ème mot mémoire en premier et l'envoie au processeur, ce qui fait qu'il est chargé très rapidement. Pas besoin d'attendre que le processeur charge les 4 mots mémoire précédents comme avec l'''early restart''. Dans le détail, le processeur charge le 5ème mot mémoire en premier, puis charge les 11 suivants, et termine par les 4 mots mémoire du début. En clair, le ''critical word load'' commence par charger le mot mémoire lu, puis les blocs suivants, avant de revenir au début du bloc pour charger les blocs restants. Ainsi, la donnée demandée par le processeur sera la première disponible. Pour cela, l'organisation du tampon de remplissage de ligne est modifiée de manière à rendre cela possible. Il a une taille égale à une ligne de cache complète, qui contient elle-même plusieurs mots mémoire. Dans le ''line-fill buffer'', chaque mot mémoire est stocké avec un tag, qui indique l'adresse du mot mémoire stocké dans le ''line-fill buffer''. Le ''line-fill buffer'' est donc un cache un peu particulier, qui fonctionne comme un cache du point de vue du processeur, comme une mémoire FIFO pour les transferts avec le cache. Ainsi, un processeur qui veut lire dans le cache après un défaut peut accéder à la donnée directement depuis le tampon de remplissage de ligne, alors que la ligne de cache n'a pas encore été totalement recopiée en mémoire. ==Les caches pipelinés== Il est possible de pipeliner l'accès au cache, ce qui demande juste de rajouter des registres au bon endroits et quelques circuits de contrôle. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, même ceux avec un pipeline dynamique. Et l'implémentation peut se faire de plusieurs manières différentes. Il faut dire que découper une mémoire cache en plusieurs étapes peut se faire de plusieurs manière. Commençons par la plus simple. Beaucoup de processeurs des années 2000 avaient une fréquence peu élevée comparée aux standards d'aujourd'hui, ce qui fait que leur cache avait bien un temps d'accès d'un cycle d'horloge. Mais l'accès au cache se faisait en deux cycles d'horloge : un cycle pour calculer une adresse et un cycle pour l'accès mémoire proprement dit. Et ces deux étapes étaient pipelinées, à savoir que deux micro-opérations mémoire peuvent s'exécuter en même temps : une dans l'étage de calcul d'adresse, une autre dans le cache. L'unité mémoire était donc pipelinée, alors que l'accès au cache ne l'était pas. Les processeurs qui implémentaient cette technique regroupent les micro-architectures K5 et K6 d'AMD, les processeurs Intel de micro-architecture P6 (Pentium 2 et 3) et quelques autres. Il est aussi possible de pipeliner l'accès au cache lui-même. Avec un cache pipeliné, l'accès au cache ne se fait pas en un seul cycle, mais en plusieurs. Cependant, on peut lancer un nouvel accès au cache à chaque cycle d'horloge, comme un processeur pipeliné lance une nouvelle instruction à chaque cycle. L'implémentation est assez simple : il suffit d'ajouter des registres dans le cache. Pour cela, on profite que le cache est composé de plusieurs composants séparés, qui échangent des données dans un ordre bien précis, d'un composant à un autre. De plus, le trajet des informations dans un cache est linéaire, ce qui les rend parfois pour l'usage d'un pipeline. ===Le ''pipelining'' des caches ''direct-mapped''=== Voici ce qui se passe avec un cache directement adressé. Pour rappel ce genre de cache est conçu en combinant une mémoire RAM, généralement une SRAM, avec quelques circuits de comparaison et un MUX. L'accès au cache se fait globalement en deux étapes : la première lit la donnée et le ''tag'' dans la SRAM, et on utilise les deux dans une seconde étape. Nous avions vu il y a quelques chapitre comment pipeliner des mémoires, dans le chapitre sur les mémoires évoluées. Et bien ces méthodes peuvent s'utiliser pour la mémoire RAM interne au cache !L'idée est d'insérer un registre entre la sortie de la RAM et la suite du cache, pour en faire un cache pipéliné, à deux étages. Le premier étage lit dans la SRAM, le second fait le reste. L'implémentation sur les caches associatifs à plusieurs voies est globalement la même à quelques détails près. [[File:Cache directement adressé pipeliné - deux étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - deux étages]] Avec le circuit précédent, il est possible d'aller plus loin, cette fois en pipelinant l'accès à la mémoire RAM interne au cache. Pour comprendre comment, rappelons qu'une mémoire SRAM est composée d'un plan mémoire et d'un décodeur. L'accès à la mémoire demande d'abord que le décodeur fasse son travail pour sélectionner la case mémoire adéquate, puis ensuite la lecture ou écriture a lieu dans cette case mémoire. L'accès se fait donc en deux étapes successives séparées, on a juste à mettre un registre entre les deux. Il suffit donc de mettre un gros registre entre le décodeur et le plan mémoire. [[File:Cache directement adressé pipeliné - trois étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - trois étages]] Et on peut aller encore plus loin en découpant le décodeur en deux circuits séparés. En effet, rappelez-vous le chapitre sur les circuits de sélection : nous avions vu qu'il est possible de créer des décodeurs en assemblant des décodeurs plus petits, contrôlés par un circuit de prédécodage. Et bien on peut encore une ajouter un registre entre ce circuit de prédécodage et les petits décodeurs. : Théoriquement, toute l'adresse est fournie d'un seul coup au cache, la quasi-totalité des processeurs présentent l'adresse complète à un cache pipeliné. Mais le Pentium 4 fait autrement. Il faut noter que les premiers étages manipulent l'indice dans la SRAM, qui est dans les bits de poids faible, alors que les étages ultérieurs manipulent le ''tag'' qui est dans les bits de poids fort. Les concepteurs du Pentium 4 ont alors décidé de présenter les bits de poids faible lors du premier cycle d'accès au cache, puis ceux de poids fort au second cycle. Pour cela, l'ALU fonctionnait à une fréquence double de celle du processeur, tout comme le cache L1. Il n'y avait pas de pipeline proprement dit, mais cela réduisait grandement la latence d'accès au cache. ===Le ''pipelining'' des caches splittés/sériels=== Il est aussi possible de pipeliner un cache dits splittés, aussi appelés à accès sériel. Pour rappel, les caches à accès sériel vérifient si il y a succès ou défaut de cache, avant d'accéder aux lignes de cache en cas de succès. Ils font donc différemment des autres caches, qui accèdent à une ligne de cache, avant de déterminer s'il y a succès ou défaut en lisant le tag de la ligne de cache. Les caches sériels disposent de deux SRAM : une pour les tags des lignes de cache et une pour les données. Ils accèdent à la SRAM pour les tags, avant d’accéder à la SRAM des données en cas de succès de cache. Vu que l'accès se fait en deux étapes, une vérification des tags suivie de la lecture/écriture des données, il est facile à pipeliner. Pipeliner le cache permet de régler le problème des accès au cache L1, et elle est tout le temps utilisé sur les processeurs modernes. Mais que faire en cas de défaut de cache ? ==Les caches non bloquants== Un '''cache bloquant''' est un cache auquel le processeur ne peut pas accéder pendant un défaut de cache. Il faut attendre que la lecture ou écriture en RAM soit terminée avant de pouvoir utiliser de nouveau le cache. Un '''cache non bloquant''' n'a pas ce problème : on peut l'utiliser pendant un défaut de cache. Les caches non bloquants permettent de démarrer une nouvelle lecture ou écriture alors qu'une autre est en cours, ce qui permet d'exécuter plusieurs lectures ou écritures en même temps. ===Les ''Miss Handling Status Registers''=== Lors d'un défaut de cache, la mémoire RAM est consultée pendant le défaut de cache, mais le cache est inutilisé. Un défaut de cache n'utilise pas le cache, ce n'est qu'un accès en mémoire RAM, sur le bus mémoire (ou un accès aux niveaux de cache inférieurs, peu importe). Le cache en lui-même est laissé libre, rien n’empêche d'y accéder, il est en réalité intrinsèquement non-bloquant. Les caches, bloquants comme non-bloquants, sont en réalité composés d'une mémoire cache proprement dite, entourée de circuits qui servent d'interface entre le processeur et le cache lui-même. Et parmi les circuits tout autour, certains gèrent l'accès au cache lors d'un défaut de cache. Ils sont regroupés sous le terme de '''''Miss Handling Architecture''''' (MHA). La différence entre un cache bloquant et un cache non-bloquant est en réalité liée à l'implémentation de la MHA. Les caches bloquants coupent volontairement l'accès au cache lors d'un défaut, car les défauts sont plus simples à gérer ainsi. Le défaut de cache rapatrie une donnée depuis la RAM, qui sera écrite dans le cache. Et il ne faut pas qu'une tentative d'accès à cette donnée ait lieu avant qu'elle ne soit chargée. Mais ce blocage est général et touche tout le cache, alors que seule une ligne de cache est concernée par le défaut de cache. L'idée derrière un cache non-bloquant est que seule la ligne de cache est bloquée, mais les autres sont accessibles. L'idée est alors de mémoriser les lignes de cache concernées par le défaut de cache, afin d'en bloquer l'accès. À chaque accès, on vérifie si la ligne de cache est déjà réservée par un défaut de cache. La lecture/écriture est alors bloquée si c'est le cas, mais elle accepte les accès sinon. Pour cela, la ''Miss Handling Architecture'' contient des registres qui mémorisent des informations sur les défauts de cache en cours. Ils portent le nom de '''''miss status handling registers''''', que j’appellerais dorénavant MSHR, qui sont aussi appelés des '''''miss buffer'''''. Le contenu des MSHR varie beaucoup suivant le processeur, mais ces derniers stockent au minimum les informations suivantes : * Le numéro de la ligne de cache dans laquelle les données sont chargées. * Un bit de validité qui indique si le MSHR est vide ou pas, qui est mis à 0 quand le défaut de cache est résolu. * Un ou plusieurs champs de lecture/écriture, qui contiennent des informations sur la lecture/écriture. ** Pour une lecture, elle contient des informations sur la destination de la donnée, à savoir qui prévenir quand le défaut de cache est terminé. C'est parfois un nom/numéro de registre (celui dans lequel charger la donnée), mais c'est souvent le numéro de l'entrée dans le ''load/store queue''. ** Pour les écritures, elle contient la donnée à écrire, ou éventuellement un numéro de ''load/store queue'' où se trouve la donnée à écrire. Il faut noter que le bus mémoire ne peut gérer qu'un seul défaut de cache à la fois. Aussi, il est intéressant de regarder ce qui se passe lorsqu'un second défaut de cache survient, pendant qu'un premier est en cours. Dans ce cas, il y a deux réponses qui correspondent à deux types de caches non-bloquants, qui portent les noms barbares de caches de type succès après défaut et défaut après défaut. Sur le premier type, il ne peut pas y avoir plusieurs défauts de cache simultanés. Dès qu'un second défaut de cache survient, le cache stoppe son activité et on ne peut plus démarrer de nouvelle lecture/écriture, tant que le premier défaut de cache n'est pas résolu. Le second type est plus souple et autorise la survenue de plusieurs défauts de cache simultanés. Du moins, jusqu'à une certaine limite, car le cache ne peut supporter qu'un nombre limité d'accès mémoires simultanés (pipelinés). ===Les accès simultanés à une même ligne de cache=== Il arrive que le processeur fasse plusieurs accès mémoire simultanés à la même ligne de cache. Si la ligne de cache en question n'a pas encore été chargée dans le cache, alors on a plusieurs défauts de cache consécutifs pour la même ligne de cache, et le cache non-bloquant doit gérer la situation. Pour la suite, il va falloir faire une petite distinction entre les défauts primaires et secondaires. Imaginons qu'un défaut de cache ait lieu et demande à charger une donnée dans la ligne de cache numéro N. Il s'agit du premier défaut impliquant cette ligne de cache précise, ce qui lui vaut le nom de '''défaut de cache primaire'''. Mais par la suite, d'autres accès mémoire à la même ligne de cache ont lieu, alors que la ligne de cache n'est pas encore disponible. Dans ce cas, il s'agit de '''défauts de cache secondaires'''. Pour l'unité d'accès mémoire, les défauts de cache primaire et secondaire sont différents (ils prennent tous une entrée dans la ''load/store queue''). Mais pour le cache, ils ne correspondent qu'à un seul accès au cache : celui qui demande de charger la ligne de cache demandée. Les défauts de cache primaire et secondaire à la même ligne de cache se voient attribuer un MSHR unique. La gestion des défauts de cache secondaires dépend du cache non-bloquant. La solution la plus simple ne permet pas les défauts de cache secondaires. Le cache ne permet pas deux défauts de cache simultanés pour la même ligne de cache. Les autres solutions le permettent, en fusionnant des accès simultanés à la même ligne de cache en un seul au niveau des MSHR. Dans tous les cas, détecter les défauts de cache secondaires sont un problème qu'il faut détecter. La ''Miss Handling Architecture'' doit détecter les défauts de cache secondaire. Pour cela, elle procède comme suit. Lors de chaque défaut de cache, la MHA récupère le numéro de la ligne de cache associé. Il vérifie alors chaque MSHR pour vérifier s'il contient le numéro en question. S'il n'y a aucune correspondance dans les MSHR, alors c'est signe que le défaut de cache est un défaut primaire. Mais s'il y en a une, alors c'est un défaut secondaire. Évidemment, cela signifie que lors d'un défaut de cache, le numéro de ligne de cache est envoyé à tous les MSHR, pour comparaison. Les MSHR sont donc regroupés dans une mémoire associative, une sorte de mini-cache, faciliter l'implémentation. Lors d'un défaut de cache primaire, l'accès à un cache non-bloquant se fait comme suit : le processeur envoie une adresse au cache, accède à celui-ci, et détecte la survenue d'un défaut de cache. Il en profite alors pour attribuer une ligne de cache dans laquelle sera chargée ce défaut. L'attribution est très simple dans le cas des caches ''direct mapped'', ou associatifs par voie, pour lesquels l'attribution se fait assez simplement. Il mémorise alors cette information dans les MSHR, après avoir vérifié que le défaut de cache n'était pas un défaut secondaire. Lors des accès ultérieurs à une adresse proche, censée être dans la même ligne de cache, le processeur va encore une fois rencontrer un défaut de cache. Il va alors déterminer le numéro de la ligne de cache associée à l'adresse, et comparer ce numéro avec les MSHRs. Si un MSHR contient ce numéro, c'est signe que le défaut de cache est un défaut secondaire. La MHA réagit alors différemment selon le processeur considéré. Une première solution n'autorise pas les défauts de cache secondaires. Si l'un d'entre eux survient, le processeur est gelé par un ''pipeline stall'', une bulle de pipeline. Une autre solution fusionne les défauts de cache secondaires avec le défaut de cache primaire : tout cela ne correspond qu'à un seul défaut de cache pour lui. ===Les MSHR simples=== Un cache non-bloquant à '''MSHR simple''' contient juste plusieurs MSHR qui mémorisent juste un numéro de ligne de cache, un bit de validité, le champ de lecture/écriture, et l'adresse exacte de lecture/écriture. Avec cette organisation, il est possible d'avoir plusieurs défauts de cache séparés, mais à la condition que chaque défaut accède à une ligne de cache différente. Deux accès simultanés à une même ligne de cache ne sont pas possibles, les défauts de cache secondaires ne sont pas autorisés. Ainsi, chaque défaut de cache se voit attribuer son propre MSHR, chacun contient un numéro de ligne de cache différent. Pour comprendre pourquoi c'est impossible de gérer les défauts secondaires, il faut regarder le champ de lecture/écriture. Si on veut effectuer plusieurs écritures consécutives à la même adresse, le MSHR n'aura pas de quoi mémoriser les deux données à écrire. Il pourra mémoriser la première donnée à écrire, pas la seconde. Même chose lors d'une lecture : le champ lecture/écriture peut mémorisr la destination de la première lecture, pas de la seconde. L'avantage est que la MHA n'a besoin que des MSHR et de quelques circuits annexes. Les autres solutions rajoutent des circuits annexes pour gérer les défauts de cache secondaires, qui utilisent beaucoup de circuits. Le cout en circuit est donc élevé, mais le gain en performance est là. Passons maintenant aux caches non-bloquants qui autorisent les défauts de cache secondaire. La solution la plus simple consiste à utiliser ===Les MSHR adressés implicitement=== Avec les '''MSHR adressés implicitement''', il est possible de fusionner plusieurs accès mémoire à une même ligne de cache, mais sous une condition très importante : ces accès lisent/écrivent des mots mémoire différents. Par exemple, imaginons qu'une ligne de cache contienne 8 mots mémoire de 64 bits. Si un premier accès mémoire lit le mot mémoire numéro 7 (dernier mot mémoire de la ligne), et le second accès le mot mémoire numéro 3, alors la fusion est possible. Mais si deux accès mémoire veulent lire/écrire le mot mémoire numéro 7, alors la fusion n'est pas possible et le processeur se bloque, un ''pipeline stall'' survient. Un MSHR adressé implicitement est un MSHR simple, qui contient naturellement un numéro de ligne de cache (tag) et un bit de validité, sauf que le champ de lecture/écriture est dupliqué. Les différents champs lecture/écriture d'un MSHR sont regroupés dans une mémoire RAM/cache qui contient autant d'entrées qu'il y a de mot mémoire dans une ligne de cache. Chaque entrée de la table est associée à un mot mémoire de la ligne de cache et stocke des informations sur celui-ci. Une entrée mémorise, au minimum : * Un champ de lecture/écriture qui contient soit la destination de la lecture, soit la donnée à écrire. * Un bit R/W permettant d'interpréter correctement le champ de lecture/écriture. * Un bit de validité pour chaque entrée de la table, qui dit si un défaut de cache antérieur accède déjà à ce mot mémoire. La fusion de deux défauts de cache est ainsi assez simple. Un défaut de cache primaire/secondaire configure l'entrée associée au mot mémoire lu/écrit. Si un défaut secondaire ultérieur lit/écrit un mot mémoire différent (dont le bit de validité de l'entrée dans la table est à zéro), pas de conflit : il configure une autre entrée, vide. Mais s'il lit/écrit un mot mémoire pour lequel l'entrée de la table est occupée, il y a conflit, le processeur est bloqué par un ''pipeline stall'', une bulle de pipeline. Avec cette organisation, le nombre de MSHR indique combien de lignes de cache peuvent être lues en même temps depuis la mémoire. Quant au nombre d'entrées par MSHR, il détermine combien d'accès mémoires qui ne se recouvrent pas peuvent avoir lieu en même temps. ===Les MSHR adressés explicitement=== Les '''MSHR adressés explicitement''' n'ont pas les contraintes des MSHR adressés implicitement. Avec eux, il est possible d'avoir plusieurs défauts de cache pointant vers la même ligne de cache, mais aussi vers le même mot mémoire. De tels défauts de cache apparaissent sur les processeurs à exécution dans le désordre, mais sont très rares, voire inexistants, sur les processeurs ''in-order''. L'idée est encore que chaque MSHR est associée à une table mémoire qui mémorise des entrées. Cette table n'est autre qu'une mémoire FIFO. Sauf que cette fois-ci, une entrée n'est pas associée à un mot mémoire. Une entrée est associée à un défaut de cache. Une entrée mémorise là encore la destination de la lecture ou la donnée de l'écriture suivi du bit R/W, ainsi que d'un bit de validité par entrée, mais aussi : la position du mot mémoire lu dans la ligne de cache. C'est cette dernière information qui n'était pas présente dans les MSHR adressés implicitement. De plus, le nombre d'entrée par MSHR n'est pas égal au nombre de mots mémoires dans une ligne de cache, mais peut être arbitrairement grand. L'avantage d'un tel cache est qu'il est capable de traité les défauts secondaires concernant un même mot mémoire, et ce, car les accès à la ligne de cache concernée se produisent dans l'ordre de génération des défauts (la table mémoire est une FIFO). ===Les MSHRs inversés=== Généralement, plus on veut supporter de défauts de cache, plus le nombre de MSHR et d'entrées augmente. Mais au-delà d'un certain nombre d'entrées et de MSHR, les MSHR adressés implicitement et explicitement ont tendance à bouffer un peu trop de circuits. Utiliser une organisation un peu moins gourmande en circuits est donc une nécessité. Cette organisation plus économe se base sur des MSHR inversés. Les MSHR inversés ne contiennent qu'une seule entrée, en quelque sorte : au lieu d’utiliser n MSHR de m entrées chacun, on va utiliser n × m MSHR inversés. La différence, c'est que plusieurs MSHR peuvent contenir un tag identique, contrairement aux MSHR adressés implicitement et explicitement. Lorsqu'un défaut de cache a lieu, chaque MSHR est vérifié. Si jamais aucun MSHR ne contient de tag identique à celui utilisé par le défaut, un MSHR vide est choisi pour stocker ce défaut, et une requête de lecture en mémoire est lancée. Dans le cas contraire, un MSHR est réservé au défaut de cache, mais la requête n'est pas lancée. Quand la donnée est disponible, les MSHR correspondant à la ligne qui vient d'être chargée vont être utilisés un par un pour résoudre les défauts de cache en attente. ===Les MSHR intégrés au cache=== Certains chercheurs ont remarqué que pendant qu'une ligne de cache est en train de subir un défaut de cache, celle-ci reste inutilisée, et son contenu est destiné à être perdu une fois le défaut de cache résolu. Ils se sont dit que, plutôt que d'utiliser des MSHR séparés, il vaudrait mieux utiliser la ligne de cache pour stocker les informations sur les défauts de cache en attente dans cette ligne de cache. Pour éviter tout problème, il faut rajouter un bit dans les bits de contrôle de la ligne de cache, qui sert à indiquer que la ligne de cache est occupée : un défaut de cache a eu lieu dans cette ligne, et elle stocke donc des informations pour résoudre les défauts de cache. ==L'entrelacement mémoire== Nous venons de voir comment une mémoire cache peut gérer plusieurs accès mémoire simultanés. Il se trouve que ce genre d'optimisation dépasse largement le cadre du cache. Les mémoires RAM ne sont pas en reste, elles aussi ! Il est possible de pipeliner l'accès à une mémoire RAM, en utilisant des techniques dites d''''entrelacement'''. Précisons cependant que cette optimisation n'est pas utilisée sur les mémoires SDRAM ou DDR modernes, du moins pas sans modifications majeures. Mais divers ordinateurs assez anciens, dont des superordinateurs, ont utilisé cette technique. La technique demande d'utiliser plusieurs mémoires séparées. Sur les anciens ordinateurs, les mémoires en question sont des chips mémoire, à savoir des circuits intégrés de DRAM. Pour faire plus général, nous allons utiliser le terme de '''banques''', ou encore de bancs mémoire. La différence est qu'il existe des mémoires multi-banque, qui regroupent plusieurs banques indépendantes dans un seul boitier, dans un seul chip mémoire. En clair, de telles mémoires regroupent plusieurs sous-mémoires dans un seul circuit intégré. Il est aussi possible d'utiliser plusieurs chips mémoire séparés, chacun ayant une banque. Reste à combiner ces banques pour former un pseudo-pipeline. ===Utiliser plusieurs banques sans entrelacement=== Sans optimisation particulière, les adresses sont réparties dans les banques comme indiqué ci-dessous. Il s'agit de ce qui s'appelle un arrangement horizontal, et nous avions vu celui-ci dans le chapiutre sur les mémoires SDRAM. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Avec cet arrangement, les bits de poids fort de l'adresse sont utilisées pour sélectionner la banque adéquate, et le reste de l'adresse est envoyé sur le bus d'adresse. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Adresse de banque !! Adresse dans la banque |- | Quelques bits de poids fort || Reste de l'adresse |} L'entrelacement change cette répartition, afin d'accélérer les accès mémoire. L'entrelacement classique vise à améliorer les accès à des adresses mémoires consécutives, mais des formes plus évoluées d'entrelacement visent à optimiser des accès mémoire arbitraires. Voyons-les dans l'ordre, du plus simple au plus complexe. ===L'entrelacement classique=== Avec l''''entrelacement classique''', des adresses consécutives sont réparties dans des banques différentes. [[File:Répartition des adresses dans une mémoire interleaved.png|centre|vignette|upright=1.5|Répartition des adresses dans une mémoire interleaved.]] L'entrelacement simple permet d'accélérer les accès mémoire qui se font à des adresses consécutives. Avec l'entrelacement, chaque accès mémoire tombe dans une banque mémoire différente, ce qui fait qu'on peut démarrer un nouvel accès mémoire à chaque cycle d'horloge. Pendant qu'une banque est occupée par un accès mémoire, on démarre le suivant dans une autre banque, et ainsi de suite. Le tout se termine soit quand on a épuisé toutes les banques libres, soit quand l'accès en rafales se termine. Pas besoin d'attendre que la mémoire ait fini sa lecture/écriture avant de démarrer la lecture/écriture suivante. [[File:Pipemining mémoire.png|centre|vignette|upright=2.5|Pipelining mémoire]] L'animation suivante illustre bien le processus, avec quatres banques. [[File:Interleaving.gif|centre|vignette|upright=2.5|Pipelining mémoire]] L'entrelacement simple utilise les bits de poids faible pour sélectionner la banque, et les bits de poids fort pour la case mémoire. [[File:Adresse mémoire d'une mémoire entrelacée.png|centre|vignette|upright=2|Adresse mémoire d'une mémoire entrelacée]] Précisons que le temps d'accès mémoire ne change pas beaucoup avec l'entrelacement. Par contre, on peut faire plus d'accès mémoire simultanés. On peut démarrer un accès mémoire par cycle d'horloge, mais l'accès en lui-même prend plusieurs cycles. Les mémoires à entrelacement ont donc un débit supérieur aux mémoires qui ne l'utilisent pas. L'entrelacement classique pourrait être utilisé pour accélérer les transferts entre mémoire RAM et cache, qui se font en blocs d'adresses consécutives. Mais de nos jours, il n'est pas utilisé dans cette optique. Il faut dire qu'il est redondant avec le mode rafale des mémoires DRAM. Les deux font globalement la même chose et on voit mal comment utiliser les deux en même temps. Le mode rafale étant plus simple à implémenter, et plus léger niveau utilisation du bus de commande mémoire, il est préféré à l'entrelacement. Par contre, l'entrelacement a eu son heure de gloire sur d'anciens processeurs qui n'avaient pas de mémoire cache, alors qu'ils disposaient d'un pipeline. L'exemple typique est celui des nombreux ''processeurs vectoriels'', que nous n'avons pas encore abordé à ce stade du cours. De tels processeurs avaient un pipeline, une unité de calcul fortement pipelinée, et parfois même de l'exécution dans le désordre et du renommage de registres ! ===L'entrelacement par décalage=== L'entrelacement simple est très utile pour les accès en rafle ou équivalents. Par contre, il existe de rares situations où il n'est pas optimal. Une de ces situations est celle des accès en enjambée (en ''stride''), où l'on accède en série à des adresses sont séparées par N mots mémoires. [[File:Accès par enjambées.png|centre|vignette|upright=2|Accès par enjambées.]] De tels accès surviennent quand un logiciel accède à des structures de données spécifiques, à savoir des tableaux de structures, des matrices, ou d'autres structures de données dans le genre. Un cas classique est celui du parcours d'une matrice colonne par colonne. Les matrices de nombres sont mémorisées ligne par ligne, ce qui fait que parcourir une colonne demande de passer d'une ligne à la suivante et de faire des grands sauts en mémoire RAM, mais tous espacés par la même distance. Avec l'entrelacement simple, les accès en enjambée sont moins performants que les accès à des adresses consécutives, mais cela ne fonctionne pas trop mal. Le pire cas est celui où l'on a N banques et où les données sont justement placées toutes les N adresses. Des accès consécutifs vont tous tomber dans la même banque, on ne peut plus accéder à des banques différentes en parallèle. Mais en dehors de ce cas, on voit une amélioration par rapport à la situation sans entrelacement. Pour obtenir des performances maximales pour les accès en enjambées, il faut répartir les mots mémoires dans la mémoire autrement. L'organisation idéale est la suivante. : Dans les explications qui vont suivre, la variable N représente le nombre de banques, qui sont numérotées de 0 à N-1. On commence par organiser les N premières adresses comme une mémoire entrelacée simple : l'adresse 0 correspond à la banque 0, l'adresse 1 à la banque 1, etc. Pour le bloc suivant, on décale tout d'une adresse, à savoir qu'on commence à remplir les banques à partir de la seconde, non de la première. Une fois la fin du bloc atteinte, on finit de remplir le bloc en repartant du début du bloc. Le troisième bloc d'adresse subit le même traitement, sauf qu'on commence à remplir à partir de la seconde banque. Et on poursuit l’assignation des adresses en décalant d'un cran en plus à chaque bloc. Ainsi, chaque bloc verra ses adresses décalées d'un cran en plus comparé au bloc précédent. Si jamais le décalage dépasse la fin d'un bloc, alors on reprend au début. [[File:Mémoire interleaved par décalage.png|centre|vignette|upright=1.5|Mémoire entrelacée par décalage.]] En faisant cela, on remarque que les banques situées à N adresses d'intervalle sont différentes. Dans l'exemple du dessus, nous avons ajouté un décalage de 1 à chaque nouveau bloc à remplir. Mais on aurait tout aussi bien pu prendre un décalage de 2, 3, etc. Dans tous les cas, on obtient un entrelacement par décalage. Ce décalage est appelé le pas d'entrelacement, noté P. Le calcul de l'adresse à envoyer à la banque, ainsi que la banque à sélectionner se fait en utilisant les formules suivantes : * adresse à envoyer à la banque = adresse totale / N ; * numéro de la banque = (adresse + décalage) modulo N ; * décalage = (adresse totale * P) mod N. Avec cet entrelacement par décalage, on peut prouver que la bande passante maximale est atteinte si le nombre de banques est un nombre premier. Seulement, utiliser un nombre de banques premier peut créer des trous dans la mémoire, des mots mémoires inadressables. Pour éviter cela, il faut faire en sorte que N et la taille d'une banque soient premiers entre eux : ils ne doivent pas avoir de diviseur commun. Dans ce cas, les formules se simplifient : * adresse à envoyer à la banque = adresse totale / taille de la banque ; * numéro de la banque = adresse modulo N. ===L'entrelacement pseudo-aléatoire=== Une dernière méthode de répartition consiste à répartir les adresses dans les banques de manière "pseudo-aléatoire". L'idée exacte est de faire passer l'adresse dans une '''fonction de hachage''', plus ou moins complexe. Pour rappel, une fonction de hachage prend une entrée de grande taille et fournit en sortie un résultat de petite taille. Idéalement, elles doivent donner des résultats aussi différents que possible pour des entrées similaires, histoire de simuler une sorte de pseudo-aléatoire. Ici, une adresse est transformée en un numéro de banque. Une fonction de hachage simple ne fait que permuter des bits de l'adresse pour obtenir son résultat. Elle ne fait qu'échanger des bits de place avant de couper l'adresse en deux morceaux : un pour la sélection de la banque, et un autre pour la sélection de l'adresse dans la banque. Cette permutation est fixe, et ne change pas suivant l'adresse. Des fonctions de hachage plus complexes font des XOR bit à bit entre certains bits de l'adresse. Il existe aussi d'autres techniques qui donnent le numéro de banque à partir d'un polynôme modulo N, appliqué sur l'adresse. ==Les contrôleurs mémoires SDRAM/DDR optimisés== Après avoir vu le cas des mémoires RAM, nous allons nous concentrer sur le cas particulier des mémoires SDRAM. Les mémoires SDRAM et DDR modernes sont capables de gérer plusieurs accès simultanés à la mémoire RAM. Et les contrôleurs mémoire intègrent des optimisations spécifiques afin d'en profiter au maximum. ===La mise en attente des accès mémoire=== Comme pour les mémoires caches, il existe des contrôleurs mémoire bloquants et non-bloquants. Les premiers n'acceptent qu'un seul accès mémoire à la fois, les seconds acceptent plusieurs accès mémoire simultanés. Pour simplifier, nous allons dire que le contrôleur mémoire n’exécute qu'un seul accès mémoire à la fois, et qu'ils met en attente les accès en trop. C'est une simplification assez irréaliste, vu que les mémoires modernes disposent de plusieurs banques accessibles en parallèle. Mais nous introduirons ce détail un peu plus tard. Avec un contrôleur mémoire de ce type, les accès mémoire sont mis en attente dans une mémoire FIFO, histoire de les exécuter dans leur ordre d'arrivée. Les accès mémoire quittent alors la mémoire dans leur ordre d'arrivée, les lectures sont renvoyées dans l'ordre demandé. En plus d'une mémoire FIFO pour les commandes mémoire, on trouve une mémoire FIFO pour les données à écrire. Elle permet d'accumuler plusieurs écritures, tout en permettant que la donnée adéquate soit envoyée à la RAM au bon moment. Il y a aussi une FIFO pour les données lues, qui sert au cas où le cache ne soit pas disponible quand on lui envoie la donnée lue. [[File:Module d'interface avec la mémoire.png|centre|vignette|upright=2.5|Contrôleur mémoire avec mise en attente des requêtes processeur et autres optimisations.]] Une optimisation regroupe plusieurs accès à des données consécutives en un seul accès en rafale. Le contrôleur mémoire analyse les requêtes mises en attente et détecte si certaines se font à des adresses consécutives. Si c'est le cas, il fusionne ces requêtes en une lecture/écriture en rafale. Une telle optimisation est appelée la '''combinaison de lecture''' pour les lectures, et la '''combinaison d'écriture''' pour les écritures. ===Le pipeline des mémoires SDRAM=== Les SDRAM ont une forme de pseudo-pipeline très limité. Elles sont parfois capables de démarrer une commande avant que la précédente soit terminée. Même si elle parait très limitée, cette possibilité est exploitée au mieux par la présence de plusieurs banques dans la mémoire SDRAM. Les chips mémoires de SDRAM sont composés de plusieurs banques, chacune étant une sorte de mini-mémoire miniature. Chaque banque a son propre décodeur, son propre tampon de ligne, ses propres multiplexeurs de colonne, sa logique de rafraichissement mémoire, etc. C'est comme si une SDRAM regroupait plusieurs mémoires séparées dans un même circuit intégré. Et cela permet de faire plusieurs accès mémoire en même temps, dans des banques différentes. Il est possible de lancer un accès mémoire dans deux banques en même temps, par exemple. Ou encore, on peut lancer un nouvel accès mémoire dans une banque si elle est inoccupée. La possibilité améliore grandement la performance de la SDRAM. Mais nous en reparlerons dans un chapitre ultérieur. {|class="wikitable" |+ Pipelining basique sur les SDRAM |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 |- ! Banque Numéro 1 | || || || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || || || |- ! Banque Numéro 2 | || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || |- ! Banque Numéro 3 | || || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || |} L'implémentation n'a rien de vraiment compliqué. Il suffit d'utiliser une mémoire FIFO par banque. Ainsi, les accès dans une même banque se feront en série, dans l'ordre d'arrivée, mais des accès à des banques différentes se feront dans le désordre. Cela ne pose pas de problème car les accès se font à des adresses différentes, ce qui fait qu'il n'y a pas de conflits majeurs. [[File:Gestion parallèle des banques.png|centre|vignette|upright=2.5|Gestion parallèle des banques.]] ===Le ré-ordonnancement des commandes mémoires=== Un contrôleur mémoire moderne est capable de changer l'ordre des requêtes mémoires, pour gagner en performances. En clair : il peut faire l'équivalent mémoire de l'exécution dans le désordre des processeurs haute performance. Une différence majeure est que le contrôleur mémoire ne remet pas les accès mémoire dans l'ordre avant de les envoyer au processeur. C'est le processeur qui remet en ordre les accès mémoire, dans sa ''load queue''. Pour cela, les requêtes mémoire sont "numérotées", à savoir qu'on leur attribue un identifiant binaire. Le contrôleur mémoire exécute les lectures/écritures dans le désordre, mais garde la trace de leur identifiant. Il sait dans quel ordre il exécute les accès mémoire et connait leur latence. Ce qui fait qu'il sait que la donnée lue à tel instant est associée à la requête mémoire numéro X. Pour les lectures, il renvoie la donnée lue avec l'identifiant. Le processeur sait alors à quelle lecture la donnée lue correspond et il se débrouille en interne pour gérer la situation. En clair : le processeur voit les accès mémoire dans le désordre, c'est lui qui les remet en ordre. Maintenant que ces précisions sont faites, posons cette question : quel est l'intérêt de faire les accès mémoire dans le désordre ? Accéder à des banques en parallèle est une réponse possible, mais le ré-ordonnancement est surtout utile si tous les accès mémoire atterrissent dans la même banque. Pour comprendre pourquoi, faisons un rappel rapide sur le tampon de ligne. Les mémoires SDRAM sont des mémoires à tampon de ligne. Elles font un accès mémoire en deux étapes. La première étape recopie une ligne de N * 64 bits dans un tampon interne à la SDRAM. La seconde étape sélectionne une colonne, à savoir une donnée de 64 bits, qui est soit envoyée sur le bus de données pour une lecture, soit modifiée par une écriture. [[File:Mémoire à tampon de ligne.png|centre|vignette|upright=2|Mémoire à tampon de ligne]] Les deux étapes correspondent à deux commandes séparées : une commande ACT qui précise l'adresse de la ligne, et une commande READ ou WRITE qui précise l'adresse de la colonne. [[File:Signaux RAS et CAS.png|centre|vignette|upright=2|Signaux RAS et CAS.]] Les SDRAM permettent de se passer de la première étape si des accès consécutifs se font dans la même ligne. Une fois activée, la ligne reste ouverte et on peut accéder plusieurs fois de suite dedans. On a alors juste à préciser l'adresse de colonne dedans. [[File:Sélection d'une ligne sur une mémoire FPM ou EDO.png|centre|vignette|upright=2|Sélection d'une ligne sur une mémoire FPM ou EDO.]] L'exécution dans le désordre des lectures/écritures SDRAM vise à faire en sorte que des accès consécutifs se fassent dans une même ligne. Elle marche si plusieurs accès à une même ligne ne sont pas consécutifs, et qu'il y a des accès entre les deux. Après ré-ordonnancement, ces accès mémoire deviennent consécutifs, ils sont exécutés l'un à la suite de l'autre. Pour rendre le tout plus concret, voici un exemple. Imaginez que l'on sait les 3 accès mémoire suivants : * Une lecture ligne A ; * Une écriture ligne B ; * Une lecture ligne A. Sans réordonnancent, on doit émettre deux commandes PRECHARGE : une quand on passe de la ligne A à la ligne B, et une autre pour le passage inverse. pour éviter cela, le contrôleur mémoire peut retarder l'écriture, ou au contraire avancer la seconde lecture. le résultat est alors le suivant : * Lecture ligne A ; * Lecture ligne A ; * Une écriture ligne B. On a donc deux accès consécutifs à la même ligne, suivi par une écriture dans une autre ligne. La technique marche parce que l'on a un mix adéquat de lectures et d'écritures. Mais il existe des possibilités de réorganisation autres qui ne sont pas valides. Il faut par exemple prendre garde à éviter d'intervertir lectures et écritures à une même adresse, sous peine de problèmes. Encore une fois, il faut faire attention aux dépendances mémoires. Le controleur mémoire teste les dépendances mémoires avant d'envoyer des commandes à la mémoire DDR/SDRAM. ==L'entrelacement sur les mémoires SDRAM== L''''entrelacement''' fonctionne sur les mémoires SDRAM, mais il doit être fortement modifié. Concrètement, tout ce qui a étré dit plus est inapplicable pour les SDRAM, du fait de la présence du tampon de ligne. Des adresses consécutives atterrissent déjà dans la même ligne, ce qui fait qu'on n'a pas besoin d'entrelacement au niveau d'une ligne. Et ne parlons pas de ce la présence de rangées et de canaux mémoire ! Cependant, il peut y avoir un entrelacement lié à la présence des banques SDRAM. L'entrelacement est géré juste avant le séquenceur mémoire. Le '''circuit d'entrelacement''' intervertit certains bits de l'adresse lors des accès mémoires. On trouve aussi des mémoires FIFOs pour mettre en attente les requêtes mémoire provenant du processeur. Elles permettent de recevoir plusieurs accès mémoire consécutifs. Si vous vous demandez comment plusieurs accès mémoire consécutifs peuvent avoir lieu, sachez que c'est une bonne question. Pour le moment, nous allons dire que ces accès proviennent du processeur ou de la mémoire cache, sans expliquer comment. Le contrôleur mémoire reçoit une série d'accès mémoire assez rapprochés, qu'il doit exécuter au mieux. [[File:Controleur mémoire d'une SDRAM avec entrelacement.png|centre|vignette|upright=2.5|Contrôleur mémoire d'une SDRAM avec entrelacement.]] Pour gérer l'entrelacement, le contrôleur de SDRAM prend en entrée l'adresse envoyée par le processeur, et la découpe en plusieurs champs : un pour sélectionner la banque adéquate, un autre pour la ligne, un autre pour la colonne, un autre pour la rangée. Une solution simple désactive l'entrelacement. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de banque || Adresse de ligne || Adresse de colonne |} Si cette solution fonctionne bien si les accès mémoire sont assez aléatoires, en pratique, mieux vaut utiliser l'entrelacement. ===L'entrelacement de banques/lignes=== Une première méthode d'entrelacement est l''''entrelacement de banques'''. Elle répartit deux lignes consécutives dans deux banques différentes. Pour comprendre l'idée, prenons un exemple. Imaginons une mémoire avec deux banques et 4 lignes. Imaginons qu'on parcoure/balaye la mémoire RAM en partant des adresses basses. Sans entrelacement de ligne, les accès se feront comme suit, de gauche à droite : {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Banque numéro 1 !! colspan="4" | Banque numéro 2 |- | Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 || Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 |} Avec l'entrelacement de banques, les accès se feront comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 |- | Ligne 1 || Ligne 1 || Ligne 2 || Ligne 2 || Ligne 3 || Ligne 3 || Ligne 4 || Ligne 4 |} Avec N banques, la première ligne ira dans la première banque, la seconde ligne dans la seconde, et ainsi de suite jusqu'à la énième banque qui va dans la banque numéro N. Et la ligne N+1 repart dans la première banque. L'avantage est que passer d'une ligne à la suivante est assez rapide. Au lieu de devoir fermer la première ligne et ouvrir la suivante on ouvrira directement la ligne suivante dans une banque différente, ce qui sera plus rapide. La commande PRECHARGE pourra être envoyée à la seconde banque en avance. Pour cela, l'adresse est découpée comme suit. {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne (précise à l'octet) |} ===L'entrelacement de rangées=== Il est aussi possible de faire la même chose, mais avec les rangées. Pour simplifier fortement, une rangée est simplement un chip mémoire. En réalité, les rangées ne sont pas des chip mémoire, mais un ensemble de chips mémoire regroupés ensemble histoire d'atteindre les 64 bits du bus de données. Par exemple, une rangée peut combiner 8 chips mémoire avec un bus de données de 8 bits chacun pour obtenir les 64 bits du bus de données d'une SDRAM. Mais nous passons ce détail sous silence dans les explications qui vont suivre, par souci de simplification. Pour faire comprendre l'entrelacement de rangée, le mieux est d'utiliser un exemple, le même que précédemment. Sans entrelacement de rangée, on change de chip mémoire une fois qu'on a balayé toutes ses adresses. {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Rangée numéro 1 !! colspan="4" | Rangée numéro 2 |- | Banque 1 || Banque 2 || Banque 3 || Banque 4 || Banque 1 || Banque 2 || Banque 3 || Banque 4 |} Avec l'entrelacement de ligne, on change de rangée dès qu'on change de banque/ligne. {|class="wikitable" |+ Adresse mémoire |- ! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 |- | Banque 1 || Banque 1 || Banque 2 || Banque 2 || Banque 3 || Banque 3 || Banque 4 || Banque 4 |} En clair, quand on a épuisé toutes les banques dans une même rangée, on passe à la rangée suivante au lieu de rester dans la même rangée. Notons que dans l'exemple précédent, on a combiné l'entrelacement de rangée et de banque, mais on aurait pu utiliser l'entrelacement de rangée seul. Mais ce n'est pas le cas le plus courant en pratique. Toujours est-il qu'en combinant entrelacement de rangée et de ligne, le découpage de l'adresse est le suivant : {|class="wikitable" |+ Adresse mémoire |- | Adresse de ligne || Numéro de rangée || Adresse de banque || Adresse de colonne (précise à l'octet) |} D'autres méthodes d'entrelacement sont possibles, mais elles sont plus complexes. Chaque contrôleur mémoire fait un peu à sa sauce. Ils permettent en général de choisir entre plusieurs entrelacements. Au minimum, ils permettent de désactiver l'entrelacement, d'activer un entrelacement de ligne, et d'autres entrelacements plus complexes, dont certains sont propriétaires et tenus secrets par le fabricant. ===L'entrelacement avec le ''dual channel''=== [[File:Dual channel slots.jpg|vignette|Slots mémoires avec ''dual channel''.]] L'usage du ''dual channel'' complique encore l'entrelacement. Là encore, il y a deux grandes solutions : avec et sans entrelacement des canaux mémoire. Rappelons le principe : deux barrettes de RAM sont accédées en parallèle. Et pour cela, il faut utiliser l'entrelacement. Typiquement, chaque barrette mémoire fournit 64 bits, ce qui fait que l'on peut accéder à 128 bits d'un seul coup, par exemple avec un accès en rafale. Sans ''dual channel'', la première barrette correspond à la moitié haute de la RAM, la seconde barrette correspond à la moitié basse. Avec ''dual channel'', une forme spécifique d'entrelacement est activée. Concrétement, deux blocs de 64 bits sont placés dans des canaux mémoire séparés. Idem avec du triple ou quadruple canal, mais c'est alors trois ou quatre blocs de 64 bits qui sont dispersés dans des canaux consécutifs. Le découpage de l'adresse est alors le suivant : {|class="wikitable" |+ Adresse mémoire |- ! colspan="6" | Sans entrelacement |- | Numéro de canal/barrette || Reste de l'adresse || Position de l'octet dans un bloc de 64 bits |- | colspan="6" | |- ! colspan="6" | Avec entrelacement |- | Reste de l'adresse || Numéro de canal/barrette || Position de l'octet dans un bloc de 64 bits |} Les contrôleurs de SDRAM précédents sont assez basiques et ne représentent pas les contrôleurs les plus évolués. Ils étaient utilisés sur les anciens PC, à une époque où ils étaient encore sur la carte mère du processeur. Mais de nos jours, le contrôleur mémoire est intégré au processeur. Et il incorpore de nombreuses optimisations afin de gagner en performances. Malheureusement, ces optimisations sont elles-mêmes dépendantes des optimisations intégrées dans le processeur, ce qui fait qu'on ne peut pas en parler ici. Elles demandent que le contrôleur mémoire reçoive plusieurs accès mémoire simultanés, ce qui n'a aucun sens à ce stade du cours. Aussi, nous allons les passer sous silence pour le moment. Nous les verrons dans le chapitre sur le parallélisme mémoire. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=La désambiguïsation mémoire | prevText=La désambiguïsation mémoire | next=Les processeurs superscalaires | nextText=Les processeurs superscalaires }} </noinclude> b6kw10c58snnu586haribp0f8z3d39o 764662 764661 2026-04-23T14:59:11Z Mewtow 31375 /* L'entrelacement par décalage */ 764662 wikitext text/x-wiki Dans ce chapitre, nous allons voir les techniques qui permettent de gérer plusieurs accès mémoire simultanés directement au niveau du cache ou de la mémoire RAM. Par plusieurs accès mémoire simultanés, vous pensez sans doute à l'usage de cache multiports, voire de mémoires RAM multiport. C'est en effet une solution, mais ce n'est clairement pas la seule. Par exemple, il est possible de pipeliner l'accès au cache, voire à la mémoire RAM. Il existe de nombreuses techniques de '''parallélisme mémoire''', et nous allons les voir dans ce chapitre. Pour donner un exemple, il est possible de pipeliner les accès mémoire. Il est possible de pipeliner l'accès au cache et/ou l'accès à la mémoire, et nous verrons comment faire dans ce chapitre. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, qu'ils aient ou non un pipeline dynamique. Par contre, pipeliner la mémoire est une technique ancienne, aujourd'hui peu utilisée. Elle était utilisée sur des très vieux ordinateurs, qui avaient un pipeline mais pas de mémoire cache. Mais de nos jours, les mémoires SDRAM et DDR ne sont pas adaptées pour ça, il est très difficile de les pipeliner correctement. Le parallélisme mémoire est utile aussi bien sur des processeurs à émission dans l'ordre que dans le désordre. Par exemple, un processeur ''in-order'' avec des lectures non-bloquantes peut e, profiter. Il peut techniquement lancer une seconde lecture, après avoir rencontré une première lecture non-bloquante. En général, le nombre de lectures consécutives est cependant limité à moins d'une dizaine. À l'opposé, les processeurs sans lectures non-bloquantes profitent pas du parallélisme mémoire. À la rigueur, ils peuvent en profiter s'ils intègrent des techniques de préchargement. Pendant que le processeur exécute une lecture/écriture, il peut précharger une autre donnée en parallèle. Inutile de dire que sans pipeline processeur, le parallélisme mémoire ne sert pas à grand-chose. ==Le ''line fill buffer'' et les techniques associées== Lors d'un défaut de cache, le processeur doit attendre que toute la ligne de cache soit chargée avant d'être utilisable. Or, la taille d'une ligne de cache est supérieure à la largeur du bus mémoire, ce qui fait qu'une ligne de cache est chargée en plusieurs fois, morceaux par morceaux, mot mémoire par mot mémoire. Le chargement peut se faire directement dans le cache, mais ce n'est pas une solution très pratique. À la place, beaucoup de processeurs ajoutent une mémoire tampon entre la RAM et le cache, appelée le '''tampon de remplissage de ligne''' (''line-fill buffer'') dans les processeurs modernes. Lors d'un défaut de cache, le processeur charge la donnée de la RAM dans le tampon de remplissage de ligne, mot mémoire par mot mémoire. Une fois plein, le tampon de remplissage de ligne est recopié dans la ligne de cache. Le tampon de remplissage de ligne contient une ligne de cache, si ce n'est que certains bits de contrôle ne sont pas présents. Le tampon de remplissage de ligne contient un bit de validité pour chaque mot mémoire de la ligne de cache, qui indiquent si le mot mémoire a été chargé. Par exemple, prenons un processeur 64 bits, qui gère donc des mots mémoire de 8 octets, avec des lignes de cache 256 octets/32 mots mémoire. Le tampon de remplissage de ligne contiendra 32 bits de validité. Si le processeur a chargé les 6 premiers mots mémoire, les 6 premiers bits de validité seront mis à 1, les autres seront encore à zéro. Il faut noter que la disponibilité de la ligne complète se détermine assez facilement en faisant un ET logique entre tous les bits de validité. Le processeur sait ainsi quand la ligne de cache est disponible entièrement et donc quand la transférer dans le cache. La même chose existe avec une hiérarchie de cache, sauf que l'on trouve une mémoire tampon entre chaque niveau de cache. Il y a un tampon de remplissage de ligne entre le cache L1 et le cache L2, entre le cache L2 et le L3, etc. Si le cache ne gère qu'un seul défaut de cache à la fois, le ''fill line buffer'' est une mémoire très simple, qui ne mémorise qu'une seule ligne de cache. Et cela vaut aussi bien pour un cache bloquant que non-bloquant. Mais sur les caches capables de gérer plusieurs défauts simultanés, le tampon de remplissage de ligne est une mémoire de type FIFO ou LIFO, capable de mémoriser plusieurs lignes de cache. Notons que le tampon de remplissage de ligne est très utile pour implémenter certaines techniques, par exemple le contournement du cache. Nous avions vu dans le chapitre sur le cache que certains accès mémoire doivent contourner le cache, pour des raisons de cohérence des caches. C'est notamment nécessaire pour accéder aux périphériques, mais c'est aussi utile pour des raisons de performances dans des cas très spécifiques. Les accès qui contournent le cache se font directement dans le tampon de remplissage de ligne : le processeur écrit ou lit les données depuis ce tampon de remplissage de ligne, sans accéder au cache. ===L'''early restart'' et le ''critical word load''=== La présence du ''line buffer'' permet une optimisation assez intéressante, qui permet de réduire la latence des défauts de cache. L'optimisation consiste à lire un mot mémoire dans le tampon de remplissage de ligne, même si la ligne de cache complète n'a pas encore été chargée. Il existe deux manières de faire cela, qui portent les noms d'''early restart'' et de ''critical word load''. La première est la version la plus simple, la seconde est plus complexe mais plus performante. Avec la technique d''''''early restart''''', la ligne de cache est chargée normalement, en partant de son premier mot mémoire. Dès que le mot mémoire lu/écrit par le processeur est copié dans le ''line fill buffer'', il est envoyé au processeur immédiatement. Illustrons le tout par un exemple, où une ligne de cache fait 16 mots mémoire. Le processeur effectue une lecture, qui lit le 5ème mot mémoire. Sans ''early restart'', le processeur doit charger les 16 mots mémoire avant de faire la lecture dans le cache. Avec ''early restart'', le processeur reçoit la donnée dès que le 5ème mot mémoire est disponible. Le processeur doit attendre que les 4 premiers mots mémoire soient chargés, puis le 5ème mot mémoire arrive et est envoyé directement au processeur, il est lu directement depuis le ''line fill buffer''. Les 11 mots mémoire suivants sont ensuite chargés dans le cache pendant que le processeur fait des calculs dans son coin. Le ''critical word load'' est une optimisation de la technique précédente où le chargement de la ligne de cache commence directement à la donnée demandée par le processeur. Pour reprendre l'exemple précédent, où le processeur demande le 5ème mot mémoire sur 16, le ''critical word load'' charge le 5ème mot mémoire en premier et l'envoie au processeur, ce qui fait qu'il est chargé très rapidement. Pas besoin d'attendre que le processeur charge les 4 mots mémoire précédents comme avec l'''early restart''. Dans le détail, le processeur charge le 5ème mot mémoire en premier, puis charge les 11 suivants, et termine par les 4 mots mémoire du début. En clair, le ''critical word load'' commence par charger le mot mémoire lu, puis les blocs suivants, avant de revenir au début du bloc pour charger les blocs restants. Ainsi, la donnée demandée par le processeur sera la première disponible. Pour cela, l'organisation du tampon de remplissage de ligne est modifiée de manière à rendre cela possible. Il a une taille égale à une ligne de cache complète, qui contient elle-même plusieurs mots mémoire. Dans le ''line-fill buffer'', chaque mot mémoire est stocké avec un tag, qui indique l'adresse du mot mémoire stocké dans le ''line-fill buffer''. Le ''line-fill buffer'' est donc un cache un peu particulier, qui fonctionne comme un cache du point de vue du processeur, comme une mémoire FIFO pour les transferts avec le cache. Ainsi, un processeur qui veut lire dans le cache après un défaut peut accéder à la donnée directement depuis le tampon de remplissage de ligne, alors que la ligne de cache n'a pas encore été totalement recopiée en mémoire. ==Les caches pipelinés== Il est possible de pipeliner l'accès au cache, ce qui demande juste de rajouter des registres au bon endroits et quelques circuits de contrôle. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, même ceux avec un pipeline dynamique. Et l'implémentation peut se faire de plusieurs manières différentes. Il faut dire que découper une mémoire cache en plusieurs étapes peut se faire de plusieurs manière. Commençons par la plus simple. Beaucoup de processeurs des années 2000 avaient une fréquence peu élevée comparée aux standards d'aujourd'hui, ce qui fait que leur cache avait bien un temps d'accès d'un cycle d'horloge. Mais l'accès au cache se faisait en deux cycles d'horloge : un cycle pour calculer une adresse et un cycle pour l'accès mémoire proprement dit. Et ces deux étapes étaient pipelinées, à savoir que deux micro-opérations mémoire peuvent s'exécuter en même temps : une dans l'étage de calcul d'adresse, une autre dans le cache. L'unité mémoire était donc pipelinée, alors que l'accès au cache ne l'était pas. Les processeurs qui implémentaient cette technique regroupent les micro-architectures K5 et K6 d'AMD, les processeurs Intel de micro-architecture P6 (Pentium 2 et 3) et quelques autres. Il est aussi possible de pipeliner l'accès au cache lui-même. Avec un cache pipeliné, l'accès au cache ne se fait pas en un seul cycle, mais en plusieurs. Cependant, on peut lancer un nouvel accès au cache à chaque cycle d'horloge, comme un processeur pipeliné lance une nouvelle instruction à chaque cycle. L'implémentation est assez simple : il suffit d'ajouter des registres dans le cache. Pour cela, on profite que le cache est composé de plusieurs composants séparés, qui échangent des données dans un ordre bien précis, d'un composant à un autre. De plus, le trajet des informations dans un cache est linéaire, ce qui les rend parfois pour l'usage d'un pipeline. ===Le ''pipelining'' des caches ''direct-mapped''=== Voici ce qui se passe avec un cache directement adressé. Pour rappel ce genre de cache est conçu en combinant une mémoire RAM, généralement une SRAM, avec quelques circuits de comparaison et un MUX. L'accès au cache se fait globalement en deux étapes : la première lit la donnée et le ''tag'' dans la SRAM, et on utilise les deux dans une seconde étape. Nous avions vu il y a quelques chapitre comment pipeliner des mémoires, dans le chapitre sur les mémoires évoluées. Et bien ces méthodes peuvent s'utiliser pour la mémoire RAM interne au cache !L'idée est d'insérer un registre entre la sortie de la RAM et la suite du cache, pour en faire un cache pipéliné, à deux étages. Le premier étage lit dans la SRAM, le second fait le reste. L'implémentation sur les caches associatifs à plusieurs voies est globalement la même à quelques détails près. [[File:Cache directement adressé pipeliné - deux étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - deux étages]] Avec le circuit précédent, il est possible d'aller plus loin, cette fois en pipelinant l'accès à la mémoire RAM interne au cache. Pour comprendre comment, rappelons qu'une mémoire SRAM est composée d'un plan mémoire et d'un décodeur. L'accès à la mémoire demande d'abord que le décodeur fasse son travail pour sélectionner la case mémoire adéquate, puis ensuite la lecture ou écriture a lieu dans cette case mémoire. L'accès se fait donc en deux étapes successives séparées, on a juste à mettre un registre entre les deux. Il suffit donc de mettre un gros registre entre le décodeur et le plan mémoire. [[File:Cache directement adressé pipeliné - trois étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - trois étages]] Et on peut aller encore plus loin en découpant le décodeur en deux circuits séparés. En effet, rappelez-vous le chapitre sur les circuits de sélection : nous avions vu qu'il est possible de créer des décodeurs en assemblant des décodeurs plus petits, contrôlés par un circuit de prédécodage. Et bien on peut encore une ajouter un registre entre ce circuit de prédécodage et les petits décodeurs. : Théoriquement, toute l'adresse est fournie d'un seul coup au cache, la quasi-totalité des processeurs présentent l'adresse complète à un cache pipeliné. Mais le Pentium 4 fait autrement. Il faut noter que les premiers étages manipulent l'indice dans la SRAM, qui est dans les bits de poids faible, alors que les étages ultérieurs manipulent le ''tag'' qui est dans les bits de poids fort. Les concepteurs du Pentium 4 ont alors décidé de présenter les bits de poids faible lors du premier cycle d'accès au cache, puis ceux de poids fort au second cycle. Pour cela, l'ALU fonctionnait à une fréquence double de celle du processeur, tout comme le cache L1. Il n'y avait pas de pipeline proprement dit, mais cela réduisait grandement la latence d'accès au cache. ===Le ''pipelining'' des caches splittés/sériels=== Il est aussi possible de pipeliner un cache dits splittés, aussi appelés à accès sériel. Pour rappel, les caches à accès sériel vérifient si il y a succès ou défaut de cache, avant d'accéder aux lignes de cache en cas de succès. Ils font donc différemment des autres caches, qui accèdent à une ligne de cache, avant de déterminer s'il y a succès ou défaut en lisant le tag de la ligne de cache. Les caches sériels disposent de deux SRAM : une pour les tags des lignes de cache et une pour les données. Ils accèdent à la SRAM pour les tags, avant d’accéder à la SRAM des données en cas de succès de cache. Vu que l'accès se fait en deux étapes, une vérification des tags suivie de la lecture/écriture des données, il est facile à pipeliner. Pipeliner le cache permet de régler le problème des accès au cache L1, et elle est tout le temps utilisé sur les processeurs modernes. Mais que faire en cas de défaut de cache ? ==Les caches non bloquants== Un '''cache bloquant''' est un cache auquel le processeur ne peut pas accéder pendant un défaut de cache. Il faut attendre que la lecture ou écriture en RAM soit terminée avant de pouvoir utiliser de nouveau le cache. Un '''cache non bloquant''' n'a pas ce problème : on peut l'utiliser pendant un défaut de cache. Les caches non bloquants permettent de démarrer une nouvelle lecture ou écriture alors qu'une autre est en cours, ce qui permet d'exécuter plusieurs lectures ou écritures en même temps. ===Les ''Miss Handling Status Registers''=== Lors d'un défaut de cache, la mémoire RAM est consultée pendant le défaut de cache, mais le cache est inutilisé. Un défaut de cache n'utilise pas le cache, ce n'est qu'un accès en mémoire RAM, sur le bus mémoire (ou un accès aux niveaux de cache inférieurs, peu importe). Le cache en lui-même est laissé libre, rien n’empêche d'y accéder, il est en réalité intrinsèquement non-bloquant. Les caches, bloquants comme non-bloquants, sont en réalité composés d'une mémoire cache proprement dite, entourée de circuits qui servent d'interface entre le processeur et le cache lui-même. Et parmi les circuits tout autour, certains gèrent l'accès au cache lors d'un défaut de cache. Ils sont regroupés sous le terme de '''''Miss Handling Architecture''''' (MHA). La différence entre un cache bloquant et un cache non-bloquant est en réalité liée à l'implémentation de la MHA. Les caches bloquants coupent volontairement l'accès au cache lors d'un défaut, car les défauts sont plus simples à gérer ainsi. Le défaut de cache rapatrie une donnée depuis la RAM, qui sera écrite dans le cache. Et il ne faut pas qu'une tentative d'accès à cette donnée ait lieu avant qu'elle ne soit chargée. Mais ce blocage est général et touche tout le cache, alors que seule une ligne de cache est concernée par le défaut de cache. L'idée derrière un cache non-bloquant est que seule la ligne de cache est bloquée, mais les autres sont accessibles. L'idée est alors de mémoriser les lignes de cache concernées par le défaut de cache, afin d'en bloquer l'accès. À chaque accès, on vérifie si la ligne de cache est déjà réservée par un défaut de cache. La lecture/écriture est alors bloquée si c'est le cas, mais elle accepte les accès sinon. Pour cela, la ''Miss Handling Architecture'' contient des registres qui mémorisent des informations sur les défauts de cache en cours. Ils portent le nom de '''''miss status handling registers''''', que j’appellerais dorénavant MSHR, qui sont aussi appelés des '''''miss buffer'''''. Le contenu des MSHR varie beaucoup suivant le processeur, mais ces derniers stockent au minimum les informations suivantes : * Le numéro de la ligne de cache dans laquelle les données sont chargées. * Un bit de validité qui indique si le MSHR est vide ou pas, qui est mis à 0 quand le défaut de cache est résolu. * Un ou plusieurs champs de lecture/écriture, qui contiennent des informations sur la lecture/écriture. ** Pour une lecture, elle contient des informations sur la destination de la donnée, à savoir qui prévenir quand le défaut de cache est terminé. C'est parfois un nom/numéro de registre (celui dans lequel charger la donnée), mais c'est souvent le numéro de l'entrée dans le ''load/store queue''. ** Pour les écritures, elle contient la donnée à écrire, ou éventuellement un numéro de ''load/store queue'' où se trouve la donnée à écrire. Il faut noter que le bus mémoire ne peut gérer qu'un seul défaut de cache à la fois. Aussi, il est intéressant de regarder ce qui se passe lorsqu'un second défaut de cache survient, pendant qu'un premier est en cours. Dans ce cas, il y a deux réponses qui correspondent à deux types de caches non-bloquants, qui portent les noms barbares de caches de type succès après défaut et défaut après défaut. Sur le premier type, il ne peut pas y avoir plusieurs défauts de cache simultanés. Dès qu'un second défaut de cache survient, le cache stoppe son activité et on ne peut plus démarrer de nouvelle lecture/écriture, tant que le premier défaut de cache n'est pas résolu. Le second type est plus souple et autorise la survenue de plusieurs défauts de cache simultanés. Du moins, jusqu'à une certaine limite, car le cache ne peut supporter qu'un nombre limité d'accès mémoires simultanés (pipelinés). ===Les accès simultanés à une même ligne de cache=== Il arrive que le processeur fasse plusieurs accès mémoire simultanés à la même ligne de cache. Si la ligne de cache en question n'a pas encore été chargée dans le cache, alors on a plusieurs défauts de cache consécutifs pour la même ligne de cache, et le cache non-bloquant doit gérer la situation. Pour la suite, il va falloir faire une petite distinction entre les défauts primaires et secondaires. Imaginons qu'un défaut de cache ait lieu et demande à charger une donnée dans la ligne de cache numéro N. Il s'agit du premier défaut impliquant cette ligne de cache précise, ce qui lui vaut le nom de '''défaut de cache primaire'''. Mais par la suite, d'autres accès mémoire à la même ligne de cache ont lieu, alors que la ligne de cache n'est pas encore disponible. Dans ce cas, il s'agit de '''défauts de cache secondaires'''. Pour l'unité d'accès mémoire, les défauts de cache primaire et secondaire sont différents (ils prennent tous une entrée dans la ''load/store queue''). Mais pour le cache, ils ne correspondent qu'à un seul accès au cache : celui qui demande de charger la ligne de cache demandée. Les défauts de cache primaire et secondaire à la même ligne de cache se voient attribuer un MSHR unique. La gestion des défauts de cache secondaires dépend du cache non-bloquant. La solution la plus simple ne permet pas les défauts de cache secondaires. Le cache ne permet pas deux défauts de cache simultanés pour la même ligne de cache. Les autres solutions le permettent, en fusionnant des accès simultanés à la même ligne de cache en un seul au niveau des MSHR. Dans tous les cas, détecter les défauts de cache secondaires sont un problème qu'il faut détecter. La ''Miss Handling Architecture'' doit détecter les défauts de cache secondaire. Pour cela, elle procède comme suit. Lors de chaque défaut de cache, la MHA récupère le numéro de la ligne de cache associé. Il vérifie alors chaque MSHR pour vérifier s'il contient le numéro en question. S'il n'y a aucune correspondance dans les MSHR, alors c'est signe que le défaut de cache est un défaut primaire. Mais s'il y en a une, alors c'est un défaut secondaire. Évidemment, cela signifie que lors d'un défaut de cache, le numéro de ligne de cache est envoyé à tous les MSHR, pour comparaison. Les MSHR sont donc regroupés dans une mémoire associative, une sorte de mini-cache, faciliter l'implémentation. Lors d'un défaut de cache primaire, l'accès à un cache non-bloquant se fait comme suit : le processeur envoie une adresse au cache, accède à celui-ci, et détecte la survenue d'un défaut de cache. Il en profite alors pour attribuer une ligne de cache dans laquelle sera chargée ce défaut. L'attribution est très simple dans le cas des caches ''direct mapped'', ou associatifs par voie, pour lesquels l'attribution se fait assez simplement. Il mémorise alors cette information dans les MSHR, après avoir vérifié que le défaut de cache n'était pas un défaut secondaire. Lors des accès ultérieurs à une adresse proche, censée être dans la même ligne de cache, le processeur va encore une fois rencontrer un défaut de cache. Il va alors déterminer le numéro de la ligne de cache associée à l'adresse, et comparer ce numéro avec les MSHRs. Si un MSHR contient ce numéro, c'est signe que le défaut de cache est un défaut secondaire. La MHA réagit alors différemment selon le processeur considéré. Une première solution n'autorise pas les défauts de cache secondaires. Si l'un d'entre eux survient, le processeur est gelé par un ''pipeline stall'', une bulle de pipeline. Une autre solution fusionne les défauts de cache secondaires avec le défaut de cache primaire : tout cela ne correspond qu'à un seul défaut de cache pour lui. ===Les MSHR simples=== Un cache non-bloquant à '''MSHR simple''' contient juste plusieurs MSHR qui mémorisent juste un numéro de ligne de cache, un bit de validité, le champ de lecture/écriture, et l'adresse exacte de lecture/écriture. Avec cette organisation, il est possible d'avoir plusieurs défauts de cache séparés, mais à la condition que chaque défaut accède à une ligne de cache différente. Deux accès simultanés à une même ligne de cache ne sont pas possibles, les défauts de cache secondaires ne sont pas autorisés. Ainsi, chaque défaut de cache se voit attribuer son propre MSHR, chacun contient un numéro de ligne de cache différent. Pour comprendre pourquoi c'est impossible de gérer les défauts secondaires, il faut regarder le champ de lecture/écriture. Si on veut effectuer plusieurs écritures consécutives à la même adresse, le MSHR n'aura pas de quoi mémoriser les deux données à écrire. Il pourra mémoriser la première donnée à écrire, pas la seconde. Même chose lors d'une lecture : le champ lecture/écriture peut mémorisr la destination de la première lecture, pas de la seconde. L'avantage est que la MHA n'a besoin que des MSHR et de quelques circuits annexes. Les autres solutions rajoutent des circuits annexes pour gérer les défauts de cache secondaires, qui utilisent beaucoup de circuits. Le cout en circuit est donc élevé, mais le gain en performance est là. Passons maintenant aux caches non-bloquants qui autorisent les défauts de cache secondaire. La solution la plus simple consiste à utiliser ===Les MSHR adressés implicitement=== Avec les '''MSHR adressés implicitement''', il est possible de fusionner plusieurs accès mémoire à une même ligne de cache, mais sous une condition très importante : ces accès lisent/écrivent des mots mémoire différents. Par exemple, imaginons qu'une ligne de cache contienne 8 mots mémoire de 64 bits. Si un premier accès mémoire lit le mot mémoire numéro 7 (dernier mot mémoire de la ligne), et le second accès le mot mémoire numéro 3, alors la fusion est possible. Mais si deux accès mémoire veulent lire/écrire le mot mémoire numéro 7, alors la fusion n'est pas possible et le processeur se bloque, un ''pipeline stall'' survient. Un MSHR adressé implicitement est un MSHR simple, qui contient naturellement un numéro de ligne de cache (tag) et un bit de validité, sauf que le champ de lecture/écriture est dupliqué. Les différents champs lecture/écriture d'un MSHR sont regroupés dans une mémoire RAM/cache qui contient autant d'entrées qu'il y a de mot mémoire dans une ligne de cache. Chaque entrée de la table est associée à un mot mémoire de la ligne de cache et stocke des informations sur celui-ci. Une entrée mémorise, au minimum : * Un champ de lecture/écriture qui contient soit la destination de la lecture, soit la donnée à écrire. * Un bit R/W permettant d'interpréter correctement le champ de lecture/écriture. * Un bit de validité pour chaque entrée de la table, qui dit si un défaut de cache antérieur accède déjà à ce mot mémoire. La fusion de deux défauts de cache est ainsi assez simple. Un défaut de cache primaire/secondaire configure l'entrée associée au mot mémoire lu/écrit. Si un défaut secondaire ultérieur lit/écrit un mot mémoire différent (dont le bit de validité de l'entrée dans la table est à zéro), pas de conflit : il configure une autre entrée, vide. Mais s'il lit/écrit un mot mémoire pour lequel l'entrée de la table est occupée, il y a conflit, le processeur est bloqué par un ''pipeline stall'', une bulle de pipeline. Avec cette organisation, le nombre de MSHR indique combien de lignes de cache peuvent être lues en même temps depuis la mémoire. Quant au nombre d'entrées par MSHR, il détermine combien d'accès mémoires qui ne se recouvrent pas peuvent avoir lieu en même temps. ===Les MSHR adressés explicitement=== Les '''MSHR adressés explicitement''' n'ont pas les contraintes des MSHR adressés implicitement. Avec eux, il est possible d'avoir plusieurs défauts de cache pointant vers la même ligne de cache, mais aussi vers le même mot mémoire. De tels défauts de cache apparaissent sur les processeurs à exécution dans le désordre, mais sont très rares, voire inexistants, sur les processeurs ''in-order''. L'idée est encore que chaque MSHR est associée à une table mémoire qui mémorise des entrées. Cette table n'est autre qu'une mémoire FIFO. Sauf que cette fois-ci, une entrée n'est pas associée à un mot mémoire. Une entrée est associée à un défaut de cache. Une entrée mémorise là encore la destination de la lecture ou la donnée de l'écriture suivi du bit R/W, ainsi que d'un bit de validité par entrée, mais aussi : la position du mot mémoire lu dans la ligne de cache. C'est cette dernière information qui n'était pas présente dans les MSHR adressés implicitement. De plus, le nombre d'entrée par MSHR n'est pas égal au nombre de mots mémoires dans une ligne de cache, mais peut être arbitrairement grand. L'avantage d'un tel cache est qu'il est capable de traité les défauts secondaires concernant un même mot mémoire, et ce, car les accès à la ligne de cache concernée se produisent dans l'ordre de génération des défauts (la table mémoire est une FIFO). ===Les MSHRs inversés=== Généralement, plus on veut supporter de défauts de cache, plus le nombre de MSHR et d'entrées augmente. Mais au-delà d'un certain nombre d'entrées et de MSHR, les MSHR adressés implicitement et explicitement ont tendance à bouffer un peu trop de circuits. Utiliser une organisation un peu moins gourmande en circuits est donc une nécessité. Cette organisation plus économe se base sur des MSHR inversés. Les MSHR inversés ne contiennent qu'une seule entrée, en quelque sorte : au lieu d’utiliser n MSHR de m entrées chacun, on va utiliser n × m MSHR inversés. La différence, c'est que plusieurs MSHR peuvent contenir un tag identique, contrairement aux MSHR adressés implicitement et explicitement. Lorsqu'un défaut de cache a lieu, chaque MSHR est vérifié. Si jamais aucun MSHR ne contient de tag identique à celui utilisé par le défaut, un MSHR vide est choisi pour stocker ce défaut, et une requête de lecture en mémoire est lancée. Dans le cas contraire, un MSHR est réservé au défaut de cache, mais la requête n'est pas lancée. Quand la donnée est disponible, les MSHR correspondant à la ligne qui vient d'être chargée vont être utilisés un par un pour résoudre les défauts de cache en attente. ===Les MSHR intégrés au cache=== Certains chercheurs ont remarqué que pendant qu'une ligne de cache est en train de subir un défaut de cache, celle-ci reste inutilisée, et son contenu est destiné à être perdu une fois le défaut de cache résolu. Ils se sont dit que, plutôt que d'utiliser des MSHR séparés, il vaudrait mieux utiliser la ligne de cache pour stocker les informations sur les défauts de cache en attente dans cette ligne de cache. Pour éviter tout problème, il faut rajouter un bit dans les bits de contrôle de la ligne de cache, qui sert à indiquer que la ligne de cache est occupée : un défaut de cache a eu lieu dans cette ligne, et elle stocke donc des informations pour résoudre les défauts de cache. ==L'entrelacement mémoire== Nous venons de voir comment une mémoire cache peut gérer plusieurs accès mémoire simultanés. Il se trouve que ce genre d'optimisation dépasse largement le cadre du cache. Les mémoires RAM ne sont pas en reste, elles aussi ! Il est possible de pipeliner l'accès à une mémoire RAM, en utilisant des techniques dites d''''entrelacement'''. Précisons cependant que cette optimisation n'est pas utilisée sur les mémoires SDRAM ou DDR modernes, du moins pas sans modifications majeures. Mais divers ordinateurs assez anciens, dont des superordinateurs, ont utilisé cette technique. La technique demande d'utiliser plusieurs mémoires séparées. Sur les anciens ordinateurs, les mémoires en question sont des chips mémoire, à savoir des circuits intégrés de DRAM. Pour faire plus général, nous allons utiliser le terme de '''banques''', ou encore de bancs mémoire. La différence est qu'il existe des mémoires multi-banque, qui regroupent plusieurs banques indépendantes dans un seul boitier, dans un seul chip mémoire. En clair, de telles mémoires regroupent plusieurs sous-mémoires dans un seul circuit intégré. Il est aussi possible d'utiliser plusieurs chips mémoire séparés, chacun ayant une banque. Reste à combiner ces banques pour former un pseudo-pipeline. ===Utiliser plusieurs banques sans entrelacement=== Sans optimisation particulière, les adresses sont réparties dans les banques comme indiqué ci-dessous. Il s'agit de ce qui s'appelle un arrangement horizontal, et nous avions vu celui-ci dans le chapiutre sur les mémoires SDRAM. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Avec cet arrangement, les bits de poids fort de l'adresse sont utilisées pour sélectionner la banque adéquate, et le reste de l'adresse est envoyé sur le bus d'adresse. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Adresse de banque !! Adresse dans la banque |- | Quelques bits de poids fort || Reste de l'adresse |} L'entrelacement change cette répartition, afin d'accélérer les accès mémoire. L'entrelacement classique vise à améliorer les accès à des adresses mémoires consécutives, mais des formes plus évoluées d'entrelacement visent à optimiser des accès mémoire arbitraires. Voyons-les dans l'ordre, du plus simple au plus complexe. ===L'entrelacement classique=== Avec l''''entrelacement classique''', des adresses consécutives sont réparties dans des banques différentes. [[File:Répartition des adresses dans une mémoire interleaved.png|centre|vignette|upright=1.5|Répartition des adresses dans une mémoire interleaved.]] L'entrelacement simple permet d'accélérer les accès mémoire qui se font à des adresses consécutives. Avec l'entrelacement, chaque accès mémoire tombe dans une banque mémoire différente, ce qui fait qu'on peut démarrer un nouvel accès mémoire à chaque cycle d'horloge. Pendant qu'une banque est occupée par un accès mémoire, on démarre le suivant dans une autre banque, et ainsi de suite. Le tout se termine soit quand on a épuisé toutes les banques libres, soit quand l'accès en rafales se termine. Pas besoin d'attendre que la mémoire ait fini sa lecture/écriture avant de démarrer la lecture/écriture suivante. [[File:Pipemining mémoire.png|centre|vignette|upright=2.5|Pipelining mémoire]] L'animation suivante illustre bien le processus, avec quatres banques. [[File:Interleaving.gif|centre|vignette|upright=2.5|Pipelining mémoire]] L'entrelacement simple utilise les bits de poids faible pour sélectionner la banque, et les bits de poids fort pour la case mémoire. [[File:Adresse mémoire d'une mémoire entrelacée.png|centre|vignette|upright=2|Adresse mémoire d'une mémoire entrelacée]] Précisons que le temps d'accès mémoire ne change pas beaucoup avec l'entrelacement. Par contre, on peut faire plus d'accès mémoire simultanés. On peut démarrer un accès mémoire par cycle d'horloge, mais l'accès en lui-même prend plusieurs cycles. Les mémoires à entrelacement ont donc un débit supérieur aux mémoires qui ne l'utilisent pas. L'entrelacement classique pourrait être utilisé pour accélérer les transferts entre mémoire RAM et cache, qui se font en blocs d'adresses consécutives. Mais de nos jours, il n'est pas utilisé dans cette optique. Il faut dire qu'il est redondant avec le mode rafale des mémoires DRAM. Les deux font globalement la même chose et on voit mal comment utiliser les deux en même temps. Le mode rafale étant plus simple à implémenter, et plus léger niveau utilisation du bus de commande mémoire, il est préféré à l'entrelacement. Par contre, l'entrelacement a eu son heure de gloire sur d'anciens processeurs qui n'avaient pas de mémoire cache, alors qu'ils disposaient d'un pipeline. L'exemple typique est celui des nombreux ''processeurs vectoriels'', que nous n'avons pas encore abordé à ce stade du cours. De tels processeurs avaient un pipeline, une unité de calcul fortement pipelinée, et parfois même de l'exécution dans le désordre et du renommage de registres ! ===L'entrelacement par décalage=== L'entrelacement simple est très utile pour les accès en rafle ou équivalents. Par contre, il existe de rares situations où il n'est pas optimal. Une de ces situations est celle des accès en enjambée (en ''stride''), où l'on accède en série à des adresses sont séparées par N mots mémoires. [[File:Accès par enjambées.png|centre|vignette|upright=2|Accès par enjambées.]] De tels accès surviennent quand un logiciel accède à des structures de données spécifiques, à savoir des tableaux de structures, des matrices, ou d'autres structures de données dans le genre. Un cas classique est celui du parcours d'une matrice colonne par colonne. Les matrices de nombres sont mémorisées ligne par ligne, ce qui fait que parcourir une colonne demande de passer d'une ligne à la suivante et de faire des grands sauts en mémoire RAM, mais tous espacés par la même distance. Avec l'entrelacement simple, les accès en enjambée sont moins performants que les accès à des adresses consécutives, mais cela ne fonctionne pas trop mal. Le pire cas est celui où l'on a N banques et où les données sont justement placées toutes les N adresses. Des accès consécutifs vont tous tomber dans la même banque, on ne peut plus accéder à des banques différentes en parallèle. Mais en dehors de ce cas, on voit une amélioration par rapport à la situation sans entrelacement. Pour obtenir des performances maximales pour les accès en enjambées, il faut répartir les mots mémoires dans la mémoire autrement. L'organisation idéale est la suivante. : Dans les explications qui vont suivre, la variable N représente le nombre de banques, qui sont numérotées de 0 à N-1. On commence par organiser les N premières adresses comme une mémoire entrelacée simple : l'adresse 0 correspond à la banque 0, l'adresse 1 à la banque 1, etc. Pour le bloc suivant, on décale tout d'une adresse, à savoir qu'on commence à remplir les banques à partir de la seconde, non de la première. Une fois la fin du bloc atteinte, on finit de remplir le bloc en repartant du début du bloc. Le troisième bloc d'adresse subit le même traitement, sauf qu'on commence à remplir à partir de la seconde banque. Et on poursuit l’assignation des adresses en décalant d'un cran en plus à chaque bloc. Ainsi, chaque bloc verra ses adresses décalées d'un cran en plus comparé au bloc précédent. Si jamais le décalage dépasse la fin d'un bloc, alors on reprend au début. [[File:Mémoire interleaved par décalage.png|centre|vignette|upright=1.5|Mémoire entrelacée par décalage.]] En faisant cela, on remarque que les banques situées à N adresses d'intervalle sont différentes. Dans l'exemple du dessus, nous avons ajouté un décalage de 1 à chaque nouveau bloc à remplir. Mais on aurait tout aussi bien pu prendre un décalage de 2, 3, etc. Dans tous les cas, on obtient un entrelacement par décalage. Ce décalage est appelé le pas d'entrelacement, noté P. Le calcul de l'adresse à envoyer à la banque, ainsi que la banque à sélectionner se fait en utilisant les formules suivantes : * adresse à envoyer à la banque = adresse totale / N ; * numéro de la banque = (adresse + décalage) modulo N ; * décalage = (adresse totale * P) mod N. Avec cet entrelacement par décalage, on peut prouver que la bande passante maximale est atteinte si le nombre de banques est un nombre premier. Seulement, utiliser un nombre de banques premier peut créer des trous dans la mémoire, des mots mémoires inadressables. Malgré ce défaut, la technique a été utilisée sur quelques ordinateurs, avec l'exemple notable du superordinateur ''Burroughs Scientific Processor''. Pour éviter cela, il y a plusieurs solutions. Par exemple, on peut faire en sorte que N et la taille d'une banque soient premiers entre eux : ils ne doivent pas avoir de diviseur commun. Mais en pratique, elles n'ont pas vraiment été implémentées dans une vraie machine, et sont restées à l'état de recherche, aussi je les passe sous silence. ===L'entrelacement pseudo-aléatoire=== Une dernière méthode de répartition consiste à répartir les adresses dans les banques de manière "pseudo-aléatoire". L'idée exacte est de faire passer l'adresse dans une '''fonction de hachage''', plus ou moins complexe. Pour rappel, une fonction de hachage prend une entrée de grande taille et fournit en sortie un résultat de petite taille. Idéalement, elles doivent donner des résultats aussi différents que possible pour des entrées similaires, histoire de simuler une sorte de pseudo-aléatoire. Ici, une adresse est transformée en un numéro de banque. Une fonction de hachage simple ne fait que permuter des bits de l'adresse pour obtenir son résultat. Elle ne fait qu'échanger des bits de place avant de couper l'adresse en deux morceaux : un pour la sélection de la banque, et un autre pour la sélection de l'adresse dans la banque. Cette permutation est fixe, et ne change pas suivant l'adresse. Des fonctions de hachage plus complexes font des XOR bit à bit entre certains bits de l'adresse. Il existe aussi d'autres techniques qui donnent le numéro de banque à partir d'un polynôme modulo N, appliqué sur l'adresse. ==Les contrôleurs mémoires SDRAM/DDR optimisés== Après avoir vu le cas des mémoires RAM, nous allons nous concentrer sur le cas particulier des mémoires SDRAM. Les mémoires SDRAM et DDR modernes sont capables de gérer plusieurs accès simultanés à la mémoire RAM. Et les contrôleurs mémoire intègrent des optimisations spécifiques afin d'en profiter au maximum. ===La mise en attente des accès mémoire=== Comme pour les mémoires caches, il existe des contrôleurs mémoire bloquants et non-bloquants. Les premiers n'acceptent qu'un seul accès mémoire à la fois, les seconds acceptent plusieurs accès mémoire simultanés. Pour simplifier, nous allons dire que le contrôleur mémoire n’exécute qu'un seul accès mémoire à la fois, et qu'ils met en attente les accès en trop. C'est une simplification assez irréaliste, vu que les mémoires modernes disposent de plusieurs banques accessibles en parallèle. Mais nous introduirons ce détail un peu plus tard. Avec un contrôleur mémoire de ce type, les accès mémoire sont mis en attente dans une mémoire FIFO, histoire de les exécuter dans leur ordre d'arrivée. Les accès mémoire quittent alors la mémoire dans leur ordre d'arrivée, les lectures sont renvoyées dans l'ordre demandé. En plus d'une mémoire FIFO pour les commandes mémoire, on trouve une mémoire FIFO pour les données à écrire. Elle permet d'accumuler plusieurs écritures, tout en permettant que la donnée adéquate soit envoyée à la RAM au bon moment. Il y a aussi une FIFO pour les données lues, qui sert au cas où le cache ne soit pas disponible quand on lui envoie la donnée lue. [[File:Module d'interface avec la mémoire.png|centre|vignette|upright=2.5|Contrôleur mémoire avec mise en attente des requêtes processeur et autres optimisations.]] Une optimisation regroupe plusieurs accès à des données consécutives en un seul accès en rafale. Le contrôleur mémoire analyse les requêtes mises en attente et détecte si certaines se font à des adresses consécutives. Si c'est le cas, il fusionne ces requêtes en une lecture/écriture en rafale. Une telle optimisation est appelée la '''combinaison de lecture''' pour les lectures, et la '''combinaison d'écriture''' pour les écritures. ===Le pipeline des mémoires SDRAM=== Les SDRAM ont une forme de pseudo-pipeline très limité. Elles sont parfois capables de démarrer une commande avant que la précédente soit terminée. Même si elle parait très limitée, cette possibilité est exploitée au mieux par la présence de plusieurs banques dans la mémoire SDRAM. Les chips mémoires de SDRAM sont composés de plusieurs banques, chacune étant une sorte de mini-mémoire miniature. Chaque banque a son propre décodeur, son propre tampon de ligne, ses propres multiplexeurs de colonne, sa logique de rafraichissement mémoire, etc. C'est comme si une SDRAM regroupait plusieurs mémoires séparées dans un même circuit intégré. Et cela permet de faire plusieurs accès mémoire en même temps, dans des banques différentes. Il est possible de lancer un accès mémoire dans deux banques en même temps, par exemple. Ou encore, on peut lancer un nouvel accès mémoire dans une banque si elle est inoccupée. La possibilité améliore grandement la performance de la SDRAM. Mais nous en reparlerons dans un chapitre ultérieur. {|class="wikitable" |+ Pipelining basique sur les SDRAM |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 |- ! Banque Numéro 1 | || || || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || || || |- ! Banque Numéro 2 | || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || |- ! Banque Numéro 3 | || || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || |} L'implémentation n'a rien de vraiment compliqué. Il suffit d'utiliser une mémoire FIFO par banque. Ainsi, les accès dans une même banque se feront en série, dans l'ordre d'arrivée, mais des accès à des banques différentes se feront dans le désordre. Cela ne pose pas de problème car les accès se font à des adresses différentes, ce qui fait qu'il n'y a pas de conflits majeurs. [[File:Gestion parallèle des banques.png|centre|vignette|upright=2.5|Gestion parallèle des banques.]] ===Le ré-ordonnancement des commandes mémoires=== Un contrôleur mémoire moderne est capable de changer l'ordre des requêtes mémoires, pour gagner en performances. En clair : il peut faire l'équivalent mémoire de l'exécution dans le désordre des processeurs haute performance. Une différence majeure est que le contrôleur mémoire ne remet pas les accès mémoire dans l'ordre avant de les envoyer au processeur. C'est le processeur qui remet en ordre les accès mémoire, dans sa ''load queue''. Pour cela, les requêtes mémoire sont "numérotées", à savoir qu'on leur attribue un identifiant binaire. Le contrôleur mémoire exécute les lectures/écritures dans le désordre, mais garde la trace de leur identifiant. Il sait dans quel ordre il exécute les accès mémoire et connait leur latence. Ce qui fait qu'il sait que la donnée lue à tel instant est associée à la requête mémoire numéro X. Pour les lectures, il renvoie la donnée lue avec l'identifiant. Le processeur sait alors à quelle lecture la donnée lue correspond et il se débrouille en interne pour gérer la situation. En clair : le processeur voit les accès mémoire dans le désordre, c'est lui qui les remet en ordre. Maintenant que ces précisions sont faites, posons cette question : quel est l'intérêt de faire les accès mémoire dans le désordre ? Accéder à des banques en parallèle est une réponse possible, mais le ré-ordonnancement est surtout utile si tous les accès mémoire atterrissent dans la même banque. Pour comprendre pourquoi, faisons un rappel rapide sur le tampon de ligne. Les mémoires SDRAM sont des mémoires à tampon de ligne. Elles font un accès mémoire en deux étapes. La première étape recopie une ligne de N * 64 bits dans un tampon interne à la SDRAM. La seconde étape sélectionne une colonne, à savoir une donnée de 64 bits, qui est soit envoyée sur le bus de données pour une lecture, soit modifiée par une écriture. [[File:Mémoire à tampon de ligne.png|centre|vignette|upright=2|Mémoire à tampon de ligne]] Les deux étapes correspondent à deux commandes séparées : une commande ACT qui précise l'adresse de la ligne, et une commande READ ou WRITE qui précise l'adresse de la colonne. [[File:Signaux RAS et CAS.png|centre|vignette|upright=2|Signaux RAS et CAS.]] Les SDRAM permettent de se passer de la première étape si des accès consécutifs se font dans la même ligne. Une fois activée, la ligne reste ouverte et on peut accéder plusieurs fois de suite dedans. On a alors juste à préciser l'adresse de colonne dedans. [[File:Sélection d'une ligne sur une mémoire FPM ou EDO.png|centre|vignette|upright=2|Sélection d'une ligne sur une mémoire FPM ou EDO.]] L'exécution dans le désordre des lectures/écritures SDRAM vise à faire en sorte que des accès consécutifs se fassent dans une même ligne. Elle marche si plusieurs accès à une même ligne ne sont pas consécutifs, et qu'il y a des accès entre les deux. Après ré-ordonnancement, ces accès mémoire deviennent consécutifs, ils sont exécutés l'un à la suite de l'autre. Pour rendre le tout plus concret, voici un exemple. Imaginez que l'on sait les 3 accès mémoire suivants : * Une lecture ligne A ; * Une écriture ligne B ; * Une lecture ligne A. Sans réordonnancent, on doit émettre deux commandes PRECHARGE : une quand on passe de la ligne A à la ligne B, et une autre pour le passage inverse. pour éviter cela, le contrôleur mémoire peut retarder l'écriture, ou au contraire avancer la seconde lecture. le résultat est alors le suivant : * Lecture ligne A ; * Lecture ligne A ; * Une écriture ligne B. On a donc deux accès consécutifs à la même ligne, suivi par une écriture dans une autre ligne. La technique marche parce que l'on a un mix adéquat de lectures et d'écritures. Mais il existe des possibilités de réorganisation autres qui ne sont pas valides. Il faut par exemple prendre garde à éviter d'intervertir lectures et écritures à une même adresse, sous peine de problèmes. Encore une fois, il faut faire attention aux dépendances mémoires. Le controleur mémoire teste les dépendances mémoires avant d'envoyer des commandes à la mémoire DDR/SDRAM. ==L'entrelacement sur les mémoires SDRAM== L''''entrelacement''' fonctionne sur les mémoires SDRAM, mais il doit être fortement modifié. Concrètement, tout ce qui a étré dit plus est inapplicable pour les SDRAM, du fait de la présence du tampon de ligne. Des adresses consécutives atterrissent déjà dans la même ligne, ce qui fait qu'on n'a pas besoin d'entrelacement au niveau d'une ligne. Et ne parlons pas de ce la présence de rangées et de canaux mémoire ! Cependant, il peut y avoir un entrelacement lié à la présence des banques SDRAM. L'entrelacement est géré juste avant le séquenceur mémoire. Le '''circuit d'entrelacement''' intervertit certains bits de l'adresse lors des accès mémoires. On trouve aussi des mémoires FIFOs pour mettre en attente les requêtes mémoire provenant du processeur. Elles permettent de recevoir plusieurs accès mémoire consécutifs. Si vous vous demandez comment plusieurs accès mémoire consécutifs peuvent avoir lieu, sachez que c'est une bonne question. Pour le moment, nous allons dire que ces accès proviennent du processeur ou de la mémoire cache, sans expliquer comment. Le contrôleur mémoire reçoit une série d'accès mémoire assez rapprochés, qu'il doit exécuter au mieux. [[File:Controleur mémoire d'une SDRAM avec entrelacement.png|centre|vignette|upright=2.5|Contrôleur mémoire d'une SDRAM avec entrelacement.]] Pour gérer l'entrelacement, le contrôleur de SDRAM prend en entrée l'adresse envoyée par le processeur, et la découpe en plusieurs champs : un pour sélectionner la banque adéquate, un autre pour la ligne, un autre pour la colonne, un autre pour la rangée. Une solution simple désactive l'entrelacement. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de banque || Adresse de ligne || Adresse de colonne |} Si cette solution fonctionne bien si les accès mémoire sont assez aléatoires, en pratique, mieux vaut utiliser l'entrelacement. ===L'entrelacement de banques/lignes=== Une première méthode d'entrelacement est l''''entrelacement de banques'''. Elle répartit deux lignes consécutives dans deux banques différentes. Pour comprendre l'idée, prenons un exemple. Imaginons une mémoire avec deux banques et 4 lignes. Imaginons qu'on parcoure/balaye la mémoire RAM en partant des adresses basses. Sans entrelacement de ligne, les accès se feront comme suit, de gauche à droite : {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Banque numéro 1 !! colspan="4" | Banque numéro 2 |- | Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 || Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 |} Avec l'entrelacement de banques, les accès se feront comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 |- | Ligne 1 || Ligne 1 || Ligne 2 || Ligne 2 || Ligne 3 || Ligne 3 || Ligne 4 || Ligne 4 |} Avec N banques, la première ligne ira dans la première banque, la seconde ligne dans la seconde, et ainsi de suite jusqu'à la énième banque qui va dans la banque numéro N. Et la ligne N+1 repart dans la première banque. L'avantage est que passer d'une ligne à la suivante est assez rapide. Au lieu de devoir fermer la première ligne et ouvrir la suivante on ouvrira directement la ligne suivante dans une banque différente, ce qui sera plus rapide. La commande PRECHARGE pourra être envoyée à la seconde banque en avance. Pour cela, l'adresse est découpée comme suit. {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne (précise à l'octet) |} ===L'entrelacement de rangées=== Il est aussi possible de faire la même chose, mais avec les rangées. Pour simplifier fortement, une rangée est simplement un chip mémoire. En réalité, les rangées ne sont pas des chip mémoire, mais un ensemble de chips mémoire regroupés ensemble histoire d'atteindre les 64 bits du bus de données. Par exemple, une rangée peut combiner 8 chips mémoire avec un bus de données de 8 bits chacun pour obtenir les 64 bits du bus de données d'une SDRAM. Mais nous passons ce détail sous silence dans les explications qui vont suivre, par souci de simplification. Pour faire comprendre l'entrelacement de rangée, le mieux est d'utiliser un exemple, le même que précédemment. Sans entrelacement de rangée, on change de chip mémoire une fois qu'on a balayé toutes ses adresses. {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Rangée numéro 1 !! colspan="4" | Rangée numéro 2 |- | Banque 1 || Banque 2 || Banque 3 || Banque 4 || Banque 1 || Banque 2 || Banque 3 || Banque 4 |} Avec l'entrelacement de ligne, on change de rangée dès qu'on change de banque/ligne. {|class="wikitable" |+ Adresse mémoire |- ! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 |- | Banque 1 || Banque 1 || Banque 2 || Banque 2 || Banque 3 || Banque 3 || Banque 4 || Banque 4 |} En clair, quand on a épuisé toutes les banques dans une même rangée, on passe à la rangée suivante au lieu de rester dans la même rangée. Notons que dans l'exemple précédent, on a combiné l'entrelacement de rangée et de banque, mais on aurait pu utiliser l'entrelacement de rangée seul. Mais ce n'est pas le cas le plus courant en pratique. Toujours est-il qu'en combinant entrelacement de rangée et de ligne, le découpage de l'adresse est le suivant : {|class="wikitable" |+ Adresse mémoire |- | Adresse de ligne || Numéro de rangée || Adresse de banque || Adresse de colonne (précise à l'octet) |} D'autres méthodes d'entrelacement sont possibles, mais elles sont plus complexes. Chaque contrôleur mémoire fait un peu à sa sauce. Ils permettent en général de choisir entre plusieurs entrelacements. Au minimum, ils permettent de désactiver l'entrelacement, d'activer un entrelacement de ligne, et d'autres entrelacements plus complexes, dont certains sont propriétaires et tenus secrets par le fabricant. ===L'entrelacement avec le ''dual channel''=== [[File:Dual channel slots.jpg|vignette|Slots mémoires avec ''dual channel''.]] L'usage du ''dual channel'' complique encore l'entrelacement. Là encore, il y a deux grandes solutions : avec et sans entrelacement des canaux mémoire. Rappelons le principe : deux barrettes de RAM sont accédées en parallèle. Et pour cela, il faut utiliser l'entrelacement. Typiquement, chaque barrette mémoire fournit 64 bits, ce qui fait que l'on peut accéder à 128 bits d'un seul coup, par exemple avec un accès en rafale. Sans ''dual channel'', la première barrette correspond à la moitié haute de la RAM, la seconde barrette correspond à la moitié basse. Avec ''dual channel'', une forme spécifique d'entrelacement est activée. Concrétement, deux blocs de 64 bits sont placés dans des canaux mémoire séparés. Idem avec du triple ou quadruple canal, mais c'est alors trois ou quatre blocs de 64 bits qui sont dispersés dans des canaux consécutifs. Le découpage de l'adresse est alors le suivant : {|class="wikitable" |+ Adresse mémoire |- ! colspan="6" | Sans entrelacement |- | Numéro de canal/barrette || Reste de l'adresse || Position de l'octet dans un bloc de 64 bits |- | colspan="6" | |- ! colspan="6" | Avec entrelacement |- | Reste de l'adresse || Numéro de canal/barrette || Position de l'octet dans un bloc de 64 bits |} Les contrôleurs de SDRAM précédents sont assez basiques et ne représentent pas les contrôleurs les plus évolués. Ils étaient utilisés sur les anciens PC, à une époque où ils étaient encore sur la carte mère du processeur. Mais de nos jours, le contrôleur mémoire est intégré au processeur. Et il incorpore de nombreuses optimisations afin de gagner en performances. Malheureusement, ces optimisations sont elles-mêmes dépendantes des optimisations intégrées dans le processeur, ce qui fait qu'on ne peut pas en parler ici. Elles demandent que le contrôleur mémoire reçoive plusieurs accès mémoire simultanés, ce qui n'a aucun sens à ce stade du cours. Aussi, nous allons les passer sous silence pour le moment. Nous les verrons dans le chapitre sur le parallélisme mémoire. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=La désambiguïsation mémoire | prevText=La désambiguïsation mémoire | next=Les processeurs superscalaires | nextText=Les processeurs superscalaires }} </noinclude> n7cpbj7tvg50l13da6f4r1bh46qzg0l 764663 764662 2026-04-23T14:59:44Z Mewtow 31375 /* L'entrelacement pseudo-aléatoire */ 764663 wikitext text/x-wiki Dans ce chapitre, nous allons voir les techniques qui permettent de gérer plusieurs accès mémoire simultanés directement au niveau du cache ou de la mémoire RAM. Par plusieurs accès mémoire simultanés, vous pensez sans doute à l'usage de cache multiports, voire de mémoires RAM multiport. C'est en effet une solution, mais ce n'est clairement pas la seule. Par exemple, il est possible de pipeliner l'accès au cache, voire à la mémoire RAM. Il existe de nombreuses techniques de '''parallélisme mémoire''', et nous allons les voir dans ce chapitre. Pour donner un exemple, il est possible de pipeliner les accès mémoire. Il est possible de pipeliner l'accès au cache et/ou l'accès à la mémoire, et nous verrons comment faire dans ce chapitre. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, qu'ils aient ou non un pipeline dynamique. Par contre, pipeliner la mémoire est une technique ancienne, aujourd'hui peu utilisée. Elle était utilisée sur des très vieux ordinateurs, qui avaient un pipeline mais pas de mémoire cache. Mais de nos jours, les mémoires SDRAM et DDR ne sont pas adaptées pour ça, il est très difficile de les pipeliner correctement. Le parallélisme mémoire est utile aussi bien sur des processeurs à émission dans l'ordre que dans le désordre. Par exemple, un processeur ''in-order'' avec des lectures non-bloquantes peut e, profiter. Il peut techniquement lancer une seconde lecture, après avoir rencontré une première lecture non-bloquante. En général, le nombre de lectures consécutives est cependant limité à moins d'une dizaine. À l'opposé, les processeurs sans lectures non-bloquantes profitent pas du parallélisme mémoire. À la rigueur, ils peuvent en profiter s'ils intègrent des techniques de préchargement. Pendant que le processeur exécute une lecture/écriture, il peut précharger une autre donnée en parallèle. Inutile de dire que sans pipeline processeur, le parallélisme mémoire ne sert pas à grand-chose. ==Le ''line fill buffer'' et les techniques associées== Lors d'un défaut de cache, le processeur doit attendre que toute la ligne de cache soit chargée avant d'être utilisable. Or, la taille d'une ligne de cache est supérieure à la largeur du bus mémoire, ce qui fait qu'une ligne de cache est chargée en plusieurs fois, morceaux par morceaux, mot mémoire par mot mémoire. Le chargement peut se faire directement dans le cache, mais ce n'est pas une solution très pratique. À la place, beaucoup de processeurs ajoutent une mémoire tampon entre la RAM et le cache, appelée le '''tampon de remplissage de ligne''' (''line-fill buffer'') dans les processeurs modernes. Lors d'un défaut de cache, le processeur charge la donnée de la RAM dans le tampon de remplissage de ligne, mot mémoire par mot mémoire. Une fois plein, le tampon de remplissage de ligne est recopié dans la ligne de cache. Le tampon de remplissage de ligne contient une ligne de cache, si ce n'est que certains bits de contrôle ne sont pas présents. Le tampon de remplissage de ligne contient un bit de validité pour chaque mot mémoire de la ligne de cache, qui indiquent si le mot mémoire a été chargé. Par exemple, prenons un processeur 64 bits, qui gère donc des mots mémoire de 8 octets, avec des lignes de cache 256 octets/32 mots mémoire. Le tampon de remplissage de ligne contiendra 32 bits de validité. Si le processeur a chargé les 6 premiers mots mémoire, les 6 premiers bits de validité seront mis à 1, les autres seront encore à zéro. Il faut noter que la disponibilité de la ligne complète se détermine assez facilement en faisant un ET logique entre tous les bits de validité. Le processeur sait ainsi quand la ligne de cache est disponible entièrement et donc quand la transférer dans le cache. La même chose existe avec une hiérarchie de cache, sauf que l'on trouve une mémoire tampon entre chaque niveau de cache. Il y a un tampon de remplissage de ligne entre le cache L1 et le cache L2, entre le cache L2 et le L3, etc. Si le cache ne gère qu'un seul défaut de cache à la fois, le ''fill line buffer'' est une mémoire très simple, qui ne mémorise qu'une seule ligne de cache. Et cela vaut aussi bien pour un cache bloquant que non-bloquant. Mais sur les caches capables de gérer plusieurs défauts simultanés, le tampon de remplissage de ligne est une mémoire de type FIFO ou LIFO, capable de mémoriser plusieurs lignes de cache. Notons que le tampon de remplissage de ligne est très utile pour implémenter certaines techniques, par exemple le contournement du cache. Nous avions vu dans le chapitre sur le cache que certains accès mémoire doivent contourner le cache, pour des raisons de cohérence des caches. C'est notamment nécessaire pour accéder aux périphériques, mais c'est aussi utile pour des raisons de performances dans des cas très spécifiques. Les accès qui contournent le cache se font directement dans le tampon de remplissage de ligne : le processeur écrit ou lit les données depuis ce tampon de remplissage de ligne, sans accéder au cache. ===L'''early restart'' et le ''critical word load''=== La présence du ''line buffer'' permet une optimisation assez intéressante, qui permet de réduire la latence des défauts de cache. L'optimisation consiste à lire un mot mémoire dans le tampon de remplissage de ligne, même si la ligne de cache complète n'a pas encore été chargée. Il existe deux manières de faire cela, qui portent les noms d'''early restart'' et de ''critical word load''. La première est la version la plus simple, la seconde est plus complexe mais plus performante. Avec la technique d''''''early restart''''', la ligne de cache est chargée normalement, en partant de son premier mot mémoire. Dès que le mot mémoire lu/écrit par le processeur est copié dans le ''line fill buffer'', il est envoyé au processeur immédiatement. Illustrons le tout par un exemple, où une ligne de cache fait 16 mots mémoire. Le processeur effectue une lecture, qui lit le 5ème mot mémoire. Sans ''early restart'', le processeur doit charger les 16 mots mémoire avant de faire la lecture dans le cache. Avec ''early restart'', le processeur reçoit la donnée dès que le 5ème mot mémoire est disponible. Le processeur doit attendre que les 4 premiers mots mémoire soient chargés, puis le 5ème mot mémoire arrive et est envoyé directement au processeur, il est lu directement depuis le ''line fill buffer''. Les 11 mots mémoire suivants sont ensuite chargés dans le cache pendant que le processeur fait des calculs dans son coin. Le ''critical word load'' est une optimisation de la technique précédente où le chargement de la ligne de cache commence directement à la donnée demandée par le processeur. Pour reprendre l'exemple précédent, où le processeur demande le 5ème mot mémoire sur 16, le ''critical word load'' charge le 5ème mot mémoire en premier et l'envoie au processeur, ce qui fait qu'il est chargé très rapidement. Pas besoin d'attendre que le processeur charge les 4 mots mémoire précédents comme avec l'''early restart''. Dans le détail, le processeur charge le 5ème mot mémoire en premier, puis charge les 11 suivants, et termine par les 4 mots mémoire du début. En clair, le ''critical word load'' commence par charger le mot mémoire lu, puis les blocs suivants, avant de revenir au début du bloc pour charger les blocs restants. Ainsi, la donnée demandée par le processeur sera la première disponible. Pour cela, l'organisation du tampon de remplissage de ligne est modifiée de manière à rendre cela possible. Il a une taille égale à une ligne de cache complète, qui contient elle-même plusieurs mots mémoire. Dans le ''line-fill buffer'', chaque mot mémoire est stocké avec un tag, qui indique l'adresse du mot mémoire stocké dans le ''line-fill buffer''. Le ''line-fill buffer'' est donc un cache un peu particulier, qui fonctionne comme un cache du point de vue du processeur, comme une mémoire FIFO pour les transferts avec le cache. Ainsi, un processeur qui veut lire dans le cache après un défaut peut accéder à la donnée directement depuis le tampon de remplissage de ligne, alors que la ligne de cache n'a pas encore été totalement recopiée en mémoire. ==Les caches pipelinés== Il est possible de pipeliner l'accès au cache, ce qui demande juste de rajouter des registres au bon endroits et quelques circuits de contrôle. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, même ceux avec un pipeline dynamique. Et l'implémentation peut se faire de plusieurs manières différentes. Il faut dire que découper une mémoire cache en plusieurs étapes peut se faire de plusieurs manière. Commençons par la plus simple. Beaucoup de processeurs des années 2000 avaient une fréquence peu élevée comparée aux standards d'aujourd'hui, ce qui fait que leur cache avait bien un temps d'accès d'un cycle d'horloge. Mais l'accès au cache se faisait en deux cycles d'horloge : un cycle pour calculer une adresse et un cycle pour l'accès mémoire proprement dit. Et ces deux étapes étaient pipelinées, à savoir que deux micro-opérations mémoire peuvent s'exécuter en même temps : une dans l'étage de calcul d'adresse, une autre dans le cache. L'unité mémoire était donc pipelinée, alors que l'accès au cache ne l'était pas. Les processeurs qui implémentaient cette technique regroupent les micro-architectures K5 et K6 d'AMD, les processeurs Intel de micro-architecture P6 (Pentium 2 et 3) et quelques autres. Il est aussi possible de pipeliner l'accès au cache lui-même. Avec un cache pipeliné, l'accès au cache ne se fait pas en un seul cycle, mais en plusieurs. Cependant, on peut lancer un nouvel accès au cache à chaque cycle d'horloge, comme un processeur pipeliné lance une nouvelle instruction à chaque cycle. L'implémentation est assez simple : il suffit d'ajouter des registres dans le cache. Pour cela, on profite que le cache est composé de plusieurs composants séparés, qui échangent des données dans un ordre bien précis, d'un composant à un autre. De plus, le trajet des informations dans un cache est linéaire, ce qui les rend parfois pour l'usage d'un pipeline. ===Le ''pipelining'' des caches ''direct-mapped''=== Voici ce qui se passe avec un cache directement adressé. Pour rappel ce genre de cache est conçu en combinant une mémoire RAM, généralement une SRAM, avec quelques circuits de comparaison et un MUX. L'accès au cache se fait globalement en deux étapes : la première lit la donnée et le ''tag'' dans la SRAM, et on utilise les deux dans une seconde étape. Nous avions vu il y a quelques chapitre comment pipeliner des mémoires, dans le chapitre sur les mémoires évoluées. Et bien ces méthodes peuvent s'utiliser pour la mémoire RAM interne au cache !L'idée est d'insérer un registre entre la sortie de la RAM et la suite du cache, pour en faire un cache pipéliné, à deux étages. Le premier étage lit dans la SRAM, le second fait le reste. L'implémentation sur les caches associatifs à plusieurs voies est globalement la même à quelques détails près. [[File:Cache directement adressé pipeliné - deux étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - deux étages]] Avec le circuit précédent, il est possible d'aller plus loin, cette fois en pipelinant l'accès à la mémoire RAM interne au cache. Pour comprendre comment, rappelons qu'une mémoire SRAM est composée d'un plan mémoire et d'un décodeur. L'accès à la mémoire demande d'abord que le décodeur fasse son travail pour sélectionner la case mémoire adéquate, puis ensuite la lecture ou écriture a lieu dans cette case mémoire. L'accès se fait donc en deux étapes successives séparées, on a juste à mettre un registre entre les deux. Il suffit donc de mettre un gros registre entre le décodeur et le plan mémoire. [[File:Cache directement adressé pipeliné - trois étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - trois étages]] Et on peut aller encore plus loin en découpant le décodeur en deux circuits séparés. En effet, rappelez-vous le chapitre sur les circuits de sélection : nous avions vu qu'il est possible de créer des décodeurs en assemblant des décodeurs plus petits, contrôlés par un circuit de prédécodage. Et bien on peut encore une ajouter un registre entre ce circuit de prédécodage et les petits décodeurs. : Théoriquement, toute l'adresse est fournie d'un seul coup au cache, la quasi-totalité des processeurs présentent l'adresse complète à un cache pipeliné. Mais le Pentium 4 fait autrement. Il faut noter que les premiers étages manipulent l'indice dans la SRAM, qui est dans les bits de poids faible, alors que les étages ultérieurs manipulent le ''tag'' qui est dans les bits de poids fort. Les concepteurs du Pentium 4 ont alors décidé de présenter les bits de poids faible lors du premier cycle d'accès au cache, puis ceux de poids fort au second cycle. Pour cela, l'ALU fonctionnait à une fréquence double de celle du processeur, tout comme le cache L1. Il n'y avait pas de pipeline proprement dit, mais cela réduisait grandement la latence d'accès au cache. ===Le ''pipelining'' des caches splittés/sériels=== Il est aussi possible de pipeliner un cache dits splittés, aussi appelés à accès sériel. Pour rappel, les caches à accès sériel vérifient si il y a succès ou défaut de cache, avant d'accéder aux lignes de cache en cas de succès. Ils font donc différemment des autres caches, qui accèdent à une ligne de cache, avant de déterminer s'il y a succès ou défaut en lisant le tag de la ligne de cache. Les caches sériels disposent de deux SRAM : une pour les tags des lignes de cache et une pour les données. Ils accèdent à la SRAM pour les tags, avant d’accéder à la SRAM des données en cas de succès de cache. Vu que l'accès se fait en deux étapes, une vérification des tags suivie de la lecture/écriture des données, il est facile à pipeliner. Pipeliner le cache permet de régler le problème des accès au cache L1, et elle est tout le temps utilisé sur les processeurs modernes. Mais que faire en cas de défaut de cache ? ==Les caches non bloquants== Un '''cache bloquant''' est un cache auquel le processeur ne peut pas accéder pendant un défaut de cache. Il faut attendre que la lecture ou écriture en RAM soit terminée avant de pouvoir utiliser de nouveau le cache. Un '''cache non bloquant''' n'a pas ce problème : on peut l'utiliser pendant un défaut de cache. Les caches non bloquants permettent de démarrer une nouvelle lecture ou écriture alors qu'une autre est en cours, ce qui permet d'exécuter plusieurs lectures ou écritures en même temps. ===Les ''Miss Handling Status Registers''=== Lors d'un défaut de cache, la mémoire RAM est consultée pendant le défaut de cache, mais le cache est inutilisé. Un défaut de cache n'utilise pas le cache, ce n'est qu'un accès en mémoire RAM, sur le bus mémoire (ou un accès aux niveaux de cache inférieurs, peu importe). Le cache en lui-même est laissé libre, rien n’empêche d'y accéder, il est en réalité intrinsèquement non-bloquant. Les caches, bloquants comme non-bloquants, sont en réalité composés d'une mémoire cache proprement dite, entourée de circuits qui servent d'interface entre le processeur et le cache lui-même. Et parmi les circuits tout autour, certains gèrent l'accès au cache lors d'un défaut de cache. Ils sont regroupés sous le terme de '''''Miss Handling Architecture''''' (MHA). La différence entre un cache bloquant et un cache non-bloquant est en réalité liée à l'implémentation de la MHA. Les caches bloquants coupent volontairement l'accès au cache lors d'un défaut, car les défauts sont plus simples à gérer ainsi. Le défaut de cache rapatrie une donnée depuis la RAM, qui sera écrite dans le cache. Et il ne faut pas qu'une tentative d'accès à cette donnée ait lieu avant qu'elle ne soit chargée. Mais ce blocage est général et touche tout le cache, alors que seule une ligne de cache est concernée par le défaut de cache. L'idée derrière un cache non-bloquant est que seule la ligne de cache est bloquée, mais les autres sont accessibles. L'idée est alors de mémoriser les lignes de cache concernées par le défaut de cache, afin d'en bloquer l'accès. À chaque accès, on vérifie si la ligne de cache est déjà réservée par un défaut de cache. La lecture/écriture est alors bloquée si c'est le cas, mais elle accepte les accès sinon. Pour cela, la ''Miss Handling Architecture'' contient des registres qui mémorisent des informations sur les défauts de cache en cours. Ils portent le nom de '''''miss status handling registers''''', que j’appellerais dorénavant MSHR, qui sont aussi appelés des '''''miss buffer'''''. Le contenu des MSHR varie beaucoup suivant le processeur, mais ces derniers stockent au minimum les informations suivantes : * Le numéro de la ligne de cache dans laquelle les données sont chargées. * Un bit de validité qui indique si le MSHR est vide ou pas, qui est mis à 0 quand le défaut de cache est résolu. * Un ou plusieurs champs de lecture/écriture, qui contiennent des informations sur la lecture/écriture. ** Pour une lecture, elle contient des informations sur la destination de la donnée, à savoir qui prévenir quand le défaut de cache est terminé. C'est parfois un nom/numéro de registre (celui dans lequel charger la donnée), mais c'est souvent le numéro de l'entrée dans le ''load/store queue''. ** Pour les écritures, elle contient la donnée à écrire, ou éventuellement un numéro de ''load/store queue'' où se trouve la donnée à écrire. Il faut noter que le bus mémoire ne peut gérer qu'un seul défaut de cache à la fois. Aussi, il est intéressant de regarder ce qui se passe lorsqu'un second défaut de cache survient, pendant qu'un premier est en cours. Dans ce cas, il y a deux réponses qui correspondent à deux types de caches non-bloquants, qui portent les noms barbares de caches de type succès après défaut et défaut après défaut. Sur le premier type, il ne peut pas y avoir plusieurs défauts de cache simultanés. Dès qu'un second défaut de cache survient, le cache stoppe son activité et on ne peut plus démarrer de nouvelle lecture/écriture, tant que le premier défaut de cache n'est pas résolu. Le second type est plus souple et autorise la survenue de plusieurs défauts de cache simultanés. Du moins, jusqu'à une certaine limite, car le cache ne peut supporter qu'un nombre limité d'accès mémoires simultanés (pipelinés). ===Les accès simultanés à une même ligne de cache=== Il arrive que le processeur fasse plusieurs accès mémoire simultanés à la même ligne de cache. Si la ligne de cache en question n'a pas encore été chargée dans le cache, alors on a plusieurs défauts de cache consécutifs pour la même ligne de cache, et le cache non-bloquant doit gérer la situation. Pour la suite, il va falloir faire une petite distinction entre les défauts primaires et secondaires. Imaginons qu'un défaut de cache ait lieu et demande à charger une donnée dans la ligne de cache numéro N. Il s'agit du premier défaut impliquant cette ligne de cache précise, ce qui lui vaut le nom de '''défaut de cache primaire'''. Mais par la suite, d'autres accès mémoire à la même ligne de cache ont lieu, alors que la ligne de cache n'est pas encore disponible. Dans ce cas, il s'agit de '''défauts de cache secondaires'''. Pour l'unité d'accès mémoire, les défauts de cache primaire et secondaire sont différents (ils prennent tous une entrée dans la ''load/store queue''). Mais pour le cache, ils ne correspondent qu'à un seul accès au cache : celui qui demande de charger la ligne de cache demandée. Les défauts de cache primaire et secondaire à la même ligne de cache se voient attribuer un MSHR unique. La gestion des défauts de cache secondaires dépend du cache non-bloquant. La solution la plus simple ne permet pas les défauts de cache secondaires. Le cache ne permet pas deux défauts de cache simultanés pour la même ligne de cache. Les autres solutions le permettent, en fusionnant des accès simultanés à la même ligne de cache en un seul au niveau des MSHR. Dans tous les cas, détecter les défauts de cache secondaires sont un problème qu'il faut détecter. La ''Miss Handling Architecture'' doit détecter les défauts de cache secondaire. Pour cela, elle procède comme suit. Lors de chaque défaut de cache, la MHA récupère le numéro de la ligne de cache associé. Il vérifie alors chaque MSHR pour vérifier s'il contient le numéro en question. S'il n'y a aucune correspondance dans les MSHR, alors c'est signe que le défaut de cache est un défaut primaire. Mais s'il y en a une, alors c'est un défaut secondaire. Évidemment, cela signifie que lors d'un défaut de cache, le numéro de ligne de cache est envoyé à tous les MSHR, pour comparaison. Les MSHR sont donc regroupés dans une mémoire associative, une sorte de mini-cache, faciliter l'implémentation. Lors d'un défaut de cache primaire, l'accès à un cache non-bloquant se fait comme suit : le processeur envoie une adresse au cache, accède à celui-ci, et détecte la survenue d'un défaut de cache. Il en profite alors pour attribuer une ligne de cache dans laquelle sera chargée ce défaut. L'attribution est très simple dans le cas des caches ''direct mapped'', ou associatifs par voie, pour lesquels l'attribution se fait assez simplement. Il mémorise alors cette information dans les MSHR, après avoir vérifié que le défaut de cache n'était pas un défaut secondaire. Lors des accès ultérieurs à une adresse proche, censée être dans la même ligne de cache, le processeur va encore une fois rencontrer un défaut de cache. Il va alors déterminer le numéro de la ligne de cache associée à l'adresse, et comparer ce numéro avec les MSHRs. Si un MSHR contient ce numéro, c'est signe que le défaut de cache est un défaut secondaire. La MHA réagit alors différemment selon le processeur considéré. Une première solution n'autorise pas les défauts de cache secondaires. Si l'un d'entre eux survient, le processeur est gelé par un ''pipeline stall'', une bulle de pipeline. Une autre solution fusionne les défauts de cache secondaires avec le défaut de cache primaire : tout cela ne correspond qu'à un seul défaut de cache pour lui. ===Les MSHR simples=== Un cache non-bloquant à '''MSHR simple''' contient juste plusieurs MSHR qui mémorisent juste un numéro de ligne de cache, un bit de validité, le champ de lecture/écriture, et l'adresse exacte de lecture/écriture. Avec cette organisation, il est possible d'avoir plusieurs défauts de cache séparés, mais à la condition que chaque défaut accède à une ligne de cache différente. Deux accès simultanés à une même ligne de cache ne sont pas possibles, les défauts de cache secondaires ne sont pas autorisés. Ainsi, chaque défaut de cache se voit attribuer son propre MSHR, chacun contient un numéro de ligne de cache différent. Pour comprendre pourquoi c'est impossible de gérer les défauts secondaires, il faut regarder le champ de lecture/écriture. Si on veut effectuer plusieurs écritures consécutives à la même adresse, le MSHR n'aura pas de quoi mémoriser les deux données à écrire. Il pourra mémoriser la première donnée à écrire, pas la seconde. Même chose lors d'une lecture : le champ lecture/écriture peut mémorisr la destination de la première lecture, pas de la seconde. L'avantage est que la MHA n'a besoin que des MSHR et de quelques circuits annexes. Les autres solutions rajoutent des circuits annexes pour gérer les défauts de cache secondaires, qui utilisent beaucoup de circuits. Le cout en circuit est donc élevé, mais le gain en performance est là. Passons maintenant aux caches non-bloquants qui autorisent les défauts de cache secondaire. La solution la plus simple consiste à utiliser ===Les MSHR adressés implicitement=== Avec les '''MSHR adressés implicitement''', il est possible de fusionner plusieurs accès mémoire à une même ligne de cache, mais sous une condition très importante : ces accès lisent/écrivent des mots mémoire différents. Par exemple, imaginons qu'une ligne de cache contienne 8 mots mémoire de 64 bits. Si un premier accès mémoire lit le mot mémoire numéro 7 (dernier mot mémoire de la ligne), et le second accès le mot mémoire numéro 3, alors la fusion est possible. Mais si deux accès mémoire veulent lire/écrire le mot mémoire numéro 7, alors la fusion n'est pas possible et le processeur se bloque, un ''pipeline stall'' survient. Un MSHR adressé implicitement est un MSHR simple, qui contient naturellement un numéro de ligne de cache (tag) et un bit de validité, sauf que le champ de lecture/écriture est dupliqué. Les différents champs lecture/écriture d'un MSHR sont regroupés dans une mémoire RAM/cache qui contient autant d'entrées qu'il y a de mot mémoire dans une ligne de cache. Chaque entrée de la table est associée à un mot mémoire de la ligne de cache et stocke des informations sur celui-ci. Une entrée mémorise, au minimum : * Un champ de lecture/écriture qui contient soit la destination de la lecture, soit la donnée à écrire. * Un bit R/W permettant d'interpréter correctement le champ de lecture/écriture. * Un bit de validité pour chaque entrée de la table, qui dit si un défaut de cache antérieur accède déjà à ce mot mémoire. La fusion de deux défauts de cache est ainsi assez simple. Un défaut de cache primaire/secondaire configure l'entrée associée au mot mémoire lu/écrit. Si un défaut secondaire ultérieur lit/écrit un mot mémoire différent (dont le bit de validité de l'entrée dans la table est à zéro), pas de conflit : il configure une autre entrée, vide. Mais s'il lit/écrit un mot mémoire pour lequel l'entrée de la table est occupée, il y a conflit, le processeur est bloqué par un ''pipeline stall'', une bulle de pipeline. Avec cette organisation, le nombre de MSHR indique combien de lignes de cache peuvent être lues en même temps depuis la mémoire. Quant au nombre d'entrées par MSHR, il détermine combien d'accès mémoires qui ne se recouvrent pas peuvent avoir lieu en même temps. ===Les MSHR adressés explicitement=== Les '''MSHR adressés explicitement''' n'ont pas les contraintes des MSHR adressés implicitement. Avec eux, il est possible d'avoir plusieurs défauts de cache pointant vers la même ligne de cache, mais aussi vers le même mot mémoire. De tels défauts de cache apparaissent sur les processeurs à exécution dans le désordre, mais sont très rares, voire inexistants, sur les processeurs ''in-order''. L'idée est encore que chaque MSHR est associée à une table mémoire qui mémorise des entrées. Cette table n'est autre qu'une mémoire FIFO. Sauf que cette fois-ci, une entrée n'est pas associée à un mot mémoire. Une entrée est associée à un défaut de cache. Une entrée mémorise là encore la destination de la lecture ou la donnée de l'écriture suivi du bit R/W, ainsi que d'un bit de validité par entrée, mais aussi : la position du mot mémoire lu dans la ligne de cache. C'est cette dernière information qui n'était pas présente dans les MSHR adressés implicitement. De plus, le nombre d'entrée par MSHR n'est pas égal au nombre de mots mémoires dans une ligne de cache, mais peut être arbitrairement grand. L'avantage d'un tel cache est qu'il est capable de traité les défauts secondaires concernant un même mot mémoire, et ce, car les accès à la ligne de cache concernée se produisent dans l'ordre de génération des défauts (la table mémoire est une FIFO). ===Les MSHRs inversés=== Généralement, plus on veut supporter de défauts de cache, plus le nombre de MSHR et d'entrées augmente. Mais au-delà d'un certain nombre d'entrées et de MSHR, les MSHR adressés implicitement et explicitement ont tendance à bouffer un peu trop de circuits. Utiliser une organisation un peu moins gourmande en circuits est donc une nécessité. Cette organisation plus économe se base sur des MSHR inversés. Les MSHR inversés ne contiennent qu'une seule entrée, en quelque sorte : au lieu d’utiliser n MSHR de m entrées chacun, on va utiliser n × m MSHR inversés. La différence, c'est que plusieurs MSHR peuvent contenir un tag identique, contrairement aux MSHR adressés implicitement et explicitement. Lorsqu'un défaut de cache a lieu, chaque MSHR est vérifié. Si jamais aucun MSHR ne contient de tag identique à celui utilisé par le défaut, un MSHR vide est choisi pour stocker ce défaut, et une requête de lecture en mémoire est lancée. Dans le cas contraire, un MSHR est réservé au défaut de cache, mais la requête n'est pas lancée. Quand la donnée est disponible, les MSHR correspondant à la ligne qui vient d'être chargée vont être utilisés un par un pour résoudre les défauts de cache en attente. ===Les MSHR intégrés au cache=== Certains chercheurs ont remarqué que pendant qu'une ligne de cache est en train de subir un défaut de cache, celle-ci reste inutilisée, et son contenu est destiné à être perdu une fois le défaut de cache résolu. Ils se sont dit que, plutôt que d'utiliser des MSHR séparés, il vaudrait mieux utiliser la ligne de cache pour stocker les informations sur les défauts de cache en attente dans cette ligne de cache. Pour éviter tout problème, il faut rajouter un bit dans les bits de contrôle de la ligne de cache, qui sert à indiquer que la ligne de cache est occupée : un défaut de cache a eu lieu dans cette ligne, et elle stocke donc des informations pour résoudre les défauts de cache. ==L'entrelacement mémoire== Nous venons de voir comment une mémoire cache peut gérer plusieurs accès mémoire simultanés. Il se trouve que ce genre d'optimisation dépasse largement le cadre du cache. Les mémoires RAM ne sont pas en reste, elles aussi ! Il est possible de pipeliner l'accès à une mémoire RAM, en utilisant des techniques dites d''''entrelacement'''. Précisons cependant que cette optimisation n'est pas utilisée sur les mémoires SDRAM ou DDR modernes, du moins pas sans modifications majeures. Mais divers ordinateurs assez anciens, dont des superordinateurs, ont utilisé cette technique. La technique demande d'utiliser plusieurs mémoires séparées. Sur les anciens ordinateurs, les mémoires en question sont des chips mémoire, à savoir des circuits intégrés de DRAM. Pour faire plus général, nous allons utiliser le terme de '''banques''', ou encore de bancs mémoire. La différence est qu'il existe des mémoires multi-banque, qui regroupent plusieurs banques indépendantes dans un seul boitier, dans un seul chip mémoire. En clair, de telles mémoires regroupent plusieurs sous-mémoires dans un seul circuit intégré. Il est aussi possible d'utiliser plusieurs chips mémoire séparés, chacun ayant une banque. Reste à combiner ces banques pour former un pseudo-pipeline. ===Utiliser plusieurs banques sans entrelacement=== Sans optimisation particulière, les adresses sont réparties dans les banques comme indiqué ci-dessous. Il s'agit de ce qui s'appelle un arrangement horizontal, et nous avions vu celui-ci dans le chapiutre sur les mémoires SDRAM. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Avec cet arrangement, les bits de poids fort de l'adresse sont utilisées pour sélectionner la banque adéquate, et le reste de l'adresse est envoyé sur le bus d'adresse. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Adresse de banque !! Adresse dans la banque |- | Quelques bits de poids fort || Reste de l'adresse |} L'entrelacement change cette répartition, afin d'accélérer les accès mémoire. L'entrelacement classique vise à améliorer les accès à des adresses mémoires consécutives, mais des formes plus évoluées d'entrelacement visent à optimiser des accès mémoire arbitraires. Voyons-les dans l'ordre, du plus simple au plus complexe. ===L'entrelacement classique=== Avec l''''entrelacement classique''', des adresses consécutives sont réparties dans des banques différentes. [[File:Répartition des adresses dans une mémoire interleaved.png|centre|vignette|upright=1.5|Répartition des adresses dans une mémoire interleaved.]] L'entrelacement simple permet d'accélérer les accès mémoire qui se font à des adresses consécutives. Avec l'entrelacement, chaque accès mémoire tombe dans une banque mémoire différente, ce qui fait qu'on peut démarrer un nouvel accès mémoire à chaque cycle d'horloge. Pendant qu'une banque est occupée par un accès mémoire, on démarre le suivant dans une autre banque, et ainsi de suite. Le tout se termine soit quand on a épuisé toutes les banques libres, soit quand l'accès en rafales se termine. Pas besoin d'attendre que la mémoire ait fini sa lecture/écriture avant de démarrer la lecture/écriture suivante. [[File:Pipemining mémoire.png|centre|vignette|upright=2.5|Pipelining mémoire]] L'animation suivante illustre bien le processus, avec quatres banques. [[File:Interleaving.gif|centre|vignette|upright=2.5|Pipelining mémoire]] L'entrelacement simple utilise les bits de poids faible pour sélectionner la banque, et les bits de poids fort pour la case mémoire. [[File:Adresse mémoire d'une mémoire entrelacée.png|centre|vignette|upright=2|Adresse mémoire d'une mémoire entrelacée]] Précisons que le temps d'accès mémoire ne change pas beaucoup avec l'entrelacement. Par contre, on peut faire plus d'accès mémoire simultanés. On peut démarrer un accès mémoire par cycle d'horloge, mais l'accès en lui-même prend plusieurs cycles. Les mémoires à entrelacement ont donc un débit supérieur aux mémoires qui ne l'utilisent pas. L'entrelacement classique pourrait être utilisé pour accélérer les transferts entre mémoire RAM et cache, qui se font en blocs d'adresses consécutives. Mais de nos jours, il n'est pas utilisé dans cette optique. Il faut dire qu'il est redondant avec le mode rafale des mémoires DRAM. Les deux font globalement la même chose et on voit mal comment utiliser les deux en même temps. Le mode rafale étant plus simple à implémenter, et plus léger niveau utilisation du bus de commande mémoire, il est préféré à l'entrelacement. Par contre, l'entrelacement a eu son heure de gloire sur d'anciens processeurs qui n'avaient pas de mémoire cache, alors qu'ils disposaient d'un pipeline. L'exemple typique est celui des nombreux ''processeurs vectoriels'', que nous n'avons pas encore abordé à ce stade du cours. De tels processeurs avaient un pipeline, une unité de calcul fortement pipelinée, et parfois même de l'exécution dans le désordre et du renommage de registres ! ===L'entrelacement par décalage=== L'entrelacement simple est très utile pour les accès en rafle ou équivalents. Par contre, il existe de rares situations où il n'est pas optimal. Une de ces situations est celle des accès en enjambée (en ''stride''), où l'on accède en série à des adresses sont séparées par N mots mémoires. [[File:Accès par enjambées.png|centre|vignette|upright=2|Accès par enjambées.]] De tels accès surviennent quand un logiciel accède à des structures de données spécifiques, à savoir des tableaux de structures, des matrices, ou d'autres structures de données dans le genre. Un cas classique est celui du parcours d'une matrice colonne par colonne. Les matrices de nombres sont mémorisées ligne par ligne, ce qui fait que parcourir une colonne demande de passer d'une ligne à la suivante et de faire des grands sauts en mémoire RAM, mais tous espacés par la même distance. Avec l'entrelacement simple, les accès en enjambée sont moins performants que les accès à des adresses consécutives, mais cela ne fonctionne pas trop mal. Le pire cas est celui où l'on a N banques et où les données sont justement placées toutes les N adresses. Des accès consécutifs vont tous tomber dans la même banque, on ne peut plus accéder à des banques différentes en parallèle. Mais en dehors de ce cas, on voit une amélioration par rapport à la situation sans entrelacement. Pour obtenir des performances maximales pour les accès en enjambées, il faut répartir les mots mémoires dans la mémoire autrement. L'organisation idéale est la suivante. : Dans les explications qui vont suivre, la variable N représente le nombre de banques, qui sont numérotées de 0 à N-1. On commence par organiser les N premières adresses comme une mémoire entrelacée simple : l'adresse 0 correspond à la banque 0, l'adresse 1 à la banque 1, etc. Pour le bloc suivant, on décale tout d'une adresse, à savoir qu'on commence à remplir les banques à partir de la seconde, non de la première. Une fois la fin du bloc atteinte, on finit de remplir le bloc en repartant du début du bloc. Le troisième bloc d'adresse subit le même traitement, sauf qu'on commence à remplir à partir de la seconde banque. Et on poursuit l’assignation des adresses en décalant d'un cran en plus à chaque bloc. Ainsi, chaque bloc verra ses adresses décalées d'un cran en plus comparé au bloc précédent. Si jamais le décalage dépasse la fin d'un bloc, alors on reprend au début. [[File:Mémoire interleaved par décalage.png|centre|vignette|upright=1.5|Mémoire entrelacée par décalage.]] En faisant cela, on remarque que les banques situées à N adresses d'intervalle sont différentes. Dans l'exemple du dessus, nous avons ajouté un décalage de 1 à chaque nouveau bloc à remplir. Mais on aurait tout aussi bien pu prendre un décalage de 2, 3, etc. Dans tous les cas, on obtient un entrelacement par décalage. Ce décalage est appelé le pas d'entrelacement, noté P. Le calcul de l'adresse à envoyer à la banque, ainsi que la banque à sélectionner se fait en utilisant les formules suivantes : * adresse à envoyer à la banque = adresse totale / N ; * numéro de la banque = (adresse + décalage) modulo N ; * décalage = (adresse totale * P) mod N. Avec cet entrelacement par décalage, on peut prouver que la bande passante maximale est atteinte si le nombre de banques est un nombre premier. Seulement, utiliser un nombre de banques premier peut créer des trous dans la mémoire, des mots mémoires inadressables. Malgré ce défaut, la technique a été utilisée sur quelques ordinateurs, avec l'exemple notable du superordinateur ''Burroughs Scientific Processor''. Pour éviter cela, il y a plusieurs solutions. Par exemple, on peut faire en sorte que N et la taille d'une banque soient premiers entre eux : ils ne doivent pas avoir de diviseur commun. Mais en pratique, elles n'ont pas vraiment été implémentées dans une vraie machine, et sont restées à l'état de recherche, aussi je les passe sous silence. ===L'entrelacement pseudo-aléatoire=== Une dernière méthode de répartition consiste à répartir les adresses dans les banques de manière "pseudo-aléatoire". L'idée exacte est de faire passer l'adresse dans une '''fonction de hachage''', plus ou moins complexe. Pour rappel, une fonction de hachage prend une entrée de grande taille et fournit en sortie un résultat de petite taille. Idéalement, elles doivent donner des résultats aussi différents que possible pour des entrées similaires, histoire de simuler une sorte de pseudo-aléatoire. Ici, une adresse est transformée en un numéro de banque. Une fonction de hachage simple ne fait que permuter des bits de l'adresse pour obtenir son résultat. Elle ne fait qu'échanger des bits de place avant de couper l'adresse en deux morceaux : un pour la sélection de la banque, et un autre pour la sélection de l'adresse dans la banque. Cette permutation est fixe, et ne change pas suivant l'adresse. Des fonctions de hachage plus complexes font des XOR bit à bit entre certains bits de l'adresse. ==Les contrôleurs mémoires SDRAM/DDR optimisés== Après avoir vu le cas des mémoires RAM, nous allons nous concentrer sur le cas particulier des mémoires SDRAM. Les mémoires SDRAM et DDR modernes sont capables de gérer plusieurs accès simultanés à la mémoire RAM. Et les contrôleurs mémoire intègrent des optimisations spécifiques afin d'en profiter au maximum. ===La mise en attente des accès mémoire=== Comme pour les mémoires caches, il existe des contrôleurs mémoire bloquants et non-bloquants. Les premiers n'acceptent qu'un seul accès mémoire à la fois, les seconds acceptent plusieurs accès mémoire simultanés. Pour simplifier, nous allons dire que le contrôleur mémoire n’exécute qu'un seul accès mémoire à la fois, et qu'ils met en attente les accès en trop. C'est une simplification assez irréaliste, vu que les mémoires modernes disposent de plusieurs banques accessibles en parallèle. Mais nous introduirons ce détail un peu plus tard. Avec un contrôleur mémoire de ce type, les accès mémoire sont mis en attente dans une mémoire FIFO, histoire de les exécuter dans leur ordre d'arrivée. Les accès mémoire quittent alors la mémoire dans leur ordre d'arrivée, les lectures sont renvoyées dans l'ordre demandé. En plus d'une mémoire FIFO pour les commandes mémoire, on trouve une mémoire FIFO pour les données à écrire. Elle permet d'accumuler plusieurs écritures, tout en permettant que la donnée adéquate soit envoyée à la RAM au bon moment. Il y a aussi une FIFO pour les données lues, qui sert au cas où le cache ne soit pas disponible quand on lui envoie la donnée lue. [[File:Module d'interface avec la mémoire.png|centre|vignette|upright=2.5|Contrôleur mémoire avec mise en attente des requêtes processeur et autres optimisations.]] Une optimisation regroupe plusieurs accès à des données consécutives en un seul accès en rafale. Le contrôleur mémoire analyse les requêtes mises en attente et détecte si certaines se font à des adresses consécutives. Si c'est le cas, il fusionne ces requêtes en une lecture/écriture en rafale. Une telle optimisation est appelée la '''combinaison de lecture''' pour les lectures, et la '''combinaison d'écriture''' pour les écritures. ===Le pipeline des mémoires SDRAM=== Les SDRAM ont une forme de pseudo-pipeline très limité. Elles sont parfois capables de démarrer une commande avant que la précédente soit terminée. Même si elle parait très limitée, cette possibilité est exploitée au mieux par la présence de plusieurs banques dans la mémoire SDRAM. Les chips mémoires de SDRAM sont composés de plusieurs banques, chacune étant une sorte de mini-mémoire miniature. Chaque banque a son propre décodeur, son propre tampon de ligne, ses propres multiplexeurs de colonne, sa logique de rafraichissement mémoire, etc. C'est comme si une SDRAM regroupait plusieurs mémoires séparées dans un même circuit intégré. Et cela permet de faire plusieurs accès mémoire en même temps, dans des banques différentes. Il est possible de lancer un accès mémoire dans deux banques en même temps, par exemple. Ou encore, on peut lancer un nouvel accès mémoire dans une banque si elle est inoccupée. La possibilité améliore grandement la performance de la SDRAM. Mais nous en reparlerons dans un chapitre ultérieur. {|class="wikitable" |+ Pipelining basique sur les SDRAM |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 |- ! Banque Numéro 1 | || || || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || || || |- ! Banque Numéro 2 | || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || |- ! Banque Numéro 3 | || || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || |} L'implémentation n'a rien de vraiment compliqué. Il suffit d'utiliser une mémoire FIFO par banque. Ainsi, les accès dans une même banque se feront en série, dans l'ordre d'arrivée, mais des accès à des banques différentes se feront dans le désordre. Cela ne pose pas de problème car les accès se font à des adresses différentes, ce qui fait qu'il n'y a pas de conflits majeurs. [[File:Gestion parallèle des banques.png|centre|vignette|upright=2.5|Gestion parallèle des banques.]] ===Le ré-ordonnancement des commandes mémoires=== Un contrôleur mémoire moderne est capable de changer l'ordre des requêtes mémoires, pour gagner en performances. En clair : il peut faire l'équivalent mémoire de l'exécution dans le désordre des processeurs haute performance. Une différence majeure est que le contrôleur mémoire ne remet pas les accès mémoire dans l'ordre avant de les envoyer au processeur. C'est le processeur qui remet en ordre les accès mémoire, dans sa ''load queue''. Pour cela, les requêtes mémoire sont "numérotées", à savoir qu'on leur attribue un identifiant binaire. Le contrôleur mémoire exécute les lectures/écritures dans le désordre, mais garde la trace de leur identifiant. Il sait dans quel ordre il exécute les accès mémoire et connait leur latence. Ce qui fait qu'il sait que la donnée lue à tel instant est associée à la requête mémoire numéro X. Pour les lectures, il renvoie la donnée lue avec l'identifiant. Le processeur sait alors à quelle lecture la donnée lue correspond et il se débrouille en interne pour gérer la situation. En clair : le processeur voit les accès mémoire dans le désordre, c'est lui qui les remet en ordre. Maintenant que ces précisions sont faites, posons cette question : quel est l'intérêt de faire les accès mémoire dans le désordre ? Accéder à des banques en parallèle est une réponse possible, mais le ré-ordonnancement est surtout utile si tous les accès mémoire atterrissent dans la même banque. Pour comprendre pourquoi, faisons un rappel rapide sur le tampon de ligne. Les mémoires SDRAM sont des mémoires à tampon de ligne. Elles font un accès mémoire en deux étapes. La première étape recopie une ligne de N * 64 bits dans un tampon interne à la SDRAM. La seconde étape sélectionne une colonne, à savoir une donnée de 64 bits, qui est soit envoyée sur le bus de données pour une lecture, soit modifiée par une écriture. [[File:Mémoire à tampon de ligne.png|centre|vignette|upright=2|Mémoire à tampon de ligne]] Les deux étapes correspondent à deux commandes séparées : une commande ACT qui précise l'adresse de la ligne, et une commande READ ou WRITE qui précise l'adresse de la colonne. [[File:Signaux RAS et CAS.png|centre|vignette|upright=2|Signaux RAS et CAS.]] Les SDRAM permettent de se passer de la première étape si des accès consécutifs se font dans la même ligne. Une fois activée, la ligne reste ouverte et on peut accéder plusieurs fois de suite dedans. On a alors juste à préciser l'adresse de colonne dedans. [[File:Sélection d'une ligne sur une mémoire FPM ou EDO.png|centre|vignette|upright=2|Sélection d'une ligne sur une mémoire FPM ou EDO.]] L'exécution dans le désordre des lectures/écritures SDRAM vise à faire en sorte que des accès consécutifs se fassent dans une même ligne. Elle marche si plusieurs accès à une même ligne ne sont pas consécutifs, et qu'il y a des accès entre les deux. Après ré-ordonnancement, ces accès mémoire deviennent consécutifs, ils sont exécutés l'un à la suite de l'autre. Pour rendre le tout plus concret, voici un exemple. Imaginez que l'on sait les 3 accès mémoire suivants : * Une lecture ligne A ; * Une écriture ligne B ; * Une lecture ligne A. Sans réordonnancent, on doit émettre deux commandes PRECHARGE : une quand on passe de la ligne A à la ligne B, et une autre pour le passage inverse. pour éviter cela, le contrôleur mémoire peut retarder l'écriture, ou au contraire avancer la seconde lecture. le résultat est alors le suivant : * Lecture ligne A ; * Lecture ligne A ; * Une écriture ligne B. On a donc deux accès consécutifs à la même ligne, suivi par une écriture dans une autre ligne. La technique marche parce que l'on a un mix adéquat de lectures et d'écritures. Mais il existe des possibilités de réorganisation autres qui ne sont pas valides. Il faut par exemple prendre garde à éviter d'intervertir lectures et écritures à une même adresse, sous peine de problèmes. Encore une fois, il faut faire attention aux dépendances mémoires. Le controleur mémoire teste les dépendances mémoires avant d'envoyer des commandes à la mémoire DDR/SDRAM. ==L'entrelacement sur les mémoires SDRAM== L''''entrelacement''' fonctionne sur les mémoires SDRAM, mais il doit être fortement modifié. Concrètement, tout ce qui a étré dit plus est inapplicable pour les SDRAM, du fait de la présence du tampon de ligne. Des adresses consécutives atterrissent déjà dans la même ligne, ce qui fait qu'on n'a pas besoin d'entrelacement au niveau d'une ligne. Et ne parlons pas de ce la présence de rangées et de canaux mémoire ! Cependant, il peut y avoir un entrelacement lié à la présence des banques SDRAM. L'entrelacement est géré juste avant le séquenceur mémoire. Le '''circuit d'entrelacement''' intervertit certains bits de l'adresse lors des accès mémoires. On trouve aussi des mémoires FIFOs pour mettre en attente les requêtes mémoire provenant du processeur. Elles permettent de recevoir plusieurs accès mémoire consécutifs. Si vous vous demandez comment plusieurs accès mémoire consécutifs peuvent avoir lieu, sachez que c'est une bonne question. Pour le moment, nous allons dire que ces accès proviennent du processeur ou de la mémoire cache, sans expliquer comment. Le contrôleur mémoire reçoit une série d'accès mémoire assez rapprochés, qu'il doit exécuter au mieux. [[File:Controleur mémoire d'une SDRAM avec entrelacement.png|centre|vignette|upright=2.5|Contrôleur mémoire d'une SDRAM avec entrelacement.]] Pour gérer l'entrelacement, le contrôleur de SDRAM prend en entrée l'adresse envoyée par le processeur, et la découpe en plusieurs champs : un pour sélectionner la banque adéquate, un autre pour la ligne, un autre pour la colonne, un autre pour la rangée. Une solution simple désactive l'entrelacement. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de banque || Adresse de ligne || Adresse de colonne |} Si cette solution fonctionne bien si les accès mémoire sont assez aléatoires, en pratique, mieux vaut utiliser l'entrelacement. ===L'entrelacement de banques/lignes=== Une première méthode d'entrelacement est l''''entrelacement de banques'''. Elle répartit deux lignes consécutives dans deux banques différentes. Pour comprendre l'idée, prenons un exemple. Imaginons une mémoire avec deux banques et 4 lignes. Imaginons qu'on parcoure/balaye la mémoire RAM en partant des adresses basses. Sans entrelacement de ligne, les accès se feront comme suit, de gauche à droite : {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Banque numéro 1 !! colspan="4" | Banque numéro 2 |- | Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 || Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 |} Avec l'entrelacement de banques, les accès se feront comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 |- | Ligne 1 || Ligne 1 || Ligne 2 || Ligne 2 || Ligne 3 || Ligne 3 || Ligne 4 || Ligne 4 |} Avec N banques, la première ligne ira dans la première banque, la seconde ligne dans la seconde, et ainsi de suite jusqu'à la énième banque qui va dans la banque numéro N. Et la ligne N+1 repart dans la première banque. L'avantage est que passer d'une ligne à la suivante est assez rapide. Au lieu de devoir fermer la première ligne et ouvrir la suivante on ouvrira directement la ligne suivante dans une banque différente, ce qui sera plus rapide. La commande PRECHARGE pourra être envoyée à la seconde banque en avance. Pour cela, l'adresse est découpée comme suit. {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne (précise à l'octet) |} ===L'entrelacement de rangées=== Il est aussi possible de faire la même chose, mais avec les rangées. Pour simplifier fortement, une rangée est simplement un chip mémoire. En réalité, les rangées ne sont pas des chip mémoire, mais un ensemble de chips mémoire regroupés ensemble histoire d'atteindre les 64 bits du bus de données. Par exemple, une rangée peut combiner 8 chips mémoire avec un bus de données de 8 bits chacun pour obtenir les 64 bits du bus de données d'une SDRAM. Mais nous passons ce détail sous silence dans les explications qui vont suivre, par souci de simplification. Pour faire comprendre l'entrelacement de rangée, le mieux est d'utiliser un exemple, le même que précédemment. Sans entrelacement de rangée, on change de chip mémoire une fois qu'on a balayé toutes ses adresses. {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Rangée numéro 1 !! colspan="4" | Rangée numéro 2 |- | Banque 1 || Banque 2 || Banque 3 || Banque 4 || Banque 1 || Banque 2 || Banque 3 || Banque 4 |} Avec l'entrelacement de ligne, on change de rangée dès qu'on change de banque/ligne. {|class="wikitable" |+ Adresse mémoire |- ! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 |- | Banque 1 || Banque 1 || Banque 2 || Banque 2 || Banque 3 || Banque 3 || Banque 4 || Banque 4 |} En clair, quand on a épuisé toutes les banques dans une même rangée, on passe à la rangée suivante au lieu de rester dans la même rangée. Notons que dans l'exemple précédent, on a combiné l'entrelacement de rangée et de banque, mais on aurait pu utiliser l'entrelacement de rangée seul. Mais ce n'est pas le cas le plus courant en pratique. Toujours est-il qu'en combinant entrelacement de rangée et de ligne, le découpage de l'adresse est le suivant : {|class="wikitable" |+ Adresse mémoire |- | Adresse de ligne || Numéro de rangée || Adresse de banque || Adresse de colonne (précise à l'octet) |} D'autres méthodes d'entrelacement sont possibles, mais elles sont plus complexes. Chaque contrôleur mémoire fait un peu à sa sauce. Ils permettent en général de choisir entre plusieurs entrelacements. Au minimum, ils permettent de désactiver l'entrelacement, d'activer un entrelacement de ligne, et d'autres entrelacements plus complexes, dont certains sont propriétaires et tenus secrets par le fabricant. ===L'entrelacement avec le ''dual channel''=== [[File:Dual channel slots.jpg|vignette|Slots mémoires avec ''dual channel''.]] L'usage du ''dual channel'' complique encore l'entrelacement. Là encore, il y a deux grandes solutions : avec et sans entrelacement des canaux mémoire. Rappelons le principe : deux barrettes de RAM sont accédées en parallèle. Et pour cela, il faut utiliser l'entrelacement. Typiquement, chaque barrette mémoire fournit 64 bits, ce qui fait que l'on peut accéder à 128 bits d'un seul coup, par exemple avec un accès en rafale. Sans ''dual channel'', la première barrette correspond à la moitié haute de la RAM, la seconde barrette correspond à la moitié basse. Avec ''dual channel'', une forme spécifique d'entrelacement est activée. Concrétement, deux blocs de 64 bits sont placés dans des canaux mémoire séparés. Idem avec du triple ou quadruple canal, mais c'est alors trois ou quatre blocs de 64 bits qui sont dispersés dans des canaux consécutifs. Le découpage de l'adresse est alors le suivant : {|class="wikitable" |+ Adresse mémoire |- ! colspan="6" | Sans entrelacement |- | Numéro de canal/barrette || Reste de l'adresse || Position de l'octet dans un bloc de 64 bits |- | colspan="6" | |- ! colspan="6" | Avec entrelacement |- | Reste de l'adresse || Numéro de canal/barrette || Position de l'octet dans un bloc de 64 bits |} Les contrôleurs de SDRAM précédents sont assez basiques et ne représentent pas les contrôleurs les plus évolués. Ils étaient utilisés sur les anciens PC, à une époque où ils étaient encore sur la carte mère du processeur. Mais de nos jours, le contrôleur mémoire est intégré au processeur. Et il incorpore de nombreuses optimisations afin de gagner en performances. Malheureusement, ces optimisations sont elles-mêmes dépendantes des optimisations intégrées dans le processeur, ce qui fait qu'on ne peut pas en parler ici. Elles demandent que le contrôleur mémoire reçoive plusieurs accès mémoire simultanés, ce qui n'a aucun sens à ce stade du cours. Aussi, nous allons les passer sous silence pour le moment. Nous les verrons dans le chapitre sur le parallélisme mémoire. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=La désambiguïsation mémoire | prevText=La désambiguïsation mémoire | next=Les processeurs superscalaires | nextText=Les processeurs superscalaires }} </noinclude> 7w1knn15sazpqye5cxu30oho3ta04k9 764664 764663 2026-04-23T15:08:58Z Mewtow 31375 /* L'entrelacement avec le dual channel */ 764664 wikitext text/x-wiki Dans ce chapitre, nous allons voir les techniques qui permettent de gérer plusieurs accès mémoire simultanés directement au niveau du cache ou de la mémoire RAM. Par plusieurs accès mémoire simultanés, vous pensez sans doute à l'usage de cache multiports, voire de mémoires RAM multiport. C'est en effet une solution, mais ce n'est clairement pas la seule. Par exemple, il est possible de pipeliner l'accès au cache, voire à la mémoire RAM. Il existe de nombreuses techniques de '''parallélisme mémoire''', et nous allons les voir dans ce chapitre. Pour donner un exemple, il est possible de pipeliner les accès mémoire. Il est possible de pipeliner l'accès au cache et/ou l'accès à la mémoire, et nous verrons comment faire dans ce chapitre. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, qu'ils aient ou non un pipeline dynamique. Par contre, pipeliner la mémoire est une technique ancienne, aujourd'hui peu utilisée. Elle était utilisée sur des très vieux ordinateurs, qui avaient un pipeline mais pas de mémoire cache. Mais de nos jours, les mémoires SDRAM et DDR ne sont pas adaptées pour ça, il est très difficile de les pipeliner correctement. Le parallélisme mémoire est utile aussi bien sur des processeurs à émission dans l'ordre que dans le désordre. Par exemple, un processeur ''in-order'' avec des lectures non-bloquantes peut e, profiter. Il peut techniquement lancer une seconde lecture, après avoir rencontré une première lecture non-bloquante. En général, le nombre de lectures consécutives est cependant limité à moins d'une dizaine. À l'opposé, les processeurs sans lectures non-bloquantes profitent pas du parallélisme mémoire. À la rigueur, ils peuvent en profiter s'ils intègrent des techniques de préchargement. Pendant que le processeur exécute une lecture/écriture, il peut précharger une autre donnée en parallèle. Inutile de dire que sans pipeline processeur, le parallélisme mémoire ne sert pas à grand-chose. ==Le ''line fill buffer'' et les techniques associées== Lors d'un défaut de cache, le processeur doit attendre que toute la ligne de cache soit chargée avant d'être utilisable. Or, la taille d'une ligne de cache est supérieure à la largeur du bus mémoire, ce qui fait qu'une ligne de cache est chargée en plusieurs fois, morceaux par morceaux, mot mémoire par mot mémoire. Le chargement peut se faire directement dans le cache, mais ce n'est pas une solution très pratique. À la place, beaucoup de processeurs ajoutent une mémoire tampon entre la RAM et le cache, appelée le '''tampon de remplissage de ligne''' (''line-fill buffer'') dans les processeurs modernes. Lors d'un défaut de cache, le processeur charge la donnée de la RAM dans le tampon de remplissage de ligne, mot mémoire par mot mémoire. Une fois plein, le tampon de remplissage de ligne est recopié dans la ligne de cache. Le tampon de remplissage de ligne contient une ligne de cache, si ce n'est que certains bits de contrôle ne sont pas présents. Le tampon de remplissage de ligne contient un bit de validité pour chaque mot mémoire de la ligne de cache, qui indiquent si le mot mémoire a été chargé. Par exemple, prenons un processeur 64 bits, qui gère donc des mots mémoire de 8 octets, avec des lignes de cache 256 octets/32 mots mémoire. Le tampon de remplissage de ligne contiendra 32 bits de validité. Si le processeur a chargé les 6 premiers mots mémoire, les 6 premiers bits de validité seront mis à 1, les autres seront encore à zéro. Il faut noter que la disponibilité de la ligne complète se détermine assez facilement en faisant un ET logique entre tous les bits de validité. Le processeur sait ainsi quand la ligne de cache est disponible entièrement et donc quand la transférer dans le cache. La même chose existe avec une hiérarchie de cache, sauf que l'on trouve une mémoire tampon entre chaque niveau de cache. Il y a un tampon de remplissage de ligne entre le cache L1 et le cache L2, entre le cache L2 et le L3, etc. Si le cache ne gère qu'un seul défaut de cache à la fois, le ''fill line buffer'' est une mémoire très simple, qui ne mémorise qu'une seule ligne de cache. Et cela vaut aussi bien pour un cache bloquant que non-bloquant. Mais sur les caches capables de gérer plusieurs défauts simultanés, le tampon de remplissage de ligne est une mémoire de type FIFO ou LIFO, capable de mémoriser plusieurs lignes de cache. Notons que le tampon de remplissage de ligne est très utile pour implémenter certaines techniques, par exemple le contournement du cache. Nous avions vu dans le chapitre sur le cache que certains accès mémoire doivent contourner le cache, pour des raisons de cohérence des caches. C'est notamment nécessaire pour accéder aux périphériques, mais c'est aussi utile pour des raisons de performances dans des cas très spécifiques. Les accès qui contournent le cache se font directement dans le tampon de remplissage de ligne : le processeur écrit ou lit les données depuis ce tampon de remplissage de ligne, sans accéder au cache. ===L'''early restart'' et le ''critical word load''=== La présence du ''line buffer'' permet une optimisation assez intéressante, qui permet de réduire la latence des défauts de cache. L'optimisation consiste à lire un mot mémoire dans le tampon de remplissage de ligne, même si la ligne de cache complète n'a pas encore été chargée. Il existe deux manières de faire cela, qui portent les noms d'''early restart'' et de ''critical word load''. La première est la version la plus simple, la seconde est plus complexe mais plus performante. Avec la technique d''''''early restart''''', la ligne de cache est chargée normalement, en partant de son premier mot mémoire. Dès que le mot mémoire lu/écrit par le processeur est copié dans le ''line fill buffer'', il est envoyé au processeur immédiatement. Illustrons le tout par un exemple, où une ligne de cache fait 16 mots mémoire. Le processeur effectue une lecture, qui lit le 5ème mot mémoire. Sans ''early restart'', le processeur doit charger les 16 mots mémoire avant de faire la lecture dans le cache. Avec ''early restart'', le processeur reçoit la donnée dès que le 5ème mot mémoire est disponible. Le processeur doit attendre que les 4 premiers mots mémoire soient chargés, puis le 5ème mot mémoire arrive et est envoyé directement au processeur, il est lu directement depuis le ''line fill buffer''. Les 11 mots mémoire suivants sont ensuite chargés dans le cache pendant que le processeur fait des calculs dans son coin. Le ''critical word load'' est une optimisation de la technique précédente où le chargement de la ligne de cache commence directement à la donnée demandée par le processeur. Pour reprendre l'exemple précédent, où le processeur demande le 5ème mot mémoire sur 16, le ''critical word load'' charge le 5ème mot mémoire en premier et l'envoie au processeur, ce qui fait qu'il est chargé très rapidement. Pas besoin d'attendre que le processeur charge les 4 mots mémoire précédents comme avec l'''early restart''. Dans le détail, le processeur charge le 5ème mot mémoire en premier, puis charge les 11 suivants, et termine par les 4 mots mémoire du début. En clair, le ''critical word load'' commence par charger le mot mémoire lu, puis les blocs suivants, avant de revenir au début du bloc pour charger les blocs restants. Ainsi, la donnée demandée par le processeur sera la première disponible. Pour cela, l'organisation du tampon de remplissage de ligne est modifiée de manière à rendre cela possible. Il a une taille égale à une ligne de cache complète, qui contient elle-même plusieurs mots mémoire. Dans le ''line-fill buffer'', chaque mot mémoire est stocké avec un tag, qui indique l'adresse du mot mémoire stocké dans le ''line-fill buffer''. Le ''line-fill buffer'' est donc un cache un peu particulier, qui fonctionne comme un cache du point de vue du processeur, comme une mémoire FIFO pour les transferts avec le cache. Ainsi, un processeur qui veut lire dans le cache après un défaut peut accéder à la donnée directement depuis le tampon de remplissage de ligne, alors que la ligne de cache n'a pas encore été totalement recopiée en mémoire. ==Les caches pipelinés== Il est possible de pipeliner l'accès au cache, ce qui demande juste de rajouter des registres au bon endroits et quelques circuits de contrôle. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, même ceux avec un pipeline dynamique. Et l'implémentation peut se faire de plusieurs manières différentes. Il faut dire que découper une mémoire cache en plusieurs étapes peut se faire de plusieurs manière. Commençons par la plus simple. Beaucoup de processeurs des années 2000 avaient une fréquence peu élevée comparée aux standards d'aujourd'hui, ce qui fait que leur cache avait bien un temps d'accès d'un cycle d'horloge. Mais l'accès au cache se faisait en deux cycles d'horloge : un cycle pour calculer une adresse et un cycle pour l'accès mémoire proprement dit. Et ces deux étapes étaient pipelinées, à savoir que deux micro-opérations mémoire peuvent s'exécuter en même temps : une dans l'étage de calcul d'adresse, une autre dans le cache. L'unité mémoire était donc pipelinée, alors que l'accès au cache ne l'était pas. Les processeurs qui implémentaient cette technique regroupent les micro-architectures K5 et K6 d'AMD, les processeurs Intel de micro-architecture P6 (Pentium 2 et 3) et quelques autres. Il est aussi possible de pipeliner l'accès au cache lui-même. Avec un cache pipeliné, l'accès au cache ne se fait pas en un seul cycle, mais en plusieurs. Cependant, on peut lancer un nouvel accès au cache à chaque cycle d'horloge, comme un processeur pipeliné lance une nouvelle instruction à chaque cycle. L'implémentation est assez simple : il suffit d'ajouter des registres dans le cache. Pour cela, on profite que le cache est composé de plusieurs composants séparés, qui échangent des données dans un ordre bien précis, d'un composant à un autre. De plus, le trajet des informations dans un cache est linéaire, ce qui les rend parfois pour l'usage d'un pipeline. ===Le ''pipelining'' des caches ''direct-mapped''=== Voici ce qui se passe avec un cache directement adressé. Pour rappel ce genre de cache est conçu en combinant une mémoire RAM, généralement une SRAM, avec quelques circuits de comparaison et un MUX. L'accès au cache se fait globalement en deux étapes : la première lit la donnée et le ''tag'' dans la SRAM, et on utilise les deux dans une seconde étape. Nous avions vu il y a quelques chapitre comment pipeliner des mémoires, dans le chapitre sur les mémoires évoluées. Et bien ces méthodes peuvent s'utiliser pour la mémoire RAM interne au cache !L'idée est d'insérer un registre entre la sortie de la RAM et la suite du cache, pour en faire un cache pipéliné, à deux étages. Le premier étage lit dans la SRAM, le second fait le reste. L'implémentation sur les caches associatifs à plusieurs voies est globalement la même à quelques détails près. [[File:Cache directement adressé pipeliné - deux étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - deux étages]] Avec le circuit précédent, il est possible d'aller plus loin, cette fois en pipelinant l'accès à la mémoire RAM interne au cache. Pour comprendre comment, rappelons qu'une mémoire SRAM est composée d'un plan mémoire et d'un décodeur. L'accès à la mémoire demande d'abord que le décodeur fasse son travail pour sélectionner la case mémoire adéquate, puis ensuite la lecture ou écriture a lieu dans cette case mémoire. L'accès se fait donc en deux étapes successives séparées, on a juste à mettre un registre entre les deux. Il suffit donc de mettre un gros registre entre le décodeur et le plan mémoire. [[File:Cache directement adressé pipeliné - trois étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - trois étages]] Et on peut aller encore plus loin en découpant le décodeur en deux circuits séparés. En effet, rappelez-vous le chapitre sur les circuits de sélection : nous avions vu qu'il est possible de créer des décodeurs en assemblant des décodeurs plus petits, contrôlés par un circuit de prédécodage. Et bien on peut encore une ajouter un registre entre ce circuit de prédécodage et les petits décodeurs. : Théoriquement, toute l'adresse est fournie d'un seul coup au cache, la quasi-totalité des processeurs présentent l'adresse complète à un cache pipeliné. Mais le Pentium 4 fait autrement. Il faut noter que les premiers étages manipulent l'indice dans la SRAM, qui est dans les bits de poids faible, alors que les étages ultérieurs manipulent le ''tag'' qui est dans les bits de poids fort. Les concepteurs du Pentium 4 ont alors décidé de présenter les bits de poids faible lors du premier cycle d'accès au cache, puis ceux de poids fort au second cycle. Pour cela, l'ALU fonctionnait à une fréquence double de celle du processeur, tout comme le cache L1. Il n'y avait pas de pipeline proprement dit, mais cela réduisait grandement la latence d'accès au cache. ===Le ''pipelining'' des caches splittés/sériels=== Il est aussi possible de pipeliner un cache dits splittés, aussi appelés à accès sériel. Pour rappel, les caches à accès sériel vérifient si il y a succès ou défaut de cache, avant d'accéder aux lignes de cache en cas de succès. Ils font donc différemment des autres caches, qui accèdent à une ligne de cache, avant de déterminer s'il y a succès ou défaut en lisant le tag de la ligne de cache. Les caches sériels disposent de deux SRAM : une pour les tags des lignes de cache et une pour les données. Ils accèdent à la SRAM pour les tags, avant d’accéder à la SRAM des données en cas de succès de cache. Vu que l'accès se fait en deux étapes, une vérification des tags suivie de la lecture/écriture des données, il est facile à pipeliner. Pipeliner le cache permet de régler le problème des accès au cache L1, et elle est tout le temps utilisé sur les processeurs modernes. Mais que faire en cas de défaut de cache ? ==Les caches non bloquants== Un '''cache bloquant''' est un cache auquel le processeur ne peut pas accéder pendant un défaut de cache. Il faut attendre que la lecture ou écriture en RAM soit terminée avant de pouvoir utiliser de nouveau le cache. Un '''cache non bloquant''' n'a pas ce problème : on peut l'utiliser pendant un défaut de cache. Les caches non bloquants permettent de démarrer une nouvelle lecture ou écriture alors qu'une autre est en cours, ce qui permet d'exécuter plusieurs lectures ou écritures en même temps. ===Les ''Miss Handling Status Registers''=== Lors d'un défaut de cache, la mémoire RAM est consultée pendant le défaut de cache, mais le cache est inutilisé. Un défaut de cache n'utilise pas le cache, ce n'est qu'un accès en mémoire RAM, sur le bus mémoire (ou un accès aux niveaux de cache inférieurs, peu importe). Le cache en lui-même est laissé libre, rien n’empêche d'y accéder, il est en réalité intrinsèquement non-bloquant. Les caches, bloquants comme non-bloquants, sont en réalité composés d'une mémoire cache proprement dite, entourée de circuits qui servent d'interface entre le processeur et le cache lui-même. Et parmi les circuits tout autour, certains gèrent l'accès au cache lors d'un défaut de cache. Ils sont regroupés sous le terme de '''''Miss Handling Architecture''''' (MHA). La différence entre un cache bloquant et un cache non-bloquant est en réalité liée à l'implémentation de la MHA. Les caches bloquants coupent volontairement l'accès au cache lors d'un défaut, car les défauts sont plus simples à gérer ainsi. Le défaut de cache rapatrie une donnée depuis la RAM, qui sera écrite dans le cache. Et il ne faut pas qu'une tentative d'accès à cette donnée ait lieu avant qu'elle ne soit chargée. Mais ce blocage est général et touche tout le cache, alors que seule une ligne de cache est concernée par le défaut de cache. L'idée derrière un cache non-bloquant est que seule la ligne de cache est bloquée, mais les autres sont accessibles. L'idée est alors de mémoriser les lignes de cache concernées par le défaut de cache, afin d'en bloquer l'accès. À chaque accès, on vérifie si la ligne de cache est déjà réservée par un défaut de cache. La lecture/écriture est alors bloquée si c'est le cas, mais elle accepte les accès sinon. Pour cela, la ''Miss Handling Architecture'' contient des registres qui mémorisent des informations sur les défauts de cache en cours. Ils portent le nom de '''''miss status handling registers''''', que j’appellerais dorénavant MSHR, qui sont aussi appelés des '''''miss buffer'''''. Le contenu des MSHR varie beaucoup suivant le processeur, mais ces derniers stockent au minimum les informations suivantes : * Le numéro de la ligne de cache dans laquelle les données sont chargées. * Un bit de validité qui indique si le MSHR est vide ou pas, qui est mis à 0 quand le défaut de cache est résolu. * Un ou plusieurs champs de lecture/écriture, qui contiennent des informations sur la lecture/écriture. ** Pour une lecture, elle contient des informations sur la destination de la donnée, à savoir qui prévenir quand le défaut de cache est terminé. C'est parfois un nom/numéro de registre (celui dans lequel charger la donnée), mais c'est souvent le numéro de l'entrée dans le ''load/store queue''. ** Pour les écritures, elle contient la donnée à écrire, ou éventuellement un numéro de ''load/store queue'' où se trouve la donnée à écrire. Il faut noter que le bus mémoire ne peut gérer qu'un seul défaut de cache à la fois. Aussi, il est intéressant de regarder ce qui se passe lorsqu'un second défaut de cache survient, pendant qu'un premier est en cours. Dans ce cas, il y a deux réponses qui correspondent à deux types de caches non-bloquants, qui portent les noms barbares de caches de type succès après défaut et défaut après défaut. Sur le premier type, il ne peut pas y avoir plusieurs défauts de cache simultanés. Dès qu'un second défaut de cache survient, le cache stoppe son activité et on ne peut plus démarrer de nouvelle lecture/écriture, tant que le premier défaut de cache n'est pas résolu. Le second type est plus souple et autorise la survenue de plusieurs défauts de cache simultanés. Du moins, jusqu'à une certaine limite, car le cache ne peut supporter qu'un nombre limité d'accès mémoires simultanés (pipelinés). ===Les accès simultanés à une même ligne de cache=== Il arrive que le processeur fasse plusieurs accès mémoire simultanés à la même ligne de cache. Si la ligne de cache en question n'a pas encore été chargée dans le cache, alors on a plusieurs défauts de cache consécutifs pour la même ligne de cache, et le cache non-bloquant doit gérer la situation. Pour la suite, il va falloir faire une petite distinction entre les défauts primaires et secondaires. Imaginons qu'un défaut de cache ait lieu et demande à charger une donnée dans la ligne de cache numéro N. Il s'agit du premier défaut impliquant cette ligne de cache précise, ce qui lui vaut le nom de '''défaut de cache primaire'''. Mais par la suite, d'autres accès mémoire à la même ligne de cache ont lieu, alors que la ligne de cache n'est pas encore disponible. Dans ce cas, il s'agit de '''défauts de cache secondaires'''. Pour l'unité d'accès mémoire, les défauts de cache primaire et secondaire sont différents (ils prennent tous une entrée dans la ''load/store queue''). Mais pour le cache, ils ne correspondent qu'à un seul accès au cache : celui qui demande de charger la ligne de cache demandée. Les défauts de cache primaire et secondaire à la même ligne de cache se voient attribuer un MSHR unique. La gestion des défauts de cache secondaires dépend du cache non-bloquant. La solution la plus simple ne permet pas les défauts de cache secondaires. Le cache ne permet pas deux défauts de cache simultanés pour la même ligne de cache. Les autres solutions le permettent, en fusionnant des accès simultanés à la même ligne de cache en un seul au niveau des MSHR. Dans tous les cas, détecter les défauts de cache secondaires sont un problème qu'il faut détecter. La ''Miss Handling Architecture'' doit détecter les défauts de cache secondaire. Pour cela, elle procède comme suit. Lors de chaque défaut de cache, la MHA récupère le numéro de la ligne de cache associé. Il vérifie alors chaque MSHR pour vérifier s'il contient le numéro en question. S'il n'y a aucune correspondance dans les MSHR, alors c'est signe que le défaut de cache est un défaut primaire. Mais s'il y en a une, alors c'est un défaut secondaire. Évidemment, cela signifie que lors d'un défaut de cache, le numéro de ligne de cache est envoyé à tous les MSHR, pour comparaison. Les MSHR sont donc regroupés dans une mémoire associative, une sorte de mini-cache, faciliter l'implémentation. Lors d'un défaut de cache primaire, l'accès à un cache non-bloquant se fait comme suit : le processeur envoie une adresse au cache, accède à celui-ci, et détecte la survenue d'un défaut de cache. Il en profite alors pour attribuer une ligne de cache dans laquelle sera chargée ce défaut. L'attribution est très simple dans le cas des caches ''direct mapped'', ou associatifs par voie, pour lesquels l'attribution se fait assez simplement. Il mémorise alors cette information dans les MSHR, après avoir vérifié que le défaut de cache n'était pas un défaut secondaire. Lors des accès ultérieurs à une adresse proche, censée être dans la même ligne de cache, le processeur va encore une fois rencontrer un défaut de cache. Il va alors déterminer le numéro de la ligne de cache associée à l'adresse, et comparer ce numéro avec les MSHRs. Si un MSHR contient ce numéro, c'est signe que le défaut de cache est un défaut secondaire. La MHA réagit alors différemment selon le processeur considéré. Une première solution n'autorise pas les défauts de cache secondaires. Si l'un d'entre eux survient, le processeur est gelé par un ''pipeline stall'', une bulle de pipeline. Une autre solution fusionne les défauts de cache secondaires avec le défaut de cache primaire : tout cela ne correspond qu'à un seul défaut de cache pour lui. ===Les MSHR simples=== Un cache non-bloquant à '''MSHR simple''' contient juste plusieurs MSHR qui mémorisent juste un numéro de ligne de cache, un bit de validité, le champ de lecture/écriture, et l'adresse exacte de lecture/écriture. Avec cette organisation, il est possible d'avoir plusieurs défauts de cache séparés, mais à la condition que chaque défaut accède à une ligne de cache différente. Deux accès simultanés à une même ligne de cache ne sont pas possibles, les défauts de cache secondaires ne sont pas autorisés. Ainsi, chaque défaut de cache se voit attribuer son propre MSHR, chacun contient un numéro de ligne de cache différent. Pour comprendre pourquoi c'est impossible de gérer les défauts secondaires, il faut regarder le champ de lecture/écriture. Si on veut effectuer plusieurs écritures consécutives à la même adresse, le MSHR n'aura pas de quoi mémoriser les deux données à écrire. Il pourra mémoriser la première donnée à écrire, pas la seconde. Même chose lors d'une lecture : le champ lecture/écriture peut mémorisr la destination de la première lecture, pas de la seconde. L'avantage est que la MHA n'a besoin que des MSHR et de quelques circuits annexes. Les autres solutions rajoutent des circuits annexes pour gérer les défauts de cache secondaires, qui utilisent beaucoup de circuits. Le cout en circuit est donc élevé, mais le gain en performance est là. Passons maintenant aux caches non-bloquants qui autorisent les défauts de cache secondaire. La solution la plus simple consiste à utiliser ===Les MSHR adressés implicitement=== Avec les '''MSHR adressés implicitement''', il est possible de fusionner plusieurs accès mémoire à une même ligne de cache, mais sous une condition très importante : ces accès lisent/écrivent des mots mémoire différents. Par exemple, imaginons qu'une ligne de cache contienne 8 mots mémoire de 64 bits. Si un premier accès mémoire lit le mot mémoire numéro 7 (dernier mot mémoire de la ligne), et le second accès le mot mémoire numéro 3, alors la fusion est possible. Mais si deux accès mémoire veulent lire/écrire le mot mémoire numéro 7, alors la fusion n'est pas possible et le processeur se bloque, un ''pipeline stall'' survient. Un MSHR adressé implicitement est un MSHR simple, qui contient naturellement un numéro de ligne de cache (tag) et un bit de validité, sauf que le champ de lecture/écriture est dupliqué. Les différents champs lecture/écriture d'un MSHR sont regroupés dans une mémoire RAM/cache qui contient autant d'entrées qu'il y a de mot mémoire dans une ligne de cache. Chaque entrée de la table est associée à un mot mémoire de la ligne de cache et stocke des informations sur celui-ci. Une entrée mémorise, au minimum : * Un champ de lecture/écriture qui contient soit la destination de la lecture, soit la donnée à écrire. * Un bit R/W permettant d'interpréter correctement le champ de lecture/écriture. * Un bit de validité pour chaque entrée de la table, qui dit si un défaut de cache antérieur accède déjà à ce mot mémoire. La fusion de deux défauts de cache est ainsi assez simple. Un défaut de cache primaire/secondaire configure l'entrée associée au mot mémoire lu/écrit. Si un défaut secondaire ultérieur lit/écrit un mot mémoire différent (dont le bit de validité de l'entrée dans la table est à zéro), pas de conflit : il configure une autre entrée, vide. Mais s'il lit/écrit un mot mémoire pour lequel l'entrée de la table est occupée, il y a conflit, le processeur est bloqué par un ''pipeline stall'', une bulle de pipeline. Avec cette organisation, le nombre de MSHR indique combien de lignes de cache peuvent être lues en même temps depuis la mémoire. Quant au nombre d'entrées par MSHR, il détermine combien d'accès mémoires qui ne se recouvrent pas peuvent avoir lieu en même temps. ===Les MSHR adressés explicitement=== Les '''MSHR adressés explicitement''' n'ont pas les contraintes des MSHR adressés implicitement. Avec eux, il est possible d'avoir plusieurs défauts de cache pointant vers la même ligne de cache, mais aussi vers le même mot mémoire. De tels défauts de cache apparaissent sur les processeurs à exécution dans le désordre, mais sont très rares, voire inexistants, sur les processeurs ''in-order''. L'idée est encore que chaque MSHR est associée à une table mémoire qui mémorise des entrées. Cette table n'est autre qu'une mémoire FIFO. Sauf que cette fois-ci, une entrée n'est pas associée à un mot mémoire. Une entrée est associée à un défaut de cache. Une entrée mémorise là encore la destination de la lecture ou la donnée de l'écriture suivi du bit R/W, ainsi que d'un bit de validité par entrée, mais aussi : la position du mot mémoire lu dans la ligne de cache. C'est cette dernière information qui n'était pas présente dans les MSHR adressés implicitement. De plus, le nombre d'entrée par MSHR n'est pas égal au nombre de mots mémoires dans une ligne de cache, mais peut être arbitrairement grand. L'avantage d'un tel cache est qu'il est capable de traité les défauts secondaires concernant un même mot mémoire, et ce, car les accès à la ligne de cache concernée se produisent dans l'ordre de génération des défauts (la table mémoire est une FIFO). ===Les MSHRs inversés=== Généralement, plus on veut supporter de défauts de cache, plus le nombre de MSHR et d'entrées augmente. Mais au-delà d'un certain nombre d'entrées et de MSHR, les MSHR adressés implicitement et explicitement ont tendance à bouffer un peu trop de circuits. Utiliser une organisation un peu moins gourmande en circuits est donc une nécessité. Cette organisation plus économe se base sur des MSHR inversés. Les MSHR inversés ne contiennent qu'une seule entrée, en quelque sorte : au lieu d’utiliser n MSHR de m entrées chacun, on va utiliser n × m MSHR inversés. La différence, c'est que plusieurs MSHR peuvent contenir un tag identique, contrairement aux MSHR adressés implicitement et explicitement. Lorsqu'un défaut de cache a lieu, chaque MSHR est vérifié. Si jamais aucun MSHR ne contient de tag identique à celui utilisé par le défaut, un MSHR vide est choisi pour stocker ce défaut, et une requête de lecture en mémoire est lancée. Dans le cas contraire, un MSHR est réservé au défaut de cache, mais la requête n'est pas lancée. Quand la donnée est disponible, les MSHR correspondant à la ligne qui vient d'être chargée vont être utilisés un par un pour résoudre les défauts de cache en attente. ===Les MSHR intégrés au cache=== Certains chercheurs ont remarqué que pendant qu'une ligne de cache est en train de subir un défaut de cache, celle-ci reste inutilisée, et son contenu est destiné à être perdu une fois le défaut de cache résolu. Ils se sont dit que, plutôt que d'utiliser des MSHR séparés, il vaudrait mieux utiliser la ligne de cache pour stocker les informations sur les défauts de cache en attente dans cette ligne de cache. Pour éviter tout problème, il faut rajouter un bit dans les bits de contrôle de la ligne de cache, qui sert à indiquer que la ligne de cache est occupée : un défaut de cache a eu lieu dans cette ligne, et elle stocke donc des informations pour résoudre les défauts de cache. ==L'entrelacement mémoire== Nous venons de voir comment une mémoire cache peut gérer plusieurs accès mémoire simultanés. Il se trouve que ce genre d'optimisation dépasse largement le cadre du cache. Les mémoires RAM ne sont pas en reste, elles aussi ! Il est possible de pipeliner l'accès à une mémoire RAM, en utilisant des techniques dites d''''entrelacement'''. Précisons cependant que cette optimisation n'est pas utilisée sur les mémoires SDRAM ou DDR modernes, du moins pas sans modifications majeures. Mais divers ordinateurs assez anciens, dont des superordinateurs, ont utilisé cette technique. La technique demande d'utiliser plusieurs mémoires séparées. Sur les anciens ordinateurs, les mémoires en question sont des chips mémoire, à savoir des circuits intégrés de DRAM. Pour faire plus général, nous allons utiliser le terme de '''banques''', ou encore de bancs mémoire. La différence est qu'il existe des mémoires multi-banque, qui regroupent plusieurs banques indépendantes dans un seul boitier, dans un seul chip mémoire. En clair, de telles mémoires regroupent plusieurs sous-mémoires dans un seul circuit intégré. Il est aussi possible d'utiliser plusieurs chips mémoire séparés, chacun ayant une banque. Reste à combiner ces banques pour former un pseudo-pipeline. ===Utiliser plusieurs banques sans entrelacement=== Sans optimisation particulière, les adresses sont réparties dans les banques comme indiqué ci-dessous. Il s'agit de ce qui s'appelle un arrangement horizontal, et nous avions vu celui-ci dans le chapiutre sur les mémoires SDRAM. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Avec cet arrangement, les bits de poids fort de l'adresse sont utilisées pour sélectionner la banque adéquate, et le reste de l'adresse est envoyé sur le bus d'adresse. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Adresse de banque !! Adresse dans la banque |- | Quelques bits de poids fort || Reste de l'adresse |} L'entrelacement change cette répartition, afin d'accélérer les accès mémoire. L'entrelacement classique vise à améliorer les accès à des adresses mémoires consécutives, mais des formes plus évoluées d'entrelacement visent à optimiser des accès mémoire arbitraires. Voyons-les dans l'ordre, du plus simple au plus complexe. ===L'entrelacement classique=== Avec l''''entrelacement classique''', des adresses consécutives sont réparties dans des banques différentes. [[File:Répartition des adresses dans une mémoire interleaved.png|centre|vignette|upright=1.5|Répartition des adresses dans une mémoire interleaved.]] L'entrelacement simple permet d'accélérer les accès mémoire qui se font à des adresses consécutives. Avec l'entrelacement, chaque accès mémoire tombe dans une banque mémoire différente, ce qui fait qu'on peut démarrer un nouvel accès mémoire à chaque cycle d'horloge. Pendant qu'une banque est occupée par un accès mémoire, on démarre le suivant dans une autre banque, et ainsi de suite. Le tout se termine soit quand on a épuisé toutes les banques libres, soit quand l'accès en rafales se termine. Pas besoin d'attendre que la mémoire ait fini sa lecture/écriture avant de démarrer la lecture/écriture suivante. [[File:Pipemining mémoire.png|centre|vignette|upright=2.5|Pipelining mémoire]] L'animation suivante illustre bien le processus, avec quatres banques. [[File:Interleaving.gif|centre|vignette|upright=2.5|Pipelining mémoire]] L'entrelacement simple utilise les bits de poids faible pour sélectionner la banque, et les bits de poids fort pour la case mémoire. [[File:Adresse mémoire d'une mémoire entrelacée.png|centre|vignette|upright=2|Adresse mémoire d'une mémoire entrelacée]] Précisons que le temps d'accès mémoire ne change pas beaucoup avec l'entrelacement. Par contre, on peut faire plus d'accès mémoire simultanés. On peut démarrer un accès mémoire par cycle d'horloge, mais l'accès en lui-même prend plusieurs cycles. Les mémoires à entrelacement ont donc un débit supérieur aux mémoires qui ne l'utilisent pas. L'entrelacement classique pourrait être utilisé pour accélérer les transferts entre mémoire RAM et cache, qui se font en blocs d'adresses consécutives. Mais de nos jours, il n'est pas utilisé dans cette optique. Il faut dire qu'il est redondant avec le mode rafale des mémoires DRAM. Les deux font globalement la même chose et on voit mal comment utiliser les deux en même temps. Le mode rafale étant plus simple à implémenter, et plus léger niveau utilisation du bus de commande mémoire, il est préféré à l'entrelacement. Par contre, l'entrelacement a eu son heure de gloire sur d'anciens processeurs qui n'avaient pas de mémoire cache, alors qu'ils disposaient d'un pipeline. L'exemple typique est celui des nombreux ''processeurs vectoriels'', que nous n'avons pas encore abordé à ce stade du cours. De tels processeurs avaient un pipeline, une unité de calcul fortement pipelinée, et parfois même de l'exécution dans le désordre et du renommage de registres ! ===L'entrelacement par décalage=== L'entrelacement simple est très utile pour les accès en rafle ou équivalents. Par contre, il existe de rares situations où il n'est pas optimal. Une de ces situations est celle des accès en enjambée (en ''stride''), où l'on accède en série à des adresses sont séparées par N mots mémoires. [[File:Accès par enjambées.png|centre|vignette|upright=2|Accès par enjambées.]] De tels accès surviennent quand un logiciel accède à des structures de données spécifiques, à savoir des tableaux de structures, des matrices, ou d'autres structures de données dans le genre. Un cas classique est celui du parcours d'une matrice colonne par colonne. Les matrices de nombres sont mémorisées ligne par ligne, ce qui fait que parcourir une colonne demande de passer d'une ligne à la suivante et de faire des grands sauts en mémoire RAM, mais tous espacés par la même distance. Avec l'entrelacement simple, les accès en enjambée sont moins performants que les accès à des adresses consécutives, mais cela ne fonctionne pas trop mal. Le pire cas est celui où l'on a N banques et où les données sont justement placées toutes les N adresses. Des accès consécutifs vont tous tomber dans la même banque, on ne peut plus accéder à des banques différentes en parallèle. Mais en dehors de ce cas, on voit une amélioration par rapport à la situation sans entrelacement. Pour obtenir des performances maximales pour les accès en enjambées, il faut répartir les mots mémoires dans la mémoire autrement. L'organisation idéale est la suivante. : Dans les explications qui vont suivre, la variable N représente le nombre de banques, qui sont numérotées de 0 à N-1. On commence par organiser les N premières adresses comme une mémoire entrelacée simple : l'adresse 0 correspond à la banque 0, l'adresse 1 à la banque 1, etc. Pour le bloc suivant, on décale tout d'une adresse, à savoir qu'on commence à remplir les banques à partir de la seconde, non de la première. Une fois la fin du bloc atteinte, on finit de remplir le bloc en repartant du début du bloc. Le troisième bloc d'adresse subit le même traitement, sauf qu'on commence à remplir à partir de la seconde banque. Et on poursuit l’assignation des adresses en décalant d'un cran en plus à chaque bloc. Ainsi, chaque bloc verra ses adresses décalées d'un cran en plus comparé au bloc précédent. Si jamais le décalage dépasse la fin d'un bloc, alors on reprend au début. [[File:Mémoire interleaved par décalage.png|centre|vignette|upright=1.5|Mémoire entrelacée par décalage.]] En faisant cela, on remarque que les banques situées à N adresses d'intervalle sont différentes. Dans l'exemple du dessus, nous avons ajouté un décalage de 1 à chaque nouveau bloc à remplir. Mais on aurait tout aussi bien pu prendre un décalage de 2, 3, etc. Dans tous les cas, on obtient un entrelacement par décalage. Ce décalage est appelé le pas d'entrelacement, noté P. Le calcul de l'adresse à envoyer à la banque, ainsi que la banque à sélectionner se fait en utilisant les formules suivantes : * adresse à envoyer à la banque = adresse totale / N ; * numéro de la banque = (adresse + décalage) modulo N ; * décalage = (adresse totale * P) mod N. Avec cet entrelacement par décalage, on peut prouver que la bande passante maximale est atteinte si le nombre de banques est un nombre premier. Seulement, utiliser un nombre de banques premier peut créer des trous dans la mémoire, des mots mémoires inadressables. Malgré ce défaut, la technique a été utilisée sur quelques ordinateurs, avec l'exemple notable du superordinateur ''Burroughs Scientific Processor''. Pour éviter cela, il y a plusieurs solutions. Par exemple, on peut faire en sorte que N et la taille d'une banque soient premiers entre eux : ils ne doivent pas avoir de diviseur commun. Mais en pratique, elles n'ont pas vraiment été implémentées dans une vraie machine, et sont restées à l'état de recherche, aussi je les passe sous silence. ===L'entrelacement pseudo-aléatoire=== Une dernière méthode de répartition consiste à répartir les adresses dans les banques de manière "pseudo-aléatoire". L'idée exacte est de faire passer l'adresse dans une '''fonction de hachage''', plus ou moins complexe. Pour rappel, une fonction de hachage prend une entrée de grande taille et fournit en sortie un résultat de petite taille. Idéalement, elles doivent donner des résultats aussi différents que possible pour des entrées similaires, histoire de simuler une sorte de pseudo-aléatoire. Ici, une adresse est transformée en un numéro de banque. Une fonction de hachage simple ne fait que permuter des bits de l'adresse pour obtenir son résultat. Elle ne fait qu'échanger des bits de place avant de couper l'adresse en deux morceaux : un pour la sélection de la banque, et un autre pour la sélection de l'adresse dans la banque. Cette permutation est fixe, et ne change pas suivant l'adresse. Des fonctions de hachage plus complexes font des XOR bit à bit entre certains bits de l'adresse. ==Les contrôleurs mémoires SDRAM/DDR optimisés== Après avoir vu le cas des mémoires RAM, nous allons nous concentrer sur le cas particulier des mémoires SDRAM. Les mémoires SDRAM et DDR modernes sont capables de gérer plusieurs accès simultanés à la mémoire RAM. Et les contrôleurs mémoire intègrent des optimisations spécifiques afin d'en profiter au maximum. ===La mise en attente des accès mémoire=== Comme pour les mémoires caches, il existe des contrôleurs mémoire bloquants et non-bloquants. Les premiers n'acceptent qu'un seul accès mémoire à la fois, les seconds acceptent plusieurs accès mémoire simultanés. Pour simplifier, nous allons dire que le contrôleur mémoire n’exécute qu'un seul accès mémoire à la fois, et qu'ils met en attente les accès en trop. C'est une simplification assez irréaliste, vu que les mémoires modernes disposent de plusieurs banques accessibles en parallèle. Mais nous introduirons ce détail un peu plus tard. Avec un contrôleur mémoire de ce type, les accès mémoire sont mis en attente dans une mémoire FIFO, histoire de les exécuter dans leur ordre d'arrivée. Les accès mémoire quittent alors la mémoire dans leur ordre d'arrivée, les lectures sont renvoyées dans l'ordre demandé. En plus d'une mémoire FIFO pour les commandes mémoire, on trouve une mémoire FIFO pour les données à écrire. Elle permet d'accumuler plusieurs écritures, tout en permettant que la donnée adéquate soit envoyée à la RAM au bon moment. Il y a aussi une FIFO pour les données lues, qui sert au cas où le cache ne soit pas disponible quand on lui envoie la donnée lue. [[File:Module d'interface avec la mémoire.png|centre|vignette|upright=2.5|Contrôleur mémoire avec mise en attente des requêtes processeur et autres optimisations.]] Une optimisation regroupe plusieurs accès à des données consécutives en un seul accès en rafale. Le contrôleur mémoire analyse les requêtes mises en attente et détecte si certaines se font à des adresses consécutives. Si c'est le cas, il fusionne ces requêtes en une lecture/écriture en rafale. Une telle optimisation est appelée la '''combinaison de lecture''' pour les lectures, et la '''combinaison d'écriture''' pour les écritures. ===Le pipeline des mémoires SDRAM=== Les SDRAM ont une forme de pseudo-pipeline très limité. Elles sont parfois capables de démarrer une commande avant que la précédente soit terminée. Même si elle parait très limitée, cette possibilité est exploitée au mieux par la présence de plusieurs banques dans la mémoire SDRAM. Les chips mémoires de SDRAM sont composés de plusieurs banques, chacune étant une sorte de mini-mémoire miniature. Chaque banque a son propre décodeur, son propre tampon de ligne, ses propres multiplexeurs de colonne, sa logique de rafraichissement mémoire, etc. C'est comme si une SDRAM regroupait plusieurs mémoires séparées dans un même circuit intégré. Et cela permet de faire plusieurs accès mémoire en même temps, dans des banques différentes. Il est possible de lancer un accès mémoire dans deux banques en même temps, par exemple. Ou encore, on peut lancer un nouvel accès mémoire dans une banque si elle est inoccupée. La possibilité améliore grandement la performance de la SDRAM. Mais nous en reparlerons dans un chapitre ultérieur. {|class="wikitable" |+ Pipelining basique sur les SDRAM |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 |- ! Banque Numéro 1 | || || || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || || || |- ! Banque Numéro 2 | || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || |- ! Banque Numéro 3 | || || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || |} L'implémentation n'a rien de vraiment compliqué. Il suffit d'utiliser une mémoire FIFO par banque. Ainsi, les accès dans une même banque se feront en série, dans l'ordre d'arrivée, mais des accès à des banques différentes se feront dans le désordre. Cela ne pose pas de problème car les accès se font à des adresses différentes, ce qui fait qu'il n'y a pas de conflits majeurs. [[File:Gestion parallèle des banques.png|centre|vignette|upright=2.5|Gestion parallèle des banques.]] ===Le ré-ordonnancement des commandes mémoires=== Un contrôleur mémoire moderne est capable de changer l'ordre des requêtes mémoires, pour gagner en performances. En clair : il peut faire l'équivalent mémoire de l'exécution dans le désordre des processeurs haute performance. Une différence majeure est que le contrôleur mémoire ne remet pas les accès mémoire dans l'ordre avant de les envoyer au processeur. C'est le processeur qui remet en ordre les accès mémoire, dans sa ''load queue''. Pour cela, les requêtes mémoire sont "numérotées", à savoir qu'on leur attribue un identifiant binaire. Le contrôleur mémoire exécute les lectures/écritures dans le désordre, mais garde la trace de leur identifiant. Il sait dans quel ordre il exécute les accès mémoire et connait leur latence. Ce qui fait qu'il sait que la donnée lue à tel instant est associée à la requête mémoire numéro X. Pour les lectures, il renvoie la donnée lue avec l'identifiant. Le processeur sait alors à quelle lecture la donnée lue correspond et il se débrouille en interne pour gérer la situation. En clair : le processeur voit les accès mémoire dans le désordre, c'est lui qui les remet en ordre. Maintenant que ces précisions sont faites, posons cette question : quel est l'intérêt de faire les accès mémoire dans le désordre ? Accéder à des banques en parallèle est une réponse possible, mais le ré-ordonnancement est surtout utile si tous les accès mémoire atterrissent dans la même banque. Pour comprendre pourquoi, faisons un rappel rapide sur le tampon de ligne. Les mémoires SDRAM sont des mémoires à tampon de ligne. Elles font un accès mémoire en deux étapes. La première étape recopie une ligne de N * 64 bits dans un tampon interne à la SDRAM. La seconde étape sélectionne une colonne, à savoir une donnée de 64 bits, qui est soit envoyée sur le bus de données pour une lecture, soit modifiée par une écriture. [[File:Mémoire à tampon de ligne.png|centre|vignette|upright=2|Mémoire à tampon de ligne]] Les deux étapes correspondent à deux commandes séparées : une commande ACT qui précise l'adresse de la ligne, et une commande READ ou WRITE qui précise l'adresse de la colonne. [[File:Signaux RAS et CAS.png|centre|vignette|upright=2|Signaux RAS et CAS.]] Les SDRAM permettent de se passer de la première étape si des accès consécutifs se font dans la même ligne. Une fois activée, la ligne reste ouverte et on peut accéder plusieurs fois de suite dedans. On a alors juste à préciser l'adresse de colonne dedans. [[File:Sélection d'une ligne sur une mémoire FPM ou EDO.png|centre|vignette|upright=2|Sélection d'une ligne sur une mémoire FPM ou EDO.]] L'exécution dans le désordre des lectures/écritures SDRAM vise à faire en sorte que des accès consécutifs se fassent dans une même ligne. Elle marche si plusieurs accès à une même ligne ne sont pas consécutifs, et qu'il y a des accès entre les deux. Après ré-ordonnancement, ces accès mémoire deviennent consécutifs, ils sont exécutés l'un à la suite de l'autre. Pour rendre le tout plus concret, voici un exemple. Imaginez que l'on sait les 3 accès mémoire suivants : * Une lecture ligne A ; * Une écriture ligne B ; * Une lecture ligne A. Sans réordonnancent, on doit émettre deux commandes PRECHARGE : une quand on passe de la ligne A à la ligne B, et une autre pour le passage inverse. pour éviter cela, le contrôleur mémoire peut retarder l'écriture, ou au contraire avancer la seconde lecture. le résultat est alors le suivant : * Lecture ligne A ; * Lecture ligne A ; * Une écriture ligne B. On a donc deux accès consécutifs à la même ligne, suivi par une écriture dans une autre ligne. La technique marche parce que l'on a un mix adéquat de lectures et d'écritures. Mais il existe des possibilités de réorganisation autres qui ne sont pas valides. Il faut par exemple prendre garde à éviter d'intervertir lectures et écritures à une même adresse, sous peine de problèmes. Encore une fois, il faut faire attention aux dépendances mémoires. Le controleur mémoire teste les dépendances mémoires avant d'envoyer des commandes à la mémoire DDR/SDRAM. ==L'entrelacement sur les mémoires SDRAM== L''''entrelacement''' fonctionne sur les mémoires SDRAM, mais il doit être fortement modifié. Concrètement, tout ce qui a étré dit plus est inapplicable pour les SDRAM, du fait de la présence du tampon de ligne. Des adresses consécutives atterrissent déjà dans la même ligne, ce qui fait qu'on n'a pas besoin d'entrelacement au niveau d'une ligne. Et ne parlons pas de ce la présence de rangées et de canaux mémoire ! Cependant, il peut y avoir un entrelacement lié à la présence des banques SDRAM. L'entrelacement est géré juste avant le séquenceur mémoire. Le '''circuit d'entrelacement''' intervertit certains bits de l'adresse lors des accès mémoires. On trouve aussi des mémoires FIFOs pour mettre en attente les requêtes mémoire provenant du processeur. Elles permettent de recevoir plusieurs accès mémoire consécutifs. Si vous vous demandez comment plusieurs accès mémoire consécutifs peuvent avoir lieu, sachez que c'est une bonne question. Pour le moment, nous allons dire que ces accès proviennent du processeur ou de la mémoire cache, sans expliquer comment. Le contrôleur mémoire reçoit une série d'accès mémoire assez rapprochés, qu'il doit exécuter au mieux. [[File:Controleur mémoire d'une SDRAM avec entrelacement.png|centre|vignette|upright=2.5|Contrôleur mémoire d'une SDRAM avec entrelacement.]] Pour gérer l'entrelacement, le contrôleur de SDRAM prend en entrée l'adresse envoyée par le processeur, et la découpe en plusieurs champs : un pour sélectionner la banque adéquate, un autre pour la ligne, un autre pour la colonne, un autre pour la rangée. Une solution simple désactive l'entrelacement. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de banque || Adresse de ligne || Adresse de colonne |} Si cette solution fonctionne bien si les accès mémoire sont assez aléatoires, en pratique, mieux vaut utiliser l'entrelacement. ===L'entrelacement de banques/lignes=== Une première méthode d'entrelacement est l''''entrelacement de banques'''. Elle répartit deux lignes consécutives dans deux banques différentes. Pour comprendre l'idée, prenons un exemple. Imaginons une mémoire avec deux banques et 4 lignes. Imaginons qu'on parcoure/balaye la mémoire RAM en partant des adresses basses. Sans entrelacement de ligne, les accès se feront comme suit, de gauche à droite : {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Banque numéro 1 !! colspan="4" | Banque numéro 2 |- | Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 || Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 |} Avec l'entrelacement de banques, les accès se feront comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 |- | Ligne 1 || Ligne 1 || Ligne 2 || Ligne 2 || Ligne 3 || Ligne 3 || Ligne 4 || Ligne 4 |} Avec N banques, la première ligne ira dans la première banque, la seconde ligne dans la seconde, et ainsi de suite jusqu'à la énième banque qui va dans la banque numéro N. Et la ligne N+1 repart dans la première banque. L'avantage est que passer d'une ligne à la suivante est assez rapide. Au lieu de devoir fermer la première ligne et ouvrir la suivante on ouvrira directement la ligne suivante dans une banque différente, ce qui sera plus rapide. La commande PRECHARGE pourra être envoyée à la seconde banque en avance. Pour cela, l'adresse est découpée comme suit. {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne (précise à l'octet) |} ===L'entrelacement de rangées=== Il est aussi possible de faire la même chose, mais avec les rangées. Pour simplifier fortement, une rangée est simplement un chip mémoire. En réalité, les rangées ne sont pas des chip mémoire, mais un ensemble de chips mémoire regroupés ensemble histoire d'atteindre les 64 bits du bus de données. Par exemple, une rangée peut combiner 8 chips mémoire avec un bus de données de 8 bits chacun pour obtenir les 64 bits du bus de données d'une SDRAM. Mais nous passons ce détail sous silence dans les explications qui vont suivre, par souci de simplification. Pour faire comprendre l'entrelacement de rangée, le mieux est d'utiliser un exemple, le même que précédemment. Sans entrelacement de rangée, on change de chip mémoire une fois qu'on a balayé toutes ses adresses. {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Rangée numéro 1 !! colspan="4" | Rangée numéro 2 |- | Banque 1 || Banque 2 || Banque 3 || Banque 4 || Banque 1 || Banque 2 || Banque 3 || Banque 4 |} Avec l'entrelacement de ligne, on change de rangée dès qu'on change de banque/ligne. {|class="wikitable" |+ Adresse mémoire |- ! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 |- | Banque 1 || Banque 1 || Banque 2 || Banque 2 || Banque 3 || Banque 3 || Banque 4 || Banque 4 |} En clair, quand on a épuisé toutes les banques dans une même rangée, on passe à la rangée suivante au lieu de rester dans la même rangée. Notons que dans l'exemple précédent, on a combiné l'entrelacement de rangée et de banque, mais on aurait pu utiliser l'entrelacement de rangée seul. Mais ce n'est pas le cas le plus courant en pratique. Toujours est-il qu'en combinant entrelacement de rangée et de ligne, le découpage de l'adresse est le suivant : {|class="wikitable" |+ Adresse mémoire |- | Adresse de ligne || Numéro de rangée || Adresse de banque || Adresse de colonne (précise à l'octet) |} D'autres méthodes d'entrelacement sont possibles, mais elles sont plus complexes. Chaque contrôleur mémoire fait un peu à sa sauce. Ils permettent en général de choisir entre plusieurs entrelacements. Au minimum, ils permettent de désactiver l'entrelacement, d'activer un entrelacement de ligne, et d'autres entrelacements plus complexes, dont certains sont propriétaires et tenus secrets par le fabricant. ===L'entrelacement avec le ''dual channel''=== [[File:Dual channel slots.jpg|vignette|Slots mémoires avec ''dual channel''.]] L'usage du ''dual channel'' complique encore l'entrelacement. Là encore, il y a deux grandes solutions : avec et sans entrelacement des canaux mémoire. Rappelons le principe : deux barrettes de RAM sont accédées en parallèle. Et pour cela, il faut utiliser l'entrelacement. Typiquement, chaque barrette mémoire fournit 64 bits, ce qui fait que l'on peut accéder à 128 bits d'un seul coup, par exemple avec un accès en rafale. Sans ''dual channel'', la première barrette correspond à la moitié haute de la RAM, la seconde barrette correspond à la moitié basse. Avec ''dual channel'', une forme spécifique d'entrelacement est activée. Concrétement, deux blocs de 64 bits sont placés dans des canaux mémoire séparés. Idem avec du triple ou quadruple canal, mais c'est alors trois ou quatre blocs de 64 bits qui sont dispersés dans des canaux consécutifs. Le découpage de l'adresse est alors le suivant : {|class="wikitable" |+ Adresse mémoire |- ! colspan="6" | Sans entrelacement |- | Numéro de canal/barrette || Reste de l'adresse || Position de l'octet dans un bloc de 64 bits |- | colspan="6" | |- ! colspan="6" | Avec entrelacement |- | Reste de l'adresse || Numéro de canal/barrette || Position de l'octet dans un bloc de 64 bits |} [[File:Décodage d'adresse avec dual channel.png|centre|vignette|upright=2|Décodage d'adresse avec dual channel.]] Les contrôleurs de SDRAM précédents sont assez basiques et ne représentent pas les contrôleurs les plus évolués. Ils étaient utilisés sur les anciens PC, à une époque où ils étaient encore sur la carte mère du processeur. Mais de nos jours, le contrôleur mémoire est intégré au processeur. Et il incorpore de nombreuses optimisations afin de gagner en performances. Malheureusement, ces optimisations sont elles-mêmes dépendantes des optimisations intégrées dans le processeur, ce qui fait qu'on ne peut pas en parler ici. Elles demandent que le contrôleur mémoire reçoive plusieurs accès mémoire simultanés, ce qui n'a aucun sens à ce stade du cours. Aussi, nous allons les passer sous silence pour le moment. Nous les verrons dans le chapitre sur le parallélisme mémoire. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=La désambiguïsation mémoire | prevText=La désambiguïsation mémoire | next=Les processeurs superscalaires | nextText=Les processeurs superscalaires }} </noinclude> 1jfi0p6211gtr7i0drtcbx6ejdscou9 764681 764664 2026-04-23T18:58:28Z Mewtow 31375 /* L'entrelacement classique */ 764681 wikitext text/x-wiki Dans ce chapitre, nous allons voir les techniques qui permettent de gérer plusieurs accès mémoire simultanés directement au niveau du cache ou de la mémoire RAM. Par plusieurs accès mémoire simultanés, vous pensez sans doute à l'usage de cache multiports, voire de mémoires RAM multiport. C'est en effet une solution, mais ce n'est clairement pas la seule. Par exemple, il est possible de pipeliner l'accès au cache, voire à la mémoire RAM. Il existe de nombreuses techniques de '''parallélisme mémoire''', et nous allons les voir dans ce chapitre. Pour donner un exemple, il est possible de pipeliner les accès mémoire. Il est possible de pipeliner l'accès au cache et/ou l'accès à la mémoire, et nous verrons comment faire dans ce chapitre. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, qu'ils aient ou non un pipeline dynamique. Par contre, pipeliner la mémoire est une technique ancienne, aujourd'hui peu utilisée. Elle était utilisée sur des très vieux ordinateurs, qui avaient un pipeline mais pas de mémoire cache. Mais de nos jours, les mémoires SDRAM et DDR ne sont pas adaptées pour ça, il est très difficile de les pipeliner correctement. Le parallélisme mémoire est utile aussi bien sur des processeurs à émission dans l'ordre que dans le désordre. Par exemple, un processeur ''in-order'' avec des lectures non-bloquantes peut e, profiter. Il peut techniquement lancer une seconde lecture, après avoir rencontré une première lecture non-bloquante. En général, le nombre de lectures consécutives est cependant limité à moins d'une dizaine. À l'opposé, les processeurs sans lectures non-bloquantes profitent pas du parallélisme mémoire. À la rigueur, ils peuvent en profiter s'ils intègrent des techniques de préchargement. Pendant que le processeur exécute une lecture/écriture, il peut précharger une autre donnée en parallèle. Inutile de dire que sans pipeline processeur, le parallélisme mémoire ne sert pas à grand-chose. ==Le ''line fill buffer'' et les techniques associées== Lors d'un défaut de cache, le processeur doit attendre que toute la ligne de cache soit chargée avant d'être utilisable. Or, la taille d'une ligne de cache est supérieure à la largeur du bus mémoire, ce qui fait qu'une ligne de cache est chargée en plusieurs fois, morceaux par morceaux, mot mémoire par mot mémoire. Le chargement peut se faire directement dans le cache, mais ce n'est pas une solution très pratique. À la place, beaucoup de processeurs ajoutent une mémoire tampon entre la RAM et le cache, appelée le '''tampon de remplissage de ligne''' (''line-fill buffer'') dans les processeurs modernes. Lors d'un défaut de cache, le processeur charge la donnée de la RAM dans le tampon de remplissage de ligne, mot mémoire par mot mémoire. Une fois plein, le tampon de remplissage de ligne est recopié dans la ligne de cache. Le tampon de remplissage de ligne contient une ligne de cache, si ce n'est que certains bits de contrôle ne sont pas présents. Le tampon de remplissage de ligne contient un bit de validité pour chaque mot mémoire de la ligne de cache, qui indiquent si le mot mémoire a été chargé. Par exemple, prenons un processeur 64 bits, qui gère donc des mots mémoire de 8 octets, avec des lignes de cache 256 octets/32 mots mémoire. Le tampon de remplissage de ligne contiendra 32 bits de validité. Si le processeur a chargé les 6 premiers mots mémoire, les 6 premiers bits de validité seront mis à 1, les autres seront encore à zéro. Il faut noter que la disponibilité de la ligne complète se détermine assez facilement en faisant un ET logique entre tous les bits de validité. Le processeur sait ainsi quand la ligne de cache est disponible entièrement et donc quand la transférer dans le cache. La même chose existe avec une hiérarchie de cache, sauf que l'on trouve une mémoire tampon entre chaque niveau de cache. Il y a un tampon de remplissage de ligne entre le cache L1 et le cache L2, entre le cache L2 et le L3, etc. Si le cache ne gère qu'un seul défaut de cache à la fois, le ''fill line buffer'' est une mémoire très simple, qui ne mémorise qu'une seule ligne de cache. Et cela vaut aussi bien pour un cache bloquant que non-bloquant. Mais sur les caches capables de gérer plusieurs défauts simultanés, le tampon de remplissage de ligne est une mémoire de type FIFO ou LIFO, capable de mémoriser plusieurs lignes de cache. Notons que le tampon de remplissage de ligne est très utile pour implémenter certaines techniques, par exemple le contournement du cache. Nous avions vu dans le chapitre sur le cache que certains accès mémoire doivent contourner le cache, pour des raisons de cohérence des caches. C'est notamment nécessaire pour accéder aux périphériques, mais c'est aussi utile pour des raisons de performances dans des cas très spécifiques. Les accès qui contournent le cache se font directement dans le tampon de remplissage de ligne : le processeur écrit ou lit les données depuis ce tampon de remplissage de ligne, sans accéder au cache. ===L'''early restart'' et le ''critical word load''=== La présence du ''line buffer'' permet une optimisation assez intéressante, qui permet de réduire la latence des défauts de cache. L'optimisation consiste à lire un mot mémoire dans le tampon de remplissage de ligne, même si la ligne de cache complète n'a pas encore été chargée. Il existe deux manières de faire cela, qui portent les noms d'''early restart'' et de ''critical word load''. La première est la version la plus simple, la seconde est plus complexe mais plus performante. Avec la technique d''''''early restart''''', la ligne de cache est chargée normalement, en partant de son premier mot mémoire. Dès que le mot mémoire lu/écrit par le processeur est copié dans le ''line fill buffer'', il est envoyé au processeur immédiatement. Illustrons le tout par un exemple, où une ligne de cache fait 16 mots mémoire. Le processeur effectue une lecture, qui lit le 5ème mot mémoire. Sans ''early restart'', le processeur doit charger les 16 mots mémoire avant de faire la lecture dans le cache. Avec ''early restart'', le processeur reçoit la donnée dès que le 5ème mot mémoire est disponible. Le processeur doit attendre que les 4 premiers mots mémoire soient chargés, puis le 5ème mot mémoire arrive et est envoyé directement au processeur, il est lu directement depuis le ''line fill buffer''. Les 11 mots mémoire suivants sont ensuite chargés dans le cache pendant que le processeur fait des calculs dans son coin. Le ''critical word load'' est une optimisation de la technique précédente où le chargement de la ligne de cache commence directement à la donnée demandée par le processeur. Pour reprendre l'exemple précédent, où le processeur demande le 5ème mot mémoire sur 16, le ''critical word load'' charge le 5ème mot mémoire en premier et l'envoie au processeur, ce qui fait qu'il est chargé très rapidement. Pas besoin d'attendre que le processeur charge les 4 mots mémoire précédents comme avec l'''early restart''. Dans le détail, le processeur charge le 5ème mot mémoire en premier, puis charge les 11 suivants, et termine par les 4 mots mémoire du début. En clair, le ''critical word load'' commence par charger le mot mémoire lu, puis les blocs suivants, avant de revenir au début du bloc pour charger les blocs restants. Ainsi, la donnée demandée par le processeur sera la première disponible. Pour cela, l'organisation du tampon de remplissage de ligne est modifiée de manière à rendre cela possible. Il a une taille égale à une ligne de cache complète, qui contient elle-même plusieurs mots mémoire. Dans le ''line-fill buffer'', chaque mot mémoire est stocké avec un tag, qui indique l'adresse du mot mémoire stocké dans le ''line-fill buffer''. Le ''line-fill buffer'' est donc un cache un peu particulier, qui fonctionne comme un cache du point de vue du processeur, comme une mémoire FIFO pour les transferts avec le cache. Ainsi, un processeur qui veut lire dans le cache après un défaut peut accéder à la donnée directement depuis le tampon de remplissage de ligne, alors que la ligne de cache n'a pas encore été totalement recopiée en mémoire. ==Les caches pipelinés== Il est possible de pipeliner l'accès au cache, ce qui demande juste de rajouter des registres au bon endroits et quelques circuits de contrôle. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, même ceux avec un pipeline dynamique. Et l'implémentation peut se faire de plusieurs manières différentes. Il faut dire que découper une mémoire cache en plusieurs étapes peut se faire de plusieurs manière. Commençons par la plus simple. Beaucoup de processeurs des années 2000 avaient une fréquence peu élevée comparée aux standards d'aujourd'hui, ce qui fait que leur cache avait bien un temps d'accès d'un cycle d'horloge. Mais l'accès au cache se faisait en deux cycles d'horloge : un cycle pour calculer une adresse et un cycle pour l'accès mémoire proprement dit. Et ces deux étapes étaient pipelinées, à savoir que deux micro-opérations mémoire peuvent s'exécuter en même temps : une dans l'étage de calcul d'adresse, une autre dans le cache. L'unité mémoire était donc pipelinée, alors que l'accès au cache ne l'était pas. Les processeurs qui implémentaient cette technique regroupent les micro-architectures K5 et K6 d'AMD, les processeurs Intel de micro-architecture P6 (Pentium 2 et 3) et quelques autres. Il est aussi possible de pipeliner l'accès au cache lui-même. Avec un cache pipeliné, l'accès au cache ne se fait pas en un seul cycle, mais en plusieurs. Cependant, on peut lancer un nouvel accès au cache à chaque cycle d'horloge, comme un processeur pipeliné lance une nouvelle instruction à chaque cycle. L'implémentation est assez simple : il suffit d'ajouter des registres dans le cache. Pour cela, on profite que le cache est composé de plusieurs composants séparés, qui échangent des données dans un ordre bien précis, d'un composant à un autre. De plus, le trajet des informations dans un cache est linéaire, ce qui les rend parfois pour l'usage d'un pipeline. ===Le ''pipelining'' des caches ''direct-mapped''=== Voici ce qui se passe avec un cache directement adressé. Pour rappel ce genre de cache est conçu en combinant une mémoire RAM, généralement une SRAM, avec quelques circuits de comparaison et un MUX. L'accès au cache se fait globalement en deux étapes : la première lit la donnée et le ''tag'' dans la SRAM, et on utilise les deux dans une seconde étape. Nous avions vu il y a quelques chapitre comment pipeliner des mémoires, dans le chapitre sur les mémoires évoluées. Et bien ces méthodes peuvent s'utiliser pour la mémoire RAM interne au cache !L'idée est d'insérer un registre entre la sortie de la RAM et la suite du cache, pour en faire un cache pipéliné, à deux étages. Le premier étage lit dans la SRAM, le second fait le reste. L'implémentation sur les caches associatifs à plusieurs voies est globalement la même à quelques détails près. [[File:Cache directement adressé pipeliné - deux étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - deux étages]] Avec le circuit précédent, il est possible d'aller plus loin, cette fois en pipelinant l'accès à la mémoire RAM interne au cache. Pour comprendre comment, rappelons qu'une mémoire SRAM est composée d'un plan mémoire et d'un décodeur. L'accès à la mémoire demande d'abord que le décodeur fasse son travail pour sélectionner la case mémoire adéquate, puis ensuite la lecture ou écriture a lieu dans cette case mémoire. L'accès se fait donc en deux étapes successives séparées, on a juste à mettre un registre entre les deux. Il suffit donc de mettre un gros registre entre le décodeur et le plan mémoire. [[File:Cache directement adressé pipeliné - trois étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - trois étages]] Et on peut aller encore plus loin en découpant le décodeur en deux circuits séparés. En effet, rappelez-vous le chapitre sur les circuits de sélection : nous avions vu qu'il est possible de créer des décodeurs en assemblant des décodeurs plus petits, contrôlés par un circuit de prédécodage. Et bien on peut encore une ajouter un registre entre ce circuit de prédécodage et les petits décodeurs. : Théoriquement, toute l'adresse est fournie d'un seul coup au cache, la quasi-totalité des processeurs présentent l'adresse complète à un cache pipeliné. Mais le Pentium 4 fait autrement. Il faut noter que les premiers étages manipulent l'indice dans la SRAM, qui est dans les bits de poids faible, alors que les étages ultérieurs manipulent le ''tag'' qui est dans les bits de poids fort. Les concepteurs du Pentium 4 ont alors décidé de présenter les bits de poids faible lors du premier cycle d'accès au cache, puis ceux de poids fort au second cycle. Pour cela, l'ALU fonctionnait à une fréquence double de celle du processeur, tout comme le cache L1. Il n'y avait pas de pipeline proprement dit, mais cela réduisait grandement la latence d'accès au cache. ===Le ''pipelining'' des caches splittés/sériels=== Il est aussi possible de pipeliner un cache dits splittés, aussi appelés à accès sériel. Pour rappel, les caches à accès sériel vérifient si il y a succès ou défaut de cache, avant d'accéder aux lignes de cache en cas de succès. Ils font donc différemment des autres caches, qui accèdent à une ligne de cache, avant de déterminer s'il y a succès ou défaut en lisant le tag de la ligne de cache. Les caches sériels disposent de deux SRAM : une pour les tags des lignes de cache et une pour les données. Ils accèdent à la SRAM pour les tags, avant d’accéder à la SRAM des données en cas de succès de cache. Vu que l'accès se fait en deux étapes, une vérification des tags suivie de la lecture/écriture des données, il est facile à pipeliner. Pipeliner le cache permet de régler le problème des accès au cache L1, et elle est tout le temps utilisé sur les processeurs modernes. Mais que faire en cas de défaut de cache ? ==Les caches non bloquants== Un '''cache bloquant''' est un cache auquel le processeur ne peut pas accéder pendant un défaut de cache. Il faut attendre que la lecture ou écriture en RAM soit terminée avant de pouvoir utiliser de nouveau le cache. Un '''cache non bloquant''' n'a pas ce problème : on peut l'utiliser pendant un défaut de cache. Les caches non bloquants permettent de démarrer une nouvelle lecture ou écriture alors qu'une autre est en cours, ce qui permet d'exécuter plusieurs lectures ou écritures en même temps. ===Les ''Miss Handling Status Registers''=== Lors d'un défaut de cache, la mémoire RAM est consultée pendant le défaut de cache, mais le cache est inutilisé. Un défaut de cache n'utilise pas le cache, ce n'est qu'un accès en mémoire RAM, sur le bus mémoire (ou un accès aux niveaux de cache inférieurs, peu importe). Le cache en lui-même est laissé libre, rien n’empêche d'y accéder, il est en réalité intrinsèquement non-bloquant. Les caches, bloquants comme non-bloquants, sont en réalité composés d'une mémoire cache proprement dite, entourée de circuits qui servent d'interface entre le processeur et le cache lui-même. Et parmi les circuits tout autour, certains gèrent l'accès au cache lors d'un défaut de cache. Ils sont regroupés sous le terme de '''''Miss Handling Architecture''''' (MHA). La différence entre un cache bloquant et un cache non-bloquant est en réalité liée à l'implémentation de la MHA. Les caches bloquants coupent volontairement l'accès au cache lors d'un défaut, car les défauts sont plus simples à gérer ainsi. Le défaut de cache rapatrie une donnée depuis la RAM, qui sera écrite dans le cache. Et il ne faut pas qu'une tentative d'accès à cette donnée ait lieu avant qu'elle ne soit chargée. Mais ce blocage est général et touche tout le cache, alors que seule une ligne de cache est concernée par le défaut de cache. L'idée derrière un cache non-bloquant est que seule la ligne de cache est bloquée, mais les autres sont accessibles. L'idée est alors de mémoriser les lignes de cache concernées par le défaut de cache, afin d'en bloquer l'accès. À chaque accès, on vérifie si la ligne de cache est déjà réservée par un défaut de cache. La lecture/écriture est alors bloquée si c'est le cas, mais elle accepte les accès sinon. Pour cela, la ''Miss Handling Architecture'' contient des registres qui mémorisent des informations sur les défauts de cache en cours. Ils portent le nom de '''''miss status handling registers''''', que j’appellerais dorénavant MSHR, qui sont aussi appelés des '''''miss buffer'''''. Le contenu des MSHR varie beaucoup suivant le processeur, mais ces derniers stockent au minimum les informations suivantes : * Le numéro de la ligne de cache dans laquelle les données sont chargées. * Un bit de validité qui indique si le MSHR est vide ou pas, qui est mis à 0 quand le défaut de cache est résolu. * Un ou plusieurs champs de lecture/écriture, qui contiennent des informations sur la lecture/écriture. ** Pour une lecture, elle contient des informations sur la destination de la donnée, à savoir qui prévenir quand le défaut de cache est terminé. C'est parfois un nom/numéro de registre (celui dans lequel charger la donnée), mais c'est souvent le numéro de l'entrée dans le ''load/store queue''. ** Pour les écritures, elle contient la donnée à écrire, ou éventuellement un numéro de ''load/store queue'' où se trouve la donnée à écrire. Il faut noter que le bus mémoire ne peut gérer qu'un seul défaut de cache à la fois. Aussi, il est intéressant de regarder ce qui se passe lorsqu'un second défaut de cache survient, pendant qu'un premier est en cours. Dans ce cas, il y a deux réponses qui correspondent à deux types de caches non-bloquants, qui portent les noms barbares de caches de type succès après défaut et défaut après défaut. Sur le premier type, il ne peut pas y avoir plusieurs défauts de cache simultanés. Dès qu'un second défaut de cache survient, le cache stoppe son activité et on ne peut plus démarrer de nouvelle lecture/écriture, tant que le premier défaut de cache n'est pas résolu. Le second type est plus souple et autorise la survenue de plusieurs défauts de cache simultanés. Du moins, jusqu'à une certaine limite, car le cache ne peut supporter qu'un nombre limité d'accès mémoires simultanés (pipelinés). ===Les accès simultanés à une même ligne de cache=== Il arrive que le processeur fasse plusieurs accès mémoire simultanés à la même ligne de cache. Si la ligne de cache en question n'a pas encore été chargée dans le cache, alors on a plusieurs défauts de cache consécutifs pour la même ligne de cache, et le cache non-bloquant doit gérer la situation. Pour la suite, il va falloir faire une petite distinction entre les défauts primaires et secondaires. Imaginons qu'un défaut de cache ait lieu et demande à charger une donnée dans la ligne de cache numéro N. Il s'agit du premier défaut impliquant cette ligne de cache précise, ce qui lui vaut le nom de '''défaut de cache primaire'''. Mais par la suite, d'autres accès mémoire à la même ligne de cache ont lieu, alors que la ligne de cache n'est pas encore disponible. Dans ce cas, il s'agit de '''défauts de cache secondaires'''. Pour l'unité d'accès mémoire, les défauts de cache primaire et secondaire sont différents (ils prennent tous une entrée dans la ''load/store queue''). Mais pour le cache, ils ne correspondent qu'à un seul accès au cache : celui qui demande de charger la ligne de cache demandée. Les défauts de cache primaire et secondaire à la même ligne de cache se voient attribuer un MSHR unique. La gestion des défauts de cache secondaires dépend du cache non-bloquant. La solution la plus simple ne permet pas les défauts de cache secondaires. Le cache ne permet pas deux défauts de cache simultanés pour la même ligne de cache. Les autres solutions le permettent, en fusionnant des accès simultanés à la même ligne de cache en un seul au niveau des MSHR. Dans tous les cas, détecter les défauts de cache secondaires sont un problème qu'il faut détecter. La ''Miss Handling Architecture'' doit détecter les défauts de cache secondaire. Pour cela, elle procède comme suit. Lors de chaque défaut de cache, la MHA récupère le numéro de la ligne de cache associé. Il vérifie alors chaque MSHR pour vérifier s'il contient le numéro en question. S'il n'y a aucune correspondance dans les MSHR, alors c'est signe que le défaut de cache est un défaut primaire. Mais s'il y en a une, alors c'est un défaut secondaire. Évidemment, cela signifie que lors d'un défaut de cache, le numéro de ligne de cache est envoyé à tous les MSHR, pour comparaison. Les MSHR sont donc regroupés dans une mémoire associative, une sorte de mini-cache, faciliter l'implémentation. Lors d'un défaut de cache primaire, l'accès à un cache non-bloquant se fait comme suit : le processeur envoie une adresse au cache, accède à celui-ci, et détecte la survenue d'un défaut de cache. Il en profite alors pour attribuer une ligne de cache dans laquelle sera chargée ce défaut. L'attribution est très simple dans le cas des caches ''direct mapped'', ou associatifs par voie, pour lesquels l'attribution se fait assez simplement. Il mémorise alors cette information dans les MSHR, après avoir vérifié que le défaut de cache n'était pas un défaut secondaire. Lors des accès ultérieurs à une adresse proche, censée être dans la même ligne de cache, le processeur va encore une fois rencontrer un défaut de cache. Il va alors déterminer le numéro de la ligne de cache associée à l'adresse, et comparer ce numéro avec les MSHRs. Si un MSHR contient ce numéro, c'est signe que le défaut de cache est un défaut secondaire. La MHA réagit alors différemment selon le processeur considéré. Une première solution n'autorise pas les défauts de cache secondaires. Si l'un d'entre eux survient, le processeur est gelé par un ''pipeline stall'', une bulle de pipeline. Une autre solution fusionne les défauts de cache secondaires avec le défaut de cache primaire : tout cela ne correspond qu'à un seul défaut de cache pour lui. ===Les MSHR simples=== Un cache non-bloquant à '''MSHR simple''' contient juste plusieurs MSHR qui mémorisent juste un numéro de ligne de cache, un bit de validité, le champ de lecture/écriture, et l'adresse exacte de lecture/écriture. Avec cette organisation, il est possible d'avoir plusieurs défauts de cache séparés, mais à la condition que chaque défaut accède à une ligne de cache différente. Deux accès simultanés à une même ligne de cache ne sont pas possibles, les défauts de cache secondaires ne sont pas autorisés. Ainsi, chaque défaut de cache se voit attribuer son propre MSHR, chacun contient un numéro de ligne de cache différent. Pour comprendre pourquoi c'est impossible de gérer les défauts secondaires, il faut regarder le champ de lecture/écriture. Si on veut effectuer plusieurs écritures consécutives à la même adresse, le MSHR n'aura pas de quoi mémoriser les deux données à écrire. Il pourra mémoriser la première donnée à écrire, pas la seconde. Même chose lors d'une lecture : le champ lecture/écriture peut mémorisr la destination de la première lecture, pas de la seconde. L'avantage est que la MHA n'a besoin que des MSHR et de quelques circuits annexes. Les autres solutions rajoutent des circuits annexes pour gérer les défauts de cache secondaires, qui utilisent beaucoup de circuits. Le cout en circuit est donc élevé, mais le gain en performance est là. Passons maintenant aux caches non-bloquants qui autorisent les défauts de cache secondaire. La solution la plus simple consiste à utiliser ===Les MSHR adressés implicitement=== Avec les '''MSHR adressés implicitement''', il est possible de fusionner plusieurs accès mémoire à une même ligne de cache, mais sous une condition très importante : ces accès lisent/écrivent des mots mémoire différents. Par exemple, imaginons qu'une ligne de cache contienne 8 mots mémoire de 64 bits. Si un premier accès mémoire lit le mot mémoire numéro 7 (dernier mot mémoire de la ligne), et le second accès le mot mémoire numéro 3, alors la fusion est possible. Mais si deux accès mémoire veulent lire/écrire le mot mémoire numéro 7, alors la fusion n'est pas possible et le processeur se bloque, un ''pipeline stall'' survient. Un MSHR adressé implicitement est un MSHR simple, qui contient naturellement un numéro de ligne de cache (tag) et un bit de validité, sauf que le champ de lecture/écriture est dupliqué. Les différents champs lecture/écriture d'un MSHR sont regroupés dans une mémoire RAM/cache qui contient autant d'entrées qu'il y a de mot mémoire dans une ligne de cache. Chaque entrée de la table est associée à un mot mémoire de la ligne de cache et stocke des informations sur celui-ci. Une entrée mémorise, au minimum : * Un champ de lecture/écriture qui contient soit la destination de la lecture, soit la donnée à écrire. * Un bit R/W permettant d'interpréter correctement le champ de lecture/écriture. * Un bit de validité pour chaque entrée de la table, qui dit si un défaut de cache antérieur accède déjà à ce mot mémoire. La fusion de deux défauts de cache est ainsi assez simple. Un défaut de cache primaire/secondaire configure l'entrée associée au mot mémoire lu/écrit. Si un défaut secondaire ultérieur lit/écrit un mot mémoire différent (dont le bit de validité de l'entrée dans la table est à zéro), pas de conflit : il configure une autre entrée, vide. Mais s'il lit/écrit un mot mémoire pour lequel l'entrée de la table est occupée, il y a conflit, le processeur est bloqué par un ''pipeline stall'', une bulle de pipeline. Avec cette organisation, le nombre de MSHR indique combien de lignes de cache peuvent être lues en même temps depuis la mémoire. Quant au nombre d'entrées par MSHR, il détermine combien d'accès mémoires qui ne se recouvrent pas peuvent avoir lieu en même temps. ===Les MSHR adressés explicitement=== Les '''MSHR adressés explicitement''' n'ont pas les contraintes des MSHR adressés implicitement. Avec eux, il est possible d'avoir plusieurs défauts de cache pointant vers la même ligne de cache, mais aussi vers le même mot mémoire. De tels défauts de cache apparaissent sur les processeurs à exécution dans le désordre, mais sont très rares, voire inexistants, sur les processeurs ''in-order''. L'idée est encore que chaque MSHR est associée à une table mémoire qui mémorise des entrées. Cette table n'est autre qu'une mémoire FIFO. Sauf que cette fois-ci, une entrée n'est pas associée à un mot mémoire. Une entrée est associée à un défaut de cache. Une entrée mémorise là encore la destination de la lecture ou la donnée de l'écriture suivi du bit R/W, ainsi que d'un bit de validité par entrée, mais aussi : la position du mot mémoire lu dans la ligne de cache. C'est cette dernière information qui n'était pas présente dans les MSHR adressés implicitement. De plus, le nombre d'entrée par MSHR n'est pas égal au nombre de mots mémoires dans une ligne de cache, mais peut être arbitrairement grand. L'avantage d'un tel cache est qu'il est capable de traité les défauts secondaires concernant un même mot mémoire, et ce, car les accès à la ligne de cache concernée se produisent dans l'ordre de génération des défauts (la table mémoire est une FIFO). ===Les MSHRs inversés=== Généralement, plus on veut supporter de défauts de cache, plus le nombre de MSHR et d'entrées augmente. Mais au-delà d'un certain nombre d'entrées et de MSHR, les MSHR adressés implicitement et explicitement ont tendance à bouffer un peu trop de circuits. Utiliser une organisation un peu moins gourmande en circuits est donc une nécessité. Cette organisation plus économe se base sur des MSHR inversés. Les MSHR inversés ne contiennent qu'une seule entrée, en quelque sorte : au lieu d’utiliser n MSHR de m entrées chacun, on va utiliser n × m MSHR inversés. La différence, c'est que plusieurs MSHR peuvent contenir un tag identique, contrairement aux MSHR adressés implicitement et explicitement. Lorsqu'un défaut de cache a lieu, chaque MSHR est vérifié. Si jamais aucun MSHR ne contient de tag identique à celui utilisé par le défaut, un MSHR vide est choisi pour stocker ce défaut, et une requête de lecture en mémoire est lancée. Dans le cas contraire, un MSHR est réservé au défaut de cache, mais la requête n'est pas lancée. Quand la donnée est disponible, les MSHR correspondant à la ligne qui vient d'être chargée vont être utilisés un par un pour résoudre les défauts de cache en attente. ===Les MSHR intégrés au cache=== Certains chercheurs ont remarqué que pendant qu'une ligne de cache est en train de subir un défaut de cache, celle-ci reste inutilisée, et son contenu est destiné à être perdu une fois le défaut de cache résolu. Ils se sont dit que, plutôt que d'utiliser des MSHR séparés, il vaudrait mieux utiliser la ligne de cache pour stocker les informations sur les défauts de cache en attente dans cette ligne de cache. Pour éviter tout problème, il faut rajouter un bit dans les bits de contrôle de la ligne de cache, qui sert à indiquer que la ligne de cache est occupée : un défaut de cache a eu lieu dans cette ligne, et elle stocke donc des informations pour résoudre les défauts de cache. ==L'entrelacement mémoire== Nous venons de voir comment une mémoire cache peut gérer plusieurs accès mémoire simultanés. Il se trouve que ce genre d'optimisation dépasse largement le cadre du cache. Les mémoires RAM ne sont pas en reste, elles aussi ! Il est possible de pipeliner l'accès à une mémoire RAM, en utilisant des techniques dites d''''entrelacement'''. Précisons cependant que cette optimisation n'est pas utilisée sur les mémoires SDRAM ou DDR modernes, du moins pas sans modifications majeures. Mais divers ordinateurs assez anciens, dont des superordinateurs, ont utilisé cette technique. La technique demande d'utiliser plusieurs mémoires séparées. Sur les anciens ordinateurs, les mémoires en question sont des chips mémoire, à savoir des circuits intégrés de DRAM. Pour faire plus général, nous allons utiliser le terme de '''banques''', ou encore de bancs mémoire. La différence est qu'il existe des mémoires multi-banque, qui regroupent plusieurs banques indépendantes dans un seul boitier, dans un seul chip mémoire. En clair, de telles mémoires regroupent plusieurs sous-mémoires dans un seul circuit intégré. Il est aussi possible d'utiliser plusieurs chips mémoire séparés, chacun ayant une banque. Reste à combiner ces banques pour former un pseudo-pipeline. ===Utiliser plusieurs banques sans entrelacement=== Sans optimisation particulière, les adresses sont réparties dans les banques comme indiqué ci-dessous. Il s'agit de ce qui s'appelle un arrangement horizontal, et nous avions vu celui-ci dans le chapiutre sur les mémoires SDRAM. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Avec cet arrangement, les bits de poids fort de l'adresse sont utilisées pour sélectionner la banque adéquate, et le reste de l'adresse est envoyé sur le bus d'adresse. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Adresse de banque !! Adresse dans la banque |- | Quelques bits de poids fort || Reste de l'adresse |} L'entrelacement change cette répartition, afin d'accélérer les accès mémoire. L'entrelacement classique vise à améliorer les accès à des adresses mémoires consécutives, mais des formes plus évoluées d'entrelacement visent à optimiser des accès mémoire arbitraires. Voyons-les dans l'ordre, du plus simple au plus complexe. ===L'entrelacement classique=== Avec l''''entrelacement classique''', des adresses consécutives sont réparties dans des banques consécutives. Précisons que cette attribution des adresses n'implique absolument pas la mémoire virtuelle ou n'importe quel mécanisme dans le processeur. La répartition décide que telle adresse mémoire va dans telle banque, à telle adresse dans la banque. Elle est donc le fait du contrôleur mémoire, donc en dehors du processeur (les contrôleurs mémoires n’étaient pas encore intégrés dans le processeur à l'époque). [[File:Répartition des adresses dans une mémoire interleaved.png|centre|vignette|upright=1.5|Répartition des adresses dans une mémoire interleaved.]] L'entrelacement simple permet d'accélérer les accès mémoire qui se font à des adresses consécutives. Avec l'entrelacement, chaque accès mémoire tombe dans une banque mémoire différente, ce qui fait qu'on peut démarrer un nouvel accès mémoire à chaque cycle d'horloge. Pendant qu'une banque est occupée par un accès mémoire, on démarre le suivant dans une autre banque, et ainsi de suite. Le tout se termine soit quand on a épuisé toutes les banques libres, soit quand l'accès en rafales se termine. Pas besoin d'attendre que la mémoire ait fini sa lecture/écriture avant de démarrer la lecture/écriture suivante. [[File:Pipemining mémoire.png|centre|vignette|upright=2.5|Pipelining mémoire]] L'animation suivante illustre bien le processus, avec quatres banques. [[File:Interleaving.gif|centre|vignette|upright=2.5|Pipelining mémoire]] L'entrelacement simple utilise les bits de poids faible pour sélectionner la banque, et les bits de poids fort pour la case mémoire. [[File:Adresse mémoire d'une mémoire entrelacée.png|centre|vignette|upright=2|Adresse mémoire d'une mémoire entrelacée]] Précisons que le temps d'accès mémoire ne change pas beaucoup avec l'entrelacement. Par contre, on peut faire plus d'accès mémoire simultanés. On peut démarrer un accès mémoire par cycle d'horloge, mais l'accès en lui-même prend plusieurs cycles. Les mémoires à entrelacement ont donc un débit supérieur aux mémoires qui ne l'utilisent pas. L'entrelacement classique pourrait être utilisé pour accélérer les transferts entre mémoire RAM et cache, qui se font en blocs d'adresses consécutives. Mais de nos jours, il n'est pas utilisé dans cette optique. Il faut dire qu'il est redondant avec le mode rafale des mémoires DRAM. Les deux font globalement la même chose et on voit mal comment utiliser les deux en même temps. Le mode rafale étant plus simple à implémenter, et plus léger niveau utilisation du bus de commande mémoire, il est préféré à l'entrelacement. Par contre, l'entrelacement a eu son heure de gloire sur d'anciens processeurs qui n'avaient pas de mémoire cache, alors qu'ils disposaient d'un pipeline. L'exemple typique est celui des nombreux ''processeurs vectoriels'', que nous n'avons pas encore abordé à ce stade du cours. De tels processeurs avaient un pipeline, une unité de calcul fortement pipelinée, et parfois même de l'exécution dans le désordre et du renommage de registres ! ===L'entrelacement par décalage=== L'entrelacement simple est très utile pour les accès en rafle ou équivalents. Par contre, il existe de rares situations où il n'est pas optimal. Une de ces situations est celle des accès en enjambée (en ''stride''), où l'on accède en série à des adresses sont séparées par N mots mémoires. [[File:Accès par enjambées.png|centre|vignette|upright=2|Accès par enjambées.]] De tels accès surviennent quand un logiciel accède à des structures de données spécifiques, à savoir des tableaux de structures, des matrices, ou d'autres structures de données dans le genre. Un cas classique est celui du parcours d'une matrice colonne par colonne. Les matrices de nombres sont mémorisées ligne par ligne, ce qui fait que parcourir une colonne demande de passer d'une ligne à la suivante et de faire des grands sauts en mémoire RAM, mais tous espacés par la même distance. Avec l'entrelacement simple, les accès en enjambée sont moins performants que les accès à des adresses consécutives, mais cela ne fonctionne pas trop mal. Le pire cas est celui où l'on a N banques et où les données sont justement placées toutes les N adresses. Des accès consécutifs vont tous tomber dans la même banque, on ne peut plus accéder à des banques différentes en parallèle. Mais en dehors de ce cas, on voit une amélioration par rapport à la situation sans entrelacement. Pour obtenir des performances maximales pour les accès en enjambées, il faut répartir les mots mémoires dans la mémoire autrement. L'organisation idéale est la suivante. : Dans les explications qui vont suivre, la variable N représente le nombre de banques, qui sont numérotées de 0 à N-1. On commence par organiser les N premières adresses comme une mémoire entrelacée simple : l'adresse 0 correspond à la banque 0, l'adresse 1 à la banque 1, etc. Pour le bloc suivant, on décale tout d'une adresse, à savoir qu'on commence à remplir les banques à partir de la seconde, non de la première. Une fois la fin du bloc atteinte, on finit de remplir le bloc en repartant du début du bloc. Le troisième bloc d'adresse subit le même traitement, sauf qu'on commence à remplir à partir de la seconde banque. Et on poursuit l’assignation des adresses en décalant d'un cran en plus à chaque bloc. Ainsi, chaque bloc verra ses adresses décalées d'un cran en plus comparé au bloc précédent. Si jamais le décalage dépasse la fin d'un bloc, alors on reprend au début. [[File:Mémoire interleaved par décalage.png|centre|vignette|upright=1.5|Mémoire entrelacée par décalage.]] En faisant cela, on remarque que les banques situées à N adresses d'intervalle sont différentes. Dans l'exemple du dessus, nous avons ajouté un décalage de 1 à chaque nouveau bloc à remplir. Mais on aurait tout aussi bien pu prendre un décalage de 2, 3, etc. Dans tous les cas, on obtient un entrelacement par décalage. Ce décalage est appelé le pas d'entrelacement, noté P. Le calcul de l'adresse à envoyer à la banque, ainsi que la banque à sélectionner se fait en utilisant les formules suivantes : * adresse à envoyer à la banque = adresse totale / N ; * numéro de la banque = (adresse + décalage) modulo N ; * décalage = (adresse totale * P) mod N. Avec cet entrelacement par décalage, on peut prouver que la bande passante maximale est atteinte si le nombre de banques est un nombre premier. Seulement, utiliser un nombre de banques premier peut créer des trous dans la mémoire, des mots mémoires inadressables. Malgré ce défaut, la technique a été utilisée sur quelques ordinateurs, avec l'exemple notable du superordinateur ''Burroughs Scientific Processor''. Pour éviter cela, il y a plusieurs solutions. Par exemple, on peut faire en sorte que N et la taille d'une banque soient premiers entre eux : ils ne doivent pas avoir de diviseur commun. Mais en pratique, elles n'ont pas vraiment été implémentées dans une vraie machine, et sont restées à l'état de recherche, aussi je les passe sous silence. ===L'entrelacement pseudo-aléatoire=== Une dernière méthode de répartition consiste à répartir les adresses dans les banques de manière "pseudo-aléatoire". L'idée exacte est de faire passer l'adresse dans une '''fonction de hachage''', plus ou moins complexe. Pour rappel, une fonction de hachage prend une entrée de grande taille et fournit en sortie un résultat de petite taille. Idéalement, elles doivent donner des résultats aussi différents que possible pour des entrées similaires, histoire de simuler une sorte de pseudo-aléatoire. Ici, une adresse est transformée en un numéro de banque. Une fonction de hachage simple ne fait que permuter des bits de l'adresse pour obtenir son résultat. Elle ne fait qu'échanger des bits de place avant de couper l'adresse en deux morceaux : un pour la sélection de la banque, et un autre pour la sélection de l'adresse dans la banque. Cette permutation est fixe, et ne change pas suivant l'adresse. Des fonctions de hachage plus complexes font des XOR bit à bit entre certains bits de l'adresse. ==Les contrôleurs mémoires SDRAM/DDR optimisés== Après avoir vu le cas des mémoires RAM, nous allons nous concentrer sur le cas particulier des mémoires SDRAM. Les mémoires SDRAM et DDR modernes sont capables de gérer plusieurs accès simultanés à la mémoire RAM. Et les contrôleurs mémoire intègrent des optimisations spécifiques afin d'en profiter au maximum. ===La mise en attente des accès mémoire=== Comme pour les mémoires caches, il existe des contrôleurs mémoire bloquants et non-bloquants. Les premiers n'acceptent qu'un seul accès mémoire à la fois, les seconds acceptent plusieurs accès mémoire simultanés. Pour simplifier, nous allons dire que le contrôleur mémoire n’exécute qu'un seul accès mémoire à la fois, et qu'ils met en attente les accès en trop. C'est une simplification assez irréaliste, vu que les mémoires modernes disposent de plusieurs banques accessibles en parallèle. Mais nous introduirons ce détail un peu plus tard. Avec un contrôleur mémoire de ce type, les accès mémoire sont mis en attente dans une mémoire FIFO, histoire de les exécuter dans leur ordre d'arrivée. Les accès mémoire quittent alors la mémoire dans leur ordre d'arrivée, les lectures sont renvoyées dans l'ordre demandé. En plus d'une mémoire FIFO pour les commandes mémoire, on trouve une mémoire FIFO pour les données à écrire. Elle permet d'accumuler plusieurs écritures, tout en permettant que la donnée adéquate soit envoyée à la RAM au bon moment. Il y a aussi une FIFO pour les données lues, qui sert au cas où le cache ne soit pas disponible quand on lui envoie la donnée lue. [[File:Module d'interface avec la mémoire.png|centre|vignette|upright=2.5|Contrôleur mémoire avec mise en attente des requêtes processeur et autres optimisations.]] Une optimisation regroupe plusieurs accès à des données consécutives en un seul accès en rafale. Le contrôleur mémoire analyse les requêtes mises en attente et détecte si certaines se font à des adresses consécutives. Si c'est le cas, il fusionne ces requêtes en une lecture/écriture en rafale. Une telle optimisation est appelée la '''combinaison de lecture''' pour les lectures, et la '''combinaison d'écriture''' pour les écritures. ===Le pipeline des mémoires SDRAM=== Les SDRAM ont une forme de pseudo-pipeline très limité. Elles sont parfois capables de démarrer une commande avant que la précédente soit terminée. Même si elle parait très limitée, cette possibilité est exploitée au mieux par la présence de plusieurs banques dans la mémoire SDRAM. Les chips mémoires de SDRAM sont composés de plusieurs banques, chacune étant une sorte de mini-mémoire miniature. Chaque banque a son propre décodeur, son propre tampon de ligne, ses propres multiplexeurs de colonne, sa logique de rafraichissement mémoire, etc. C'est comme si une SDRAM regroupait plusieurs mémoires séparées dans un même circuit intégré. Et cela permet de faire plusieurs accès mémoire en même temps, dans des banques différentes. Il est possible de lancer un accès mémoire dans deux banques en même temps, par exemple. Ou encore, on peut lancer un nouvel accès mémoire dans une banque si elle est inoccupée. La possibilité améliore grandement la performance de la SDRAM. Mais nous en reparlerons dans un chapitre ultérieur. {|class="wikitable" |+ Pipelining basique sur les SDRAM |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 |- ! Banque Numéro 1 | || || || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || || || |- ! Banque Numéro 2 | || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || |- ! Banque Numéro 3 | || || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || |} L'implémentation n'a rien de vraiment compliqué. Il suffit d'utiliser une mémoire FIFO par banque. Ainsi, les accès dans une même banque se feront en série, dans l'ordre d'arrivée, mais des accès à des banques différentes se feront dans le désordre. Cela ne pose pas de problème car les accès se font à des adresses différentes, ce qui fait qu'il n'y a pas de conflits majeurs. [[File:Gestion parallèle des banques.png|centre|vignette|upright=2.5|Gestion parallèle des banques.]] ===Le ré-ordonnancement des commandes mémoires=== Un contrôleur mémoire moderne est capable de changer l'ordre des requêtes mémoires, pour gagner en performances. En clair : il peut faire l'équivalent mémoire de l'exécution dans le désordre des processeurs haute performance. Une différence majeure est que le contrôleur mémoire ne remet pas les accès mémoire dans l'ordre avant de les envoyer au processeur. C'est le processeur qui remet en ordre les accès mémoire, dans sa ''load queue''. Pour cela, les requêtes mémoire sont "numérotées", à savoir qu'on leur attribue un identifiant binaire. Le contrôleur mémoire exécute les lectures/écritures dans le désordre, mais garde la trace de leur identifiant. Il sait dans quel ordre il exécute les accès mémoire et connait leur latence. Ce qui fait qu'il sait que la donnée lue à tel instant est associée à la requête mémoire numéro X. Pour les lectures, il renvoie la donnée lue avec l'identifiant. Le processeur sait alors à quelle lecture la donnée lue correspond et il se débrouille en interne pour gérer la situation. En clair : le processeur voit les accès mémoire dans le désordre, c'est lui qui les remet en ordre. Maintenant que ces précisions sont faites, posons cette question : quel est l'intérêt de faire les accès mémoire dans le désordre ? Accéder à des banques en parallèle est une réponse possible, mais le ré-ordonnancement est surtout utile si tous les accès mémoire atterrissent dans la même banque. Pour comprendre pourquoi, faisons un rappel rapide sur le tampon de ligne. Les mémoires SDRAM sont des mémoires à tampon de ligne. Elles font un accès mémoire en deux étapes. La première étape recopie une ligne de N * 64 bits dans un tampon interne à la SDRAM. La seconde étape sélectionne une colonne, à savoir une donnée de 64 bits, qui est soit envoyée sur le bus de données pour une lecture, soit modifiée par une écriture. [[File:Mémoire à tampon de ligne.png|centre|vignette|upright=2|Mémoire à tampon de ligne]] Les deux étapes correspondent à deux commandes séparées : une commande ACT qui précise l'adresse de la ligne, et une commande READ ou WRITE qui précise l'adresse de la colonne. [[File:Signaux RAS et CAS.png|centre|vignette|upright=2|Signaux RAS et CAS.]] Les SDRAM permettent de se passer de la première étape si des accès consécutifs se font dans la même ligne. Une fois activée, la ligne reste ouverte et on peut accéder plusieurs fois de suite dedans. On a alors juste à préciser l'adresse de colonne dedans. [[File:Sélection d'une ligne sur une mémoire FPM ou EDO.png|centre|vignette|upright=2|Sélection d'une ligne sur une mémoire FPM ou EDO.]] L'exécution dans le désordre des lectures/écritures SDRAM vise à faire en sorte que des accès consécutifs se fassent dans une même ligne. Elle marche si plusieurs accès à une même ligne ne sont pas consécutifs, et qu'il y a des accès entre les deux. Après ré-ordonnancement, ces accès mémoire deviennent consécutifs, ils sont exécutés l'un à la suite de l'autre. Pour rendre le tout plus concret, voici un exemple. Imaginez que l'on sait les 3 accès mémoire suivants : * Une lecture ligne A ; * Une écriture ligne B ; * Une lecture ligne A. Sans réordonnancent, on doit émettre deux commandes PRECHARGE : une quand on passe de la ligne A à la ligne B, et une autre pour le passage inverse. pour éviter cela, le contrôleur mémoire peut retarder l'écriture, ou au contraire avancer la seconde lecture. le résultat est alors le suivant : * Lecture ligne A ; * Lecture ligne A ; * Une écriture ligne B. On a donc deux accès consécutifs à la même ligne, suivi par une écriture dans une autre ligne. La technique marche parce que l'on a un mix adéquat de lectures et d'écritures. Mais il existe des possibilités de réorganisation autres qui ne sont pas valides. Il faut par exemple prendre garde à éviter d'intervertir lectures et écritures à une même adresse, sous peine de problèmes. Encore une fois, il faut faire attention aux dépendances mémoires. Le controleur mémoire teste les dépendances mémoires avant d'envoyer des commandes à la mémoire DDR/SDRAM. ==L'entrelacement sur les mémoires SDRAM== L''''entrelacement''' fonctionne sur les mémoires SDRAM, mais il doit être fortement modifié. Concrètement, tout ce qui a étré dit plus est inapplicable pour les SDRAM, du fait de la présence du tampon de ligne. Des adresses consécutives atterrissent déjà dans la même ligne, ce qui fait qu'on n'a pas besoin d'entrelacement au niveau d'une ligne. Et ne parlons pas de ce la présence de rangées et de canaux mémoire ! Cependant, il peut y avoir un entrelacement lié à la présence des banques SDRAM. L'entrelacement est géré juste avant le séquenceur mémoire. Le '''circuit d'entrelacement''' intervertit certains bits de l'adresse lors des accès mémoires. On trouve aussi des mémoires FIFOs pour mettre en attente les requêtes mémoire provenant du processeur. Elles permettent de recevoir plusieurs accès mémoire consécutifs. Si vous vous demandez comment plusieurs accès mémoire consécutifs peuvent avoir lieu, sachez que c'est une bonne question. Pour le moment, nous allons dire que ces accès proviennent du processeur ou de la mémoire cache, sans expliquer comment. Le contrôleur mémoire reçoit une série d'accès mémoire assez rapprochés, qu'il doit exécuter au mieux. [[File:Controleur mémoire d'une SDRAM avec entrelacement.png|centre|vignette|upright=2.5|Contrôleur mémoire d'une SDRAM avec entrelacement.]] Pour gérer l'entrelacement, le contrôleur de SDRAM prend en entrée l'adresse envoyée par le processeur, et la découpe en plusieurs champs : un pour sélectionner la banque adéquate, un autre pour la ligne, un autre pour la colonne, un autre pour la rangée. Une solution simple désactive l'entrelacement. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de banque || Adresse de ligne || Adresse de colonne |} Si cette solution fonctionne bien si les accès mémoire sont assez aléatoires, en pratique, mieux vaut utiliser l'entrelacement. ===L'entrelacement de banques/lignes=== Une première méthode d'entrelacement est l''''entrelacement de banques'''. Elle répartit deux lignes consécutives dans deux banques différentes. Pour comprendre l'idée, prenons un exemple. Imaginons une mémoire avec deux banques et 4 lignes. Imaginons qu'on parcoure/balaye la mémoire RAM en partant des adresses basses. Sans entrelacement de ligne, les accès se feront comme suit, de gauche à droite : {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Banque numéro 1 !! colspan="4" | Banque numéro 2 |- | Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 || Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 |} Avec l'entrelacement de banques, les accès se feront comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 |- | Ligne 1 || Ligne 1 || Ligne 2 || Ligne 2 || Ligne 3 || Ligne 3 || Ligne 4 || Ligne 4 |} Avec N banques, la première ligne ira dans la première banque, la seconde ligne dans la seconde, et ainsi de suite jusqu'à la énième banque qui va dans la banque numéro N. Et la ligne N+1 repart dans la première banque. L'avantage est que passer d'une ligne à la suivante est assez rapide. Au lieu de devoir fermer la première ligne et ouvrir la suivante on ouvrira directement la ligne suivante dans une banque différente, ce qui sera plus rapide. La commande PRECHARGE pourra être envoyée à la seconde banque en avance. Pour cela, l'adresse est découpée comme suit. {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne (précise à l'octet) |} ===L'entrelacement de rangées=== Il est aussi possible de faire la même chose, mais avec les rangées. Pour simplifier fortement, une rangée est simplement un chip mémoire. En réalité, les rangées ne sont pas des chip mémoire, mais un ensemble de chips mémoire regroupés ensemble histoire d'atteindre les 64 bits du bus de données. Par exemple, une rangée peut combiner 8 chips mémoire avec un bus de données de 8 bits chacun pour obtenir les 64 bits du bus de données d'une SDRAM. Mais nous passons ce détail sous silence dans les explications qui vont suivre, par souci de simplification. Pour faire comprendre l'entrelacement de rangée, le mieux est d'utiliser un exemple, le même que précédemment. Sans entrelacement de rangée, on change de chip mémoire une fois qu'on a balayé toutes ses adresses. {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Rangée numéro 1 !! colspan="4" | Rangée numéro 2 |- | Banque 1 || Banque 2 || Banque 3 || Banque 4 || Banque 1 || Banque 2 || Banque 3 || Banque 4 |} Avec l'entrelacement de ligne, on change de rangée dès qu'on change de banque/ligne. {|class="wikitable" |+ Adresse mémoire |- ! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 |- | Banque 1 || Banque 1 || Banque 2 || Banque 2 || Banque 3 || Banque 3 || Banque 4 || Banque 4 |} En clair, quand on a épuisé toutes les banques dans une même rangée, on passe à la rangée suivante au lieu de rester dans la même rangée. Notons que dans l'exemple précédent, on a combiné l'entrelacement de rangée et de banque, mais on aurait pu utiliser l'entrelacement de rangée seul. Mais ce n'est pas le cas le plus courant en pratique. Toujours est-il qu'en combinant entrelacement de rangée et de ligne, le découpage de l'adresse est le suivant : {|class="wikitable" |+ Adresse mémoire |- | Adresse de ligne || Numéro de rangée || Adresse de banque || Adresse de colonne (précise à l'octet) |} D'autres méthodes d'entrelacement sont possibles, mais elles sont plus complexes. Chaque contrôleur mémoire fait un peu à sa sauce. Ils permettent en général de choisir entre plusieurs entrelacements. Au minimum, ils permettent de désactiver l'entrelacement, d'activer un entrelacement de ligne, et d'autres entrelacements plus complexes, dont certains sont propriétaires et tenus secrets par le fabricant. ===L'entrelacement avec le ''dual channel''=== [[File:Dual channel slots.jpg|vignette|Slots mémoires avec ''dual channel''.]] L'usage du ''dual channel'' complique encore l'entrelacement. Là encore, il y a deux grandes solutions : avec et sans entrelacement des canaux mémoire. Rappelons le principe : deux barrettes de RAM sont accédées en parallèle. Et pour cela, il faut utiliser l'entrelacement. Typiquement, chaque barrette mémoire fournit 64 bits, ce qui fait que l'on peut accéder à 128 bits d'un seul coup, par exemple avec un accès en rafale. Sans ''dual channel'', la première barrette correspond à la moitié haute de la RAM, la seconde barrette correspond à la moitié basse. Avec ''dual channel'', une forme spécifique d'entrelacement est activée. Concrétement, deux blocs de 64 bits sont placés dans des canaux mémoire séparés. Idem avec du triple ou quadruple canal, mais c'est alors trois ou quatre blocs de 64 bits qui sont dispersés dans des canaux consécutifs. Le découpage de l'adresse est alors le suivant : {|class="wikitable" |+ Adresse mémoire |- ! colspan="6" | Sans entrelacement |- | Numéro de canal/barrette || Reste de l'adresse || Position de l'octet dans un bloc de 64 bits |- | colspan="6" | |- ! colspan="6" | Avec entrelacement |- | Reste de l'adresse || Numéro de canal/barrette || Position de l'octet dans un bloc de 64 bits |} [[File:Décodage d'adresse avec dual channel.png|centre|vignette|upright=2|Décodage d'adresse avec dual channel.]] Les contrôleurs de SDRAM précédents sont assez basiques et ne représentent pas les contrôleurs les plus évolués. Ils étaient utilisés sur les anciens PC, à une époque où ils étaient encore sur la carte mère du processeur. Mais de nos jours, le contrôleur mémoire est intégré au processeur. Et il incorpore de nombreuses optimisations afin de gagner en performances. Malheureusement, ces optimisations sont elles-mêmes dépendantes des optimisations intégrées dans le processeur, ce qui fait qu'on ne peut pas en parler ici. Elles demandent que le contrôleur mémoire reçoive plusieurs accès mémoire simultanés, ce qui n'a aucun sens à ce stade du cours. Aussi, nous allons les passer sous silence pour le moment. Nous les verrons dans le chapitre sur le parallélisme mémoire. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=La désambiguïsation mémoire | prevText=La désambiguïsation mémoire | next=Les processeurs superscalaires | nextText=Les processeurs superscalaires }} </noinclude> 2whrmvhkay5y365p91p3ize9xfrbvmd 764701 764681 2026-04-23T20:41:48Z Mewtow 31375 /* Les contrôleurs mémoires SDRAM/DDR optimisés */ 764701 wikitext text/x-wiki Dans ce chapitre, nous allons voir les techniques qui permettent de gérer plusieurs accès mémoire simultanés directement au niveau du cache ou de la mémoire RAM. Par plusieurs accès mémoire simultanés, vous pensez sans doute à l'usage de cache multiports, voire de mémoires RAM multiport. C'est en effet une solution, mais ce n'est clairement pas la seule. Par exemple, il est possible de pipeliner l'accès au cache, voire à la mémoire RAM. Il existe de nombreuses techniques de '''parallélisme mémoire''', et nous allons les voir dans ce chapitre. Pour donner un exemple, il est possible de pipeliner les accès mémoire. Il est possible de pipeliner l'accès au cache et/ou l'accès à la mémoire, et nous verrons comment faire dans ce chapitre. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, qu'ils aient ou non un pipeline dynamique. Par contre, pipeliner la mémoire est une technique ancienne, aujourd'hui peu utilisée. Elle était utilisée sur des très vieux ordinateurs, qui avaient un pipeline mais pas de mémoire cache. Mais de nos jours, les mémoires SDRAM et DDR ne sont pas adaptées pour ça, il est très difficile de les pipeliner correctement. Le parallélisme mémoire est utile aussi bien sur des processeurs à émission dans l'ordre que dans le désordre. Par exemple, un processeur ''in-order'' avec des lectures non-bloquantes peut e, profiter. Il peut techniquement lancer une seconde lecture, après avoir rencontré une première lecture non-bloquante. En général, le nombre de lectures consécutives est cependant limité à moins d'une dizaine. À l'opposé, les processeurs sans lectures non-bloquantes profitent pas du parallélisme mémoire. À la rigueur, ils peuvent en profiter s'ils intègrent des techniques de préchargement. Pendant que le processeur exécute une lecture/écriture, il peut précharger une autre donnée en parallèle. Inutile de dire que sans pipeline processeur, le parallélisme mémoire ne sert pas à grand-chose. ==Le ''line fill buffer'' et les techniques associées== Lors d'un défaut de cache, le processeur doit attendre que toute la ligne de cache soit chargée avant d'être utilisable. Or, la taille d'une ligne de cache est supérieure à la largeur du bus mémoire, ce qui fait qu'une ligne de cache est chargée en plusieurs fois, morceaux par morceaux, mot mémoire par mot mémoire. Le chargement peut se faire directement dans le cache, mais ce n'est pas une solution très pratique. À la place, beaucoup de processeurs ajoutent une mémoire tampon entre la RAM et le cache, appelée le '''tampon de remplissage de ligne''' (''line-fill buffer'') dans les processeurs modernes. Lors d'un défaut de cache, le processeur charge la donnée de la RAM dans le tampon de remplissage de ligne, mot mémoire par mot mémoire. Une fois plein, le tampon de remplissage de ligne est recopié dans la ligne de cache. Le tampon de remplissage de ligne contient une ligne de cache, si ce n'est que certains bits de contrôle ne sont pas présents. Le tampon de remplissage de ligne contient un bit de validité pour chaque mot mémoire de la ligne de cache, qui indiquent si le mot mémoire a été chargé. Par exemple, prenons un processeur 64 bits, qui gère donc des mots mémoire de 8 octets, avec des lignes de cache 256 octets/32 mots mémoire. Le tampon de remplissage de ligne contiendra 32 bits de validité. Si le processeur a chargé les 6 premiers mots mémoire, les 6 premiers bits de validité seront mis à 1, les autres seront encore à zéro. Il faut noter que la disponibilité de la ligne complète se détermine assez facilement en faisant un ET logique entre tous les bits de validité. Le processeur sait ainsi quand la ligne de cache est disponible entièrement et donc quand la transférer dans le cache. La même chose existe avec une hiérarchie de cache, sauf que l'on trouve une mémoire tampon entre chaque niveau de cache. Il y a un tampon de remplissage de ligne entre le cache L1 et le cache L2, entre le cache L2 et le L3, etc. Si le cache ne gère qu'un seul défaut de cache à la fois, le ''fill line buffer'' est une mémoire très simple, qui ne mémorise qu'une seule ligne de cache. Et cela vaut aussi bien pour un cache bloquant que non-bloquant. Mais sur les caches capables de gérer plusieurs défauts simultanés, le tampon de remplissage de ligne est une mémoire de type FIFO ou LIFO, capable de mémoriser plusieurs lignes de cache. Notons que le tampon de remplissage de ligne est très utile pour implémenter certaines techniques, par exemple le contournement du cache. Nous avions vu dans le chapitre sur le cache que certains accès mémoire doivent contourner le cache, pour des raisons de cohérence des caches. C'est notamment nécessaire pour accéder aux périphériques, mais c'est aussi utile pour des raisons de performances dans des cas très spécifiques. Les accès qui contournent le cache se font directement dans le tampon de remplissage de ligne : le processeur écrit ou lit les données depuis ce tampon de remplissage de ligne, sans accéder au cache. ===L'''early restart'' et le ''critical word load''=== La présence du ''line buffer'' permet une optimisation assez intéressante, qui permet de réduire la latence des défauts de cache. L'optimisation consiste à lire un mot mémoire dans le tampon de remplissage de ligne, même si la ligne de cache complète n'a pas encore été chargée. Il existe deux manières de faire cela, qui portent les noms d'''early restart'' et de ''critical word load''. La première est la version la plus simple, la seconde est plus complexe mais plus performante. Avec la technique d''''''early restart''''', la ligne de cache est chargée normalement, en partant de son premier mot mémoire. Dès que le mot mémoire lu/écrit par le processeur est copié dans le ''line fill buffer'', il est envoyé au processeur immédiatement. Illustrons le tout par un exemple, où une ligne de cache fait 16 mots mémoire. Le processeur effectue une lecture, qui lit le 5ème mot mémoire. Sans ''early restart'', le processeur doit charger les 16 mots mémoire avant de faire la lecture dans le cache. Avec ''early restart'', le processeur reçoit la donnée dès que le 5ème mot mémoire est disponible. Le processeur doit attendre que les 4 premiers mots mémoire soient chargés, puis le 5ème mot mémoire arrive et est envoyé directement au processeur, il est lu directement depuis le ''line fill buffer''. Les 11 mots mémoire suivants sont ensuite chargés dans le cache pendant que le processeur fait des calculs dans son coin. Le ''critical word load'' est une optimisation de la technique précédente où le chargement de la ligne de cache commence directement à la donnée demandée par le processeur. Pour reprendre l'exemple précédent, où le processeur demande le 5ème mot mémoire sur 16, le ''critical word load'' charge le 5ème mot mémoire en premier et l'envoie au processeur, ce qui fait qu'il est chargé très rapidement. Pas besoin d'attendre que le processeur charge les 4 mots mémoire précédents comme avec l'''early restart''. Dans le détail, le processeur charge le 5ème mot mémoire en premier, puis charge les 11 suivants, et termine par les 4 mots mémoire du début. En clair, le ''critical word load'' commence par charger le mot mémoire lu, puis les blocs suivants, avant de revenir au début du bloc pour charger les blocs restants. Ainsi, la donnée demandée par le processeur sera la première disponible. Pour cela, l'organisation du tampon de remplissage de ligne est modifiée de manière à rendre cela possible. Il a une taille égale à une ligne de cache complète, qui contient elle-même plusieurs mots mémoire. Dans le ''line-fill buffer'', chaque mot mémoire est stocké avec un tag, qui indique l'adresse du mot mémoire stocké dans le ''line-fill buffer''. Le ''line-fill buffer'' est donc un cache un peu particulier, qui fonctionne comme un cache du point de vue du processeur, comme une mémoire FIFO pour les transferts avec le cache. Ainsi, un processeur qui veut lire dans le cache après un défaut peut accéder à la donnée directement depuis le tampon de remplissage de ligne, alors que la ligne de cache n'a pas encore été totalement recopiée en mémoire. ==Les caches pipelinés== Il est possible de pipeliner l'accès au cache, ce qui demande juste de rajouter des registres au bon endroits et quelques circuits de contrôle. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, même ceux avec un pipeline dynamique. Et l'implémentation peut se faire de plusieurs manières différentes. Il faut dire que découper une mémoire cache en plusieurs étapes peut se faire de plusieurs manière. Commençons par la plus simple. Beaucoup de processeurs des années 2000 avaient une fréquence peu élevée comparée aux standards d'aujourd'hui, ce qui fait que leur cache avait bien un temps d'accès d'un cycle d'horloge. Mais l'accès au cache se faisait en deux cycles d'horloge : un cycle pour calculer une adresse et un cycle pour l'accès mémoire proprement dit. Et ces deux étapes étaient pipelinées, à savoir que deux micro-opérations mémoire peuvent s'exécuter en même temps : une dans l'étage de calcul d'adresse, une autre dans le cache. L'unité mémoire était donc pipelinée, alors que l'accès au cache ne l'était pas. Les processeurs qui implémentaient cette technique regroupent les micro-architectures K5 et K6 d'AMD, les processeurs Intel de micro-architecture P6 (Pentium 2 et 3) et quelques autres. Il est aussi possible de pipeliner l'accès au cache lui-même. Avec un cache pipeliné, l'accès au cache ne se fait pas en un seul cycle, mais en plusieurs. Cependant, on peut lancer un nouvel accès au cache à chaque cycle d'horloge, comme un processeur pipeliné lance une nouvelle instruction à chaque cycle. L'implémentation est assez simple : il suffit d'ajouter des registres dans le cache. Pour cela, on profite que le cache est composé de plusieurs composants séparés, qui échangent des données dans un ordre bien précis, d'un composant à un autre. De plus, le trajet des informations dans un cache est linéaire, ce qui les rend parfois pour l'usage d'un pipeline. ===Le ''pipelining'' des caches ''direct-mapped''=== Voici ce qui se passe avec un cache directement adressé. Pour rappel ce genre de cache est conçu en combinant une mémoire RAM, généralement une SRAM, avec quelques circuits de comparaison et un MUX. L'accès au cache se fait globalement en deux étapes : la première lit la donnée et le ''tag'' dans la SRAM, et on utilise les deux dans une seconde étape. Nous avions vu il y a quelques chapitre comment pipeliner des mémoires, dans le chapitre sur les mémoires évoluées. Et bien ces méthodes peuvent s'utiliser pour la mémoire RAM interne au cache !L'idée est d'insérer un registre entre la sortie de la RAM et la suite du cache, pour en faire un cache pipéliné, à deux étages. Le premier étage lit dans la SRAM, le second fait le reste. L'implémentation sur les caches associatifs à plusieurs voies est globalement la même à quelques détails près. [[File:Cache directement adressé pipeliné - deux étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - deux étages]] Avec le circuit précédent, il est possible d'aller plus loin, cette fois en pipelinant l'accès à la mémoire RAM interne au cache. Pour comprendre comment, rappelons qu'une mémoire SRAM est composée d'un plan mémoire et d'un décodeur. L'accès à la mémoire demande d'abord que le décodeur fasse son travail pour sélectionner la case mémoire adéquate, puis ensuite la lecture ou écriture a lieu dans cette case mémoire. L'accès se fait donc en deux étapes successives séparées, on a juste à mettre un registre entre les deux. Il suffit donc de mettre un gros registre entre le décodeur et le plan mémoire. [[File:Cache directement adressé pipeliné - trois étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - trois étages]] Et on peut aller encore plus loin en découpant le décodeur en deux circuits séparés. En effet, rappelez-vous le chapitre sur les circuits de sélection : nous avions vu qu'il est possible de créer des décodeurs en assemblant des décodeurs plus petits, contrôlés par un circuit de prédécodage. Et bien on peut encore une ajouter un registre entre ce circuit de prédécodage et les petits décodeurs. : Théoriquement, toute l'adresse est fournie d'un seul coup au cache, la quasi-totalité des processeurs présentent l'adresse complète à un cache pipeliné. Mais le Pentium 4 fait autrement. Il faut noter que les premiers étages manipulent l'indice dans la SRAM, qui est dans les bits de poids faible, alors que les étages ultérieurs manipulent le ''tag'' qui est dans les bits de poids fort. Les concepteurs du Pentium 4 ont alors décidé de présenter les bits de poids faible lors du premier cycle d'accès au cache, puis ceux de poids fort au second cycle. Pour cela, l'ALU fonctionnait à une fréquence double de celle du processeur, tout comme le cache L1. Il n'y avait pas de pipeline proprement dit, mais cela réduisait grandement la latence d'accès au cache. ===Le ''pipelining'' des caches splittés/sériels=== Il est aussi possible de pipeliner un cache dits splittés, aussi appelés à accès sériel. Pour rappel, les caches à accès sériel vérifient si il y a succès ou défaut de cache, avant d'accéder aux lignes de cache en cas de succès. Ils font donc différemment des autres caches, qui accèdent à une ligne de cache, avant de déterminer s'il y a succès ou défaut en lisant le tag de la ligne de cache. Les caches sériels disposent de deux SRAM : une pour les tags des lignes de cache et une pour les données. Ils accèdent à la SRAM pour les tags, avant d’accéder à la SRAM des données en cas de succès de cache. Vu que l'accès se fait en deux étapes, une vérification des tags suivie de la lecture/écriture des données, il est facile à pipeliner. Pipeliner le cache permet de régler le problème des accès au cache L1, et elle est tout le temps utilisé sur les processeurs modernes. Mais que faire en cas de défaut de cache ? ==Les caches non bloquants== Un '''cache bloquant''' est un cache auquel le processeur ne peut pas accéder pendant un défaut de cache. Il faut attendre que la lecture ou écriture en RAM soit terminée avant de pouvoir utiliser de nouveau le cache. Un '''cache non bloquant''' n'a pas ce problème : on peut l'utiliser pendant un défaut de cache. Les caches non bloquants permettent de démarrer une nouvelle lecture ou écriture alors qu'une autre est en cours, ce qui permet d'exécuter plusieurs lectures ou écritures en même temps. ===Les ''Miss Handling Status Registers''=== Lors d'un défaut de cache, la mémoire RAM est consultée pendant le défaut de cache, mais le cache est inutilisé. Un défaut de cache n'utilise pas le cache, ce n'est qu'un accès en mémoire RAM, sur le bus mémoire (ou un accès aux niveaux de cache inférieurs, peu importe). Le cache en lui-même est laissé libre, rien n’empêche d'y accéder, il est en réalité intrinsèquement non-bloquant. Les caches, bloquants comme non-bloquants, sont en réalité composés d'une mémoire cache proprement dite, entourée de circuits qui servent d'interface entre le processeur et le cache lui-même. Et parmi les circuits tout autour, certains gèrent l'accès au cache lors d'un défaut de cache. Ils sont regroupés sous le terme de '''''Miss Handling Architecture''''' (MHA). La différence entre un cache bloquant et un cache non-bloquant est en réalité liée à l'implémentation de la MHA. Les caches bloquants coupent volontairement l'accès au cache lors d'un défaut, car les défauts sont plus simples à gérer ainsi. Le défaut de cache rapatrie une donnée depuis la RAM, qui sera écrite dans le cache. Et il ne faut pas qu'une tentative d'accès à cette donnée ait lieu avant qu'elle ne soit chargée. Mais ce blocage est général et touche tout le cache, alors que seule une ligne de cache est concernée par le défaut de cache. L'idée derrière un cache non-bloquant est que seule la ligne de cache est bloquée, mais les autres sont accessibles. L'idée est alors de mémoriser les lignes de cache concernées par le défaut de cache, afin d'en bloquer l'accès. À chaque accès, on vérifie si la ligne de cache est déjà réservée par un défaut de cache. La lecture/écriture est alors bloquée si c'est le cas, mais elle accepte les accès sinon. Pour cela, la ''Miss Handling Architecture'' contient des registres qui mémorisent des informations sur les défauts de cache en cours. Ils portent le nom de '''''miss status handling registers''''', que j’appellerais dorénavant MSHR, qui sont aussi appelés des '''''miss buffer'''''. Le contenu des MSHR varie beaucoup suivant le processeur, mais ces derniers stockent au minimum les informations suivantes : * Le numéro de la ligne de cache dans laquelle les données sont chargées. * Un bit de validité qui indique si le MSHR est vide ou pas, qui est mis à 0 quand le défaut de cache est résolu. * Un ou plusieurs champs de lecture/écriture, qui contiennent des informations sur la lecture/écriture. ** Pour une lecture, elle contient des informations sur la destination de la donnée, à savoir qui prévenir quand le défaut de cache est terminé. C'est parfois un nom/numéro de registre (celui dans lequel charger la donnée), mais c'est souvent le numéro de l'entrée dans le ''load/store queue''. ** Pour les écritures, elle contient la donnée à écrire, ou éventuellement un numéro de ''load/store queue'' où se trouve la donnée à écrire. Il faut noter que le bus mémoire ne peut gérer qu'un seul défaut de cache à la fois. Aussi, il est intéressant de regarder ce qui se passe lorsqu'un second défaut de cache survient, pendant qu'un premier est en cours. Dans ce cas, il y a deux réponses qui correspondent à deux types de caches non-bloquants, qui portent les noms barbares de caches de type succès après défaut et défaut après défaut. Sur le premier type, il ne peut pas y avoir plusieurs défauts de cache simultanés. Dès qu'un second défaut de cache survient, le cache stoppe son activité et on ne peut plus démarrer de nouvelle lecture/écriture, tant que le premier défaut de cache n'est pas résolu. Le second type est plus souple et autorise la survenue de plusieurs défauts de cache simultanés. Du moins, jusqu'à une certaine limite, car le cache ne peut supporter qu'un nombre limité d'accès mémoires simultanés (pipelinés). ===Les accès simultanés à une même ligne de cache=== Il arrive que le processeur fasse plusieurs accès mémoire simultanés à la même ligne de cache. Si la ligne de cache en question n'a pas encore été chargée dans le cache, alors on a plusieurs défauts de cache consécutifs pour la même ligne de cache, et le cache non-bloquant doit gérer la situation. Pour la suite, il va falloir faire une petite distinction entre les défauts primaires et secondaires. Imaginons qu'un défaut de cache ait lieu et demande à charger une donnée dans la ligne de cache numéro N. Il s'agit du premier défaut impliquant cette ligne de cache précise, ce qui lui vaut le nom de '''défaut de cache primaire'''. Mais par la suite, d'autres accès mémoire à la même ligne de cache ont lieu, alors que la ligne de cache n'est pas encore disponible. Dans ce cas, il s'agit de '''défauts de cache secondaires'''. Pour l'unité d'accès mémoire, les défauts de cache primaire et secondaire sont différents (ils prennent tous une entrée dans la ''load/store queue''). Mais pour le cache, ils ne correspondent qu'à un seul accès au cache : celui qui demande de charger la ligne de cache demandée. Les défauts de cache primaire et secondaire à la même ligne de cache se voient attribuer un MSHR unique. La gestion des défauts de cache secondaires dépend du cache non-bloquant. La solution la plus simple ne permet pas les défauts de cache secondaires. Le cache ne permet pas deux défauts de cache simultanés pour la même ligne de cache. Les autres solutions le permettent, en fusionnant des accès simultanés à la même ligne de cache en un seul au niveau des MSHR. Dans tous les cas, détecter les défauts de cache secondaires sont un problème qu'il faut détecter. La ''Miss Handling Architecture'' doit détecter les défauts de cache secondaire. Pour cela, elle procède comme suit. Lors de chaque défaut de cache, la MHA récupère le numéro de la ligne de cache associé. Il vérifie alors chaque MSHR pour vérifier s'il contient le numéro en question. S'il n'y a aucune correspondance dans les MSHR, alors c'est signe que le défaut de cache est un défaut primaire. Mais s'il y en a une, alors c'est un défaut secondaire. Évidemment, cela signifie que lors d'un défaut de cache, le numéro de ligne de cache est envoyé à tous les MSHR, pour comparaison. Les MSHR sont donc regroupés dans une mémoire associative, une sorte de mini-cache, faciliter l'implémentation. Lors d'un défaut de cache primaire, l'accès à un cache non-bloquant se fait comme suit : le processeur envoie une adresse au cache, accède à celui-ci, et détecte la survenue d'un défaut de cache. Il en profite alors pour attribuer une ligne de cache dans laquelle sera chargée ce défaut. L'attribution est très simple dans le cas des caches ''direct mapped'', ou associatifs par voie, pour lesquels l'attribution se fait assez simplement. Il mémorise alors cette information dans les MSHR, après avoir vérifié que le défaut de cache n'était pas un défaut secondaire. Lors des accès ultérieurs à une adresse proche, censée être dans la même ligne de cache, le processeur va encore une fois rencontrer un défaut de cache. Il va alors déterminer le numéro de la ligne de cache associée à l'adresse, et comparer ce numéro avec les MSHRs. Si un MSHR contient ce numéro, c'est signe que le défaut de cache est un défaut secondaire. La MHA réagit alors différemment selon le processeur considéré. Une première solution n'autorise pas les défauts de cache secondaires. Si l'un d'entre eux survient, le processeur est gelé par un ''pipeline stall'', une bulle de pipeline. Une autre solution fusionne les défauts de cache secondaires avec le défaut de cache primaire : tout cela ne correspond qu'à un seul défaut de cache pour lui. ===Les MSHR simples=== Un cache non-bloquant à '''MSHR simple''' contient juste plusieurs MSHR qui mémorisent juste un numéro de ligne de cache, un bit de validité, le champ de lecture/écriture, et l'adresse exacte de lecture/écriture. Avec cette organisation, il est possible d'avoir plusieurs défauts de cache séparés, mais à la condition que chaque défaut accède à une ligne de cache différente. Deux accès simultanés à une même ligne de cache ne sont pas possibles, les défauts de cache secondaires ne sont pas autorisés. Ainsi, chaque défaut de cache se voit attribuer son propre MSHR, chacun contient un numéro de ligne de cache différent. Pour comprendre pourquoi c'est impossible de gérer les défauts secondaires, il faut regarder le champ de lecture/écriture. Si on veut effectuer plusieurs écritures consécutives à la même adresse, le MSHR n'aura pas de quoi mémoriser les deux données à écrire. Il pourra mémoriser la première donnée à écrire, pas la seconde. Même chose lors d'une lecture : le champ lecture/écriture peut mémorisr la destination de la première lecture, pas de la seconde. L'avantage est que la MHA n'a besoin que des MSHR et de quelques circuits annexes. Les autres solutions rajoutent des circuits annexes pour gérer les défauts de cache secondaires, qui utilisent beaucoup de circuits. Le cout en circuit est donc élevé, mais le gain en performance est là. Passons maintenant aux caches non-bloquants qui autorisent les défauts de cache secondaire. La solution la plus simple consiste à utiliser ===Les MSHR adressés implicitement=== Avec les '''MSHR adressés implicitement''', il est possible de fusionner plusieurs accès mémoire à une même ligne de cache, mais sous une condition très importante : ces accès lisent/écrivent des mots mémoire différents. Par exemple, imaginons qu'une ligne de cache contienne 8 mots mémoire de 64 bits. Si un premier accès mémoire lit le mot mémoire numéro 7 (dernier mot mémoire de la ligne), et le second accès le mot mémoire numéro 3, alors la fusion est possible. Mais si deux accès mémoire veulent lire/écrire le mot mémoire numéro 7, alors la fusion n'est pas possible et le processeur se bloque, un ''pipeline stall'' survient. Un MSHR adressé implicitement est un MSHR simple, qui contient naturellement un numéro de ligne de cache (tag) et un bit de validité, sauf que le champ de lecture/écriture est dupliqué. Les différents champs lecture/écriture d'un MSHR sont regroupés dans une mémoire RAM/cache qui contient autant d'entrées qu'il y a de mot mémoire dans une ligne de cache. Chaque entrée de la table est associée à un mot mémoire de la ligne de cache et stocke des informations sur celui-ci. Une entrée mémorise, au minimum : * Un champ de lecture/écriture qui contient soit la destination de la lecture, soit la donnée à écrire. * Un bit R/W permettant d'interpréter correctement le champ de lecture/écriture. * Un bit de validité pour chaque entrée de la table, qui dit si un défaut de cache antérieur accède déjà à ce mot mémoire. La fusion de deux défauts de cache est ainsi assez simple. Un défaut de cache primaire/secondaire configure l'entrée associée au mot mémoire lu/écrit. Si un défaut secondaire ultérieur lit/écrit un mot mémoire différent (dont le bit de validité de l'entrée dans la table est à zéro), pas de conflit : il configure une autre entrée, vide. Mais s'il lit/écrit un mot mémoire pour lequel l'entrée de la table est occupée, il y a conflit, le processeur est bloqué par un ''pipeline stall'', une bulle de pipeline. Avec cette organisation, le nombre de MSHR indique combien de lignes de cache peuvent être lues en même temps depuis la mémoire. Quant au nombre d'entrées par MSHR, il détermine combien d'accès mémoires qui ne se recouvrent pas peuvent avoir lieu en même temps. ===Les MSHR adressés explicitement=== Les '''MSHR adressés explicitement''' n'ont pas les contraintes des MSHR adressés implicitement. Avec eux, il est possible d'avoir plusieurs défauts de cache pointant vers la même ligne de cache, mais aussi vers le même mot mémoire. De tels défauts de cache apparaissent sur les processeurs à exécution dans le désordre, mais sont très rares, voire inexistants, sur les processeurs ''in-order''. L'idée est encore que chaque MSHR est associée à une table mémoire qui mémorise des entrées. Cette table n'est autre qu'une mémoire FIFO. Sauf que cette fois-ci, une entrée n'est pas associée à un mot mémoire. Une entrée est associée à un défaut de cache. Une entrée mémorise là encore la destination de la lecture ou la donnée de l'écriture suivi du bit R/W, ainsi que d'un bit de validité par entrée, mais aussi : la position du mot mémoire lu dans la ligne de cache. C'est cette dernière information qui n'était pas présente dans les MSHR adressés implicitement. De plus, le nombre d'entrée par MSHR n'est pas égal au nombre de mots mémoires dans une ligne de cache, mais peut être arbitrairement grand. L'avantage d'un tel cache est qu'il est capable de traité les défauts secondaires concernant un même mot mémoire, et ce, car les accès à la ligne de cache concernée se produisent dans l'ordre de génération des défauts (la table mémoire est une FIFO). ===Les MSHRs inversés=== Généralement, plus on veut supporter de défauts de cache, plus le nombre de MSHR et d'entrées augmente. Mais au-delà d'un certain nombre d'entrées et de MSHR, les MSHR adressés implicitement et explicitement ont tendance à bouffer un peu trop de circuits. Utiliser une organisation un peu moins gourmande en circuits est donc une nécessité. Cette organisation plus économe se base sur des MSHR inversés. Les MSHR inversés ne contiennent qu'une seule entrée, en quelque sorte : au lieu d’utiliser n MSHR de m entrées chacun, on va utiliser n × m MSHR inversés. La différence, c'est que plusieurs MSHR peuvent contenir un tag identique, contrairement aux MSHR adressés implicitement et explicitement. Lorsqu'un défaut de cache a lieu, chaque MSHR est vérifié. Si jamais aucun MSHR ne contient de tag identique à celui utilisé par le défaut, un MSHR vide est choisi pour stocker ce défaut, et une requête de lecture en mémoire est lancée. Dans le cas contraire, un MSHR est réservé au défaut de cache, mais la requête n'est pas lancée. Quand la donnée est disponible, les MSHR correspondant à la ligne qui vient d'être chargée vont être utilisés un par un pour résoudre les défauts de cache en attente. ===Les MSHR intégrés au cache=== Certains chercheurs ont remarqué que pendant qu'une ligne de cache est en train de subir un défaut de cache, celle-ci reste inutilisée, et son contenu est destiné à être perdu une fois le défaut de cache résolu. Ils se sont dit que, plutôt que d'utiliser des MSHR séparés, il vaudrait mieux utiliser la ligne de cache pour stocker les informations sur les défauts de cache en attente dans cette ligne de cache. Pour éviter tout problème, il faut rajouter un bit dans les bits de contrôle de la ligne de cache, qui sert à indiquer que la ligne de cache est occupée : un défaut de cache a eu lieu dans cette ligne, et elle stocke donc des informations pour résoudre les défauts de cache. ==L'entrelacement mémoire== Nous venons de voir comment une mémoire cache peut gérer plusieurs accès mémoire simultanés. Il se trouve que ce genre d'optimisation dépasse largement le cadre du cache. Les mémoires RAM ne sont pas en reste, elles aussi ! Il est possible de pipeliner l'accès à une mémoire RAM, en utilisant des techniques dites d''''entrelacement'''. Précisons cependant que cette optimisation n'est pas utilisée sur les mémoires SDRAM ou DDR modernes, du moins pas sans modifications majeures. Mais divers ordinateurs assez anciens, dont des superordinateurs, ont utilisé cette technique. La technique demande d'utiliser plusieurs mémoires séparées. Sur les anciens ordinateurs, les mémoires en question sont des chips mémoire, à savoir des circuits intégrés de DRAM. Pour faire plus général, nous allons utiliser le terme de '''banques''', ou encore de bancs mémoire. La différence est qu'il existe des mémoires multi-banque, qui regroupent plusieurs banques indépendantes dans un seul boitier, dans un seul chip mémoire. En clair, de telles mémoires regroupent plusieurs sous-mémoires dans un seul circuit intégré. Il est aussi possible d'utiliser plusieurs chips mémoire séparés, chacun ayant une banque. Reste à combiner ces banques pour former un pseudo-pipeline. ===Utiliser plusieurs banques sans entrelacement=== Sans optimisation particulière, les adresses sont réparties dans les banques comme indiqué ci-dessous. Il s'agit de ce qui s'appelle un arrangement horizontal, et nous avions vu celui-ci dans le chapiutre sur les mémoires SDRAM. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Avec cet arrangement, les bits de poids fort de l'adresse sont utilisées pour sélectionner la banque adéquate, et le reste de l'adresse est envoyé sur le bus d'adresse. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Adresse de banque !! Adresse dans la banque |- | Quelques bits de poids fort || Reste de l'adresse |} L'entrelacement change cette répartition, afin d'accélérer les accès mémoire. L'entrelacement classique vise à améliorer les accès à des adresses mémoires consécutives, mais des formes plus évoluées d'entrelacement visent à optimiser des accès mémoire arbitraires. Voyons-les dans l'ordre, du plus simple au plus complexe. ===L'entrelacement classique=== Avec l''''entrelacement classique''', des adresses consécutives sont réparties dans des banques consécutives. Précisons que cette attribution des adresses n'implique absolument pas la mémoire virtuelle ou n'importe quel mécanisme dans le processeur. La répartition décide que telle adresse mémoire va dans telle banque, à telle adresse dans la banque. Elle est donc le fait du contrôleur mémoire, donc en dehors du processeur (les contrôleurs mémoires n’étaient pas encore intégrés dans le processeur à l'époque). [[File:Répartition des adresses dans une mémoire interleaved.png|centre|vignette|upright=1.5|Répartition des adresses dans une mémoire interleaved.]] L'entrelacement simple permet d'accélérer les accès mémoire qui se font à des adresses consécutives. Avec l'entrelacement, chaque accès mémoire tombe dans une banque mémoire différente, ce qui fait qu'on peut démarrer un nouvel accès mémoire à chaque cycle d'horloge. Pendant qu'une banque est occupée par un accès mémoire, on démarre le suivant dans une autre banque, et ainsi de suite. Le tout se termine soit quand on a épuisé toutes les banques libres, soit quand l'accès en rafales se termine. Pas besoin d'attendre que la mémoire ait fini sa lecture/écriture avant de démarrer la lecture/écriture suivante. [[File:Pipemining mémoire.png|centre|vignette|upright=2.5|Pipelining mémoire]] L'animation suivante illustre bien le processus, avec quatres banques. [[File:Interleaving.gif|centre|vignette|upright=2.5|Pipelining mémoire]] L'entrelacement simple utilise les bits de poids faible pour sélectionner la banque, et les bits de poids fort pour la case mémoire. [[File:Adresse mémoire d'une mémoire entrelacée.png|centre|vignette|upright=2|Adresse mémoire d'une mémoire entrelacée]] Précisons que le temps d'accès mémoire ne change pas beaucoup avec l'entrelacement. Par contre, on peut faire plus d'accès mémoire simultanés. On peut démarrer un accès mémoire par cycle d'horloge, mais l'accès en lui-même prend plusieurs cycles. Les mémoires à entrelacement ont donc un débit supérieur aux mémoires qui ne l'utilisent pas. L'entrelacement classique pourrait être utilisé pour accélérer les transferts entre mémoire RAM et cache, qui se font en blocs d'adresses consécutives. Mais de nos jours, il n'est pas utilisé dans cette optique. Il faut dire qu'il est redondant avec le mode rafale des mémoires DRAM. Les deux font globalement la même chose et on voit mal comment utiliser les deux en même temps. Le mode rafale étant plus simple à implémenter, et plus léger niveau utilisation du bus de commande mémoire, il est préféré à l'entrelacement. Par contre, l'entrelacement a eu son heure de gloire sur d'anciens processeurs qui n'avaient pas de mémoire cache, alors qu'ils disposaient d'un pipeline. L'exemple typique est celui des nombreux ''processeurs vectoriels'', que nous n'avons pas encore abordé à ce stade du cours. De tels processeurs avaient un pipeline, une unité de calcul fortement pipelinée, et parfois même de l'exécution dans le désordre et du renommage de registres ! ===L'entrelacement par décalage=== L'entrelacement simple est très utile pour les accès en rafle ou équivalents. Par contre, il existe de rares situations où il n'est pas optimal. Une de ces situations est celle des accès en enjambée (en ''stride''), où l'on accède en série à des adresses sont séparées par N mots mémoires. [[File:Accès par enjambées.png|centre|vignette|upright=2|Accès par enjambées.]] De tels accès surviennent quand un logiciel accède à des structures de données spécifiques, à savoir des tableaux de structures, des matrices, ou d'autres structures de données dans le genre. Un cas classique est celui du parcours d'une matrice colonne par colonne. Les matrices de nombres sont mémorisées ligne par ligne, ce qui fait que parcourir une colonne demande de passer d'une ligne à la suivante et de faire des grands sauts en mémoire RAM, mais tous espacés par la même distance. Avec l'entrelacement simple, les accès en enjambée sont moins performants que les accès à des adresses consécutives, mais cela ne fonctionne pas trop mal. Le pire cas est celui où l'on a N banques et où les données sont justement placées toutes les N adresses. Des accès consécutifs vont tous tomber dans la même banque, on ne peut plus accéder à des banques différentes en parallèle. Mais en dehors de ce cas, on voit une amélioration par rapport à la situation sans entrelacement. Pour obtenir des performances maximales pour les accès en enjambées, il faut répartir les mots mémoires dans la mémoire autrement. L'organisation idéale est la suivante. : Dans les explications qui vont suivre, la variable N représente le nombre de banques, qui sont numérotées de 0 à N-1. On commence par organiser les N premières adresses comme une mémoire entrelacée simple : l'adresse 0 correspond à la banque 0, l'adresse 1 à la banque 1, etc. Pour le bloc suivant, on décale tout d'une adresse, à savoir qu'on commence à remplir les banques à partir de la seconde, non de la première. Une fois la fin du bloc atteinte, on finit de remplir le bloc en repartant du début du bloc. Le troisième bloc d'adresse subit le même traitement, sauf qu'on commence à remplir à partir de la seconde banque. Et on poursuit l’assignation des adresses en décalant d'un cran en plus à chaque bloc. Ainsi, chaque bloc verra ses adresses décalées d'un cran en plus comparé au bloc précédent. Si jamais le décalage dépasse la fin d'un bloc, alors on reprend au début. [[File:Mémoire interleaved par décalage.png|centre|vignette|upright=1.5|Mémoire entrelacée par décalage.]] En faisant cela, on remarque que les banques situées à N adresses d'intervalle sont différentes. Dans l'exemple du dessus, nous avons ajouté un décalage de 1 à chaque nouveau bloc à remplir. Mais on aurait tout aussi bien pu prendre un décalage de 2, 3, etc. Dans tous les cas, on obtient un entrelacement par décalage. Ce décalage est appelé le pas d'entrelacement, noté P. Le calcul de l'adresse à envoyer à la banque, ainsi que la banque à sélectionner se fait en utilisant les formules suivantes : * adresse à envoyer à la banque = adresse totale / N ; * numéro de la banque = (adresse + décalage) modulo N ; * décalage = (adresse totale * P) mod N. Avec cet entrelacement par décalage, on peut prouver que la bande passante maximale est atteinte si le nombre de banques est un nombre premier. Seulement, utiliser un nombre de banques premier peut créer des trous dans la mémoire, des mots mémoires inadressables. Malgré ce défaut, la technique a été utilisée sur quelques ordinateurs, avec l'exemple notable du superordinateur ''Burroughs Scientific Processor''. Pour éviter cela, il y a plusieurs solutions. Par exemple, on peut faire en sorte que N et la taille d'une banque soient premiers entre eux : ils ne doivent pas avoir de diviseur commun. Mais en pratique, elles n'ont pas vraiment été implémentées dans une vraie machine, et sont restées à l'état de recherche, aussi je les passe sous silence. ===L'entrelacement pseudo-aléatoire=== Une dernière méthode de répartition consiste à répartir les adresses dans les banques de manière "pseudo-aléatoire". L'idée exacte est de faire passer l'adresse dans une '''fonction de hachage''', plus ou moins complexe. Pour rappel, une fonction de hachage prend une entrée de grande taille et fournit en sortie un résultat de petite taille. Idéalement, elles doivent donner des résultats aussi différents que possible pour des entrées similaires, histoire de simuler une sorte de pseudo-aléatoire. Ici, une adresse est transformée en un numéro de banque. Une fonction de hachage simple ne fait que permuter des bits de l'adresse pour obtenir son résultat. Elle ne fait qu'échanger des bits de place avant de couper l'adresse en deux morceaux : un pour la sélection de la banque, et un autre pour la sélection de l'adresse dans la banque. Cette permutation est fixe, et ne change pas suivant l'adresse. Des fonctions de hachage plus complexes font des XOR bit à bit entre certains bits de l'adresse. ==Les contrôleurs mémoires SDRAM/DDR optimisés== Après avoir vu le cas des mémoires RAM, nous allons nous concentrer sur le cas particulier des mémoires SDRAM. Les mémoires SDRAM et DDR modernes sont capables de gérer plusieurs accès simultanés à la mémoire RAM. Et les contrôleurs mémoire intègrent des optimisations spécifiques afin d'en profiter au maximum. Peu de choses sont connues sur les contrôleurs de SDRAM/DDR modernes, les fabricants ne donnant que peu de détails dessus. Les rares simulateurs qui tentent de décrire leur fonctionnement, comme DRAM SIM I et II, sont particulièrement simples et ne vont pas dans le détail. Néanmoins, le peu qu'on sait est tout de même instructif. ===La mise en attente des accès mémoire=== Comme pour les mémoires caches, il existe des contrôleurs mémoire bloquants et non-bloquants. Les premiers n'acceptent qu'un seul accès mémoire à la fois, les seconds acceptent plusieurs accès mémoire simultanés. Pour simplifier, nous allons dire que le contrôleur mémoire n’exécute qu'un seul accès mémoire à la fois, et qu'ils met en attente les accès en trop. C'est une simplification assez irréaliste, vu que les mémoires modernes disposent de plusieurs banques accessibles en parallèle. Mais nous introduirons ce détail un peu plus tard. Avec un contrôleur mémoire de ce type, les accès mémoire sont mis en attente dans une mémoire FIFO, histoire de les exécuter dans leur ordre d'arrivée. Les accès mémoire quittent alors la mémoire dans leur ordre d'arrivée, les lectures sont renvoyées dans l'ordre demandé. En plus d'une mémoire FIFO pour les commandes mémoire, on trouve une mémoire FIFO pour les données à écrire. Elle permet d'accumuler plusieurs écritures, tout en permettant que la donnée adéquate soit envoyée à la RAM au bon moment. Il y a aussi une FIFO pour les données lues, qui sert au cas où le cache ne soit pas disponible quand on lui envoie la donnée lue. [[File:Module d'interface avec la mémoire.png|centre|vignette|upright=2.5|Contrôleur mémoire avec mise en attente des requêtes processeur et autres optimisations.]] Une optimisation regroupe plusieurs accès à des données consécutives en un seul accès en rafale. Le contrôleur mémoire analyse les requêtes mises en attente et détecte si certaines se font à des adresses consécutives. Si c'est le cas, il fusionne ces requêtes en une lecture/écriture en rafale. Une telle optimisation est appelée la '''combinaison de lecture''' pour les lectures, et la '''combinaison d'écriture''' pour les écritures. ===Le pipeline des mémoires SDRAM=== Les SDRAM ont une forme de pseudo-pipeline très limité. Elles sont parfois capables de démarrer une commande avant que la précédente soit terminée. Même si elle parait très limitée, cette possibilité est exploitée au mieux par la présence de plusieurs banques dans la mémoire SDRAM. Les chips mémoires de SDRAM sont composés de plusieurs banques, chacune étant une sorte de mini-mémoire miniature. Chaque banque a son propre décodeur, son propre tampon de ligne, ses propres multiplexeurs de colonne, sa logique de rafraichissement mémoire, etc. C'est comme si une SDRAM regroupait plusieurs mémoires séparées dans un même circuit intégré. Et cela permet de faire plusieurs accès mémoire en même temps, dans des banques différentes. Il est possible de lancer un accès mémoire dans deux banques en même temps, par exemple. Ou encore, on peut lancer un nouvel accès mémoire dans une banque si elle est inoccupée. La possibilité améliore grandement la performance de la SDRAM. Mais nous en reparlerons dans un chapitre ultérieur. {|class="wikitable" |+ Pipelining basique sur les SDRAM |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 |- ! Banque Numéro 1 | || || || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || || || |- ! Banque Numéro 2 | || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || |- ! Banque Numéro 3 | || || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || |} L'implémentation n'a rien de vraiment compliqué. Il suffit d'utiliser une mémoire FIFO par banque. Ainsi, les accès dans une même banque se feront en série, dans l'ordre d'arrivée, mais des accès à des banques différentes se feront dans le désordre. Cela ne pose pas de problème car les accès se font à des adresses différentes, ce qui fait qu'il n'y a pas de conflits majeurs. [[File:Gestion parallèle des banques.png|centre|vignette|upright=2.5|Gestion parallèle des banques.]] ===Le ré-ordonnancement des commandes mémoires=== Un contrôleur mémoire moderne est capable de changer l'ordre des requêtes mémoires, pour gagner en performances. En clair : il peut faire l'équivalent mémoire de l'exécution dans le désordre des processeurs haute performance. Une différence majeure est que le contrôleur mémoire ne remet pas les accès mémoire dans l'ordre avant de les envoyer au processeur. C'est le processeur qui remet en ordre les accès mémoire, dans sa ''load queue''. Pour cela, les requêtes mémoire sont "numérotées", à savoir qu'on leur attribue un identifiant binaire. Le contrôleur mémoire exécute les lectures/écritures dans le désordre, mais garde la trace de leur identifiant. Il sait dans quel ordre il exécute les accès mémoire et connait leur latence. Ce qui fait qu'il sait que la donnée lue à tel instant est associée à la requête mémoire numéro X. Pour les lectures, il renvoie la donnée lue avec l'identifiant. Le processeur sait alors à quelle lecture la donnée lue correspond et il se débrouille en interne pour gérer la situation. En clair : le processeur voit les accès mémoire dans le désordre, c'est lui qui les remet en ordre. Maintenant que ces précisions sont faites, posons cette question : quel est l'intérêt de faire les accès mémoire dans le désordre ? Accéder à des banques en parallèle est une réponse possible, mais le ré-ordonnancement est surtout utile si tous les accès mémoire atterrissent dans la même banque. Pour comprendre pourquoi, faisons un rappel rapide sur le tampon de ligne. Les mémoires SDRAM sont des mémoires à tampon de ligne. Elles font un accès mémoire en deux étapes. La première étape recopie une ligne de N * 64 bits dans un tampon interne à la SDRAM. La seconde étape sélectionne une colonne, à savoir une donnée de 64 bits, qui est soit envoyée sur le bus de données pour une lecture, soit modifiée par une écriture. [[File:Mémoire à tampon de ligne.png|centre|vignette|upright=2|Mémoire à tampon de ligne]] Les deux étapes correspondent à deux commandes séparées : une commande ACT qui précise l'adresse de la ligne, et une commande READ ou WRITE qui précise l'adresse de la colonne. [[File:Signaux RAS et CAS.png|centre|vignette|upright=2|Signaux RAS et CAS.]] Les SDRAM permettent de se passer de la première étape si des accès consécutifs se font dans la même ligne. Une fois activée, la ligne reste ouverte et on peut accéder plusieurs fois de suite dedans. On a alors juste à préciser l'adresse de colonne dedans. [[File:Sélection d'une ligne sur une mémoire FPM ou EDO.png|centre|vignette|upright=2|Sélection d'une ligne sur une mémoire FPM ou EDO.]] L'exécution dans le désordre des lectures/écritures SDRAM vise à faire en sorte que des accès consécutifs se fassent dans une même ligne. Elle marche si plusieurs accès à une même ligne ne sont pas consécutifs, et qu'il y a des accès entre les deux. Après ré-ordonnancement, ces accès mémoire deviennent consécutifs, ils sont exécutés l'un à la suite de l'autre. Pour rendre le tout plus concret, voici un exemple. Imaginez que l'on sait les 3 accès mémoire suivants : * Une lecture ligne A ; * Une écriture ligne B ; * Une lecture ligne A. Sans réordonnancent, on doit émettre deux commandes PRECHARGE : une quand on passe de la ligne A à la ligne B, et une autre pour le passage inverse. pour éviter cela, le contrôleur mémoire peut retarder l'écriture, ou au contraire avancer la seconde lecture. le résultat est alors le suivant : * Lecture ligne A ; * Lecture ligne A ; * Une écriture ligne B. On a donc deux accès consécutifs à la même ligne, suivi par une écriture dans une autre ligne. La technique marche parce que l'on a un mix adéquat de lectures et d'écritures. Mais il existe des possibilités de réorganisation autres qui ne sont pas valides. Il faut par exemple prendre garde à éviter d'intervertir lectures et écritures à une même adresse, sous peine de problèmes. Encore une fois, il faut faire attention aux dépendances mémoires. Le controleur mémoire teste les dépendances mémoires avant d'envoyer des commandes à la mémoire DDR/SDRAM. ==L'entrelacement sur les mémoires SDRAM== L''''entrelacement''' fonctionne sur les mémoires SDRAM, mais il doit être fortement modifié. Concrètement, tout ce qui a étré dit plus est inapplicable pour les SDRAM, du fait de la présence du tampon de ligne. Des adresses consécutives atterrissent déjà dans la même ligne, ce qui fait qu'on n'a pas besoin d'entrelacement au niveau d'une ligne. Et ne parlons pas de ce la présence de rangées et de canaux mémoire ! Cependant, il peut y avoir un entrelacement lié à la présence des banques SDRAM. L'entrelacement est géré juste avant le séquenceur mémoire. Le '''circuit d'entrelacement''' intervertit certains bits de l'adresse lors des accès mémoires. On trouve aussi des mémoires FIFOs pour mettre en attente les requêtes mémoire provenant du processeur. Elles permettent de recevoir plusieurs accès mémoire consécutifs. Si vous vous demandez comment plusieurs accès mémoire consécutifs peuvent avoir lieu, sachez que c'est une bonne question. Pour le moment, nous allons dire que ces accès proviennent du processeur ou de la mémoire cache, sans expliquer comment. Le contrôleur mémoire reçoit une série d'accès mémoire assez rapprochés, qu'il doit exécuter au mieux. [[File:Controleur mémoire d'une SDRAM avec entrelacement.png|centre|vignette|upright=2.5|Contrôleur mémoire d'une SDRAM avec entrelacement.]] Pour gérer l'entrelacement, le contrôleur de SDRAM prend en entrée l'adresse envoyée par le processeur, et la découpe en plusieurs champs : un pour sélectionner la banque adéquate, un autre pour la ligne, un autre pour la colonne, un autre pour la rangée. Une solution simple désactive l'entrelacement. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de banque || Adresse de ligne || Adresse de colonne |} Si cette solution fonctionne bien si les accès mémoire sont assez aléatoires, en pratique, mieux vaut utiliser l'entrelacement. ===L'entrelacement de banques/lignes=== Une première méthode d'entrelacement est l''''entrelacement de banques'''. Elle répartit deux lignes consécutives dans deux banques différentes. Pour comprendre l'idée, prenons un exemple. Imaginons une mémoire avec deux banques et 4 lignes. Imaginons qu'on parcoure/balaye la mémoire RAM en partant des adresses basses. Sans entrelacement de ligne, les accès se feront comme suit, de gauche à droite : {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Banque numéro 1 !! colspan="4" | Banque numéro 2 |- | Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 || Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 |} Avec l'entrelacement de banques, les accès se feront comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 |- | Ligne 1 || Ligne 1 || Ligne 2 || Ligne 2 || Ligne 3 || Ligne 3 || Ligne 4 || Ligne 4 |} Avec N banques, la première ligne ira dans la première banque, la seconde ligne dans la seconde, et ainsi de suite jusqu'à la énième banque qui va dans la banque numéro N. Et la ligne N+1 repart dans la première banque. L'avantage est que passer d'une ligne à la suivante est assez rapide. Au lieu de devoir fermer la première ligne et ouvrir la suivante on ouvrira directement la ligne suivante dans une banque différente, ce qui sera plus rapide. La commande PRECHARGE pourra être envoyée à la seconde banque en avance. Pour cela, l'adresse est découpée comme suit. {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne (précise à l'octet) |} ===L'entrelacement de rangées=== Il est aussi possible de faire la même chose, mais avec les rangées. Pour simplifier fortement, une rangée est simplement un chip mémoire. En réalité, les rangées ne sont pas des chip mémoire, mais un ensemble de chips mémoire regroupés ensemble histoire d'atteindre les 64 bits du bus de données. Par exemple, une rangée peut combiner 8 chips mémoire avec un bus de données de 8 bits chacun pour obtenir les 64 bits du bus de données d'une SDRAM. Mais nous passons ce détail sous silence dans les explications qui vont suivre, par souci de simplification. Pour faire comprendre l'entrelacement de rangée, le mieux est d'utiliser un exemple, le même que précédemment. Sans entrelacement de rangée, on change de chip mémoire une fois qu'on a balayé toutes ses adresses. {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Rangée numéro 1 !! colspan="4" | Rangée numéro 2 |- | Banque 1 || Banque 2 || Banque 3 || Banque 4 || Banque 1 || Banque 2 || Banque 3 || Banque 4 |} Avec l'entrelacement de ligne, on change de rangée dès qu'on change de banque/ligne. {|class="wikitable" |+ Adresse mémoire |- ! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 |- | Banque 1 || Banque 1 || Banque 2 || Banque 2 || Banque 3 || Banque 3 || Banque 4 || Banque 4 |} En clair, quand on a épuisé toutes les banques dans une même rangée, on passe à la rangée suivante au lieu de rester dans la même rangée. Notons que dans l'exemple précédent, on a combiné l'entrelacement de rangée et de banque, mais on aurait pu utiliser l'entrelacement de rangée seul. Mais ce n'est pas le cas le plus courant en pratique. Toujours est-il qu'en combinant entrelacement de rangée et de ligne, le découpage de l'adresse est le suivant : {|class="wikitable" |+ Adresse mémoire |- | Adresse de ligne || Numéro de rangée || Adresse de banque || Adresse de colonne (précise à l'octet) |} D'autres méthodes d'entrelacement sont possibles, mais elles sont plus complexes. Chaque contrôleur mémoire fait un peu à sa sauce. Ils permettent en général de choisir entre plusieurs entrelacements. Au minimum, ils permettent de désactiver l'entrelacement, d'activer un entrelacement de ligne, et d'autres entrelacements plus complexes, dont certains sont propriétaires et tenus secrets par le fabricant. ===L'entrelacement avec le ''dual channel''=== [[File:Dual channel slots.jpg|vignette|Slots mémoires avec ''dual channel''.]] L'usage du ''dual channel'' complique encore l'entrelacement. Là encore, il y a deux grandes solutions : avec et sans entrelacement des canaux mémoire. Rappelons le principe : deux barrettes de RAM sont accédées en parallèle. Et pour cela, il faut utiliser l'entrelacement. Typiquement, chaque barrette mémoire fournit 64 bits, ce qui fait que l'on peut accéder à 128 bits d'un seul coup, par exemple avec un accès en rafale. Sans ''dual channel'', la première barrette correspond à la moitié haute de la RAM, la seconde barrette correspond à la moitié basse. Avec ''dual channel'', une forme spécifique d'entrelacement est activée. Concrétement, deux blocs de 64 bits sont placés dans des canaux mémoire séparés. Idem avec du triple ou quadruple canal, mais c'est alors trois ou quatre blocs de 64 bits qui sont dispersés dans des canaux consécutifs. Le découpage de l'adresse est alors le suivant : {|class="wikitable" |+ Adresse mémoire |- ! colspan="6" | Sans entrelacement |- | Numéro de canal/barrette || Reste de l'adresse || Position de l'octet dans un bloc de 64 bits |- | colspan="6" | |- ! colspan="6" | Avec entrelacement |- | Reste de l'adresse || Numéro de canal/barrette || Position de l'octet dans un bloc de 64 bits |} [[File:Décodage d'adresse avec dual channel.png|centre|vignette|upright=2|Décodage d'adresse avec dual channel.]] Les contrôleurs de SDRAM précédents sont assez basiques et ne représentent pas les contrôleurs les plus évolués. Ils étaient utilisés sur les anciens PC, à une époque où ils étaient encore sur la carte mère du processeur. Mais de nos jours, le contrôleur mémoire est intégré au processeur. Et il incorpore de nombreuses optimisations afin de gagner en performances. Malheureusement, ces optimisations sont elles-mêmes dépendantes des optimisations intégrées dans le processeur, ce qui fait qu'on ne peut pas en parler ici. Elles demandent que le contrôleur mémoire reçoive plusieurs accès mémoire simultanés, ce qui n'a aucun sens à ce stade du cours. Aussi, nous allons les passer sous silence pour le moment. Nous les verrons dans le chapitre sur le parallélisme mémoire. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=La désambiguïsation mémoire | prevText=La désambiguïsation mémoire | next=Les processeurs superscalaires | nextText=Les processeurs superscalaires }} </noinclude> ln8ghfjx045bkcvn8dt0vecbulcsuhb 764705 764701 2026-04-23T21:02:24Z Mewtow 31375 /* Les contrôleurs mémoires SDRAM/DDR optimisés */ 764705 wikitext text/x-wiki Dans ce chapitre, nous allons voir les techniques qui permettent de gérer plusieurs accès mémoire simultanés directement au niveau du cache ou de la mémoire RAM. Par plusieurs accès mémoire simultanés, vous pensez sans doute à l'usage de cache multiports, voire de mémoires RAM multiport. C'est en effet une solution, mais ce n'est clairement pas la seule. Par exemple, il est possible de pipeliner l'accès au cache, voire à la mémoire RAM. Il existe de nombreuses techniques de '''parallélisme mémoire''', et nous allons les voir dans ce chapitre. Pour donner un exemple, il est possible de pipeliner les accès mémoire. Il est possible de pipeliner l'accès au cache et/ou l'accès à la mémoire, et nous verrons comment faire dans ce chapitre. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, qu'ils aient ou non un pipeline dynamique. Par contre, pipeliner la mémoire est une technique ancienne, aujourd'hui peu utilisée. Elle était utilisée sur des très vieux ordinateurs, qui avaient un pipeline mais pas de mémoire cache. Mais de nos jours, les mémoires SDRAM et DDR ne sont pas adaptées pour ça, il est très difficile de les pipeliner correctement. Le parallélisme mémoire est utile aussi bien sur des processeurs à émission dans l'ordre que dans le désordre. Par exemple, un processeur ''in-order'' avec des lectures non-bloquantes peut e, profiter. Il peut techniquement lancer une seconde lecture, après avoir rencontré une première lecture non-bloquante. En général, le nombre de lectures consécutives est cependant limité à moins d'une dizaine. À l'opposé, les processeurs sans lectures non-bloquantes profitent pas du parallélisme mémoire. À la rigueur, ils peuvent en profiter s'ils intègrent des techniques de préchargement. Pendant que le processeur exécute une lecture/écriture, il peut précharger une autre donnée en parallèle. Inutile de dire que sans pipeline processeur, le parallélisme mémoire ne sert pas à grand-chose. ==Le ''line fill buffer'' et les techniques associées== Lors d'un défaut de cache, le processeur doit attendre que toute la ligne de cache soit chargée avant d'être utilisable. Or, la taille d'une ligne de cache est supérieure à la largeur du bus mémoire, ce qui fait qu'une ligne de cache est chargée en plusieurs fois, morceaux par morceaux, mot mémoire par mot mémoire. Le chargement peut se faire directement dans le cache, mais ce n'est pas une solution très pratique. À la place, beaucoup de processeurs ajoutent une mémoire tampon entre la RAM et le cache, appelée le '''tampon de remplissage de ligne''' (''line-fill buffer'') dans les processeurs modernes. Lors d'un défaut de cache, le processeur charge la donnée de la RAM dans le tampon de remplissage de ligne, mot mémoire par mot mémoire. Une fois plein, le tampon de remplissage de ligne est recopié dans la ligne de cache. Le tampon de remplissage de ligne contient une ligne de cache, si ce n'est que certains bits de contrôle ne sont pas présents. Le tampon de remplissage de ligne contient un bit de validité pour chaque mot mémoire de la ligne de cache, qui indiquent si le mot mémoire a été chargé. Par exemple, prenons un processeur 64 bits, qui gère donc des mots mémoire de 8 octets, avec des lignes de cache 256 octets/32 mots mémoire. Le tampon de remplissage de ligne contiendra 32 bits de validité. Si le processeur a chargé les 6 premiers mots mémoire, les 6 premiers bits de validité seront mis à 1, les autres seront encore à zéro. Il faut noter que la disponibilité de la ligne complète se détermine assez facilement en faisant un ET logique entre tous les bits de validité. Le processeur sait ainsi quand la ligne de cache est disponible entièrement et donc quand la transférer dans le cache. La même chose existe avec une hiérarchie de cache, sauf que l'on trouve une mémoire tampon entre chaque niveau de cache. Il y a un tampon de remplissage de ligne entre le cache L1 et le cache L2, entre le cache L2 et le L3, etc. Si le cache ne gère qu'un seul défaut de cache à la fois, le ''fill line buffer'' est une mémoire très simple, qui ne mémorise qu'une seule ligne de cache. Et cela vaut aussi bien pour un cache bloquant que non-bloquant. Mais sur les caches capables de gérer plusieurs défauts simultanés, le tampon de remplissage de ligne est une mémoire de type FIFO ou LIFO, capable de mémoriser plusieurs lignes de cache. Notons que le tampon de remplissage de ligne est très utile pour implémenter certaines techniques, par exemple le contournement du cache. Nous avions vu dans le chapitre sur le cache que certains accès mémoire doivent contourner le cache, pour des raisons de cohérence des caches. C'est notamment nécessaire pour accéder aux périphériques, mais c'est aussi utile pour des raisons de performances dans des cas très spécifiques. Les accès qui contournent le cache se font directement dans le tampon de remplissage de ligne : le processeur écrit ou lit les données depuis ce tampon de remplissage de ligne, sans accéder au cache. ===L'''early restart'' et le ''critical word load''=== La présence du ''line buffer'' permet une optimisation assez intéressante, qui permet de réduire la latence des défauts de cache. L'optimisation consiste à lire un mot mémoire dans le tampon de remplissage de ligne, même si la ligne de cache complète n'a pas encore été chargée. Il existe deux manières de faire cela, qui portent les noms d'''early restart'' et de ''critical word load''. La première est la version la plus simple, la seconde est plus complexe mais plus performante. Avec la technique d''''''early restart''''', la ligne de cache est chargée normalement, en partant de son premier mot mémoire. Dès que le mot mémoire lu/écrit par le processeur est copié dans le ''line fill buffer'', il est envoyé au processeur immédiatement. Illustrons le tout par un exemple, où une ligne de cache fait 16 mots mémoire. Le processeur effectue une lecture, qui lit le 5ème mot mémoire. Sans ''early restart'', le processeur doit charger les 16 mots mémoire avant de faire la lecture dans le cache. Avec ''early restart'', le processeur reçoit la donnée dès que le 5ème mot mémoire est disponible. Le processeur doit attendre que les 4 premiers mots mémoire soient chargés, puis le 5ème mot mémoire arrive et est envoyé directement au processeur, il est lu directement depuis le ''line fill buffer''. Les 11 mots mémoire suivants sont ensuite chargés dans le cache pendant que le processeur fait des calculs dans son coin. Le ''critical word load'' est une optimisation de la technique précédente où le chargement de la ligne de cache commence directement à la donnée demandée par le processeur. Pour reprendre l'exemple précédent, où le processeur demande le 5ème mot mémoire sur 16, le ''critical word load'' charge le 5ème mot mémoire en premier et l'envoie au processeur, ce qui fait qu'il est chargé très rapidement. Pas besoin d'attendre que le processeur charge les 4 mots mémoire précédents comme avec l'''early restart''. Dans le détail, le processeur charge le 5ème mot mémoire en premier, puis charge les 11 suivants, et termine par les 4 mots mémoire du début. En clair, le ''critical word load'' commence par charger le mot mémoire lu, puis les blocs suivants, avant de revenir au début du bloc pour charger les blocs restants. Ainsi, la donnée demandée par le processeur sera la première disponible. Pour cela, l'organisation du tampon de remplissage de ligne est modifiée de manière à rendre cela possible. Il a une taille égale à une ligne de cache complète, qui contient elle-même plusieurs mots mémoire. Dans le ''line-fill buffer'', chaque mot mémoire est stocké avec un tag, qui indique l'adresse du mot mémoire stocké dans le ''line-fill buffer''. Le ''line-fill buffer'' est donc un cache un peu particulier, qui fonctionne comme un cache du point de vue du processeur, comme une mémoire FIFO pour les transferts avec le cache. Ainsi, un processeur qui veut lire dans le cache après un défaut peut accéder à la donnée directement depuis le tampon de remplissage de ligne, alors que la ligne de cache n'a pas encore été totalement recopiée en mémoire. ==Les caches pipelinés== Il est possible de pipeliner l'accès au cache, ce qui demande juste de rajouter des registres au bon endroits et quelques circuits de contrôle. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, même ceux avec un pipeline dynamique. Et l'implémentation peut se faire de plusieurs manières différentes. Il faut dire que découper une mémoire cache en plusieurs étapes peut se faire de plusieurs manière. Commençons par la plus simple. Beaucoup de processeurs des années 2000 avaient une fréquence peu élevée comparée aux standards d'aujourd'hui, ce qui fait que leur cache avait bien un temps d'accès d'un cycle d'horloge. Mais l'accès au cache se faisait en deux cycles d'horloge : un cycle pour calculer une adresse et un cycle pour l'accès mémoire proprement dit. Et ces deux étapes étaient pipelinées, à savoir que deux micro-opérations mémoire peuvent s'exécuter en même temps : une dans l'étage de calcul d'adresse, une autre dans le cache. L'unité mémoire était donc pipelinée, alors que l'accès au cache ne l'était pas. Les processeurs qui implémentaient cette technique regroupent les micro-architectures K5 et K6 d'AMD, les processeurs Intel de micro-architecture P6 (Pentium 2 et 3) et quelques autres. Il est aussi possible de pipeliner l'accès au cache lui-même. Avec un cache pipeliné, l'accès au cache ne se fait pas en un seul cycle, mais en plusieurs. Cependant, on peut lancer un nouvel accès au cache à chaque cycle d'horloge, comme un processeur pipeliné lance une nouvelle instruction à chaque cycle. L'implémentation est assez simple : il suffit d'ajouter des registres dans le cache. Pour cela, on profite que le cache est composé de plusieurs composants séparés, qui échangent des données dans un ordre bien précis, d'un composant à un autre. De plus, le trajet des informations dans un cache est linéaire, ce qui les rend parfois pour l'usage d'un pipeline. ===Le ''pipelining'' des caches ''direct-mapped''=== Voici ce qui se passe avec un cache directement adressé. Pour rappel ce genre de cache est conçu en combinant une mémoire RAM, généralement une SRAM, avec quelques circuits de comparaison et un MUX. L'accès au cache se fait globalement en deux étapes : la première lit la donnée et le ''tag'' dans la SRAM, et on utilise les deux dans une seconde étape. Nous avions vu il y a quelques chapitre comment pipeliner des mémoires, dans le chapitre sur les mémoires évoluées. Et bien ces méthodes peuvent s'utiliser pour la mémoire RAM interne au cache !L'idée est d'insérer un registre entre la sortie de la RAM et la suite du cache, pour en faire un cache pipéliné, à deux étages. Le premier étage lit dans la SRAM, le second fait le reste. L'implémentation sur les caches associatifs à plusieurs voies est globalement la même à quelques détails près. [[File:Cache directement adressé pipeliné - deux étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - deux étages]] Avec le circuit précédent, il est possible d'aller plus loin, cette fois en pipelinant l'accès à la mémoire RAM interne au cache. Pour comprendre comment, rappelons qu'une mémoire SRAM est composée d'un plan mémoire et d'un décodeur. L'accès à la mémoire demande d'abord que le décodeur fasse son travail pour sélectionner la case mémoire adéquate, puis ensuite la lecture ou écriture a lieu dans cette case mémoire. L'accès se fait donc en deux étapes successives séparées, on a juste à mettre un registre entre les deux. Il suffit donc de mettre un gros registre entre le décodeur et le plan mémoire. [[File:Cache directement adressé pipeliné - trois étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - trois étages]] Et on peut aller encore plus loin en découpant le décodeur en deux circuits séparés. En effet, rappelez-vous le chapitre sur les circuits de sélection : nous avions vu qu'il est possible de créer des décodeurs en assemblant des décodeurs plus petits, contrôlés par un circuit de prédécodage. Et bien on peut encore une ajouter un registre entre ce circuit de prédécodage et les petits décodeurs. : Théoriquement, toute l'adresse est fournie d'un seul coup au cache, la quasi-totalité des processeurs présentent l'adresse complète à un cache pipeliné. Mais le Pentium 4 fait autrement. Il faut noter que les premiers étages manipulent l'indice dans la SRAM, qui est dans les bits de poids faible, alors que les étages ultérieurs manipulent le ''tag'' qui est dans les bits de poids fort. Les concepteurs du Pentium 4 ont alors décidé de présenter les bits de poids faible lors du premier cycle d'accès au cache, puis ceux de poids fort au second cycle. Pour cela, l'ALU fonctionnait à une fréquence double de celle du processeur, tout comme le cache L1. Il n'y avait pas de pipeline proprement dit, mais cela réduisait grandement la latence d'accès au cache. ===Le ''pipelining'' des caches splittés/sériels=== Il est aussi possible de pipeliner un cache dits splittés, aussi appelés à accès sériel. Pour rappel, les caches à accès sériel vérifient si il y a succès ou défaut de cache, avant d'accéder aux lignes de cache en cas de succès. Ils font donc différemment des autres caches, qui accèdent à une ligne de cache, avant de déterminer s'il y a succès ou défaut en lisant le tag de la ligne de cache. Les caches sériels disposent de deux SRAM : une pour les tags des lignes de cache et une pour les données. Ils accèdent à la SRAM pour les tags, avant d’accéder à la SRAM des données en cas de succès de cache. Vu que l'accès se fait en deux étapes, une vérification des tags suivie de la lecture/écriture des données, il est facile à pipeliner. Pipeliner le cache permet de régler le problème des accès au cache L1, et elle est tout le temps utilisé sur les processeurs modernes. Mais que faire en cas de défaut de cache ? ==Les caches non bloquants== Un '''cache bloquant''' est un cache auquel le processeur ne peut pas accéder pendant un défaut de cache. Il faut attendre que la lecture ou écriture en RAM soit terminée avant de pouvoir utiliser de nouveau le cache. Un '''cache non bloquant''' n'a pas ce problème : on peut l'utiliser pendant un défaut de cache. Les caches non bloquants permettent de démarrer une nouvelle lecture ou écriture alors qu'une autre est en cours, ce qui permet d'exécuter plusieurs lectures ou écritures en même temps. ===Les ''Miss Handling Status Registers''=== Lors d'un défaut de cache, la mémoire RAM est consultée pendant le défaut de cache, mais le cache est inutilisé. Un défaut de cache n'utilise pas le cache, ce n'est qu'un accès en mémoire RAM, sur le bus mémoire (ou un accès aux niveaux de cache inférieurs, peu importe). Le cache en lui-même est laissé libre, rien n’empêche d'y accéder, il est en réalité intrinsèquement non-bloquant. Les caches, bloquants comme non-bloquants, sont en réalité composés d'une mémoire cache proprement dite, entourée de circuits qui servent d'interface entre le processeur et le cache lui-même. Et parmi les circuits tout autour, certains gèrent l'accès au cache lors d'un défaut de cache. Ils sont regroupés sous le terme de '''''Miss Handling Architecture''''' (MHA). La différence entre un cache bloquant et un cache non-bloquant est en réalité liée à l'implémentation de la MHA. Les caches bloquants coupent volontairement l'accès au cache lors d'un défaut, car les défauts sont plus simples à gérer ainsi. Le défaut de cache rapatrie une donnée depuis la RAM, qui sera écrite dans le cache. Et il ne faut pas qu'une tentative d'accès à cette donnée ait lieu avant qu'elle ne soit chargée. Mais ce blocage est général et touche tout le cache, alors que seule une ligne de cache est concernée par le défaut de cache. L'idée derrière un cache non-bloquant est que seule la ligne de cache est bloquée, mais les autres sont accessibles. L'idée est alors de mémoriser les lignes de cache concernées par le défaut de cache, afin d'en bloquer l'accès. À chaque accès, on vérifie si la ligne de cache est déjà réservée par un défaut de cache. La lecture/écriture est alors bloquée si c'est le cas, mais elle accepte les accès sinon. Pour cela, la ''Miss Handling Architecture'' contient des registres qui mémorisent des informations sur les défauts de cache en cours. Ils portent le nom de '''''miss status handling registers''''', que j’appellerais dorénavant MSHR, qui sont aussi appelés des '''''miss buffer'''''. Le contenu des MSHR varie beaucoup suivant le processeur, mais ces derniers stockent au minimum les informations suivantes : * Le numéro de la ligne de cache dans laquelle les données sont chargées. * Un bit de validité qui indique si le MSHR est vide ou pas, qui est mis à 0 quand le défaut de cache est résolu. * Un ou plusieurs champs de lecture/écriture, qui contiennent des informations sur la lecture/écriture. ** Pour une lecture, elle contient des informations sur la destination de la donnée, à savoir qui prévenir quand le défaut de cache est terminé. C'est parfois un nom/numéro de registre (celui dans lequel charger la donnée), mais c'est souvent le numéro de l'entrée dans le ''load/store queue''. ** Pour les écritures, elle contient la donnée à écrire, ou éventuellement un numéro de ''load/store queue'' où se trouve la donnée à écrire. Il faut noter que le bus mémoire ne peut gérer qu'un seul défaut de cache à la fois. Aussi, il est intéressant de regarder ce qui se passe lorsqu'un second défaut de cache survient, pendant qu'un premier est en cours. Dans ce cas, il y a deux réponses qui correspondent à deux types de caches non-bloquants, qui portent les noms barbares de caches de type succès après défaut et défaut après défaut. Sur le premier type, il ne peut pas y avoir plusieurs défauts de cache simultanés. Dès qu'un second défaut de cache survient, le cache stoppe son activité et on ne peut plus démarrer de nouvelle lecture/écriture, tant que le premier défaut de cache n'est pas résolu. Le second type est plus souple et autorise la survenue de plusieurs défauts de cache simultanés. Du moins, jusqu'à une certaine limite, car le cache ne peut supporter qu'un nombre limité d'accès mémoires simultanés (pipelinés). ===Les accès simultanés à une même ligne de cache=== Il arrive que le processeur fasse plusieurs accès mémoire simultanés à la même ligne de cache. Si la ligne de cache en question n'a pas encore été chargée dans le cache, alors on a plusieurs défauts de cache consécutifs pour la même ligne de cache, et le cache non-bloquant doit gérer la situation. Pour la suite, il va falloir faire une petite distinction entre les défauts primaires et secondaires. Imaginons qu'un défaut de cache ait lieu et demande à charger une donnée dans la ligne de cache numéro N. Il s'agit du premier défaut impliquant cette ligne de cache précise, ce qui lui vaut le nom de '''défaut de cache primaire'''. Mais par la suite, d'autres accès mémoire à la même ligne de cache ont lieu, alors que la ligne de cache n'est pas encore disponible. Dans ce cas, il s'agit de '''défauts de cache secondaires'''. Pour l'unité d'accès mémoire, les défauts de cache primaire et secondaire sont différents (ils prennent tous une entrée dans la ''load/store queue''). Mais pour le cache, ils ne correspondent qu'à un seul accès au cache : celui qui demande de charger la ligne de cache demandée. Les défauts de cache primaire et secondaire à la même ligne de cache se voient attribuer un MSHR unique. La gestion des défauts de cache secondaires dépend du cache non-bloquant. La solution la plus simple ne permet pas les défauts de cache secondaires. Le cache ne permet pas deux défauts de cache simultanés pour la même ligne de cache. Les autres solutions le permettent, en fusionnant des accès simultanés à la même ligne de cache en un seul au niveau des MSHR. Dans tous les cas, détecter les défauts de cache secondaires sont un problème qu'il faut détecter. La ''Miss Handling Architecture'' doit détecter les défauts de cache secondaire. Pour cela, elle procède comme suit. Lors de chaque défaut de cache, la MHA récupère le numéro de la ligne de cache associé. Il vérifie alors chaque MSHR pour vérifier s'il contient le numéro en question. S'il n'y a aucune correspondance dans les MSHR, alors c'est signe que le défaut de cache est un défaut primaire. Mais s'il y en a une, alors c'est un défaut secondaire. Évidemment, cela signifie que lors d'un défaut de cache, le numéro de ligne de cache est envoyé à tous les MSHR, pour comparaison. Les MSHR sont donc regroupés dans une mémoire associative, une sorte de mini-cache, faciliter l'implémentation. Lors d'un défaut de cache primaire, l'accès à un cache non-bloquant se fait comme suit : le processeur envoie une adresse au cache, accède à celui-ci, et détecte la survenue d'un défaut de cache. Il en profite alors pour attribuer une ligne de cache dans laquelle sera chargée ce défaut. L'attribution est très simple dans le cas des caches ''direct mapped'', ou associatifs par voie, pour lesquels l'attribution se fait assez simplement. Il mémorise alors cette information dans les MSHR, après avoir vérifié que le défaut de cache n'était pas un défaut secondaire. Lors des accès ultérieurs à une adresse proche, censée être dans la même ligne de cache, le processeur va encore une fois rencontrer un défaut de cache. Il va alors déterminer le numéro de la ligne de cache associée à l'adresse, et comparer ce numéro avec les MSHRs. Si un MSHR contient ce numéro, c'est signe que le défaut de cache est un défaut secondaire. La MHA réagit alors différemment selon le processeur considéré. Une première solution n'autorise pas les défauts de cache secondaires. Si l'un d'entre eux survient, le processeur est gelé par un ''pipeline stall'', une bulle de pipeline. Une autre solution fusionne les défauts de cache secondaires avec le défaut de cache primaire : tout cela ne correspond qu'à un seul défaut de cache pour lui. ===Les MSHR simples=== Un cache non-bloquant à '''MSHR simple''' contient juste plusieurs MSHR qui mémorisent juste un numéro de ligne de cache, un bit de validité, le champ de lecture/écriture, et l'adresse exacte de lecture/écriture. Avec cette organisation, il est possible d'avoir plusieurs défauts de cache séparés, mais à la condition que chaque défaut accède à une ligne de cache différente. Deux accès simultanés à une même ligne de cache ne sont pas possibles, les défauts de cache secondaires ne sont pas autorisés. Ainsi, chaque défaut de cache se voit attribuer son propre MSHR, chacun contient un numéro de ligne de cache différent. Pour comprendre pourquoi c'est impossible de gérer les défauts secondaires, il faut regarder le champ de lecture/écriture. Si on veut effectuer plusieurs écritures consécutives à la même adresse, le MSHR n'aura pas de quoi mémoriser les deux données à écrire. Il pourra mémoriser la première donnée à écrire, pas la seconde. Même chose lors d'une lecture : le champ lecture/écriture peut mémorisr la destination de la première lecture, pas de la seconde. L'avantage est que la MHA n'a besoin que des MSHR et de quelques circuits annexes. Les autres solutions rajoutent des circuits annexes pour gérer les défauts de cache secondaires, qui utilisent beaucoup de circuits. Le cout en circuit est donc élevé, mais le gain en performance est là. Passons maintenant aux caches non-bloquants qui autorisent les défauts de cache secondaire. La solution la plus simple consiste à utiliser ===Les MSHR adressés implicitement=== Avec les '''MSHR adressés implicitement''', il est possible de fusionner plusieurs accès mémoire à une même ligne de cache, mais sous une condition très importante : ces accès lisent/écrivent des mots mémoire différents. Par exemple, imaginons qu'une ligne de cache contienne 8 mots mémoire de 64 bits. Si un premier accès mémoire lit le mot mémoire numéro 7 (dernier mot mémoire de la ligne), et le second accès le mot mémoire numéro 3, alors la fusion est possible. Mais si deux accès mémoire veulent lire/écrire le mot mémoire numéro 7, alors la fusion n'est pas possible et le processeur se bloque, un ''pipeline stall'' survient. Un MSHR adressé implicitement est un MSHR simple, qui contient naturellement un numéro de ligne de cache (tag) et un bit de validité, sauf que le champ de lecture/écriture est dupliqué. Les différents champs lecture/écriture d'un MSHR sont regroupés dans une mémoire RAM/cache qui contient autant d'entrées qu'il y a de mot mémoire dans une ligne de cache. Chaque entrée de la table est associée à un mot mémoire de la ligne de cache et stocke des informations sur celui-ci. Une entrée mémorise, au minimum : * Un champ de lecture/écriture qui contient soit la destination de la lecture, soit la donnée à écrire. * Un bit R/W permettant d'interpréter correctement le champ de lecture/écriture. * Un bit de validité pour chaque entrée de la table, qui dit si un défaut de cache antérieur accède déjà à ce mot mémoire. La fusion de deux défauts de cache est ainsi assez simple. Un défaut de cache primaire/secondaire configure l'entrée associée au mot mémoire lu/écrit. Si un défaut secondaire ultérieur lit/écrit un mot mémoire différent (dont le bit de validité de l'entrée dans la table est à zéro), pas de conflit : il configure une autre entrée, vide. Mais s'il lit/écrit un mot mémoire pour lequel l'entrée de la table est occupée, il y a conflit, le processeur est bloqué par un ''pipeline stall'', une bulle de pipeline. Avec cette organisation, le nombre de MSHR indique combien de lignes de cache peuvent être lues en même temps depuis la mémoire. Quant au nombre d'entrées par MSHR, il détermine combien d'accès mémoires qui ne se recouvrent pas peuvent avoir lieu en même temps. ===Les MSHR adressés explicitement=== Les '''MSHR adressés explicitement''' n'ont pas les contraintes des MSHR adressés implicitement. Avec eux, il est possible d'avoir plusieurs défauts de cache pointant vers la même ligne de cache, mais aussi vers le même mot mémoire. De tels défauts de cache apparaissent sur les processeurs à exécution dans le désordre, mais sont très rares, voire inexistants, sur les processeurs ''in-order''. L'idée est encore que chaque MSHR est associée à une table mémoire qui mémorise des entrées. Cette table n'est autre qu'une mémoire FIFO. Sauf que cette fois-ci, une entrée n'est pas associée à un mot mémoire. Une entrée est associée à un défaut de cache. Une entrée mémorise là encore la destination de la lecture ou la donnée de l'écriture suivi du bit R/W, ainsi que d'un bit de validité par entrée, mais aussi : la position du mot mémoire lu dans la ligne de cache. C'est cette dernière information qui n'était pas présente dans les MSHR adressés implicitement. De plus, le nombre d'entrée par MSHR n'est pas égal au nombre de mots mémoires dans une ligne de cache, mais peut être arbitrairement grand. L'avantage d'un tel cache est qu'il est capable de traité les défauts secondaires concernant un même mot mémoire, et ce, car les accès à la ligne de cache concernée se produisent dans l'ordre de génération des défauts (la table mémoire est une FIFO). ===Les MSHRs inversés=== Généralement, plus on veut supporter de défauts de cache, plus le nombre de MSHR et d'entrées augmente. Mais au-delà d'un certain nombre d'entrées et de MSHR, les MSHR adressés implicitement et explicitement ont tendance à bouffer un peu trop de circuits. Utiliser une organisation un peu moins gourmande en circuits est donc une nécessité. Cette organisation plus économe se base sur des MSHR inversés. Les MSHR inversés ne contiennent qu'une seule entrée, en quelque sorte : au lieu d’utiliser n MSHR de m entrées chacun, on va utiliser n × m MSHR inversés. La différence, c'est que plusieurs MSHR peuvent contenir un tag identique, contrairement aux MSHR adressés implicitement et explicitement. Lorsqu'un défaut de cache a lieu, chaque MSHR est vérifié. Si jamais aucun MSHR ne contient de tag identique à celui utilisé par le défaut, un MSHR vide est choisi pour stocker ce défaut, et une requête de lecture en mémoire est lancée. Dans le cas contraire, un MSHR est réservé au défaut de cache, mais la requête n'est pas lancée. Quand la donnée est disponible, les MSHR correspondant à la ligne qui vient d'être chargée vont être utilisés un par un pour résoudre les défauts de cache en attente. ===Les MSHR intégrés au cache=== Certains chercheurs ont remarqué que pendant qu'une ligne de cache est en train de subir un défaut de cache, celle-ci reste inutilisée, et son contenu est destiné à être perdu une fois le défaut de cache résolu. Ils se sont dit que, plutôt que d'utiliser des MSHR séparés, il vaudrait mieux utiliser la ligne de cache pour stocker les informations sur les défauts de cache en attente dans cette ligne de cache. Pour éviter tout problème, il faut rajouter un bit dans les bits de contrôle de la ligne de cache, qui sert à indiquer que la ligne de cache est occupée : un défaut de cache a eu lieu dans cette ligne, et elle stocke donc des informations pour résoudre les défauts de cache. ==L'entrelacement mémoire== Nous venons de voir comment une mémoire cache peut gérer plusieurs accès mémoire simultanés. Il se trouve que ce genre d'optimisation dépasse largement le cadre du cache. Les mémoires RAM ne sont pas en reste, elles aussi ! Il est possible de pipeliner l'accès à une mémoire RAM, en utilisant des techniques dites d''''entrelacement'''. Précisons cependant que cette optimisation n'est pas utilisée sur les mémoires SDRAM ou DDR modernes, du moins pas sans modifications majeures. Mais divers ordinateurs assez anciens, dont des superordinateurs, ont utilisé cette technique. La technique demande d'utiliser plusieurs mémoires séparées. Sur les anciens ordinateurs, les mémoires en question sont des chips mémoire, à savoir des circuits intégrés de DRAM. Pour faire plus général, nous allons utiliser le terme de '''banques''', ou encore de bancs mémoire. La différence est qu'il existe des mémoires multi-banque, qui regroupent plusieurs banques indépendantes dans un seul boitier, dans un seul chip mémoire. En clair, de telles mémoires regroupent plusieurs sous-mémoires dans un seul circuit intégré. Il est aussi possible d'utiliser plusieurs chips mémoire séparés, chacun ayant une banque. Reste à combiner ces banques pour former un pseudo-pipeline. ===Utiliser plusieurs banques sans entrelacement=== Sans optimisation particulière, les adresses sont réparties dans les banques comme indiqué ci-dessous. Il s'agit de ce qui s'appelle un arrangement horizontal, et nous avions vu celui-ci dans le chapiutre sur les mémoires SDRAM. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Avec cet arrangement, les bits de poids fort de l'adresse sont utilisées pour sélectionner la banque adéquate, et le reste de l'adresse est envoyé sur le bus d'adresse. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Adresse de banque !! Adresse dans la banque |- | Quelques bits de poids fort || Reste de l'adresse |} L'entrelacement change cette répartition, afin d'accélérer les accès mémoire. L'entrelacement classique vise à améliorer les accès à des adresses mémoires consécutives, mais des formes plus évoluées d'entrelacement visent à optimiser des accès mémoire arbitraires. Voyons-les dans l'ordre, du plus simple au plus complexe. ===L'entrelacement classique=== Avec l''''entrelacement classique''', des adresses consécutives sont réparties dans des banques consécutives. Précisons que cette attribution des adresses n'implique absolument pas la mémoire virtuelle ou n'importe quel mécanisme dans le processeur. La répartition décide que telle adresse mémoire va dans telle banque, à telle adresse dans la banque. Elle est donc le fait du contrôleur mémoire, donc en dehors du processeur (les contrôleurs mémoires n’étaient pas encore intégrés dans le processeur à l'époque). [[File:Répartition des adresses dans une mémoire interleaved.png|centre|vignette|upright=1.5|Répartition des adresses dans une mémoire interleaved.]] L'entrelacement simple permet d'accélérer les accès mémoire qui se font à des adresses consécutives. Avec l'entrelacement, chaque accès mémoire tombe dans une banque mémoire différente, ce qui fait qu'on peut démarrer un nouvel accès mémoire à chaque cycle d'horloge. Pendant qu'une banque est occupée par un accès mémoire, on démarre le suivant dans une autre banque, et ainsi de suite. Le tout se termine soit quand on a épuisé toutes les banques libres, soit quand l'accès en rafales se termine. Pas besoin d'attendre que la mémoire ait fini sa lecture/écriture avant de démarrer la lecture/écriture suivante. [[File:Pipemining mémoire.png|centre|vignette|upright=2.5|Pipelining mémoire]] L'animation suivante illustre bien le processus, avec quatres banques. [[File:Interleaving.gif|centre|vignette|upright=2.5|Pipelining mémoire]] L'entrelacement simple utilise les bits de poids faible pour sélectionner la banque, et les bits de poids fort pour la case mémoire. [[File:Adresse mémoire d'une mémoire entrelacée.png|centre|vignette|upright=2|Adresse mémoire d'une mémoire entrelacée]] Précisons que le temps d'accès mémoire ne change pas beaucoup avec l'entrelacement. Par contre, on peut faire plus d'accès mémoire simultanés. On peut démarrer un accès mémoire par cycle d'horloge, mais l'accès en lui-même prend plusieurs cycles. Les mémoires à entrelacement ont donc un débit supérieur aux mémoires qui ne l'utilisent pas. L'entrelacement classique pourrait être utilisé pour accélérer les transferts entre mémoire RAM et cache, qui se font en blocs d'adresses consécutives. Mais de nos jours, il n'est pas utilisé dans cette optique. Il faut dire qu'il est redondant avec le mode rafale des mémoires DRAM. Les deux font globalement la même chose et on voit mal comment utiliser les deux en même temps. Le mode rafale étant plus simple à implémenter, et plus léger niveau utilisation du bus de commande mémoire, il est préféré à l'entrelacement. Par contre, l'entrelacement a eu son heure de gloire sur d'anciens processeurs qui n'avaient pas de mémoire cache, alors qu'ils disposaient d'un pipeline. L'exemple typique est celui des nombreux ''processeurs vectoriels'', que nous n'avons pas encore abordé à ce stade du cours. De tels processeurs avaient un pipeline, une unité de calcul fortement pipelinée, et parfois même de l'exécution dans le désordre et du renommage de registres ! ===L'entrelacement par décalage=== L'entrelacement simple est très utile pour les accès en rafle ou équivalents. Par contre, il existe de rares situations où il n'est pas optimal. Une de ces situations est celle des accès en enjambée (en ''stride''), où l'on accède en série à des adresses sont séparées par N mots mémoires. [[File:Accès par enjambées.png|centre|vignette|upright=2|Accès par enjambées.]] De tels accès surviennent quand un logiciel accède à des structures de données spécifiques, à savoir des tableaux de structures, des matrices, ou d'autres structures de données dans le genre. Un cas classique est celui du parcours d'une matrice colonne par colonne. Les matrices de nombres sont mémorisées ligne par ligne, ce qui fait que parcourir une colonne demande de passer d'une ligne à la suivante et de faire des grands sauts en mémoire RAM, mais tous espacés par la même distance. Avec l'entrelacement simple, les accès en enjambée sont moins performants que les accès à des adresses consécutives, mais cela ne fonctionne pas trop mal. Le pire cas est celui où l'on a N banques et où les données sont justement placées toutes les N adresses. Des accès consécutifs vont tous tomber dans la même banque, on ne peut plus accéder à des banques différentes en parallèle. Mais en dehors de ce cas, on voit une amélioration par rapport à la situation sans entrelacement. Pour obtenir des performances maximales pour les accès en enjambées, il faut répartir les mots mémoires dans la mémoire autrement. L'organisation idéale est la suivante. : Dans les explications qui vont suivre, la variable N représente le nombre de banques, qui sont numérotées de 0 à N-1. On commence par organiser les N premières adresses comme une mémoire entrelacée simple : l'adresse 0 correspond à la banque 0, l'adresse 1 à la banque 1, etc. Pour le bloc suivant, on décale tout d'une adresse, à savoir qu'on commence à remplir les banques à partir de la seconde, non de la première. Une fois la fin du bloc atteinte, on finit de remplir le bloc en repartant du début du bloc. Le troisième bloc d'adresse subit le même traitement, sauf qu'on commence à remplir à partir de la seconde banque. Et on poursuit l’assignation des adresses en décalant d'un cran en plus à chaque bloc. Ainsi, chaque bloc verra ses adresses décalées d'un cran en plus comparé au bloc précédent. Si jamais le décalage dépasse la fin d'un bloc, alors on reprend au début. [[File:Mémoire interleaved par décalage.png|centre|vignette|upright=1.5|Mémoire entrelacée par décalage.]] En faisant cela, on remarque que les banques situées à N adresses d'intervalle sont différentes. Dans l'exemple du dessus, nous avons ajouté un décalage de 1 à chaque nouveau bloc à remplir. Mais on aurait tout aussi bien pu prendre un décalage de 2, 3, etc. Dans tous les cas, on obtient un entrelacement par décalage. Ce décalage est appelé le pas d'entrelacement, noté P. Le calcul de l'adresse à envoyer à la banque, ainsi que la banque à sélectionner se fait en utilisant les formules suivantes : * adresse à envoyer à la banque = adresse totale / N ; * numéro de la banque = (adresse + décalage) modulo N ; * décalage = (adresse totale * P) mod N. Avec cet entrelacement par décalage, on peut prouver que la bande passante maximale est atteinte si le nombre de banques est un nombre premier. Seulement, utiliser un nombre de banques premier peut créer des trous dans la mémoire, des mots mémoires inadressables. Malgré ce défaut, la technique a été utilisée sur quelques ordinateurs, avec l'exemple notable du superordinateur ''Burroughs Scientific Processor''. Pour éviter cela, il y a plusieurs solutions. Par exemple, on peut faire en sorte que N et la taille d'une banque soient premiers entre eux : ils ne doivent pas avoir de diviseur commun. Mais en pratique, elles n'ont pas vraiment été implémentées dans une vraie machine, et sont restées à l'état de recherche, aussi je les passe sous silence. ===L'entrelacement pseudo-aléatoire=== Une dernière méthode de répartition consiste à répartir les adresses dans les banques de manière "pseudo-aléatoire". L'idée exacte est de faire passer l'adresse dans une '''fonction de hachage''', plus ou moins complexe. Pour rappel, une fonction de hachage prend une entrée de grande taille et fournit en sortie un résultat de petite taille. Idéalement, elles doivent donner des résultats aussi différents que possible pour des entrées similaires, histoire de simuler une sorte de pseudo-aléatoire. Ici, une adresse est transformée en un numéro de banque. Une fonction de hachage simple ne fait que permuter des bits de l'adresse pour obtenir son résultat. Elle ne fait qu'échanger des bits de place avant de couper l'adresse en deux morceaux : un pour la sélection de la banque, et un autre pour la sélection de l'adresse dans la banque. Cette permutation est fixe, et ne change pas suivant l'adresse. Des fonctions de hachage plus complexes font des XOR bit à bit entre certains bits de l'adresse. ==Les contrôleurs mémoires SDRAM/DDR optimisés== Après avoir vu le cas des mémoires RAM, nous allons nous concentrer sur le cas particulier des mémoires SDRAM. Les mémoires SDRAM et DDR modernes sont capables de gérer plusieurs accès simultanés à la mémoire RAM, et leurs contrôleurs mémoire en font tout autant. C'est une différence majeure avec les mémoires asynchrones FPM/EDO, qui n'acceptaient qu'un seul accès mémoire à la fois. Leur contrôleur mémoire n'acceptait qu'un seul accès mémoire à la fois, c'était un contrôleur mémoire bloquant. Les contrôleurs mémoires des SDRAM sont eux non-bloquants et peuvent encaisser une dizaine d'accès mémoire à la fois. Peu de choses sont connues sur les contrôleurs de SDRAM/DDR modernes, les fabricants ne donnant que peu de détails dessus. Les rares simulateurs qui tentent de décrire leur fonctionnement, comme DRAM SIM I et II, sont particulièrement simples et ne vont pas dans le détail. Néanmoins, le peu qu'on sait est tout de même instructif. ===La mise en attente des accès mémoire=== Un contrôleur de SDRAM peut accepter plusieurs accès mémoire en même temps. Mais cela ne signifie pas que la SDRAM sera capable de les exécuter immédiatement. Les accès mémoire sont traduits en une série de commandes mémoires, qui sont séparées par des délais mémoire de quelques cycles d'horloge. Les délais sont très précis, et sont à respecter à la lettre. Le contrôleur doit donc mettre en attente les accès mémoire que le processeur lui envoie, pour les exécuter dès que possible. Les accès mémoire sont mis en attente dans une mémoire FIFO, histoire de les exécuter dans leur ordre d'arrivée, notamment pour renvoyer les lectures dans l'ordre demandé. En plus d'une mémoire FIFO pour les commandes mémoire, on trouve une mémoire FIFO pour les données à écrire. Elle permet d'accumuler plusieurs écritures, tout en permettant que la donnée adéquate soit envoyée à la RAM au bon moment. Il y a aussi une FIFO pour les données lues, qui sert au cas où le cache ne soit pas disponible quand on lui envoie la donnée lue. [[File:Module d'interface avec la mémoire.png|centre|vignette|upright=2.5|Contrôleur mémoire avec mise en attente des requêtes processeur et autres optimisations.]] Une optimisation regroupe plusieurs accès à des données consécutives en un seul accès en rafale. Le contrôleur mémoire analyse les requêtes mises en attente et détecte si certaines se font à des adresses consécutives. Si c'est le cas, il fusionne ces requêtes en une lecture/écriture en rafale. Une telle optimisation est appelée la '''combinaison de lecture''' pour les lectures, et la '''combinaison d'écriture''' pour les écritures. ===Le pipeline des mémoires SDRAM=== Les SDRAM ont une forme de pseudo-pipeline très limité. Elles sont parfois capables de démarrer une commande avant que la précédente soit terminée. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | || bgcolor="#A0FFFF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || || || bgcolor="#FFA0FF" | READ (2) || || || || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 | bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 | |} Même si elle parait très limitée, cette possibilité est décuplée par la présence de plusieurs banques dans la mémoire SDRAM. Les chips mémoires de SDRAM sont composés de plusieurs banques, chacune étant une sorte de mini-mémoire miniature. Chaque banque a son propre décodeur, son propre tampon de ligne, ses propres multiplexeurs de colonne, sa logique de rafraichissement mémoire, etc. C'est comme si une SDRAM regroupait plusieurs mémoires séparées dans un même circuit intégré. Et cela permet de faire plusieurs accès mémoire en même temps, dans des banques différentes. Il est possible de lancer un accès mémoire dans deux banques en même temps, par exemple. Ou encore, on peut lancer un nouvel accès mémoire dans une banque si elle est inoccupée. La possibilité améliore grandement la performance de la SDRAM. Mais nous en reparlerons dans un chapitre ultérieur. {|class="wikitable" |+ Pipelining basique sur les SDRAM |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 |- ! Banque Numéro 1 | || || || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || || || |- ! Banque Numéro 2 | || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || |- ! Banque Numéro 3 | || || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || |} L'implémentation n'a rien de vraiment compliqué. Il suffit d'utiliser une mémoire FIFO par banque. Ainsi, les accès dans une même banque se feront en série, dans l'ordre d'arrivée, mais des accès à des banques différentes se feront dans le désordre. Cela ne pose pas de problème car les accès se font à des adresses différentes, ce qui fait qu'il n'y a pas de conflits majeurs. [[File:Gestion parallèle des banques.png|centre|vignette|upright=2.5|Gestion parallèle des banques.]] ===Le ré-ordonnancement des commandes mémoires=== Un contrôleur mémoire moderne est capable de changer l'ordre des requêtes mémoires, pour gagner en performances. En clair : il peut faire l'équivalent mémoire de l'exécution dans le désordre des processeurs haute performance. Une différence majeure est que le contrôleur mémoire ne remet pas les accès mémoire dans l'ordre avant de les envoyer au processeur. C'est le processeur qui remet en ordre les accès mémoire, dans sa ''load queue''. Pour cela, les requêtes mémoire sont "numérotées", à savoir qu'on leur attribue un identifiant binaire. Le contrôleur mémoire exécute les lectures/écritures dans le désordre, mais garde la trace de leur identifiant. Il sait dans quel ordre il exécute les accès mémoire et connait leur latence. Ce qui fait qu'il sait que la donnée lue à tel instant est associée à la requête mémoire numéro X. Pour les lectures, il renvoie la donnée lue avec l'identifiant. Le processeur sait alors à quelle lecture la donnée lue correspond et il se débrouille en interne pour gérer la situation. En clair : le processeur voit les accès mémoire dans le désordre, c'est lui qui les remet en ordre. Maintenant que ces précisions sont faites, posons cette question : quel est l'intérêt de faire les accès mémoire dans le désordre ? Accéder à des banques en parallèle est une réponse possible, mais le ré-ordonnancement est surtout utile si tous les accès mémoire atterrissent dans la même banque. Pour comprendre pourquoi, faisons un rappel rapide sur le tampon de ligne. Les mémoires SDRAM sont des mémoires à tampon de ligne. Elles font un accès mémoire en deux étapes. La première étape recopie une ligne de N * 64 bits dans un tampon interne à la SDRAM. La seconde étape sélectionne une colonne, à savoir une donnée de 64 bits, qui est soit envoyée sur le bus de données pour une lecture, soit modifiée par une écriture. [[File:Mémoire à tampon de ligne.png|centre|vignette|upright=2|Mémoire à tampon de ligne]] Les deux étapes correspondent à deux commandes séparées : une commande ACT qui précise l'adresse de la ligne, et une commande READ ou WRITE qui précise l'adresse de la colonne. [[File:Signaux RAS et CAS.png|centre|vignette|upright=2|Signaux RAS et CAS.]] Les SDRAM permettent de se passer de la première étape si des accès consécutifs se font dans la même ligne. Une fois activée, la ligne reste ouverte et on peut accéder plusieurs fois de suite dedans. On a alors juste à préciser l'adresse de colonne dedans. [[File:Sélection d'une ligne sur une mémoire FPM ou EDO.png|centre|vignette|upright=2|Sélection d'une ligne sur une mémoire FPM ou EDO.]] L'exécution dans le désordre des lectures/écritures SDRAM vise à faire en sorte que des accès consécutifs se fassent dans une même ligne. Elle marche si plusieurs accès à une même ligne ne sont pas consécutifs, et qu'il y a des accès entre les deux. Après ré-ordonnancement, ces accès mémoire deviennent consécutifs, ils sont exécutés l'un à la suite de l'autre. Pour rendre le tout plus concret, voici un exemple. Imaginez que l'on sait les 3 accès mémoire suivants : * Une lecture ligne A ; * Une écriture ligne B ; * Une lecture ligne A. Sans réordonnancent, on doit émettre deux commandes PRECHARGE : une quand on passe de la ligne A à la ligne B, et une autre pour le passage inverse. pour éviter cela, le contrôleur mémoire peut retarder l'écriture, ou au contraire avancer la seconde lecture. le résultat est alors le suivant : * Lecture ligne A ; * Lecture ligne A ; * Une écriture ligne B. On a donc deux accès consécutifs à la même ligne, suivi par une écriture dans une autre ligne. La technique marche parce que l'on a un mix adéquat de lectures et d'écritures. Mais il existe des possibilités de réorganisation autres qui ne sont pas valides. Il faut par exemple prendre garde à éviter d'intervertir lectures et écritures à une même adresse, sous peine de problèmes. Encore une fois, il faut faire attention aux dépendances mémoires. Le controleur mémoire teste les dépendances mémoires avant d'envoyer des commandes à la mémoire DDR/SDRAM. ==L'entrelacement sur les mémoires SDRAM== L''''entrelacement''' fonctionne sur les mémoires SDRAM, mais il doit être fortement modifié. Concrètement, tout ce qui a étré dit plus est inapplicable pour les SDRAM, du fait de la présence du tampon de ligne. Des adresses consécutives atterrissent déjà dans la même ligne, ce qui fait qu'on n'a pas besoin d'entrelacement au niveau d'une ligne. Et ne parlons pas de ce la présence de rangées et de canaux mémoire ! Cependant, il peut y avoir un entrelacement lié à la présence des banques SDRAM. L'entrelacement est géré juste avant le séquenceur mémoire. Le '''circuit d'entrelacement''' intervertit certains bits de l'adresse lors des accès mémoires. On trouve aussi des mémoires FIFOs pour mettre en attente les requêtes mémoire provenant du processeur. Elles permettent de recevoir plusieurs accès mémoire consécutifs. Si vous vous demandez comment plusieurs accès mémoire consécutifs peuvent avoir lieu, sachez que c'est une bonne question. Pour le moment, nous allons dire que ces accès proviennent du processeur ou de la mémoire cache, sans expliquer comment. Le contrôleur mémoire reçoit une série d'accès mémoire assez rapprochés, qu'il doit exécuter au mieux. [[File:Controleur mémoire d'une SDRAM avec entrelacement.png|centre|vignette|upright=2.5|Contrôleur mémoire d'une SDRAM avec entrelacement.]] Pour gérer l'entrelacement, le contrôleur de SDRAM prend en entrée l'adresse envoyée par le processeur, et la découpe en plusieurs champs : un pour sélectionner la banque adéquate, un autre pour la ligne, un autre pour la colonne, un autre pour la rangée. Une solution simple désactive l'entrelacement. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de banque || Adresse de ligne || Adresse de colonne |} Si cette solution fonctionne bien si les accès mémoire sont assez aléatoires, en pratique, mieux vaut utiliser l'entrelacement. ===L'entrelacement de banques/lignes=== Une première méthode d'entrelacement est l''''entrelacement de banques'''. Elle répartit deux lignes consécutives dans deux banques différentes. Pour comprendre l'idée, prenons un exemple. Imaginons une mémoire avec deux banques et 4 lignes. Imaginons qu'on parcoure/balaye la mémoire RAM en partant des adresses basses. Sans entrelacement de ligne, les accès se feront comme suit, de gauche à droite : {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Banque numéro 1 !! colspan="4" | Banque numéro 2 |- | Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 || Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 |} Avec l'entrelacement de banques, les accès se feront comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 |- | Ligne 1 || Ligne 1 || Ligne 2 || Ligne 2 || Ligne 3 || Ligne 3 || Ligne 4 || Ligne 4 |} Avec N banques, la première ligne ira dans la première banque, la seconde ligne dans la seconde, et ainsi de suite jusqu'à la énième banque qui va dans la banque numéro N. Et la ligne N+1 repart dans la première banque. L'avantage est que passer d'une ligne à la suivante est assez rapide. Au lieu de devoir fermer la première ligne et ouvrir la suivante on ouvrira directement la ligne suivante dans une banque différente, ce qui sera plus rapide. La commande PRECHARGE pourra être envoyée à la seconde banque en avance. Pour cela, l'adresse est découpée comme suit. {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne (précise à l'octet) |} ===L'entrelacement de rangées=== Il est aussi possible de faire la même chose, mais avec les rangées. Pour simplifier fortement, une rangée est simplement un chip mémoire. En réalité, les rangées ne sont pas des chip mémoire, mais un ensemble de chips mémoire regroupés ensemble histoire d'atteindre les 64 bits du bus de données. Par exemple, une rangée peut combiner 8 chips mémoire avec un bus de données de 8 bits chacun pour obtenir les 64 bits du bus de données d'une SDRAM. Mais nous passons ce détail sous silence dans les explications qui vont suivre, par souci de simplification. Pour faire comprendre l'entrelacement de rangée, le mieux est d'utiliser un exemple, le même que précédemment. Sans entrelacement de rangée, on change de chip mémoire une fois qu'on a balayé toutes ses adresses. {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Rangée numéro 1 !! colspan="4" | Rangée numéro 2 |- | Banque 1 || Banque 2 || Banque 3 || Banque 4 || Banque 1 || Banque 2 || Banque 3 || Banque 4 |} Avec l'entrelacement de ligne, on change de rangée dès qu'on change de banque/ligne. {|class="wikitable" |+ Adresse mémoire |- ! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 |- | Banque 1 || Banque 1 || Banque 2 || Banque 2 || Banque 3 || Banque 3 || Banque 4 || Banque 4 |} En clair, quand on a épuisé toutes les banques dans une même rangée, on passe à la rangée suivante au lieu de rester dans la même rangée. Notons que dans l'exemple précédent, on a combiné l'entrelacement de rangée et de banque, mais on aurait pu utiliser l'entrelacement de rangée seul. Mais ce n'est pas le cas le plus courant en pratique. Toujours est-il qu'en combinant entrelacement de rangée et de ligne, le découpage de l'adresse est le suivant : {|class="wikitable" |+ Adresse mémoire |- | Adresse de ligne || Numéro de rangée || Adresse de banque || Adresse de colonne (précise à l'octet) |} D'autres méthodes d'entrelacement sont possibles, mais elles sont plus complexes. Chaque contrôleur mémoire fait un peu à sa sauce. Ils permettent en général de choisir entre plusieurs entrelacements. Au minimum, ils permettent de désactiver l'entrelacement, d'activer un entrelacement de ligne, et d'autres entrelacements plus complexes, dont certains sont propriétaires et tenus secrets par le fabricant. ===L'entrelacement avec le ''dual channel''=== [[File:Dual channel slots.jpg|vignette|Slots mémoires avec ''dual channel''.]] L'usage du ''dual channel'' complique encore l'entrelacement. Là encore, il y a deux grandes solutions : avec et sans entrelacement des canaux mémoire. Rappelons le principe : deux barrettes de RAM sont accédées en parallèle. Et pour cela, il faut utiliser l'entrelacement. Typiquement, chaque barrette mémoire fournit 64 bits, ce qui fait que l'on peut accéder à 128 bits d'un seul coup, par exemple avec un accès en rafale. Sans ''dual channel'', la première barrette correspond à la moitié haute de la RAM, la seconde barrette correspond à la moitié basse. Avec ''dual channel'', une forme spécifique d'entrelacement est activée. Concrétement, deux blocs de 64 bits sont placés dans des canaux mémoire séparés. Idem avec du triple ou quadruple canal, mais c'est alors trois ou quatre blocs de 64 bits qui sont dispersés dans des canaux consécutifs. Le découpage de l'adresse est alors le suivant : {|class="wikitable" |+ Adresse mémoire |- ! colspan="6" | Sans entrelacement |- | Numéro de canal/barrette || Reste de l'adresse || Position de l'octet dans un bloc de 64 bits |- | colspan="6" | |- ! colspan="6" | Avec entrelacement |- | Reste de l'adresse || Numéro de canal/barrette || Position de l'octet dans un bloc de 64 bits |} [[File:Décodage d'adresse avec dual channel.png|centre|vignette|upright=2|Décodage d'adresse avec dual channel.]] Les contrôleurs de SDRAM précédents sont assez basiques et ne représentent pas les contrôleurs les plus évolués. Ils étaient utilisés sur les anciens PC, à une époque où ils étaient encore sur la carte mère du processeur. Mais de nos jours, le contrôleur mémoire est intégré au processeur. Et il incorpore de nombreuses optimisations afin de gagner en performances. Malheureusement, ces optimisations sont elles-mêmes dépendantes des optimisations intégrées dans le processeur, ce qui fait qu'on ne peut pas en parler ici. Elles demandent que le contrôleur mémoire reçoive plusieurs accès mémoire simultanés, ce qui n'a aucun sens à ce stade du cours. Aussi, nous allons les passer sous silence pour le moment. Nous les verrons dans le chapitre sur le parallélisme mémoire. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=La désambiguïsation mémoire | prevText=La désambiguïsation mémoire | next=Les processeurs superscalaires | nextText=Les processeurs superscalaires }} </noinclude> afnti90r2x22kmegnyov7y0xppa1niy 764706 764705 2026-04-23T21:04:48Z Mewtow 31375 /* La mise en attente des accès mémoire */ 764706 wikitext text/x-wiki Dans ce chapitre, nous allons voir les techniques qui permettent de gérer plusieurs accès mémoire simultanés directement au niveau du cache ou de la mémoire RAM. Par plusieurs accès mémoire simultanés, vous pensez sans doute à l'usage de cache multiports, voire de mémoires RAM multiport. C'est en effet une solution, mais ce n'est clairement pas la seule. Par exemple, il est possible de pipeliner l'accès au cache, voire à la mémoire RAM. Il existe de nombreuses techniques de '''parallélisme mémoire''', et nous allons les voir dans ce chapitre. Pour donner un exemple, il est possible de pipeliner les accès mémoire. Il est possible de pipeliner l'accès au cache et/ou l'accès à la mémoire, et nous verrons comment faire dans ce chapitre. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, qu'ils aient ou non un pipeline dynamique. Par contre, pipeliner la mémoire est une technique ancienne, aujourd'hui peu utilisée. Elle était utilisée sur des très vieux ordinateurs, qui avaient un pipeline mais pas de mémoire cache. Mais de nos jours, les mémoires SDRAM et DDR ne sont pas adaptées pour ça, il est très difficile de les pipeliner correctement. Le parallélisme mémoire est utile aussi bien sur des processeurs à émission dans l'ordre que dans le désordre. Par exemple, un processeur ''in-order'' avec des lectures non-bloquantes peut e, profiter. Il peut techniquement lancer une seconde lecture, après avoir rencontré une première lecture non-bloquante. En général, le nombre de lectures consécutives est cependant limité à moins d'une dizaine. À l'opposé, les processeurs sans lectures non-bloquantes profitent pas du parallélisme mémoire. À la rigueur, ils peuvent en profiter s'ils intègrent des techniques de préchargement. Pendant que le processeur exécute une lecture/écriture, il peut précharger une autre donnée en parallèle. Inutile de dire que sans pipeline processeur, le parallélisme mémoire ne sert pas à grand-chose. ==Le ''line fill buffer'' et les techniques associées== Lors d'un défaut de cache, le processeur doit attendre que toute la ligne de cache soit chargée avant d'être utilisable. Or, la taille d'une ligne de cache est supérieure à la largeur du bus mémoire, ce qui fait qu'une ligne de cache est chargée en plusieurs fois, morceaux par morceaux, mot mémoire par mot mémoire. Le chargement peut se faire directement dans le cache, mais ce n'est pas une solution très pratique. À la place, beaucoup de processeurs ajoutent une mémoire tampon entre la RAM et le cache, appelée le '''tampon de remplissage de ligne''' (''line-fill buffer'') dans les processeurs modernes. Lors d'un défaut de cache, le processeur charge la donnée de la RAM dans le tampon de remplissage de ligne, mot mémoire par mot mémoire. Une fois plein, le tampon de remplissage de ligne est recopié dans la ligne de cache. Le tampon de remplissage de ligne contient une ligne de cache, si ce n'est que certains bits de contrôle ne sont pas présents. Le tampon de remplissage de ligne contient un bit de validité pour chaque mot mémoire de la ligne de cache, qui indiquent si le mot mémoire a été chargé. Par exemple, prenons un processeur 64 bits, qui gère donc des mots mémoire de 8 octets, avec des lignes de cache 256 octets/32 mots mémoire. Le tampon de remplissage de ligne contiendra 32 bits de validité. Si le processeur a chargé les 6 premiers mots mémoire, les 6 premiers bits de validité seront mis à 1, les autres seront encore à zéro. Il faut noter que la disponibilité de la ligne complète se détermine assez facilement en faisant un ET logique entre tous les bits de validité. Le processeur sait ainsi quand la ligne de cache est disponible entièrement et donc quand la transférer dans le cache. La même chose existe avec une hiérarchie de cache, sauf que l'on trouve une mémoire tampon entre chaque niveau de cache. Il y a un tampon de remplissage de ligne entre le cache L1 et le cache L2, entre le cache L2 et le L3, etc. Si le cache ne gère qu'un seul défaut de cache à la fois, le ''fill line buffer'' est une mémoire très simple, qui ne mémorise qu'une seule ligne de cache. Et cela vaut aussi bien pour un cache bloquant que non-bloquant. Mais sur les caches capables de gérer plusieurs défauts simultanés, le tampon de remplissage de ligne est une mémoire de type FIFO ou LIFO, capable de mémoriser plusieurs lignes de cache. Notons que le tampon de remplissage de ligne est très utile pour implémenter certaines techniques, par exemple le contournement du cache. Nous avions vu dans le chapitre sur le cache que certains accès mémoire doivent contourner le cache, pour des raisons de cohérence des caches. C'est notamment nécessaire pour accéder aux périphériques, mais c'est aussi utile pour des raisons de performances dans des cas très spécifiques. Les accès qui contournent le cache se font directement dans le tampon de remplissage de ligne : le processeur écrit ou lit les données depuis ce tampon de remplissage de ligne, sans accéder au cache. ===L'''early restart'' et le ''critical word load''=== La présence du ''line buffer'' permet une optimisation assez intéressante, qui permet de réduire la latence des défauts de cache. L'optimisation consiste à lire un mot mémoire dans le tampon de remplissage de ligne, même si la ligne de cache complète n'a pas encore été chargée. Il existe deux manières de faire cela, qui portent les noms d'''early restart'' et de ''critical word load''. La première est la version la plus simple, la seconde est plus complexe mais plus performante. Avec la technique d''''''early restart''''', la ligne de cache est chargée normalement, en partant de son premier mot mémoire. Dès que le mot mémoire lu/écrit par le processeur est copié dans le ''line fill buffer'', il est envoyé au processeur immédiatement. Illustrons le tout par un exemple, où une ligne de cache fait 16 mots mémoire. Le processeur effectue une lecture, qui lit le 5ème mot mémoire. Sans ''early restart'', le processeur doit charger les 16 mots mémoire avant de faire la lecture dans le cache. Avec ''early restart'', le processeur reçoit la donnée dès que le 5ème mot mémoire est disponible. Le processeur doit attendre que les 4 premiers mots mémoire soient chargés, puis le 5ème mot mémoire arrive et est envoyé directement au processeur, il est lu directement depuis le ''line fill buffer''. Les 11 mots mémoire suivants sont ensuite chargés dans le cache pendant que le processeur fait des calculs dans son coin. Le ''critical word load'' est une optimisation de la technique précédente où le chargement de la ligne de cache commence directement à la donnée demandée par le processeur. Pour reprendre l'exemple précédent, où le processeur demande le 5ème mot mémoire sur 16, le ''critical word load'' charge le 5ème mot mémoire en premier et l'envoie au processeur, ce qui fait qu'il est chargé très rapidement. Pas besoin d'attendre que le processeur charge les 4 mots mémoire précédents comme avec l'''early restart''. Dans le détail, le processeur charge le 5ème mot mémoire en premier, puis charge les 11 suivants, et termine par les 4 mots mémoire du début. En clair, le ''critical word load'' commence par charger le mot mémoire lu, puis les blocs suivants, avant de revenir au début du bloc pour charger les blocs restants. Ainsi, la donnée demandée par le processeur sera la première disponible. Pour cela, l'organisation du tampon de remplissage de ligne est modifiée de manière à rendre cela possible. Il a une taille égale à une ligne de cache complète, qui contient elle-même plusieurs mots mémoire. Dans le ''line-fill buffer'', chaque mot mémoire est stocké avec un tag, qui indique l'adresse du mot mémoire stocké dans le ''line-fill buffer''. Le ''line-fill buffer'' est donc un cache un peu particulier, qui fonctionne comme un cache du point de vue du processeur, comme une mémoire FIFO pour les transferts avec le cache. Ainsi, un processeur qui veut lire dans le cache après un défaut peut accéder à la donnée directement depuis le tampon de remplissage de ligne, alors que la ligne de cache n'a pas encore été totalement recopiée en mémoire. ==Les caches pipelinés== Il est possible de pipeliner l'accès au cache, ce qui demande juste de rajouter des registres au bon endroits et quelques circuits de contrôle. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, même ceux avec un pipeline dynamique. Et l'implémentation peut se faire de plusieurs manières différentes. Il faut dire que découper une mémoire cache en plusieurs étapes peut se faire de plusieurs manière. Commençons par la plus simple. Beaucoup de processeurs des années 2000 avaient une fréquence peu élevée comparée aux standards d'aujourd'hui, ce qui fait que leur cache avait bien un temps d'accès d'un cycle d'horloge. Mais l'accès au cache se faisait en deux cycles d'horloge : un cycle pour calculer une adresse et un cycle pour l'accès mémoire proprement dit. Et ces deux étapes étaient pipelinées, à savoir que deux micro-opérations mémoire peuvent s'exécuter en même temps : une dans l'étage de calcul d'adresse, une autre dans le cache. L'unité mémoire était donc pipelinée, alors que l'accès au cache ne l'était pas. Les processeurs qui implémentaient cette technique regroupent les micro-architectures K5 et K6 d'AMD, les processeurs Intel de micro-architecture P6 (Pentium 2 et 3) et quelques autres. Il est aussi possible de pipeliner l'accès au cache lui-même. Avec un cache pipeliné, l'accès au cache ne se fait pas en un seul cycle, mais en plusieurs. Cependant, on peut lancer un nouvel accès au cache à chaque cycle d'horloge, comme un processeur pipeliné lance une nouvelle instruction à chaque cycle. L'implémentation est assez simple : il suffit d'ajouter des registres dans le cache. Pour cela, on profite que le cache est composé de plusieurs composants séparés, qui échangent des données dans un ordre bien précis, d'un composant à un autre. De plus, le trajet des informations dans un cache est linéaire, ce qui les rend parfois pour l'usage d'un pipeline. ===Le ''pipelining'' des caches ''direct-mapped''=== Voici ce qui se passe avec un cache directement adressé. Pour rappel ce genre de cache est conçu en combinant une mémoire RAM, généralement une SRAM, avec quelques circuits de comparaison et un MUX. L'accès au cache se fait globalement en deux étapes : la première lit la donnée et le ''tag'' dans la SRAM, et on utilise les deux dans une seconde étape. Nous avions vu il y a quelques chapitre comment pipeliner des mémoires, dans le chapitre sur les mémoires évoluées. Et bien ces méthodes peuvent s'utiliser pour la mémoire RAM interne au cache !L'idée est d'insérer un registre entre la sortie de la RAM et la suite du cache, pour en faire un cache pipéliné, à deux étages. Le premier étage lit dans la SRAM, le second fait le reste. L'implémentation sur les caches associatifs à plusieurs voies est globalement la même à quelques détails près. [[File:Cache directement adressé pipeliné - deux étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - deux étages]] Avec le circuit précédent, il est possible d'aller plus loin, cette fois en pipelinant l'accès à la mémoire RAM interne au cache. Pour comprendre comment, rappelons qu'une mémoire SRAM est composée d'un plan mémoire et d'un décodeur. L'accès à la mémoire demande d'abord que le décodeur fasse son travail pour sélectionner la case mémoire adéquate, puis ensuite la lecture ou écriture a lieu dans cette case mémoire. L'accès se fait donc en deux étapes successives séparées, on a juste à mettre un registre entre les deux. Il suffit donc de mettre un gros registre entre le décodeur et le plan mémoire. [[File:Cache directement adressé pipeliné - trois étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - trois étages]] Et on peut aller encore plus loin en découpant le décodeur en deux circuits séparés. En effet, rappelez-vous le chapitre sur les circuits de sélection : nous avions vu qu'il est possible de créer des décodeurs en assemblant des décodeurs plus petits, contrôlés par un circuit de prédécodage. Et bien on peut encore une ajouter un registre entre ce circuit de prédécodage et les petits décodeurs. : Théoriquement, toute l'adresse est fournie d'un seul coup au cache, la quasi-totalité des processeurs présentent l'adresse complète à un cache pipeliné. Mais le Pentium 4 fait autrement. Il faut noter que les premiers étages manipulent l'indice dans la SRAM, qui est dans les bits de poids faible, alors que les étages ultérieurs manipulent le ''tag'' qui est dans les bits de poids fort. Les concepteurs du Pentium 4 ont alors décidé de présenter les bits de poids faible lors du premier cycle d'accès au cache, puis ceux de poids fort au second cycle. Pour cela, l'ALU fonctionnait à une fréquence double de celle du processeur, tout comme le cache L1. Il n'y avait pas de pipeline proprement dit, mais cela réduisait grandement la latence d'accès au cache. ===Le ''pipelining'' des caches splittés/sériels=== Il est aussi possible de pipeliner un cache dits splittés, aussi appelés à accès sériel. Pour rappel, les caches à accès sériel vérifient si il y a succès ou défaut de cache, avant d'accéder aux lignes de cache en cas de succès. Ils font donc différemment des autres caches, qui accèdent à une ligne de cache, avant de déterminer s'il y a succès ou défaut en lisant le tag de la ligne de cache. Les caches sériels disposent de deux SRAM : une pour les tags des lignes de cache et une pour les données. Ils accèdent à la SRAM pour les tags, avant d’accéder à la SRAM des données en cas de succès de cache. Vu que l'accès se fait en deux étapes, une vérification des tags suivie de la lecture/écriture des données, il est facile à pipeliner. Pipeliner le cache permet de régler le problème des accès au cache L1, et elle est tout le temps utilisé sur les processeurs modernes. Mais que faire en cas de défaut de cache ? ==Les caches non bloquants== Un '''cache bloquant''' est un cache auquel le processeur ne peut pas accéder pendant un défaut de cache. Il faut attendre que la lecture ou écriture en RAM soit terminée avant de pouvoir utiliser de nouveau le cache. Un '''cache non bloquant''' n'a pas ce problème : on peut l'utiliser pendant un défaut de cache. Les caches non bloquants permettent de démarrer une nouvelle lecture ou écriture alors qu'une autre est en cours, ce qui permet d'exécuter plusieurs lectures ou écritures en même temps. ===Les ''Miss Handling Status Registers''=== Lors d'un défaut de cache, la mémoire RAM est consultée pendant le défaut de cache, mais le cache est inutilisé. Un défaut de cache n'utilise pas le cache, ce n'est qu'un accès en mémoire RAM, sur le bus mémoire (ou un accès aux niveaux de cache inférieurs, peu importe). Le cache en lui-même est laissé libre, rien n’empêche d'y accéder, il est en réalité intrinsèquement non-bloquant. Les caches, bloquants comme non-bloquants, sont en réalité composés d'une mémoire cache proprement dite, entourée de circuits qui servent d'interface entre le processeur et le cache lui-même. Et parmi les circuits tout autour, certains gèrent l'accès au cache lors d'un défaut de cache. Ils sont regroupés sous le terme de '''''Miss Handling Architecture''''' (MHA). La différence entre un cache bloquant et un cache non-bloquant est en réalité liée à l'implémentation de la MHA. Les caches bloquants coupent volontairement l'accès au cache lors d'un défaut, car les défauts sont plus simples à gérer ainsi. Le défaut de cache rapatrie une donnée depuis la RAM, qui sera écrite dans le cache. Et il ne faut pas qu'une tentative d'accès à cette donnée ait lieu avant qu'elle ne soit chargée. Mais ce blocage est général et touche tout le cache, alors que seule une ligne de cache est concernée par le défaut de cache. L'idée derrière un cache non-bloquant est que seule la ligne de cache est bloquée, mais les autres sont accessibles. L'idée est alors de mémoriser les lignes de cache concernées par le défaut de cache, afin d'en bloquer l'accès. À chaque accès, on vérifie si la ligne de cache est déjà réservée par un défaut de cache. La lecture/écriture est alors bloquée si c'est le cas, mais elle accepte les accès sinon. Pour cela, la ''Miss Handling Architecture'' contient des registres qui mémorisent des informations sur les défauts de cache en cours. Ils portent le nom de '''''miss status handling registers''''', que j’appellerais dorénavant MSHR, qui sont aussi appelés des '''''miss buffer'''''. Le contenu des MSHR varie beaucoup suivant le processeur, mais ces derniers stockent au minimum les informations suivantes : * Le numéro de la ligne de cache dans laquelle les données sont chargées. * Un bit de validité qui indique si le MSHR est vide ou pas, qui est mis à 0 quand le défaut de cache est résolu. * Un ou plusieurs champs de lecture/écriture, qui contiennent des informations sur la lecture/écriture. ** Pour une lecture, elle contient des informations sur la destination de la donnée, à savoir qui prévenir quand le défaut de cache est terminé. C'est parfois un nom/numéro de registre (celui dans lequel charger la donnée), mais c'est souvent le numéro de l'entrée dans le ''load/store queue''. ** Pour les écritures, elle contient la donnée à écrire, ou éventuellement un numéro de ''load/store queue'' où se trouve la donnée à écrire. Il faut noter que le bus mémoire ne peut gérer qu'un seul défaut de cache à la fois. Aussi, il est intéressant de regarder ce qui se passe lorsqu'un second défaut de cache survient, pendant qu'un premier est en cours. Dans ce cas, il y a deux réponses qui correspondent à deux types de caches non-bloquants, qui portent les noms barbares de caches de type succès après défaut et défaut après défaut. Sur le premier type, il ne peut pas y avoir plusieurs défauts de cache simultanés. Dès qu'un second défaut de cache survient, le cache stoppe son activité et on ne peut plus démarrer de nouvelle lecture/écriture, tant que le premier défaut de cache n'est pas résolu. Le second type est plus souple et autorise la survenue de plusieurs défauts de cache simultanés. Du moins, jusqu'à une certaine limite, car le cache ne peut supporter qu'un nombre limité d'accès mémoires simultanés (pipelinés). ===Les accès simultanés à une même ligne de cache=== Il arrive que le processeur fasse plusieurs accès mémoire simultanés à la même ligne de cache. Si la ligne de cache en question n'a pas encore été chargée dans le cache, alors on a plusieurs défauts de cache consécutifs pour la même ligne de cache, et le cache non-bloquant doit gérer la situation. Pour la suite, il va falloir faire une petite distinction entre les défauts primaires et secondaires. Imaginons qu'un défaut de cache ait lieu et demande à charger une donnée dans la ligne de cache numéro N. Il s'agit du premier défaut impliquant cette ligne de cache précise, ce qui lui vaut le nom de '''défaut de cache primaire'''. Mais par la suite, d'autres accès mémoire à la même ligne de cache ont lieu, alors que la ligne de cache n'est pas encore disponible. Dans ce cas, il s'agit de '''défauts de cache secondaires'''. Pour l'unité d'accès mémoire, les défauts de cache primaire et secondaire sont différents (ils prennent tous une entrée dans la ''load/store queue''). Mais pour le cache, ils ne correspondent qu'à un seul accès au cache : celui qui demande de charger la ligne de cache demandée. Les défauts de cache primaire et secondaire à la même ligne de cache se voient attribuer un MSHR unique. La gestion des défauts de cache secondaires dépend du cache non-bloquant. La solution la plus simple ne permet pas les défauts de cache secondaires. Le cache ne permet pas deux défauts de cache simultanés pour la même ligne de cache. Les autres solutions le permettent, en fusionnant des accès simultanés à la même ligne de cache en un seul au niveau des MSHR. Dans tous les cas, détecter les défauts de cache secondaires sont un problème qu'il faut détecter. La ''Miss Handling Architecture'' doit détecter les défauts de cache secondaire. Pour cela, elle procède comme suit. Lors de chaque défaut de cache, la MHA récupère le numéro de la ligne de cache associé. Il vérifie alors chaque MSHR pour vérifier s'il contient le numéro en question. S'il n'y a aucune correspondance dans les MSHR, alors c'est signe que le défaut de cache est un défaut primaire. Mais s'il y en a une, alors c'est un défaut secondaire. Évidemment, cela signifie que lors d'un défaut de cache, le numéro de ligne de cache est envoyé à tous les MSHR, pour comparaison. Les MSHR sont donc regroupés dans une mémoire associative, une sorte de mini-cache, faciliter l'implémentation. Lors d'un défaut de cache primaire, l'accès à un cache non-bloquant se fait comme suit : le processeur envoie une adresse au cache, accède à celui-ci, et détecte la survenue d'un défaut de cache. Il en profite alors pour attribuer une ligne de cache dans laquelle sera chargée ce défaut. L'attribution est très simple dans le cas des caches ''direct mapped'', ou associatifs par voie, pour lesquels l'attribution se fait assez simplement. Il mémorise alors cette information dans les MSHR, après avoir vérifié que le défaut de cache n'était pas un défaut secondaire. Lors des accès ultérieurs à une adresse proche, censée être dans la même ligne de cache, le processeur va encore une fois rencontrer un défaut de cache. Il va alors déterminer le numéro de la ligne de cache associée à l'adresse, et comparer ce numéro avec les MSHRs. Si un MSHR contient ce numéro, c'est signe que le défaut de cache est un défaut secondaire. La MHA réagit alors différemment selon le processeur considéré. Une première solution n'autorise pas les défauts de cache secondaires. Si l'un d'entre eux survient, le processeur est gelé par un ''pipeline stall'', une bulle de pipeline. Une autre solution fusionne les défauts de cache secondaires avec le défaut de cache primaire : tout cela ne correspond qu'à un seul défaut de cache pour lui. ===Les MSHR simples=== Un cache non-bloquant à '''MSHR simple''' contient juste plusieurs MSHR qui mémorisent juste un numéro de ligne de cache, un bit de validité, le champ de lecture/écriture, et l'adresse exacte de lecture/écriture. Avec cette organisation, il est possible d'avoir plusieurs défauts de cache séparés, mais à la condition que chaque défaut accède à une ligne de cache différente. Deux accès simultanés à une même ligne de cache ne sont pas possibles, les défauts de cache secondaires ne sont pas autorisés. Ainsi, chaque défaut de cache se voit attribuer son propre MSHR, chacun contient un numéro de ligne de cache différent. Pour comprendre pourquoi c'est impossible de gérer les défauts secondaires, il faut regarder le champ de lecture/écriture. Si on veut effectuer plusieurs écritures consécutives à la même adresse, le MSHR n'aura pas de quoi mémoriser les deux données à écrire. Il pourra mémoriser la première donnée à écrire, pas la seconde. Même chose lors d'une lecture : le champ lecture/écriture peut mémorisr la destination de la première lecture, pas de la seconde. L'avantage est que la MHA n'a besoin que des MSHR et de quelques circuits annexes. Les autres solutions rajoutent des circuits annexes pour gérer les défauts de cache secondaires, qui utilisent beaucoup de circuits. Le cout en circuit est donc élevé, mais le gain en performance est là. Passons maintenant aux caches non-bloquants qui autorisent les défauts de cache secondaire. La solution la plus simple consiste à utiliser ===Les MSHR adressés implicitement=== Avec les '''MSHR adressés implicitement''', il est possible de fusionner plusieurs accès mémoire à une même ligne de cache, mais sous une condition très importante : ces accès lisent/écrivent des mots mémoire différents. Par exemple, imaginons qu'une ligne de cache contienne 8 mots mémoire de 64 bits. Si un premier accès mémoire lit le mot mémoire numéro 7 (dernier mot mémoire de la ligne), et le second accès le mot mémoire numéro 3, alors la fusion est possible. Mais si deux accès mémoire veulent lire/écrire le mot mémoire numéro 7, alors la fusion n'est pas possible et le processeur se bloque, un ''pipeline stall'' survient. Un MSHR adressé implicitement est un MSHR simple, qui contient naturellement un numéro de ligne de cache (tag) et un bit de validité, sauf que le champ de lecture/écriture est dupliqué. Les différents champs lecture/écriture d'un MSHR sont regroupés dans une mémoire RAM/cache qui contient autant d'entrées qu'il y a de mot mémoire dans une ligne de cache. Chaque entrée de la table est associée à un mot mémoire de la ligne de cache et stocke des informations sur celui-ci. Une entrée mémorise, au minimum : * Un champ de lecture/écriture qui contient soit la destination de la lecture, soit la donnée à écrire. * Un bit R/W permettant d'interpréter correctement le champ de lecture/écriture. * Un bit de validité pour chaque entrée de la table, qui dit si un défaut de cache antérieur accède déjà à ce mot mémoire. La fusion de deux défauts de cache est ainsi assez simple. Un défaut de cache primaire/secondaire configure l'entrée associée au mot mémoire lu/écrit. Si un défaut secondaire ultérieur lit/écrit un mot mémoire différent (dont le bit de validité de l'entrée dans la table est à zéro), pas de conflit : il configure une autre entrée, vide. Mais s'il lit/écrit un mot mémoire pour lequel l'entrée de la table est occupée, il y a conflit, le processeur est bloqué par un ''pipeline stall'', une bulle de pipeline. Avec cette organisation, le nombre de MSHR indique combien de lignes de cache peuvent être lues en même temps depuis la mémoire. Quant au nombre d'entrées par MSHR, il détermine combien d'accès mémoires qui ne se recouvrent pas peuvent avoir lieu en même temps. ===Les MSHR adressés explicitement=== Les '''MSHR adressés explicitement''' n'ont pas les contraintes des MSHR adressés implicitement. Avec eux, il est possible d'avoir plusieurs défauts de cache pointant vers la même ligne de cache, mais aussi vers le même mot mémoire. De tels défauts de cache apparaissent sur les processeurs à exécution dans le désordre, mais sont très rares, voire inexistants, sur les processeurs ''in-order''. L'idée est encore que chaque MSHR est associée à une table mémoire qui mémorise des entrées. Cette table n'est autre qu'une mémoire FIFO. Sauf que cette fois-ci, une entrée n'est pas associée à un mot mémoire. Une entrée est associée à un défaut de cache. Une entrée mémorise là encore la destination de la lecture ou la donnée de l'écriture suivi du bit R/W, ainsi que d'un bit de validité par entrée, mais aussi : la position du mot mémoire lu dans la ligne de cache. C'est cette dernière information qui n'était pas présente dans les MSHR adressés implicitement. De plus, le nombre d'entrée par MSHR n'est pas égal au nombre de mots mémoires dans une ligne de cache, mais peut être arbitrairement grand. L'avantage d'un tel cache est qu'il est capable de traité les défauts secondaires concernant un même mot mémoire, et ce, car les accès à la ligne de cache concernée se produisent dans l'ordre de génération des défauts (la table mémoire est une FIFO). ===Les MSHRs inversés=== Généralement, plus on veut supporter de défauts de cache, plus le nombre de MSHR et d'entrées augmente. Mais au-delà d'un certain nombre d'entrées et de MSHR, les MSHR adressés implicitement et explicitement ont tendance à bouffer un peu trop de circuits. Utiliser une organisation un peu moins gourmande en circuits est donc une nécessité. Cette organisation plus économe se base sur des MSHR inversés. Les MSHR inversés ne contiennent qu'une seule entrée, en quelque sorte : au lieu d’utiliser n MSHR de m entrées chacun, on va utiliser n × m MSHR inversés. La différence, c'est que plusieurs MSHR peuvent contenir un tag identique, contrairement aux MSHR adressés implicitement et explicitement. Lorsqu'un défaut de cache a lieu, chaque MSHR est vérifié. Si jamais aucun MSHR ne contient de tag identique à celui utilisé par le défaut, un MSHR vide est choisi pour stocker ce défaut, et une requête de lecture en mémoire est lancée. Dans le cas contraire, un MSHR est réservé au défaut de cache, mais la requête n'est pas lancée. Quand la donnée est disponible, les MSHR correspondant à la ligne qui vient d'être chargée vont être utilisés un par un pour résoudre les défauts de cache en attente. ===Les MSHR intégrés au cache=== Certains chercheurs ont remarqué que pendant qu'une ligne de cache est en train de subir un défaut de cache, celle-ci reste inutilisée, et son contenu est destiné à être perdu une fois le défaut de cache résolu. Ils se sont dit que, plutôt que d'utiliser des MSHR séparés, il vaudrait mieux utiliser la ligne de cache pour stocker les informations sur les défauts de cache en attente dans cette ligne de cache. Pour éviter tout problème, il faut rajouter un bit dans les bits de contrôle de la ligne de cache, qui sert à indiquer que la ligne de cache est occupée : un défaut de cache a eu lieu dans cette ligne, et elle stocke donc des informations pour résoudre les défauts de cache. ==L'entrelacement mémoire== Nous venons de voir comment une mémoire cache peut gérer plusieurs accès mémoire simultanés. Il se trouve que ce genre d'optimisation dépasse largement le cadre du cache. Les mémoires RAM ne sont pas en reste, elles aussi ! Il est possible de pipeliner l'accès à une mémoire RAM, en utilisant des techniques dites d''''entrelacement'''. Précisons cependant que cette optimisation n'est pas utilisée sur les mémoires SDRAM ou DDR modernes, du moins pas sans modifications majeures. Mais divers ordinateurs assez anciens, dont des superordinateurs, ont utilisé cette technique. La technique demande d'utiliser plusieurs mémoires séparées. Sur les anciens ordinateurs, les mémoires en question sont des chips mémoire, à savoir des circuits intégrés de DRAM. Pour faire plus général, nous allons utiliser le terme de '''banques''', ou encore de bancs mémoire. La différence est qu'il existe des mémoires multi-banque, qui regroupent plusieurs banques indépendantes dans un seul boitier, dans un seul chip mémoire. En clair, de telles mémoires regroupent plusieurs sous-mémoires dans un seul circuit intégré. Il est aussi possible d'utiliser plusieurs chips mémoire séparés, chacun ayant une banque. Reste à combiner ces banques pour former un pseudo-pipeline. ===Utiliser plusieurs banques sans entrelacement=== Sans optimisation particulière, les adresses sont réparties dans les banques comme indiqué ci-dessous. Il s'agit de ce qui s'appelle un arrangement horizontal, et nous avions vu celui-ci dans le chapiutre sur les mémoires SDRAM. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Avec cet arrangement, les bits de poids fort de l'adresse sont utilisées pour sélectionner la banque adéquate, et le reste de l'adresse est envoyé sur le bus d'adresse. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Adresse de banque !! Adresse dans la banque |- | Quelques bits de poids fort || Reste de l'adresse |} L'entrelacement change cette répartition, afin d'accélérer les accès mémoire. L'entrelacement classique vise à améliorer les accès à des adresses mémoires consécutives, mais des formes plus évoluées d'entrelacement visent à optimiser des accès mémoire arbitraires. Voyons-les dans l'ordre, du plus simple au plus complexe. ===L'entrelacement classique=== Avec l''''entrelacement classique''', des adresses consécutives sont réparties dans des banques consécutives. Précisons que cette attribution des adresses n'implique absolument pas la mémoire virtuelle ou n'importe quel mécanisme dans le processeur. La répartition décide que telle adresse mémoire va dans telle banque, à telle adresse dans la banque. Elle est donc le fait du contrôleur mémoire, donc en dehors du processeur (les contrôleurs mémoires n’étaient pas encore intégrés dans le processeur à l'époque). [[File:Répartition des adresses dans une mémoire interleaved.png|centre|vignette|upright=1.5|Répartition des adresses dans une mémoire interleaved.]] L'entrelacement simple permet d'accélérer les accès mémoire qui se font à des adresses consécutives. Avec l'entrelacement, chaque accès mémoire tombe dans une banque mémoire différente, ce qui fait qu'on peut démarrer un nouvel accès mémoire à chaque cycle d'horloge. Pendant qu'une banque est occupée par un accès mémoire, on démarre le suivant dans une autre banque, et ainsi de suite. Le tout se termine soit quand on a épuisé toutes les banques libres, soit quand l'accès en rafales se termine. Pas besoin d'attendre que la mémoire ait fini sa lecture/écriture avant de démarrer la lecture/écriture suivante. [[File:Pipemining mémoire.png|centre|vignette|upright=2.5|Pipelining mémoire]] L'animation suivante illustre bien le processus, avec quatres banques. [[File:Interleaving.gif|centre|vignette|upright=2.5|Pipelining mémoire]] L'entrelacement simple utilise les bits de poids faible pour sélectionner la banque, et les bits de poids fort pour la case mémoire. [[File:Adresse mémoire d'une mémoire entrelacée.png|centre|vignette|upright=2|Adresse mémoire d'une mémoire entrelacée]] Précisons que le temps d'accès mémoire ne change pas beaucoup avec l'entrelacement. Par contre, on peut faire plus d'accès mémoire simultanés. On peut démarrer un accès mémoire par cycle d'horloge, mais l'accès en lui-même prend plusieurs cycles. Les mémoires à entrelacement ont donc un débit supérieur aux mémoires qui ne l'utilisent pas. L'entrelacement classique pourrait être utilisé pour accélérer les transferts entre mémoire RAM et cache, qui se font en blocs d'adresses consécutives. Mais de nos jours, il n'est pas utilisé dans cette optique. Il faut dire qu'il est redondant avec le mode rafale des mémoires DRAM. Les deux font globalement la même chose et on voit mal comment utiliser les deux en même temps. Le mode rafale étant plus simple à implémenter, et plus léger niveau utilisation du bus de commande mémoire, il est préféré à l'entrelacement. Par contre, l'entrelacement a eu son heure de gloire sur d'anciens processeurs qui n'avaient pas de mémoire cache, alors qu'ils disposaient d'un pipeline. L'exemple typique est celui des nombreux ''processeurs vectoriels'', que nous n'avons pas encore abordé à ce stade du cours. De tels processeurs avaient un pipeline, une unité de calcul fortement pipelinée, et parfois même de l'exécution dans le désordre et du renommage de registres ! ===L'entrelacement par décalage=== L'entrelacement simple est très utile pour les accès en rafle ou équivalents. Par contre, il existe de rares situations où il n'est pas optimal. Une de ces situations est celle des accès en enjambée (en ''stride''), où l'on accède en série à des adresses sont séparées par N mots mémoires. [[File:Accès par enjambées.png|centre|vignette|upright=2|Accès par enjambées.]] De tels accès surviennent quand un logiciel accède à des structures de données spécifiques, à savoir des tableaux de structures, des matrices, ou d'autres structures de données dans le genre. Un cas classique est celui du parcours d'une matrice colonne par colonne. Les matrices de nombres sont mémorisées ligne par ligne, ce qui fait que parcourir une colonne demande de passer d'une ligne à la suivante et de faire des grands sauts en mémoire RAM, mais tous espacés par la même distance. Avec l'entrelacement simple, les accès en enjambée sont moins performants que les accès à des adresses consécutives, mais cela ne fonctionne pas trop mal. Le pire cas est celui où l'on a N banques et où les données sont justement placées toutes les N adresses. Des accès consécutifs vont tous tomber dans la même banque, on ne peut plus accéder à des banques différentes en parallèle. Mais en dehors de ce cas, on voit une amélioration par rapport à la situation sans entrelacement. Pour obtenir des performances maximales pour les accès en enjambées, il faut répartir les mots mémoires dans la mémoire autrement. L'organisation idéale est la suivante. : Dans les explications qui vont suivre, la variable N représente le nombre de banques, qui sont numérotées de 0 à N-1. On commence par organiser les N premières adresses comme une mémoire entrelacée simple : l'adresse 0 correspond à la banque 0, l'adresse 1 à la banque 1, etc. Pour le bloc suivant, on décale tout d'une adresse, à savoir qu'on commence à remplir les banques à partir de la seconde, non de la première. Une fois la fin du bloc atteinte, on finit de remplir le bloc en repartant du début du bloc. Le troisième bloc d'adresse subit le même traitement, sauf qu'on commence à remplir à partir de la seconde banque. Et on poursuit l’assignation des adresses en décalant d'un cran en plus à chaque bloc. Ainsi, chaque bloc verra ses adresses décalées d'un cran en plus comparé au bloc précédent. Si jamais le décalage dépasse la fin d'un bloc, alors on reprend au début. [[File:Mémoire interleaved par décalage.png|centre|vignette|upright=1.5|Mémoire entrelacée par décalage.]] En faisant cela, on remarque que les banques situées à N adresses d'intervalle sont différentes. Dans l'exemple du dessus, nous avons ajouté un décalage de 1 à chaque nouveau bloc à remplir. Mais on aurait tout aussi bien pu prendre un décalage de 2, 3, etc. Dans tous les cas, on obtient un entrelacement par décalage. Ce décalage est appelé le pas d'entrelacement, noté P. Le calcul de l'adresse à envoyer à la banque, ainsi que la banque à sélectionner se fait en utilisant les formules suivantes : * adresse à envoyer à la banque = adresse totale / N ; * numéro de la banque = (adresse + décalage) modulo N ; * décalage = (adresse totale * P) mod N. Avec cet entrelacement par décalage, on peut prouver que la bande passante maximale est atteinte si le nombre de banques est un nombre premier. Seulement, utiliser un nombre de banques premier peut créer des trous dans la mémoire, des mots mémoires inadressables. Malgré ce défaut, la technique a été utilisée sur quelques ordinateurs, avec l'exemple notable du superordinateur ''Burroughs Scientific Processor''. Pour éviter cela, il y a plusieurs solutions. Par exemple, on peut faire en sorte que N et la taille d'une banque soient premiers entre eux : ils ne doivent pas avoir de diviseur commun. Mais en pratique, elles n'ont pas vraiment été implémentées dans une vraie machine, et sont restées à l'état de recherche, aussi je les passe sous silence. ===L'entrelacement pseudo-aléatoire=== Une dernière méthode de répartition consiste à répartir les adresses dans les banques de manière "pseudo-aléatoire". L'idée exacte est de faire passer l'adresse dans une '''fonction de hachage''', plus ou moins complexe. Pour rappel, une fonction de hachage prend une entrée de grande taille et fournit en sortie un résultat de petite taille. Idéalement, elles doivent donner des résultats aussi différents que possible pour des entrées similaires, histoire de simuler une sorte de pseudo-aléatoire. Ici, une adresse est transformée en un numéro de banque. Une fonction de hachage simple ne fait que permuter des bits de l'adresse pour obtenir son résultat. Elle ne fait qu'échanger des bits de place avant de couper l'adresse en deux morceaux : un pour la sélection de la banque, et un autre pour la sélection de l'adresse dans la banque. Cette permutation est fixe, et ne change pas suivant l'adresse. Des fonctions de hachage plus complexes font des XOR bit à bit entre certains bits de l'adresse. ==Les contrôleurs mémoires SDRAM/DDR optimisés== Après avoir vu le cas des mémoires RAM, nous allons nous concentrer sur le cas particulier des mémoires SDRAM. Les mémoires SDRAM et DDR modernes sont capables de gérer plusieurs accès simultanés à la mémoire RAM, et leurs contrôleurs mémoire en font tout autant. C'est une différence majeure avec les mémoires asynchrones FPM/EDO, qui n'acceptaient qu'un seul accès mémoire à la fois. Leur contrôleur mémoire n'acceptait qu'un seul accès mémoire à la fois, c'était un contrôleur mémoire bloquant. Les contrôleurs mémoires des SDRAM sont eux non-bloquants et peuvent encaisser une dizaine d'accès mémoire à la fois. Peu de choses sont connues sur les contrôleurs de SDRAM/DDR modernes, les fabricants ne donnant que peu de détails dessus. Les rares simulateurs qui tentent de décrire leur fonctionnement, comme DRAM SIM I et II, sont particulièrement simples et ne vont pas dans le détail. Néanmoins, le peu qu'on sait est tout de même instructif. ===La mise en attente des accès mémoire=== Un contrôleur de SDRAM peut accepter plusieurs accès mémoire en même temps. Mais cela ne signifie pas que le controleur sera pr^è à les traiter immédiatement. Pour éviter tout problème de disponibilité, le contrôleur met en attente les accès mémoire que le processeur lui envoie, pour les exécuter dès que possible. Les accès mémoire sont mis en attente dans une mémoire FIFO, histoire de les exécuter dans leur ordre d'arrivée, notamment pour renvoyer les lectures dans l'ordre demandé. Il y a aussi une mémoire FIFO pour les données à écrire et une FIFO pour les données lues. Cette dernière sert au cas où le cache ne soit pas disponible quand on lui envoie la donnée lue. [[File:Module d'interface avec la mémoire.png|centre|vignette|upright=2.5|Contrôleur mémoire avec mise en attente des requêtes processeur et autres optimisations.]] Des optimisations sont possibles dès la mémoire FIFO. Par exemple, on peut regrouper plusieurs accès à des données consécutives en un seul accès en rafale. Le contrôleur mémoire analyse les requêtes mises en attente et détecte si certaines se font à des adresses consécutives. Si c'est le cas, il fusionne ces requêtes en une lecture/écriture en rafale. Une telle optimisation est appelée la '''combinaison de lecture''' pour les lectures, et la '''combinaison d'écriture''' pour les écritures. ===Le pipeline des mémoires SDRAM=== Les SDRAM ont une forme de pseudo-pipeline très limité. Elles sont parfois capables de démarrer une commande avant que la précédente soit terminée. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | || bgcolor="#A0FFFF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || || || bgcolor="#FFA0FF" | READ (2) || || || || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 | bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 | |} Même si elle parait très limitée, cette possibilité est décuplée par la présence de plusieurs banques dans la mémoire SDRAM. Les chips mémoires de SDRAM sont composés de plusieurs banques, chacune étant une sorte de mini-mémoire miniature. Chaque banque a son propre décodeur, son propre tampon de ligne, ses propres multiplexeurs de colonne, sa logique de rafraichissement mémoire, etc. C'est comme si une SDRAM regroupait plusieurs mémoires séparées dans un même circuit intégré. Et cela permet de faire plusieurs accès mémoire en même temps, dans des banques différentes. Il est possible de lancer un accès mémoire dans deux banques en même temps, par exemple. Ou encore, on peut lancer un nouvel accès mémoire dans une banque si elle est inoccupée. La possibilité améliore grandement la performance de la SDRAM. Mais nous en reparlerons dans un chapitre ultérieur. {|class="wikitable" |+ Pipelining basique sur les SDRAM |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 |- ! Banque Numéro 1 | || || || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || || || |- ! Banque Numéro 2 | || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || |- ! Banque Numéro 3 | || || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || |} L'implémentation n'a rien de vraiment compliqué. Il suffit d'utiliser une mémoire FIFO par banque. Ainsi, les accès dans une même banque se feront en série, dans l'ordre d'arrivée, mais des accès à des banques différentes se feront dans le désordre. Cela ne pose pas de problème car les accès se font à des adresses différentes, ce qui fait qu'il n'y a pas de conflits majeurs. [[File:Gestion parallèle des banques.png|centre|vignette|upright=2.5|Gestion parallèle des banques.]] ===Le ré-ordonnancement des commandes mémoires=== Un contrôleur mémoire moderne est capable de changer l'ordre des requêtes mémoires, pour gagner en performances. En clair : il peut faire l'équivalent mémoire de l'exécution dans le désordre des processeurs haute performance. Une différence majeure est que le contrôleur mémoire ne remet pas les accès mémoire dans l'ordre avant de les envoyer au processeur. C'est le processeur qui remet en ordre les accès mémoire, dans sa ''load queue''. Pour cela, les requêtes mémoire sont "numérotées", à savoir qu'on leur attribue un identifiant binaire. Le contrôleur mémoire exécute les lectures/écritures dans le désordre, mais garde la trace de leur identifiant. Il sait dans quel ordre il exécute les accès mémoire et connait leur latence. Ce qui fait qu'il sait que la donnée lue à tel instant est associée à la requête mémoire numéro X. Pour les lectures, il renvoie la donnée lue avec l'identifiant. Le processeur sait alors à quelle lecture la donnée lue correspond et il se débrouille en interne pour gérer la situation. En clair : le processeur voit les accès mémoire dans le désordre, c'est lui qui les remet en ordre. Maintenant que ces précisions sont faites, posons cette question : quel est l'intérêt de faire les accès mémoire dans le désordre ? Accéder à des banques en parallèle est une réponse possible, mais le ré-ordonnancement est surtout utile si tous les accès mémoire atterrissent dans la même banque. Pour comprendre pourquoi, faisons un rappel rapide sur le tampon de ligne. Les mémoires SDRAM sont des mémoires à tampon de ligne. Elles font un accès mémoire en deux étapes. La première étape recopie une ligne de N * 64 bits dans un tampon interne à la SDRAM. La seconde étape sélectionne une colonne, à savoir une donnée de 64 bits, qui est soit envoyée sur le bus de données pour une lecture, soit modifiée par une écriture. [[File:Mémoire à tampon de ligne.png|centre|vignette|upright=2|Mémoire à tampon de ligne]] Les deux étapes correspondent à deux commandes séparées : une commande ACT qui précise l'adresse de la ligne, et une commande READ ou WRITE qui précise l'adresse de la colonne. [[File:Signaux RAS et CAS.png|centre|vignette|upright=2|Signaux RAS et CAS.]] Les SDRAM permettent de se passer de la première étape si des accès consécutifs se font dans la même ligne. Une fois activée, la ligne reste ouverte et on peut accéder plusieurs fois de suite dedans. On a alors juste à préciser l'adresse de colonne dedans. [[File:Sélection d'une ligne sur une mémoire FPM ou EDO.png|centre|vignette|upright=2|Sélection d'une ligne sur une mémoire FPM ou EDO.]] L'exécution dans le désordre des lectures/écritures SDRAM vise à faire en sorte que des accès consécutifs se fassent dans une même ligne. Elle marche si plusieurs accès à une même ligne ne sont pas consécutifs, et qu'il y a des accès entre les deux. Après ré-ordonnancement, ces accès mémoire deviennent consécutifs, ils sont exécutés l'un à la suite de l'autre. Pour rendre le tout plus concret, voici un exemple. Imaginez que l'on sait les 3 accès mémoire suivants : * Une lecture ligne A ; * Une écriture ligne B ; * Une lecture ligne A. Sans réordonnancent, on doit émettre deux commandes PRECHARGE : une quand on passe de la ligne A à la ligne B, et une autre pour le passage inverse. pour éviter cela, le contrôleur mémoire peut retarder l'écriture, ou au contraire avancer la seconde lecture. le résultat est alors le suivant : * Lecture ligne A ; * Lecture ligne A ; * Une écriture ligne B. On a donc deux accès consécutifs à la même ligne, suivi par une écriture dans une autre ligne. La technique marche parce que l'on a un mix adéquat de lectures et d'écritures. Mais il existe des possibilités de réorganisation autres qui ne sont pas valides. Il faut par exemple prendre garde à éviter d'intervertir lectures et écritures à une même adresse, sous peine de problèmes. Encore une fois, il faut faire attention aux dépendances mémoires. Le controleur mémoire teste les dépendances mémoires avant d'envoyer des commandes à la mémoire DDR/SDRAM. ==L'entrelacement sur les mémoires SDRAM== L''''entrelacement''' fonctionne sur les mémoires SDRAM, mais il doit être fortement modifié. Concrètement, tout ce qui a étré dit plus est inapplicable pour les SDRAM, du fait de la présence du tampon de ligne. Des adresses consécutives atterrissent déjà dans la même ligne, ce qui fait qu'on n'a pas besoin d'entrelacement au niveau d'une ligne. Et ne parlons pas de ce la présence de rangées et de canaux mémoire ! Cependant, il peut y avoir un entrelacement lié à la présence des banques SDRAM. L'entrelacement est géré juste avant le séquenceur mémoire. Le '''circuit d'entrelacement''' intervertit certains bits de l'adresse lors des accès mémoires. On trouve aussi des mémoires FIFOs pour mettre en attente les requêtes mémoire provenant du processeur. Elles permettent de recevoir plusieurs accès mémoire consécutifs. Si vous vous demandez comment plusieurs accès mémoire consécutifs peuvent avoir lieu, sachez que c'est une bonne question. Pour le moment, nous allons dire que ces accès proviennent du processeur ou de la mémoire cache, sans expliquer comment. Le contrôleur mémoire reçoit une série d'accès mémoire assez rapprochés, qu'il doit exécuter au mieux. [[File:Controleur mémoire d'une SDRAM avec entrelacement.png|centre|vignette|upright=2.5|Contrôleur mémoire d'une SDRAM avec entrelacement.]] Pour gérer l'entrelacement, le contrôleur de SDRAM prend en entrée l'adresse envoyée par le processeur, et la découpe en plusieurs champs : un pour sélectionner la banque adéquate, un autre pour la ligne, un autre pour la colonne, un autre pour la rangée. Une solution simple désactive l'entrelacement. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de banque || Adresse de ligne || Adresse de colonne |} Si cette solution fonctionne bien si les accès mémoire sont assez aléatoires, en pratique, mieux vaut utiliser l'entrelacement. ===L'entrelacement de banques/lignes=== Une première méthode d'entrelacement est l''''entrelacement de banques'''. Elle répartit deux lignes consécutives dans deux banques différentes. Pour comprendre l'idée, prenons un exemple. Imaginons une mémoire avec deux banques et 4 lignes. Imaginons qu'on parcoure/balaye la mémoire RAM en partant des adresses basses. Sans entrelacement de ligne, les accès se feront comme suit, de gauche à droite : {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Banque numéro 1 !! colspan="4" | Banque numéro 2 |- | Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 || Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 |} Avec l'entrelacement de banques, les accès se feront comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 |- | Ligne 1 || Ligne 1 || Ligne 2 || Ligne 2 || Ligne 3 || Ligne 3 || Ligne 4 || Ligne 4 |} Avec N banques, la première ligne ira dans la première banque, la seconde ligne dans la seconde, et ainsi de suite jusqu'à la énième banque qui va dans la banque numéro N. Et la ligne N+1 repart dans la première banque. L'avantage est que passer d'une ligne à la suivante est assez rapide. Au lieu de devoir fermer la première ligne et ouvrir la suivante on ouvrira directement la ligne suivante dans une banque différente, ce qui sera plus rapide. La commande PRECHARGE pourra être envoyée à la seconde banque en avance. Pour cela, l'adresse est découpée comme suit. {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne (précise à l'octet) |} ===L'entrelacement de rangées=== Il est aussi possible de faire la même chose, mais avec les rangées. Pour simplifier fortement, une rangée est simplement un chip mémoire. En réalité, les rangées ne sont pas des chip mémoire, mais un ensemble de chips mémoire regroupés ensemble histoire d'atteindre les 64 bits du bus de données. Par exemple, une rangée peut combiner 8 chips mémoire avec un bus de données de 8 bits chacun pour obtenir les 64 bits du bus de données d'une SDRAM. Mais nous passons ce détail sous silence dans les explications qui vont suivre, par souci de simplification. Pour faire comprendre l'entrelacement de rangée, le mieux est d'utiliser un exemple, le même que précédemment. Sans entrelacement de rangée, on change de chip mémoire une fois qu'on a balayé toutes ses adresses. {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Rangée numéro 1 !! colspan="4" | Rangée numéro 2 |- | Banque 1 || Banque 2 || Banque 3 || Banque 4 || Banque 1 || Banque 2 || Banque 3 || Banque 4 |} Avec l'entrelacement de ligne, on change de rangée dès qu'on change de banque/ligne. {|class="wikitable" |+ Adresse mémoire |- ! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 |- | Banque 1 || Banque 1 || Banque 2 || Banque 2 || Banque 3 || Banque 3 || Banque 4 || Banque 4 |} En clair, quand on a épuisé toutes les banques dans une même rangée, on passe à la rangée suivante au lieu de rester dans la même rangée. Notons que dans l'exemple précédent, on a combiné l'entrelacement de rangée et de banque, mais on aurait pu utiliser l'entrelacement de rangée seul. Mais ce n'est pas le cas le plus courant en pratique. Toujours est-il qu'en combinant entrelacement de rangée et de ligne, le découpage de l'adresse est le suivant : {|class="wikitable" |+ Adresse mémoire |- | Adresse de ligne || Numéro de rangée || Adresse de banque || Adresse de colonne (précise à l'octet) |} D'autres méthodes d'entrelacement sont possibles, mais elles sont plus complexes. Chaque contrôleur mémoire fait un peu à sa sauce. Ils permettent en général de choisir entre plusieurs entrelacements. Au minimum, ils permettent de désactiver l'entrelacement, d'activer un entrelacement de ligne, et d'autres entrelacements plus complexes, dont certains sont propriétaires et tenus secrets par le fabricant. ===L'entrelacement avec le ''dual channel''=== [[File:Dual channel slots.jpg|vignette|Slots mémoires avec ''dual channel''.]] L'usage du ''dual channel'' complique encore l'entrelacement. Là encore, il y a deux grandes solutions : avec et sans entrelacement des canaux mémoire. Rappelons le principe : deux barrettes de RAM sont accédées en parallèle. Et pour cela, il faut utiliser l'entrelacement. Typiquement, chaque barrette mémoire fournit 64 bits, ce qui fait que l'on peut accéder à 128 bits d'un seul coup, par exemple avec un accès en rafale. Sans ''dual channel'', la première barrette correspond à la moitié haute de la RAM, la seconde barrette correspond à la moitié basse. Avec ''dual channel'', une forme spécifique d'entrelacement est activée. Concrétement, deux blocs de 64 bits sont placés dans des canaux mémoire séparés. Idem avec du triple ou quadruple canal, mais c'est alors trois ou quatre blocs de 64 bits qui sont dispersés dans des canaux consécutifs. Le découpage de l'adresse est alors le suivant : {|class="wikitable" |+ Adresse mémoire |- ! colspan="6" | Sans entrelacement |- | Numéro de canal/barrette || Reste de l'adresse || Position de l'octet dans un bloc de 64 bits |- | colspan="6" | |- ! colspan="6" | Avec entrelacement |- | Reste de l'adresse || Numéro de canal/barrette || Position de l'octet dans un bloc de 64 bits |} [[File:Décodage d'adresse avec dual channel.png|centre|vignette|upright=2|Décodage d'adresse avec dual channel.]] Les contrôleurs de SDRAM précédents sont assez basiques et ne représentent pas les contrôleurs les plus évolués. Ils étaient utilisés sur les anciens PC, à une époque où ils étaient encore sur la carte mère du processeur. Mais de nos jours, le contrôleur mémoire est intégré au processeur. Et il incorpore de nombreuses optimisations afin de gagner en performances. Malheureusement, ces optimisations sont elles-mêmes dépendantes des optimisations intégrées dans le processeur, ce qui fait qu'on ne peut pas en parler ici. Elles demandent que le contrôleur mémoire reçoive plusieurs accès mémoire simultanés, ce qui n'a aucun sens à ce stade du cours. Aussi, nous allons les passer sous silence pour le moment. Nous les verrons dans le chapitre sur le parallélisme mémoire. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=La désambiguïsation mémoire | prevText=La désambiguïsation mémoire | next=Les processeurs superscalaires | nextText=Les processeurs superscalaires }} </noinclude> m48lfky8fb0f8523fgctedle6z4sj1h 764707 764706 2026-04-23T21:06:18Z Mewtow 31375 /* Le pipeline des mémoires SDRAM */ 764707 wikitext text/x-wiki Dans ce chapitre, nous allons voir les techniques qui permettent de gérer plusieurs accès mémoire simultanés directement au niveau du cache ou de la mémoire RAM. Par plusieurs accès mémoire simultanés, vous pensez sans doute à l'usage de cache multiports, voire de mémoires RAM multiport. C'est en effet une solution, mais ce n'est clairement pas la seule. Par exemple, il est possible de pipeliner l'accès au cache, voire à la mémoire RAM. Il existe de nombreuses techniques de '''parallélisme mémoire''', et nous allons les voir dans ce chapitre. Pour donner un exemple, il est possible de pipeliner les accès mémoire. Il est possible de pipeliner l'accès au cache et/ou l'accès à la mémoire, et nous verrons comment faire dans ce chapitre. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, qu'ils aient ou non un pipeline dynamique. Par contre, pipeliner la mémoire est une technique ancienne, aujourd'hui peu utilisée. Elle était utilisée sur des très vieux ordinateurs, qui avaient un pipeline mais pas de mémoire cache. Mais de nos jours, les mémoires SDRAM et DDR ne sont pas adaptées pour ça, il est très difficile de les pipeliner correctement. Le parallélisme mémoire est utile aussi bien sur des processeurs à émission dans l'ordre que dans le désordre. Par exemple, un processeur ''in-order'' avec des lectures non-bloquantes peut e, profiter. Il peut techniquement lancer une seconde lecture, après avoir rencontré une première lecture non-bloquante. En général, le nombre de lectures consécutives est cependant limité à moins d'une dizaine. À l'opposé, les processeurs sans lectures non-bloquantes profitent pas du parallélisme mémoire. À la rigueur, ils peuvent en profiter s'ils intègrent des techniques de préchargement. Pendant que le processeur exécute une lecture/écriture, il peut précharger une autre donnée en parallèle. Inutile de dire que sans pipeline processeur, le parallélisme mémoire ne sert pas à grand-chose. ==Le ''line fill buffer'' et les techniques associées== Lors d'un défaut de cache, le processeur doit attendre que toute la ligne de cache soit chargée avant d'être utilisable. Or, la taille d'une ligne de cache est supérieure à la largeur du bus mémoire, ce qui fait qu'une ligne de cache est chargée en plusieurs fois, morceaux par morceaux, mot mémoire par mot mémoire. Le chargement peut se faire directement dans le cache, mais ce n'est pas une solution très pratique. À la place, beaucoup de processeurs ajoutent une mémoire tampon entre la RAM et le cache, appelée le '''tampon de remplissage de ligne''' (''line-fill buffer'') dans les processeurs modernes. Lors d'un défaut de cache, le processeur charge la donnée de la RAM dans le tampon de remplissage de ligne, mot mémoire par mot mémoire. Une fois plein, le tampon de remplissage de ligne est recopié dans la ligne de cache. Le tampon de remplissage de ligne contient une ligne de cache, si ce n'est que certains bits de contrôle ne sont pas présents. Le tampon de remplissage de ligne contient un bit de validité pour chaque mot mémoire de la ligne de cache, qui indiquent si le mot mémoire a été chargé. Par exemple, prenons un processeur 64 bits, qui gère donc des mots mémoire de 8 octets, avec des lignes de cache 256 octets/32 mots mémoire. Le tampon de remplissage de ligne contiendra 32 bits de validité. Si le processeur a chargé les 6 premiers mots mémoire, les 6 premiers bits de validité seront mis à 1, les autres seront encore à zéro. Il faut noter que la disponibilité de la ligne complète se détermine assez facilement en faisant un ET logique entre tous les bits de validité. Le processeur sait ainsi quand la ligne de cache est disponible entièrement et donc quand la transférer dans le cache. La même chose existe avec une hiérarchie de cache, sauf que l'on trouve une mémoire tampon entre chaque niveau de cache. Il y a un tampon de remplissage de ligne entre le cache L1 et le cache L2, entre le cache L2 et le L3, etc. Si le cache ne gère qu'un seul défaut de cache à la fois, le ''fill line buffer'' est une mémoire très simple, qui ne mémorise qu'une seule ligne de cache. Et cela vaut aussi bien pour un cache bloquant que non-bloquant. Mais sur les caches capables de gérer plusieurs défauts simultanés, le tampon de remplissage de ligne est une mémoire de type FIFO ou LIFO, capable de mémoriser plusieurs lignes de cache. Notons que le tampon de remplissage de ligne est très utile pour implémenter certaines techniques, par exemple le contournement du cache. Nous avions vu dans le chapitre sur le cache que certains accès mémoire doivent contourner le cache, pour des raisons de cohérence des caches. C'est notamment nécessaire pour accéder aux périphériques, mais c'est aussi utile pour des raisons de performances dans des cas très spécifiques. Les accès qui contournent le cache se font directement dans le tampon de remplissage de ligne : le processeur écrit ou lit les données depuis ce tampon de remplissage de ligne, sans accéder au cache. ===L'''early restart'' et le ''critical word load''=== La présence du ''line buffer'' permet une optimisation assez intéressante, qui permet de réduire la latence des défauts de cache. L'optimisation consiste à lire un mot mémoire dans le tampon de remplissage de ligne, même si la ligne de cache complète n'a pas encore été chargée. Il existe deux manières de faire cela, qui portent les noms d'''early restart'' et de ''critical word load''. La première est la version la plus simple, la seconde est plus complexe mais plus performante. Avec la technique d''''''early restart''''', la ligne de cache est chargée normalement, en partant de son premier mot mémoire. Dès que le mot mémoire lu/écrit par le processeur est copié dans le ''line fill buffer'', il est envoyé au processeur immédiatement. Illustrons le tout par un exemple, où une ligne de cache fait 16 mots mémoire. Le processeur effectue une lecture, qui lit le 5ème mot mémoire. Sans ''early restart'', le processeur doit charger les 16 mots mémoire avant de faire la lecture dans le cache. Avec ''early restart'', le processeur reçoit la donnée dès que le 5ème mot mémoire est disponible. Le processeur doit attendre que les 4 premiers mots mémoire soient chargés, puis le 5ème mot mémoire arrive et est envoyé directement au processeur, il est lu directement depuis le ''line fill buffer''. Les 11 mots mémoire suivants sont ensuite chargés dans le cache pendant que le processeur fait des calculs dans son coin. Le ''critical word load'' est une optimisation de la technique précédente où le chargement de la ligne de cache commence directement à la donnée demandée par le processeur. Pour reprendre l'exemple précédent, où le processeur demande le 5ème mot mémoire sur 16, le ''critical word load'' charge le 5ème mot mémoire en premier et l'envoie au processeur, ce qui fait qu'il est chargé très rapidement. Pas besoin d'attendre que le processeur charge les 4 mots mémoire précédents comme avec l'''early restart''. Dans le détail, le processeur charge le 5ème mot mémoire en premier, puis charge les 11 suivants, et termine par les 4 mots mémoire du début. En clair, le ''critical word load'' commence par charger le mot mémoire lu, puis les blocs suivants, avant de revenir au début du bloc pour charger les blocs restants. Ainsi, la donnée demandée par le processeur sera la première disponible. Pour cela, l'organisation du tampon de remplissage de ligne est modifiée de manière à rendre cela possible. Il a une taille égale à une ligne de cache complète, qui contient elle-même plusieurs mots mémoire. Dans le ''line-fill buffer'', chaque mot mémoire est stocké avec un tag, qui indique l'adresse du mot mémoire stocké dans le ''line-fill buffer''. Le ''line-fill buffer'' est donc un cache un peu particulier, qui fonctionne comme un cache du point de vue du processeur, comme une mémoire FIFO pour les transferts avec le cache. Ainsi, un processeur qui veut lire dans le cache après un défaut peut accéder à la donnée directement depuis le tampon de remplissage de ligne, alors que la ligne de cache n'a pas encore été totalement recopiée en mémoire. ==Les caches pipelinés== Il est possible de pipeliner l'accès au cache, ce qui demande juste de rajouter des registres au bon endroits et quelques circuits de contrôle. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, même ceux avec un pipeline dynamique. Et l'implémentation peut se faire de plusieurs manières différentes. Il faut dire que découper une mémoire cache en plusieurs étapes peut se faire de plusieurs manière. Commençons par la plus simple. Beaucoup de processeurs des années 2000 avaient une fréquence peu élevée comparée aux standards d'aujourd'hui, ce qui fait que leur cache avait bien un temps d'accès d'un cycle d'horloge. Mais l'accès au cache se faisait en deux cycles d'horloge : un cycle pour calculer une adresse et un cycle pour l'accès mémoire proprement dit. Et ces deux étapes étaient pipelinées, à savoir que deux micro-opérations mémoire peuvent s'exécuter en même temps : une dans l'étage de calcul d'adresse, une autre dans le cache. L'unité mémoire était donc pipelinée, alors que l'accès au cache ne l'était pas. Les processeurs qui implémentaient cette technique regroupent les micro-architectures K5 et K6 d'AMD, les processeurs Intel de micro-architecture P6 (Pentium 2 et 3) et quelques autres. Il est aussi possible de pipeliner l'accès au cache lui-même. Avec un cache pipeliné, l'accès au cache ne se fait pas en un seul cycle, mais en plusieurs. Cependant, on peut lancer un nouvel accès au cache à chaque cycle d'horloge, comme un processeur pipeliné lance une nouvelle instruction à chaque cycle. L'implémentation est assez simple : il suffit d'ajouter des registres dans le cache. Pour cela, on profite que le cache est composé de plusieurs composants séparés, qui échangent des données dans un ordre bien précis, d'un composant à un autre. De plus, le trajet des informations dans un cache est linéaire, ce qui les rend parfois pour l'usage d'un pipeline. ===Le ''pipelining'' des caches ''direct-mapped''=== Voici ce qui se passe avec un cache directement adressé. Pour rappel ce genre de cache est conçu en combinant une mémoire RAM, généralement une SRAM, avec quelques circuits de comparaison et un MUX. L'accès au cache se fait globalement en deux étapes : la première lit la donnée et le ''tag'' dans la SRAM, et on utilise les deux dans une seconde étape. Nous avions vu il y a quelques chapitre comment pipeliner des mémoires, dans le chapitre sur les mémoires évoluées. Et bien ces méthodes peuvent s'utiliser pour la mémoire RAM interne au cache !L'idée est d'insérer un registre entre la sortie de la RAM et la suite du cache, pour en faire un cache pipéliné, à deux étages. Le premier étage lit dans la SRAM, le second fait le reste. L'implémentation sur les caches associatifs à plusieurs voies est globalement la même à quelques détails près. [[File:Cache directement adressé pipeliné - deux étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - deux étages]] Avec le circuit précédent, il est possible d'aller plus loin, cette fois en pipelinant l'accès à la mémoire RAM interne au cache. Pour comprendre comment, rappelons qu'une mémoire SRAM est composée d'un plan mémoire et d'un décodeur. L'accès à la mémoire demande d'abord que le décodeur fasse son travail pour sélectionner la case mémoire adéquate, puis ensuite la lecture ou écriture a lieu dans cette case mémoire. L'accès se fait donc en deux étapes successives séparées, on a juste à mettre un registre entre les deux. Il suffit donc de mettre un gros registre entre le décodeur et le plan mémoire. [[File:Cache directement adressé pipeliné - trois étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - trois étages]] Et on peut aller encore plus loin en découpant le décodeur en deux circuits séparés. En effet, rappelez-vous le chapitre sur les circuits de sélection : nous avions vu qu'il est possible de créer des décodeurs en assemblant des décodeurs plus petits, contrôlés par un circuit de prédécodage. Et bien on peut encore une ajouter un registre entre ce circuit de prédécodage et les petits décodeurs. : Théoriquement, toute l'adresse est fournie d'un seul coup au cache, la quasi-totalité des processeurs présentent l'adresse complète à un cache pipeliné. Mais le Pentium 4 fait autrement. Il faut noter que les premiers étages manipulent l'indice dans la SRAM, qui est dans les bits de poids faible, alors que les étages ultérieurs manipulent le ''tag'' qui est dans les bits de poids fort. Les concepteurs du Pentium 4 ont alors décidé de présenter les bits de poids faible lors du premier cycle d'accès au cache, puis ceux de poids fort au second cycle. Pour cela, l'ALU fonctionnait à une fréquence double de celle du processeur, tout comme le cache L1. Il n'y avait pas de pipeline proprement dit, mais cela réduisait grandement la latence d'accès au cache. ===Le ''pipelining'' des caches splittés/sériels=== Il est aussi possible de pipeliner un cache dits splittés, aussi appelés à accès sériel. Pour rappel, les caches à accès sériel vérifient si il y a succès ou défaut de cache, avant d'accéder aux lignes de cache en cas de succès. Ils font donc différemment des autres caches, qui accèdent à une ligne de cache, avant de déterminer s'il y a succès ou défaut en lisant le tag de la ligne de cache. Les caches sériels disposent de deux SRAM : une pour les tags des lignes de cache et une pour les données. Ils accèdent à la SRAM pour les tags, avant d’accéder à la SRAM des données en cas de succès de cache. Vu que l'accès se fait en deux étapes, une vérification des tags suivie de la lecture/écriture des données, il est facile à pipeliner. Pipeliner le cache permet de régler le problème des accès au cache L1, et elle est tout le temps utilisé sur les processeurs modernes. Mais que faire en cas de défaut de cache ? ==Les caches non bloquants== Un '''cache bloquant''' est un cache auquel le processeur ne peut pas accéder pendant un défaut de cache. Il faut attendre que la lecture ou écriture en RAM soit terminée avant de pouvoir utiliser de nouveau le cache. Un '''cache non bloquant''' n'a pas ce problème : on peut l'utiliser pendant un défaut de cache. Les caches non bloquants permettent de démarrer une nouvelle lecture ou écriture alors qu'une autre est en cours, ce qui permet d'exécuter plusieurs lectures ou écritures en même temps. ===Les ''Miss Handling Status Registers''=== Lors d'un défaut de cache, la mémoire RAM est consultée pendant le défaut de cache, mais le cache est inutilisé. Un défaut de cache n'utilise pas le cache, ce n'est qu'un accès en mémoire RAM, sur le bus mémoire (ou un accès aux niveaux de cache inférieurs, peu importe). Le cache en lui-même est laissé libre, rien n’empêche d'y accéder, il est en réalité intrinsèquement non-bloquant. Les caches, bloquants comme non-bloquants, sont en réalité composés d'une mémoire cache proprement dite, entourée de circuits qui servent d'interface entre le processeur et le cache lui-même. Et parmi les circuits tout autour, certains gèrent l'accès au cache lors d'un défaut de cache. Ils sont regroupés sous le terme de '''''Miss Handling Architecture''''' (MHA). La différence entre un cache bloquant et un cache non-bloquant est en réalité liée à l'implémentation de la MHA. Les caches bloquants coupent volontairement l'accès au cache lors d'un défaut, car les défauts sont plus simples à gérer ainsi. Le défaut de cache rapatrie une donnée depuis la RAM, qui sera écrite dans le cache. Et il ne faut pas qu'une tentative d'accès à cette donnée ait lieu avant qu'elle ne soit chargée. Mais ce blocage est général et touche tout le cache, alors que seule une ligne de cache est concernée par le défaut de cache. L'idée derrière un cache non-bloquant est que seule la ligne de cache est bloquée, mais les autres sont accessibles. L'idée est alors de mémoriser les lignes de cache concernées par le défaut de cache, afin d'en bloquer l'accès. À chaque accès, on vérifie si la ligne de cache est déjà réservée par un défaut de cache. La lecture/écriture est alors bloquée si c'est le cas, mais elle accepte les accès sinon. Pour cela, la ''Miss Handling Architecture'' contient des registres qui mémorisent des informations sur les défauts de cache en cours. Ils portent le nom de '''''miss status handling registers''''', que j’appellerais dorénavant MSHR, qui sont aussi appelés des '''''miss buffer'''''. Le contenu des MSHR varie beaucoup suivant le processeur, mais ces derniers stockent au minimum les informations suivantes : * Le numéro de la ligne de cache dans laquelle les données sont chargées. * Un bit de validité qui indique si le MSHR est vide ou pas, qui est mis à 0 quand le défaut de cache est résolu. * Un ou plusieurs champs de lecture/écriture, qui contiennent des informations sur la lecture/écriture. ** Pour une lecture, elle contient des informations sur la destination de la donnée, à savoir qui prévenir quand le défaut de cache est terminé. C'est parfois un nom/numéro de registre (celui dans lequel charger la donnée), mais c'est souvent le numéro de l'entrée dans le ''load/store queue''. ** Pour les écritures, elle contient la donnée à écrire, ou éventuellement un numéro de ''load/store queue'' où se trouve la donnée à écrire. Il faut noter que le bus mémoire ne peut gérer qu'un seul défaut de cache à la fois. Aussi, il est intéressant de regarder ce qui se passe lorsqu'un second défaut de cache survient, pendant qu'un premier est en cours. Dans ce cas, il y a deux réponses qui correspondent à deux types de caches non-bloquants, qui portent les noms barbares de caches de type succès après défaut et défaut après défaut. Sur le premier type, il ne peut pas y avoir plusieurs défauts de cache simultanés. Dès qu'un second défaut de cache survient, le cache stoppe son activité et on ne peut plus démarrer de nouvelle lecture/écriture, tant que le premier défaut de cache n'est pas résolu. Le second type est plus souple et autorise la survenue de plusieurs défauts de cache simultanés. Du moins, jusqu'à une certaine limite, car le cache ne peut supporter qu'un nombre limité d'accès mémoires simultanés (pipelinés). ===Les accès simultanés à une même ligne de cache=== Il arrive que le processeur fasse plusieurs accès mémoire simultanés à la même ligne de cache. Si la ligne de cache en question n'a pas encore été chargée dans le cache, alors on a plusieurs défauts de cache consécutifs pour la même ligne de cache, et le cache non-bloquant doit gérer la situation. Pour la suite, il va falloir faire une petite distinction entre les défauts primaires et secondaires. Imaginons qu'un défaut de cache ait lieu et demande à charger une donnée dans la ligne de cache numéro N. Il s'agit du premier défaut impliquant cette ligne de cache précise, ce qui lui vaut le nom de '''défaut de cache primaire'''. Mais par la suite, d'autres accès mémoire à la même ligne de cache ont lieu, alors que la ligne de cache n'est pas encore disponible. Dans ce cas, il s'agit de '''défauts de cache secondaires'''. Pour l'unité d'accès mémoire, les défauts de cache primaire et secondaire sont différents (ils prennent tous une entrée dans la ''load/store queue''). Mais pour le cache, ils ne correspondent qu'à un seul accès au cache : celui qui demande de charger la ligne de cache demandée. Les défauts de cache primaire et secondaire à la même ligne de cache se voient attribuer un MSHR unique. La gestion des défauts de cache secondaires dépend du cache non-bloquant. La solution la plus simple ne permet pas les défauts de cache secondaires. Le cache ne permet pas deux défauts de cache simultanés pour la même ligne de cache. Les autres solutions le permettent, en fusionnant des accès simultanés à la même ligne de cache en un seul au niveau des MSHR. Dans tous les cas, détecter les défauts de cache secondaires sont un problème qu'il faut détecter. La ''Miss Handling Architecture'' doit détecter les défauts de cache secondaire. Pour cela, elle procède comme suit. Lors de chaque défaut de cache, la MHA récupère le numéro de la ligne de cache associé. Il vérifie alors chaque MSHR pour vérifier s'il contient le numéro en question. S'il n'y a aucune correspondance dans les MSHR, alors c'est signe que le défaut de cache est un défaut primaire. Mais s'il y en a une, alors c'est un défaut secondaire. Évidemment, cela signifie que lors d'un défaut de cache, le numéro de ligne de cache est envoyé à tous les MSHR, pour comparaison. Les MSHR sont donc regroupés dans une mémoire associative, une sorte de mini-cache, faciliter l'implémentation. Lors d'un défaut de cache primaire, l'accès à un cache non-bloquant se fait comme suit : le processeur envoie une adresse au cache, accède à celui-ci, et détecte la survenue d'un défaut de cache. Il en profite alors pour attribuer une ligne de cache dans laquelle sera chargée ce défaut. L'attribution est très simple dans le cas des caches ''direct mapped'', ou associatifs par voie, pour lesquels l'attribution se fait assez simplement. Il mémorise alors cette information dans les MSHR, après avoir vérifié que le défaut de cache n'était pas un défaut secondaire. Lors des accès ultérieurs à une adresse proche, censée être dans la même ligne de cache, le processeur va encore une fois rencontrer un défaut de cache. Il va alors déterminer le numéro de la ligne de cache associée à l'adresse, et comparer ce numéro avec les MSHRs. Si un MSHR contient ce numéro, c'est signe que le défaut de cache est un défaut secondaire. La MHA réagit alors différemment selon le processeur considéré. Une première solution n'autorise pas les défauts de cache secondaires. Si l'un d'entre eux survient, le processeur est gelé par un ''pipeline stall'', une bulle de pipeline. Une autre solution fusionne les défauts de cache secondaires avec le défaut de cache primaire : tout cela ne correspond qu'à un seul défaut de cache pour lui. ===Les MSHR simples=== Un cache non-bloquant à '''MSHR simple''' contient juste plusieurs MSHR qui mémorisent juste un numéro de ligne de cache, un bit de validité, le champ de lecture/écriture, et l'adresse exacte de lecture/écriture. Avec cette organisation, il est possible d'avoir plusieurs défauts de cache séparés, mais à la condition que chaque défaut accède à une ligne de cache différente. Deux accès simultanés à une même ligne de cache ne sont pas possibles, les défauts de cache secondaires ne sont pas autorisés. Ainsi, chaque défaut de cache se voit attribuer son propre MSHR, chacun contient un numéro de ligne de cache différent. Pour comprendre pourquoi c'est impossible de gérer les défauts secondaires, il faut regarder le champ de lecture/écriture. Si on veut effectuer plusieurs écritures consécutives à la même adresse, le MSHR n'aura pas de quoi mémoriser les deux données à écrire. Il pourra mémoriser la première donnée à écrire, pas la seconde. Même chose lors d'une lecture : le champ lecture/écriture peut mémorisr la destination de la première lecture, pas de la seconde. L'avantage est que la MHA n'a besoin que des MSHR et de quelques circuits annexes. Les autres solutions rajoutent des circuits annexes pour gérer les défauts de cache secondaires, qui utilisent beaucoup de circuits. Le cout en circuit est donc élevé, mais le gain en performance est là. Passons maintenant aux caches non-bloquants qui autorisent les défauts de cache secondaire. La solution la plus simple consiste à utiliser ===Les MSHR adressés implicitement=== Avec les '''MSHR adressés implicitement''', il est possible de fusionner plusieurs accès mémoire à une même ligne de cache, mais sous une condition très importante : ces accès lisent/écrivent des mots mémoire différents. Par exemple, imaginons qu'une ligne de cache contienne 8 mots mémoire de 64 bits. Si un premier accès mémoire lit le mot mémoire numéro 7 (dernier mot mémoire de la ligne), et le second accès le mot mémoire numéro 3, alors la fusion est possible. Mais si deux accès mémoire veulent lire/écrire le mot mémoire numéro 7, alors la fusion n'est pas possible et le processeur se bloque, un ''pipeline stall'' survient. Un MSHR adressé implicitement est un MSHR simple, qui contient naturellement un numéro de ligne de cache (tag) et un bit de validité, sauf que le champ de lecture/écriture est dupliqué. Les différents champs lecture/écriture d'un MSHR sont regroupés dans une mémoire RAM/cache qui contient autant d'entrées qu'il y a de mot mémoire dans une ligne de cache. Chaque entrée de la table est associée à un mot mémoire de la ligne de cache et stocke des informations sur celui-ci. Une entrée mémorise, au minimum : * Un champ de lecture/écriture qui contient soit la destination de la lecture, soit la donnée à écrire. * Un bit R/W permettant d'interpréter correctement le champ de lecture/écriture. * Un bit de validité pour chaque entrée de la table, qui dit si un défaut de cache antérieur accède déjà à ce mot mémoire. La fusion de deux défauts de cache est ainsi assez simple. Un défaut de cache primaire/secondaire configure l'entrée associée au mot mémoire lu/écrit. Si un défaut secondaire ultérieur lit/écrit un mot mémoire différent (dont le bit de validité de l'entrée dans la table est à zéro), pas de conflit : il configure une autre entrée, vide. Mais s'il lit/écrit un mot mémoire pour lequel l'entrée de la table est occupée, il y a conflit, le processeur est bloqué par un ''pipeline stall'', une bulle de pipeline. Avec cette organisation, le nombre de MSHR indique combien de lignes de cache peuvent être lues en même temps depuis la mémoire. Quant au nombre d'entrées par MSHR, il détermine combien d'accès mémoires qui ne se recouvrent pas peuvent avoir lieu en même temps. ===Les MSHR adressés explicitement=== Les '''MSHR adressés explicitement''' n'ont pas les contraintes des MSHR adressés implicitement. Avec eux, il est possible d'avoir plusieurs défauts de cache pointant vers la même ligne de cache, mais aussi vers le même mot mémoire. De tels défauts de cache apparaissent sur les processeurs à exécution dans le désordre, mais sont très rares, voire inexistants, sur les processeurs ''in-order''. L'idée est encore que chaque MSHR est associée à une table mémoire qui mémorise des entrées. Cette table n'est autre qu'une mémoire FIFO. Sauf que cette fois-ci, une entrée n'est pas associée à un mot mémoire. Une entrée est associée à un défaut de cache. Une entrée mémorise là encore la destination de la lecture ou la donnée de l'écriture suivi du bit R/W, ainsi que d'un bit de validité par entrée, mais aussi : la position du mot mémoire lu dans la ligne de cache. C'est cette dernière information qui n'était pas présente dans les MSHR adressés implicitement. De plus, le nombre d'entrée par MSHR n'est pas égal au nombre de mots mémoires dans une ligne de cache, mais peut être arbitrairement grand. L'avantage d'un tel cache est qu'il est capable de traité les défauts secondaires concernant un même mot mémoire, et ce, car les accès à la ligne de cache concernée se produisent dans l'ordre de génération des défauts (la table mémoire est une FIFO). ===Les MSHRs inversés=== Généralement, plus on veut supporter de défauts de cache, plus le nombre de MSHR et d'entrées augmente. Mais au-delà d'un certain nombre d'entrées et de MSHR, les MSHR adressés implicitement et explicitement ont tendance à bouffer un peu trop de circuits. Utiliser une organisation un peu moins gourmande en circuits est donc une nécessité. Cette organisation plus économe se base sur des MSHR inversés. Les MSHR inversés ne contiennent qu'une seule entrée, en quelque sorte : au lieu d’utiliser n MSHR de m entrées chacun, on va utiliser n × m MSHR inversés. La différence, c'est que plusieurs MSHR peuvent contenir un tag identique, contrairement aux MSHR adressés implicitement et explicitement. Lorsqu'un défaut de cache a lieu, chaque MSHR est vérifié. Si jamais aucun MSHR ne contient de tag identique à celui utilisé par le défaut, un MSHR vide est choisi pour stocker ce défaut, et une requête de lecture en mémoire est lancée. Dans le cas contraire, un MSHR est réservé au défaut de cache, mais la requête n'est pas lancée. Quand la donnée est disponible, les MSHR correspondant à la ligne qui vient d'être chargée vont être utilisés un par un pour résoudre les défauts de cache en attente. ===Les MSHR intégrés au cache=== Certains chercheurs ont remarqué que pendant qu'une ligne de cache est en train de subir un défaut de cache, celle-ci reste inutilisée, et son contenu est destiné à être perdu une fois le défaut de cache résolu. Ils se sont dit que, plutôt que d'utiliser des MSHR séparés, il vaudrait mieux utiliser la ligne de cache pour stocker les informations sur les défauts de cache en attente dans cette ligne de cache. Pour éviter tout problème, il faut rajouter un bit dans les bits de contrôle de la ligne de cache, qui sert à indiquer que la ligne de cache est occupée : un défaut de cache a eu lieu dans cette ligne, et elle stocke donc des informations pour résoudre les défauts de cache. ==L'entrelacement mémoire== Nous venons de voir comment une mémoire cache peut gérer plusieurs accès mémoire simultanés. Il se trouve que ce genre d'optimisation dépasse largement le cadre du cache. Les mémoires RAM ne sont pas en reste, elles aussi ! Il est possible de pipeliner l'accès à une mémoire RAM, en utilisant des techniques dites d''''entrelacement'''. Précisons cependant que cette optimisation n'est pas utilisée sur les mémoires SDRAM ou DDR modernes, du moins pas sans modifications majeures. Mais divers ordinateurs assez anciens, dont des superordinateurs, ont utilisé cette technique. La technique demande d'utiliser plusieurs mémoires séparées. Sur les anciens ordinateurs, les mémoires en question sont des chips mémoire, à savoir des circuits intégrés de DRAM. Pour faire plus général, nous allons utiliser le terme de '''banques''', ou encore de bancs mémoire. La différence est qu'il existe des mémoires multi-banque, qui regroupent plusieurs banques indépendantes dans un seul boitier, dans un seul chip mémoire. En clair, de telles mémoires regroupent plusieurs sous-mémoires dans un seul circuit intégré. Il est aussi possible d'utiliser plusieurs chips mémoire séparés, chacun ayant une banque. Reste à combiner ces banques pour former un pseudo-pipeline. ===Utiliser plusieurs banques sans entrelacement=== Sans optimisation particulière, les adresses sont réparties dans les banques comme indiqué ci-dessous. Il s'agit de ce qui s'appelle un arrangement horizontal, et nous avions vu celui-ci dans le chapiutre sur les mémoires SDRAM. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Avec cet arrangement, les bits de poids fort de l'adresse sont utilisées pour sélectionner la banque adéquate, et le reste de l'adresse est envoyé sur le bus d'adresse. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Adresse de banque !! Adresse dans la banque |- | Quelques bits de poids fort || Reste de l'adresse |} L'entrelacement change cette répartition, afin d'accélérer les accès mémoire. L'entrelacement classique vise à améliorer les accès à des adresses mémoires consécutives, mais des formes plus évoluées d'entrelacement visent à optimiser des accès mémoire arbitraires. Voyons-les dans l'ordre, du plus simple au plus complexe. ===L'entrelacement classique=== Avec l''''entrelacement classique''', des adresses consécutives sont réparties dans des banques consécutives. Précisons que cette attribution des adresses n'implique absolument pas la mémoire virtuelle ou n'importe quel mécanisme dans le processeur. La répartition décide que telle adresse mémoire va dans telle banque, à telle adresse dans la banque. Elle est donc le fait du contrôleur mémoire, donc en dehors du processeur (les contrôleurs mémoires n’étaient pas encore intégrés dans le processeur à l'époque). [[File:Répartition des adresses dans une mémoire interleaved.png|centre|vignette|upright=1.5|Répartition des adresses dans une mémoire interleaved.]] L'entrelacement simple permet d'accélérer les accès mémoire qui se font à des adresses consécutives. Avec l'entrelacement, chaque accès mémoire tombe dans une banque mémoire différente, ce qui fait qu'on peut démarrer un nouvel accès mémoire à chaque cycle d'horloge. Pendant qu'une banque est occupée par un accès mémoire, on démarre le suivant dans une autre banque, et ainsi de suite. Le tout se termine soit quand on a épuisé toutes les banques libres, soit quand l'accès en rafales se termine. Pas besoin d'attendre que la mémoire ait fini sa lecture/écriture avant de démarrer la lecture/écriture suivante. [[File:Pipemining mémoire.png|centre|vignette|upright=2.5|Pipelining mémoire]] L'animation suivante illustre bien le processus, avec quatres banques. [[File:Interleaving.gif|centre|vignette|upright=2.5|Pipelining mémoire]] L'entrelacement simple utilise les bits de poids faible pour sélectionner la banque, et les bits de poids fort pour la case mémoire. [[File:Adresse mémoire d'une mémoire entrelacée.png|centre|vignette|upright=2|Adresse mémoire d'une mémoire entrelacée]] Précisons que le temps d'accès mémoire ne change pas beaucoup avec l'entrelacement. Par contre, on peut faire plus d'accès mémoire simultanés. On peut démarrer un accès mémoire par cycle d'horloge, mais l'accès en lui-même prend plusieurs cycles. Les mémoires à entrelacement ont donc un débit supérieur aux mémoires qui ne l'utilisent pas. L'entrelacement classique pourrait être utilisé pour accélérer les transferts entre mémoire RAM et cache, qui se font en blocs d'adresses consécutives. Mais de nos jours, il n'est pas utilisé dans cette optique. Il faut dire qu'il est redondant avec le mode rafale des mémoires DRAM. Les deux font globalement la même chose et on voit mal comment utiliser les deux en même temps. Le mode rafale étant plus simple à implémenter, et plus léger niveau utilisation du bus de commande mémoire, il est préféré à l'entrelacement. Par contre, l'entrelacement a eu son heure de gloire sur d'anciens processeurs qui n'avaient pas de mémoire cache, alors qu'ils disposaient d'un pipeline. L'exemple typique est celui des nombreux ''processeurs vectoriels'', que nous n'avons pas encore abordé à ce stade du cours. De tels processeurs avaient un pipeline, une unité de calcul fortement pipelinée, et parfois même de l'exécution dans le désordre et du renommage de registres ! ===L'entrelacement par décalage=== L'entrelacement simple est très utile pour les accès en rafle ou équivalents. Par contre, il existe de rares situations où il n'est pas optimal. Une de ces situations est celle des accès en enjambée (en ''stride''), où l'on accède en série à des adresses sont séparées par N mots mémoires. [[File:Accès par enjambées.png|centre|vignette|upright=2|Accès par enjambées.]] De tels accès surviennent quand un logiciel accède à des structures de données spécifiques, à savoir des tableaux de structures, des matrices, ou d'autres structures de données dans le genre. Un cas classique est celui du parcours d'une matrice colonne par colonne. Les matrices de nombres sont mémorisées ligne par ligne, ce qui fait que parcourir une colonne demande de passer d'une ligne à la suivante et de faire des grands sauts en mémoire RAM, mais tous espacés par la même distance. Avec l'entrelacement simple, les accès en enjambée sont moins performants que les accès à des adresses consécutives, mais cela ne fonctionne pas trop mal. Le pire cas est celui où l'on a N banques et où les données sont justement placées toutes les N adresses. Des accès consécutifs vont tous tomber dans la même banque, on ne peut plus accéder à des banques différentes en parallèle. Mais en dehors de ce cas, on voit une amélioration par rapport à la situation sans entrelacement. Pour obtenir des performances maximales pour les accès en enjambées, il faut répartir les mots mémoires dans la mémoire autrement. L'organisation idéale est la suivante. : Dans les explications qui vont suivre, la variable N représente le nombre de banques, qui sont numérotées de 0 à N-1. On commence par organiser les N premières adresses comme une mémoire entrelacée simple : l'adresse 0 correspond à la banque 0, l'adresse 1 à la banque 1, etc. Pour le bloc suivant, on décale tout d'une adresse, à savoir qu'on commence à remplir les banques à partir de la seconde, non de la première. Une fois la fin du bloc atteinte, on finit de remplir le bloc en repartant du début du bloc. Le troisième bloc d'adresse subit le même traitement, sauf qu'on commence à remplir à partir de la seconde banque. Et on poursuit l’assignation des adresses en décalant d'un cran en plus à chaque bloc. Ainsi, chaque bloc verra ses adresses décalées d'un cran en plus comparé au bloc précédent. Si jamais le décalage dépasse la fin d'un bloc, alors on reprend au début. [[File:Mémoire interleaved par décalage.png|centre|vignette|upright=1.5|Mémoire entrelacée par décalage.]] En faisant cela, on remarque que les banques situées à N adresses d'intervalle sont différentes. Dans l'exemple du dessus, nous avons ajouté un décalage de 1 à chaque nouveau bloc à remplir. Mais on aurait tout aussi bien pu prendre un décalage de 2, 3, etc. Dans tous les cas, on obtient un entrelacement par décalage. Ce décalage est appelé le pas d'entrelacement, noté P. Le calcul de l'adresse à envoyer à la banque, ainsi que la banque à sélectionner se fait en utilisant les formules suivantes : * adresse à envoyer à la banque = adresse totale / N ; * numéro de la banque = (adresse + décalage) modulo N ; * décalage = (adresse totale * P) mod N. Avec cet entrelacement par décalage, on peut prouver que la bande passante maximale est atteinte si le nombre de banques est un nombre premier. Seulement, utiliser un nombre de banques premier peut créer des trous dans la mémoire, des mots mémoires inadressables. Malgré ce défaut, la technique a été utilisée sur quelques ordinateurs, avec l'exemple notable du superordinateur ''Burroughs Scientific Processor''. Pour éviter cela, il y a plusieurs solutions. Par exemple, on peut faire en sorte que N et la taille d'une banque soient premiers entre eux : ils ne doivent pas avoir de diviseur commun. Mais en pratique, elles n'ont pas vraiment été implémentées dans une vraie machine, et sont restées à l'état de recherche, aussi je les passe sous silence. ===L'entrelacement pseudo-aléatoire=== Une dernière méthode de répartition consiste à répartir les adresses dans les banques de manière "pseudo-aléatoire". L'idée exacte est de faire passer l'adresse dans une '''fonction de hachage''', plus ou moins complexe. Pour rappel, une fonction de hachage prend une entrée de grande taille et fournit en sortie un résultat de petite taille. Idéalement, elles doivent donner des résultats aussi différents que possible pour des entrées similaires, histoire de simuler une sorte de pseudo-aléatoire. Ici, une adresse est transformée en un numéro de banque. Une fonction de hachage simple ne fait que permuter des bits de l'adresse pour obtenir son résultat. Elle ne fait qu'échanger des bits de place avant de couper l'adresse en deux morceaux : un pour la sélection de la banque, et un autre pour la sélection de l'adresse dans la banque. Cette permutation est fixe, et ne change pas suivant l'adresse. Des fonctions de hachage plus complexes font des XOR bit à bit entre certains bits de l'adresse. ==Les contrôleurs mémoires SDRAM/DDR optimisés== Après avoir vu le cas des mémoires RAM, nous allons nous concentrer sur le cas particulier des mémoires SDRAM. Les mémoires SDRAM et DDR modernes sont capables de gérer plusieurs accès simultanés à la mémoire RAM, et leurs contrôleurs mémoire en font tout autant. C'est une différence majeure avec les mémoires asynchrones FPM/EDO, qui n'acceptaient qu'un seul accès mémoire à la fois. Leur contrôleur mémoire n'acceptait qu'un seul accès mémoire à la fois, c'était un contrôleur mémoire bloquant. Les contrôleurs mémoires des SDRAM sont eux non-bloquants et peuvent encaisser une dizaine d'accès mémoire à la fois. Peu de choses sont connues sur les contrôleurs de SDRAM/DDR modernes, les fabricants ne donnant que peu de détails dessus. Les rares simulateurs qui tentent de décrire leur fonctionnement, comme DRAM SIM I et II, sont particulièrement simples et ne vont pas dans le détail. Néanmoins, le peu qu'on sait est tout de même instructif. ===La mise en attente des accès mémoire=== Un contrôleur de SDRAM peut accepter plusieurs accès mémoire en même temps. Mais cela ne signifie pas que le controleur sera pr^è à les traiter immédiatement. Pour éviter tout problème de disponibilité, le contrôleur met en attente les accès mémoire que le processeur lui envoie, pour les exécuter dès que possible. Les accès mémoire sont mis en attente dans une mémoire FIFO, histoire de les exécuter dans leur ordre d'arrivée, notamment pour renvoyer les lectures dans l'ordre demandé. Il y a aussi une mémoire FIFO pour les données à écrire et une FIFO pour les données lues. Cette dernière sert au cas où le cache ne soit pas disponible quand on lui envoie la donnée lue. [[File:Module d'interface avec la mémoire.png|centre|vignette|upright=2.5|Contrôleur mémoire avec mise en attente des requêtes processeur et autres optimisations.]] Des optimisations sont possibles dès la mémoire FIFO. Par exemple, on peut regrouper plusieurs accès à des données consécutives en un seul accès en rafale. Le contrôleur mémoire analyse les requêtes mises en attente et détecte si certaines se font à des adresses consécutives. Si c'est le cas, il fusionne ces requêtes en une lecture/écriture en rafale. Une telle optimisation est appelée la '''combinaison de lecture''' pour les lectures, et la '''combinaison d'écriture''' pour les écritures. ===Le pipeline des mémoires SDRAM=== Les accès mémoire sont traduits par un séquenceur mémoire en une série de commandes mémoires, qui sont séparées par des délais mémoire de quelques cycles d'horloge. Les délais sont très précis, et sont à respecter à la lettre. Les SDRAM sont parfois capables de démarrer une commande avant que la précédente soit terminée. Mais le respecte des délais mémoire est très limitant, ce qui fait qu'on ne peut pas parler de réel pipeline, comme c'est le cas sur les processeurs. Parlons plutot de commandes anticipées. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | || bgcolor="#A0FFFF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || || || bgcolor="#FFA0FF" | READ (2) || || || || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 | bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 | |} Même si elle parait très limitée, cette possibilité est décuplée par la présence de plusieurs banques dans la mémoire SDRAM. Les chips mémoires de SDRAM sont composés de plusieurs banques, chacune étant une sorte de mini-mémoire miniature. Chaque banque a son propre décodeur, son propre tampon de ligne, ses propres multiplexeurs de colonne, sa logique de rafraichissement mémoire, etc. C'est comme si une SDRAM regroupait plusieurs mémoires séparées dans un même circuit intégré. Et cela permet de faire plusieurs accès mémoire en même temps, dans des banques différentes. Il est possible de lancer un accès mémoire dans deux banques en même temps, par exemple. Ou encore, on peut lancer un nouvel accès mémoire dans une banque si elle est inoccupée. La possibilité améliore grandement la performance de la SDRAM. Mais nous en reparlerons dans un chapitre ultérieur. {|class="wikitable" |+ Pipelining basique sur les SDRAM |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 |- ! Banque Numéro 1 | || || || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || || || |- ! Banque Numéro 2 | || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || |- ! Banque Numéro 3 | || || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || |} L'implémentation n'a rien de vraiment compliqué. Il suffit d'utiliser une mémoire FIFO par banque. Ainsi, les accès dans une même banque se feront en série, dans l'ordre d'arrivée, mais des accès à des banques différentes se feront dans le désordre. Cela ne pose pas de problème car les accès se font à des adresses différentes, ce qui fait qu'il n'y a pas de conflits majeurs. [[File:Gestion parallèle des banques.png|centre|vignette|upright=2.5|Gestion parallèle des banques.]] ===Le ré-ordonnancement des commandes mémoires=== Un contrôleur mémoire moderne est capable de changer l'ordre des requêtes mémoires, pour gagner en performances. En clair : il peut faire l'équivalent mémoire de l'exécution dans le désordre des processeurs haute performance. Une différence majeure est que le contrôleur mémoire ne remet pas les accès mémoire dans l'ordre avant de les envoyer au processeur. C'est le processeur qui remet en ordre les accès mémoire, dans sa ''load queue''. Pour cela, les requêtes mémoire sont "numérotées", à savoir qu'on leur attribue un identifiant binaire. Le contrôleur mémoire exécute les lectures/écritures dans le désordre, mais garde la trace de leur identifiant. Il sait dans quel ordre il exécute les accès mémoire et connait leur latence. Ce qui fait qu'il sait que la donnée lue à tel instant est associée à la requête mémoire numéro X. Pour les lectures, il renvoie la donnée lue avec l'identifiant. Le processeur sait alors à quelle lecture la donnée lue correspond et il se débrouille en interne pour gérer la situation. En clair : le processeur voit les accès mémoire dans le désordre, c'est lui qui les remet en ordre. Maintenant que ces précisions sont faites, posons cette question : quel est l'intérêt de faire les accès mémoire dans le désordre ? Accéder à des banques en parallèle est une réponse possible, mais le ré-ordonnancement est surtout utile si tous les accès mémoire atterrissent dans la même banque. Pour comprendre pourquoi, faisons un rappel rapide sur le tampon de ligne. Les mémoires SDRAM sont des mémoires à tampon de ligne. Elles font un accès mémoire en deux étapes. La première étape recopie une ligne de N * 64 bits dans un tampon interne à la SDRAM. La seconde étape sélectionne une colonne, à savoir une donnée de 64 bits, qui est soit envoyée sur le bus de données pour une lecture, soit modifiée par une écriture. [[File:Mémoire à tampon de ligne.png|centre|vignette|upright=2|Mémoire à tampon de ligne]] Les deux étapes correspondent à deux commandes séparées : une commande ACT qui précise l'adresse de la ligne, et une commande READ ou WRITE qui précise l'adresse de la colonne. [[File:Signaux RAS et CAS.png|centre|vignette|upright=2|Signaux RAS et CAS.]] Les SDRAM permettent de se passer de la première étape si des accès consécutifs se font dans la même ligne. Une fois activée, la ligne reste ouverte et on peut accéder plusieurs fois de suite dedans. On a alors juste à préciser l'adresse de colonne dedans. [[File:Sélection d'une ligne sur une mémoire FPM ou EDO.png|centre|vignette|upright=2|Sélection d'une ligne sur une mémoire FPM ou EDO.]] L'exécution dans le désordre des lectures/écritures SDRAM vise à faire en sorte que des accès consécutifs se fassent dans une même ligne. Elle marche si plusieurs accès à une même ligne ne sont pas consécutifs, et qu'il y a des accès entre les deux. Après ré-ordonnancement, ces accès mémoire deviennent consécutifs, ils sont exécutés l'un à la suite de l'autre. Pour rendre le tout plus concret, voici un exemple. Imaginez que l'on sait les 3 accès mémoire suivants : * Une lecture ligne A ; * Une écriture ligne B ; * Une lecture ligne A. Sans réordonnancent, on doit émettre deux commandes PRECHARGE : une quand on passe de la ligne A à la ligne B, et une autre pour le passage inverse. pour éviter cela, le contrôleur mémoire peut retarder l'écriture, ou au contraire avancer la seconde lecture. le résultat est alors le suivant : * Lecture ligne A ; * Lecture ligne A ; * Une écriture ligne B. On a donc deux accès consécutifs à la même ligne, suivi par une écriture dans une autre ligne. La technique marche parce que l'on a un mix adéquat de lectures et d'écritures. Mais il existe des possibilités de réorganisation autres qui ne sont pas valides. Il faut par exemple prendre garde à éviter d'intervertir lectures et écritures à une même adresse, sous peine de problèmes. Encore une fois, il faut faire attention aux dépendances mémoires. Le controleur mémoire teste les dépendances mémoires avant d'envoyer des commandes à la mémoire DDR/SDRAM. ==L'entrelacement sur les mémoires SDRAM== L''''entrelacement''' fonctionne sur les mémoires SDRAM, mais il doit être fortement modifié. Concrètement, tout ce qui a étré dit plus est inapplicable pour les SDRAM, du fait de la présence du tampon de ligne. Des adresses consécutives atterrissent déjà dans la même ligne, ce qui fait qu'on n'a pas besoin d'entrelacement au niveau d'une ligne. Et ne parlons pas de ce la présence de rangées et de canaux mémoire ! Cependant, il peut y avoir un entrelacement lié à la présence des banques SDRAM. L'entrelacement est géré juste avant le séquenceur mémoire. Le '''circuit d'entrelacement''' intervertit certains bits de l'adresse lors des accès mémoires. On trouve aussi des mémoires FIFOs pour mettre en attente les requêtes mémoire provenant du processeur. Elles permettent de recevoir plusieurs accès mémoire consécutifs. Si vous vous demandez comment plusieurs accès mémoire consécutifs peuvent avoir lieu, sachez que c'est une bonne question. Pour le moment, nous allons dire que ces accès proviennent du processeur ou de la mémoire cache, sans expliquer comment. Le contrôleur mémoire reçoit une série d'accès mémoire assez rapprochés, qu'il doit exécuter au mieux. [[File:Controleur mémoire d'une SDRAM avec entrelacement.png|centre|vignette|upright=2.5|Contrôleur mémoire d'une SDRAM avec entrelacement.]] Pour gérer l'entrelacement, le contrôleur de SDRAM prend en entrée l'adresse envoyée par le processeur, et la découpe en plusieurs champs : un pour sélectionner la banque adéquate, un autre pour la ligne, un autre pour la colonne, un autre pour la rangée. Une solution simple désactive l'entrelacement. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de banque || Adresse de ligne || Adresse de colonne |} Si cette solution fonctionne bien si les accès mémoire sont assez aléatoires, en pratique, mieux vaut utiliser l'entrelacement. ===L'entrelacement de banques/lignes=== Une première méthode d'entrelacement est l''''entrelacement de banques'''. Elle répartit deux lignes consécutives dans deux banques différentes. Pour comprendre l'idée, prenons un exemple. Imaginons une mémoire avec deux banques et 4 lignes. Imaginons qu'on parcoure/balaye la mémoire RAM en partant des adresses basses. Sans entrelacement de ligne, les accès se feront comme suit, de gauche à droite : {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Banque numéro 1 !! colspan="4" | Banque numéro 2 |- | Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 || Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 |} Avec l'entrelacement de banques, les accès se feront comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 |- | Ligne 1 || Ligne 1 || Ligne 2 || Ligne 2 || Ligne 3 || Ligne 3 || Ligne 4 || Ligne 4 |} Avec N banques, la première ligne ira dans la première banque, la seconde ligne dans la seconde, et ainsi de suite jusqu'à la énième banque qui va dans la banque numéro N. Et la ligne N+1 repart dans la première banque. L'avantage est que passer d'une ligne à la suivante est assez rapide. Au lieu de devoir fermer la première ligne et ouvrir la suivante on ouvrira directement la ligne suivante dans une banque différente, ce qui sera plus rapide. La commande PRECHARGE pourra être envoyée à la seconde banque en avance. Pour cela, l'adresse est découpée comme suit. {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne (précise à l'octet) |} ===L'entrelacement de rangées=== Il est aussi possible de faire la même chose, mais avec les rangées. Pour simplifier fortement, une rangée est simplement un chip mémoire. En réalité, les rangées ne sont pas des chip mémoire, mais un ensemble de chips mémoire regroupés ensemble histoire d'atteindre les 64 bits du bus de données. Par exemple, une rangée peut combiner 8 chips mémoire avec un bus de données de 8 bits chacun pour obtenir les 64 bits du bus de données d'une SDRAM. Mais nous passons ce détail sous silence dans les explications qui vont suivre, par souci de simplification. Pour faire comprendre l'entrelacement de rangée, le mieux est d'utiliser un exemple, le même que précédemment. Sans entrelacement de rangée, on change de chip mémoire une fois qu'on a balayé toutes ses adresses. {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Rangée numéro 1 !! colspan="4" | Rangée numéro 2 |- | Banque 1 || Banque 2 || Banque 3 || Banque 4 || Banque 1 || Banque 2 || Banque 3 || Banque 4 |} Avec l'entrelacement de ligne, on change de rangée dès qu'on change de banque/ligne. {|class="wikitable" |+ Adresse mémoire |- ! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 |- | Banque 1 || Banque 1 || Banque 2 || Banque 2 || Banque 3 || Banque 3 || Banque 4 || Banque 4 |} En clair, quand on a épuisé toutes les banques dans une même rangée, on passe à la rangée suivante au lieu de rester dans la même rangée. Notons que dans l'exemple précédent, on a combiné l'entrelacement de rangée et de banque, mais on aurait pu utiliser l'entrelacement de rangée seul. Mais ce n'est pas le cas le plus courant en pratique. Toujours est-il qu'en combinant entrelacement de rangée et de ligne, le découpage de l'adresse est le suivant : {|class="wikitable" |+ Adresse mémoire |- | Adresse de ligne || Numéro de rangée || Adresse de banque || Adresse de colonne (précise à l'octet) |} D'autres méthodes d'entrelacement sont possibles, mais elles sont plus complexes. Chaque contrôleur mémoire fait un peu à sa sauce. Ils permettent en général de choisir entre plusieurs entrelacements. Au minimum, ils permettent de désactiver l'entrelacement, d'activer un entrelacement de ligne, et d'autres entrelacements plus complexes, dont certains sont propriétaires et tenus secrets par le fabricant. ===L'entrelacement avec le ''dual channel''=== [[File:Dual channel slots.jpg|vignette|Slots mémoires avec ''dual channel''.]] L'usage du ''dual channel'' complique encore l'entrelacement. Là encore, il y a deux grandes solutions : avec et sans entrelacement des canaux mémoire. Rappelons le principe : deux barrettes de RAM sont accédées en parallèle. Et pour cela, il faut utiliser l'entrelacement. Typiquement, chaque barrette mémoire fournit 64 bits, ce qui fait que l'on peut accéder à 128 bits d'un seul coup, par exemple avec un accès en rafale. Sans ''dual channel'', la première barrette correspond à la moitié haute de la RAM, la seconde barrette correspond à la moitié basse. Avec ''dual channel'', une forme spécifique d'entrelacement est activée. Concrétement, deux blocs de 64 bits sont placés dans des canaux mémoire séparés. Idem avec du triple ou quadruple canal, mais c'est alors trois ou quatre blocs de 64 bits qui sont dispersés dans des canaux consécutifs. Le découpage de l'adresse est alors le suivant : {|class="wikitable" |+ Adresse mémoire |- ! colspan="6" | Sans entrelacement |- | Numéro de canal/barrette || Reste de l'adresse || Position de l'octet dans un bloc de 64 bits |- | colspan="6" | |- ! colspan="6" | Avec entrelacement |- | Reste de l'adresse || Numéro de canal/barrette || Position de l'octet dans un bloc de 64 bits |} [[File:Décodage d'adresse avec dual channel.png|centre|vignette|upright=2|Décodage d'adresse avec dual channel.]] Les contrôleurs de SDRAM précédents sont assez basiques et ne représentent pas les contrôleurs les plus évolués. Ils étaient utilisés sur les anciens PC, à une époque où ils étaient encore sur la carte mère du processeur. Mais de nos jours, le contrôleur mémoire est intégré au processeur. Et il incorpore de nombreuses optimisations afin de gagner en performances. Malheureusement, ces optimisations sont elles-mêmes dépendantes des optimisations intégrées dans le processeur, ce qui fait qu'on ne peut pas en parler ici. Elles demandent que le contrôleur mémoire reçoive plusieurs accès mémoire simultanés, ce qui n'a aucun sens à ce stade du cours. Aussi, nous allons les passer sous silence pour le moment. Nous les verrons dans le chapitre sur le parallélisme mémoire. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=La désambiguïsation mémoire | prevText=La désambiguïsation mémoire | next=Les processeurs superscalaires | nextText=Les processeurs superscalaires }} </noinclude> dbv224gegcl2u2lscj1nfccentffd8b 764710 764707 2026-04-23T21:12:17Z Mewtow 31375 /* Les contrôleurs mémoires SDRAM/DDR optimisés */ 764710 wikitext text/x-wiki Dans ce chapitre, nous allons voir les techniques qui permettent de gérer plusieurs accès mémoire simultanés directement au niveau du cache ou de la mémoire RAM. Par plusieurs accès mémoire simultanés, vous pensez sans doute à l'usage de cache multiports, voire de mémoires RAM multiport. C'est en effet une solution, mais ce n'est clairement pas la seule. Par exemple, il est possible de pipeliner l'accès au cache, voire à la mémoire RAM. Il existe de nombreuses techniques de '''parallélisme mémoire''', et nous allons les voir dans ce chapitre. Pour donner un exemple, il est possible de pipeliner les accès mémoire. Il est possible de pipeliner l'accès au cache et/ou l'accès à la mémoire, et nous verrons comment faire dans ce chapitre. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, qu'ils aient ou non un pipeline dynamique. Par contre, pipeliner la mémoire est une technique ancienne, aujourd'hui peu utilisée. Elle était utilisée sur des très vieux ordinateurs, qui avaient un pipeline mais pas de mémoire cache. Mais de nos jours, les mémoires SDRAM et DDR ne sont pas adaptées pour ça, il est très difficile de les pipeliner correctement. Le parallélisme mémoire est utile aussi bien sur des processeurs à émission dans l'ordre que dans le désordre. Par exemple, un processeur ''in-order'' avec des lectures non-bloquantes peut e, profiter. Il peut techniquement lancer une seconde lecture, après avoir rencontré une première lecture non-bloquante. En général, le nombre de lectures consécutives est cependant limité à moins d'une dizaine. À l'opposé, les processeurs sans lectures non-bloquantes profitent pas du parallélisme mémoire. À la rigueur, ils peuvent en profiter s'ils intègrent des techniques de préchargement. Pendant que le processeur exécute une lecture/écriture, il peut précharger une autre donnée en parallèle. Inutile de dire que sans pipeline processeur, le parallélisme mémoire ne sert pas à grand-chose. ==Le ''line fill buffer'' et les techniques associées== Lors d'un défaut de cache, le processeur doit attendre que toute la ligne de cache soit chargée avant d'être utilisable. Or, la taille d'une ligne de cache est supérieure à la largeur du bus mémoire, ce qui fait qu'une ligne de cache est chargée en plusieurs fois, morceaux par morceaux, mot mémoire par mot mémoire. Le chargement peut se faire directement dans le cache, mais ce n'est pas une solution très pratique. À la place, beaucoup de processeurs ajoutent une mémoire tampon entre la RAM et le cache, appelée le '''tampon de remplissage de ligne''' (''line-fill buffer'') dans les processeurs modernes. Lors d'un défaut de cache, le processeur charge la donnée de la RAM dans le tampon de remplissage de ligne, mot mémoire par mot mémoire. Une fois plein, le tampon de remplissage de ligne est recopié dans la ligne de cache. Le tampon de remplissage de ligne contient une ligne de cache, si ce n'est que certains bits de contrôle ne sont pas présents. Le tampon de remplissage de ligne contient un bit de validité pour chaque mot mémoire de la ligne de cache, qui indiquent si le mot mémoire a été chargé. Par exemple, prenons un processeur 64 bits, qui gère donc des mots mémoire de 8 octets, avec des lignes de cache 256 octets/32 mots mémoire. Le tampon de remplissage de ligne contiendra 32 bits de validité. Si le processeur a chargé les 6 premiers mots mémoire, les 6 premiers bits de validité seront mis à 1, les autres seront encore à zéro. Il faut noter que la disponibilité de la ligne complète se détermine assez facilement en faisant un ET logique entre tous les bits de validité. Le processeur sait ainsi quand la ligne de cache est disponible entièrement et donc quand la transférer dans le cache. La même chose existe avec une hiérarchie de cache, sauf que l'on trouve une mémoire tampon entre chaque niveau de cache. Il y a un tampon de remplissage de ligne entre le cache L1 et le cache L2, entre le cache L2 et le L3, etc. Si le cache ne gère qu'un seul défaut de cache à la fois, le ''fill line buffer'' est une mémoire très simple, qui ne mémorise qu'une seule ligne de cache. Et cela vaut aussi bien pour un cache bloquant que non-bloquant. Mais sur les caches capables de gérer plusieurs défauts simultanés, le tampon de remplissage de ligne est une mémoire de type FIFO ou LIFO, capable de mémoriser plusieurs lignes de cache. Notons que le tampon de remplissage de ligne est très utile pour implémenter certaines techniques, par exemple le contournement du cache. Nous avions vu dans le chapitre sur le cache que certains accès mémoire doivent contourner le cache, pour des raisons de cohérence des caches. C'est notamment nécessaire pour accéder aux périphériques, mais c'est aussi utile pour des raisons de performances dans des cas très spécifiques. Les accès qui contournent le cache se font directement dans le tampon de remplissage de ligne : le processeur écrit ou lit les données depuis ce tampon de remplissage de ligne, sans accéder au cache. ===L'''early restart'' et le ''critical word load''=== La présence du ''line buffer'' permet une optimisation assez intéressante, qui permet de réduire la latence des défauts de cache. L'optimisation consiste à lire un mot mémoire dans le tampon de remplissage de ligne, même si la ligne de cache complète n'a pas encore été chargée. Il existe deux manières de faire cela, qui portent les noms d'''early restart'' et de ''critical word load''. La première est la version la plus simple, la seconde est plus complexe mais plus performante. Avec la technique d''''''early restart''''', la ligne de cache est chargée normalement, en partant de son premier mot mémoire. Dès que le mot mémoire lu/écrit par le processeur est copié dans le ''line fill buffer'', il est envoyé au processeur immédiatement. Illustrons le tout par un exemple, où une ligne de cache fait 16 mots mémoire. Le processeur effectue une lecture, qui lit le 5ème mot mémoire. Sans ''early restart'', le processeur doit charger les 16 mots mémoire avant de faire la lecture dans le cache. Avec ''early restart'', le processeur reçoit la donnée dès que le 5ème mot mémoire est disponible. Le processeur doit attendre que les 4 premiers mots mémoire soient chargés, puis le 5ème mot mémoire arrive et est envoyé directement au processeur, il est lu directement depuis le ''line fill buffer''. Les 11 mots mémoire suivants sont ensuite chargés dans le cache pendant que le processeur fait des calculs dans son coin. Le ''critical word load'' est une optimisation de la technique précédente où le chargement de la ligne de cache commence directement à la donnée demandée par le processeur. Pour reprendre l'exemple précédent, où le processeur demande le 5ème mot mémoire sur 16, le ''critical word load'' charge le 5ème mot mémoire en premier et l'envoie au processeur, ce qui fait qu'il est chargé très rapidement. Pas besoin d'attendre que le processeur charge les 4 mots mémoire précédents comme avec l'''early restart''. Dans le détail, le processeur charge le 5ème mot mémoire en premier, puis charge les 11 suivants, et termine par les 4 mots mémoire du début. En clair, le ''critical word load'' commence par charger le mot mémoire lu, puis les blocs suivants, avant de revenir au début du bloc pour charger les blocs restants. Ainsi, la donnée demandée par le processeur sera la première disponible. Pour cela, l'organisation du tampon de remplissage de ligne est modifiée de manière à rendre cela possible. Il a une taille égale à une ligne de cache complète, qui contient elle-même plusieurs mots mémoire. Dans le ''line-fill buffer'', chaque mot mémoire est stocké avec un tag, qui indique l'adresse du mot mémoire stocké dans le ''line-fill buffer''. Le ''line-fill buffer'' est donc un cache un peu particulier, qui fonctionne comme un cache du point de vue du processeur, comme une mémoire FIFO pour les transferts avec le cache. Ainsi, un processeur qui veut lire dans le cache après un défaut peut accéder à la donnée directement depuis le tampon de remplissage de ligne, alors que la ligne de cache n'a pas encore été totalement recopiée en mémoire. ==Les caches pipelinés== Il est possible de pipeliner l'accès au cache, ce qui demande juste de rajouter des registres au bon endroits et quelques circuits de contrôle. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, même ceux avec un pipeline dynamique. Et l'implémentation peut se faire de plusieurs manières différentes. Il faut dire que découper une mémoire cache en plusieurs étapes peut se faire de plusieurs manière. Commençons par la plus simple. Beaucoup de processeurs des années 2000 avaient une fréquence peu élevée comparée aux standards d'aujourd'hui, ce qui fait que leur cache avait bien un temps d'accès d'un cycle d'horloge. Mais l'accès au cache se faisait en deux cycles d'horloge : un cycle pour calculer une adresse et un cycle pour l'accès mémoire proprement dit. Et ces deux étapes étaient pipelinées, à savoir que deux micro-opérations mémoire peuvent s'exécuter en même temps : une dans l'étage de calcul d'adresse, une autre dans le cache. L'unité mémoire était donc pipelinée, alors que l'accès au cache ne l'était pas. Les processeurs qui implémentaient cette technique regroupent les micro-architectures K5 et K6 d'AMD, les processeurs Intel de micro-architecture P6 (Pentium 2 et 3) et quelques autres. Il est aussi possible de pipeliner l'accès au cache lui-même. Avec un cache pipeliné, l'accès au cache ne se fait pas en un seul cycle, mais en plusieurs. Cependant, on peut lancer un nouvel accès au cache à chaque cycle d'horloge, comme un processeur pipeliné lance une nouvelle instruction à chaque cycle. L'implémentation est assez simple : il suffit d'ajouter des registres dans le cache. Pour cela, on profite que le cache est composé de plusieurs composants séparés, qui échangent des données dans un ordre bien précis, d'un composant à un autre. De plus, le trajet des informations dans un cache est linéaire, ce qui les rend parfois pour l'usage d'un pipeline. ===Le ''pipelining'' des caches ''direct-mapped''=== Voici ce qui se passe avec un cache directement adressé. Pour rappel ce genre de cache est conçu en combinant une mémoire RAM, généralement une SRAM, avec quelques circuits de comparaison et un MUX. L'accès au cache se fait globalement en deux étapes : la première lit la donnée et le ''tag'' dans la SRAM, et on utilise les deux dans une seconde étape. Nous avions vu il y a quelques chapitre comment pipeliner des mémoires, dans le chapitre sur les mémoires évoluées. Et bien ces méthodes peuvent s'utiliser pour la mémoire RAM interne au cache !L'idée est d'insérer un registre entre la sortie de la RAM et la suite du cache, pour en faire un cache pipéliné, à deux étages. Le premier étage lit dans la SRAM, le second fait le reste. L'implémentation sur les caches associatifs à plusieurs voies est globalement la même à quelques détails près. [[File:Cache directement adressé pipeliné - deux étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - deux étages]] Avec le circuit précédent, il est possible d'aller plus loin, cette fois en pipelinant l'accès à la mémoire RAM interne au cache. Pour comprendre comment, rappelons qu'une mémoire SRAM est composée d'un plan mémoire et d'un décodeur. L'accès à la mémoire demande d'abord que le décodeur fasse son travail pour sélectionner la case mémoire adéquate, puis ensuite la lecture ou écriture a lieu dans cette case mémoire. L'accès se fait donc en deux étapes successives séparées, on a juste à mettre un registre entre les deux. Il suffit donc de mettre un gros registre entre le décodeur et le plan mémoire. [[File:Cache directement adressé pipeliné - trois étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - trois étages]] Et on peut aller encore plus loin en découpant le décodeur en deux circuits séparés. En effet, rappelez-vous le chapitre sur les circuits de sélection : nous avions vu qu'il est possible de créer des décodeurs en assemblant des décodeurs plus petits, contrôlés par un circuit de prédécodage. Et bien on peut encore une ajouter un registre entre ce circuit de prédécodage et les petits décodeurs. : Théoriquement, toute l'adresse est fournie d'un seul coup au cache, la quasi-totalité des processeurs présentent l'adresse complète à un cache pipeliné. Mais le Pentium 4 fait autrement. Il faut noter que les premiers étages manipulent l'indice dans la SRAM, qui est dans les bits de poids faible, alors que les étages ultérieurs manipulent le ''tag'' qui est dans les bits de poids fort. Les concepteurs du Pentium 4 ont alors décidé de présenter les bits de poids faible lors du premier cycle d'accès au cache, puis ceux de poids fort au second cycle. Pour cela, l'ALU fonctionnait à une fréquence double de celle du processeur, tout comme le cache L1. Il n'y avait pas de pipeline proprement dit, mais cela réduisait grandement la latence d'accès au cache. ===Le ''pipelining'' des caches splittés/sériels=== Il est aussi possible de pipeliner un cache dits splittés, aussi appelés à accès sériel. Pour rappel, les caches à accès sériel vérifient si il y a succès ou défaut de cache, avant d'accéder aux lignes de cache en cas de succès. Ils font donc différemment des autres caches, qui accèdent à une ligne de cache, avant de déterminer s'il y a succès ou défaut en lisant le tag de la ligne de cache. Les caches sériels disposent de deux SRAM : une pour les tags des lignes de cache et une pour les données. Ils accèdent à la SRAM pour les tags, avant d’accéder à la SRAM des données en cas de succès de cache. Vu que l'accès se fait en deux étapes, une vérification des tags suivie de la lecture/écriture des données, il est facile à pipeliner. Pipeliner le cache permet de régler le problème des accès au cache L1, et elle est tout le temps utilisé sur les processeurs modernes. Mais que faire en cas de défaut de cache ? ==Les caches non bloquants== Un '''cache bloquant''' est un cache auquel le processeur ne peut pas accéder pendant un défaut de cache. Il faut attendre que la lecture ou écriture en RAM soit terminée avant de pouvoir utiliser de nouveau le cache. Un '''cache non bloquant''' n'a pas ce problème : on peut l'utiliser pendant un défaut de cache. Les caches non bloquants permettent de démarrer une nouvelle lecture ou écriture alors qu'une autre est en cours, ce qui permet d'exécuter plusieurs lectures ou écritures en même temps. ===Les ''Miss Handling Status Registers''=== Lors d'un défaut de cache, la mémoire RAM est consultée pendant le défaut de cache, mais le cache est inutilisé. Un défaut de cache n'utilise pas le cache, ce n'est qu'un accès en mémoire RAM, sur le bus mémoire (ou un accès aux niveaux de cache inférieurs, peu importe). Le cache en lui-même est laissé libre, rien n’empêche d'y accéder, il est en réalité intrinsèquement non-bloquant. Les caches, bloquants comme non-bloquants, sont en réalité composés d'une mémoire cache proprement dite, entourée de circuits qui servent d'interface entre le processeur et le cache lui-même. Et parmi les circuits tout autour, certains gèrent l'accès au cache lors d'un défaut de cache. Ils sont regroupés sous le terme de '''''Miss Handling Architecture''''' (MHA). La différence entre un cache bloquant et un cache non-bloquant est en réalité liée à l'implémentation de la MHA. Les caches bloquants coupent volontairement l'accès au cache lors d'un défaut, car les défauts sont plus simples à gérer ainsi. Le défaut de cache rapatrie une donnée depuis la RAM, qui sera écrite dans le cache. Et il ne faut pas qu'une tentative d'accès à cette donnée ait lieu avant qu'elle ne soit chargée. Mais ce blocage est général et touche tout le cache, alors que seule une ligne de cache est concernée par le défaut de cache. L'idée derrière un cache non-bloquant est que seule la ligne de cache est bloquée, mais les autres sont accessibles. L'idée est alors de mémoriser les lignes de cache concernées par le défaut de cache, afin d'en bloquer l'accès. À chaque accès, on vérifie si la ligne de cache est déjà réservée par un défaut de cache. La lecture/écriture est alors bloquée si c'est le cas, mais elle accepte les accès sinon. Pour cela, la ''Miss Handling Architecture'' contient des registres qui mémorisent des informations sur les défauts de cache en cours. Ils portent le nom de '''''miss status handling registers''''', que j’appellerais dorénavant MSHR, qui sont aussi appelés des '''''miss buffer'''''. Le contenu des MSHR varie beaucoup suivant le processeur, mais ces derniers stockent au minimum les informations suivantes : * Le numéro de la ligne de cache dans laquelle les données sont chargées. * Un bit de validité qui indique si le MSHR est vide ou pas, qui est mis à 0 quand le défaut de cache est résolu. * Un ou plusieurs champs de lecture/écriture, qui contiennent des informations sur la lecture/écriture. ** Pour une lecture, elle contient des informations sur la destination de la donnée, à savoir qui prévenir quand le défaut de cache est terminé. C'est parfois un nom/numéro de registre (celui dans lequel charger la donnée), mais c'est souvent le numéro de l'entrée dans le ''load/store queue''. ** Pour les écritures, elle contient la donnée à écrire, ou éventuellement un numéro de ''load/store queue'' où se trouve la donnée à écrire. Il faut noter que le bus mémoire ne peut gérer qu'un seul défaut de cache à la fois. Aussi, il est intéressant de regarder ce qui se passe lorsqu'un second défaut de cache survient, pendant qu'un premier est en cours. Dans ce cas, il y a deux réponses qui correspondent à deux types de caches non-bloquants, qui portent les noms barbares de caches de type succès après défaut et défaut après défaut. Sur le premier type, il ne peut pas y avoir plusieurs défauts de cache simultanés. Dès qu'un second défaut de cache survient, le cache stoppe son activité et on ne peut plus démarrer de nouvelle lecture/écriture, tant que le premier défaut de cache n'est pas résolu. Le second type est plus souple et autorise la survenue de plusieurs défauts de cache simultanés. Du moins, jusqu'à une certaine limite, car le cache ne peut supporter qu'un nombre limité d'accès mémoires simultanés (pipelinés). ===Les accès simultanés à une même ligne de cache=== Il arrive que le processeur fasse plusieurs accès mémoire simultanés à la même ligne de cache. Si la ligne de cache en question n'a pas encore été chargée dans le cache, alors on a plusieurs défauts de cache consécutifs pour la même ligne de cache, et le cache non-bloquant doit gérer la situation. Pour la suite, il va falloir faire une petite distinction entre les défauts primaires et secondaires. Imaginons qu'un défaut de cache ait lieu et demande à charger une donnée dans la ligne de cache numéro N. Il s'agit du premier défaut impliquant cette ligne de cache précise, ce qui lui vaut le nom de '''défaut de cache primaire'''. Mais par la suite, d'autres accès mémoire à la même ligne de cache ont lieu, alors que la ligne de cache n'est pas encore disponible. Dans ce cas, il s'agit de '''défauts de cache secondaires'''. Pour l'unité d'accès mémoire, les défauts de cache primaire et secondaire sont différents (ils prennent tous une entrée dans la ''load/store queue''). Mais pour le cache, ils ne correspondent qu'à un seul accès au cache : celui qui demande de charger la ligne de cache demandée. Les défauts de cache primaire et secondaire à la même ligne de cache se voient attribuer un MSHR unique. La gestion des défauts de cache secondaires dépend du cache non-bloquant. La solution la plus simple ne permet pas les défauts de cache secondaires. Le cache ne permet pas deux défauts de cache simultanés pour la même ligne de cache. Les autres solutions le permettent, en fusionnant des accès simultanés à la même ligne de cache en un seul au niveau des MSHR. Dans tous les cas, détecter les défauts de cache secondaires sont un problème qu'il faut détecter. La ''Miss Handling Architecture'' doit détecter les défauts de cache secondaire. Pour cela, elle procède comme suit. Lors de chaque défaut de cache, la MHA récupère le numéro de la ligne de cache associé. Il vérifie alors chaque MSHR pour vérifier s'il contient le numéro en question. S'il n'y a aucune correspondance dans les MSHR, alors c'est signe que le défaut de cache est un défaut primaire. Mais s'il y en a une, alors c'est un défaut secondaire. Évidemment, cela signifie que lors d'un défaut de cache, le numéro de ligne de cache est envoyé à tous les MSHR, pour comparaison. Les MSHR sont donc regroupés dans une mémoire associative, une sorte de mini-cache, faciliter l'implémentation. Lors d'un défaut de cache primaire, l'accès à un cache non-bloquant se fait comme suit : le processeur envoie une adresse au cache, accède à celui-ci, et détecte la survenue d'un défaut de cache. Il en profite alors pour attribuer une ligne de cache dans laquelle sera chargée ce défaut. L'attribution est très simple dans le cas des caches ''direct mapped'', ou associatifs par voie, pour lesquels l'attribution se fait assez simplement. Il mémorise alors cette information dans les MSHR, après avoir vérifié que le défaut de cache n'était pas un défaut secondaire. Lors des accès ultérieurs à une adresse proche, censée être dans la même ligne de cache, le processeur va encore une fois rencontrer un défaut de cache. Il va alors déterminer le numéro de la ligne de cache associée à l'adresse, et comparer ce numéro avec les MSHRs. Si un MSHR contient ce numéro, c'est signe que le défaut de cache est un défaut secondaire. La MHA réagit alors différemment selon le processeur considéré. Une première solution n'autorise pas les défauts de cache secondaires. Si l'un d'entre eux survient, le processeur est gelé par un ''pipeline stall'', une bulle de pipeline. Une autre solution fusionne les défauts de cache secondaires avec le défaut de cache primaire : tout cela ne correspond qu'à un seul défaut de cache pour lui. ===Les MSHR simples=== Un cache non-bloquant à '''MSHR simple''' contient juste plusieurs MSHR qui mémorisent juste un numéro de ligne de cache, un bit de validité, le champ de lecture/écriture, et l'adresse exacte de lecture/écriture. Avec cette organisation, il est possible d'avoir plusieurs défauts de cache séparés, mais à la condition que chaque défaut accède à une ligne de cache différente. Deux accès simultanés à une même ligne de cache ne sont pas possibles, les défauts de cache secondaires ne sont pas autorisés. Ainsi, chaque défaut de cache se voit attribuer son propre MSHR, chacun contient un numéro de ligne de cache différent. Pour comprendre pourquoi c'est impossible de gérer les défauts secondaires, il faut regarder le champ de lecture/écriture. Si on veut effectuer plusieurs écritures consécutives à la même adresse, le MSHR n'aura pas de quoi mémoriser les deux données à écrire. Il pourra mémoriser la première donnée à écrire, pas la seconde. Même chose lors d'une lecture : le champ lecture/écriture peut mémorisr la destination de la première lecture, pas de la seconde. L'avantage est que la MHA n'a besoin que des MSHR et de quelques circuits annexes. Les autres solutions rajoutent des circuits annexes pour gérer les défauts de cache secondaires, qui utilisent beaucoup de circuits. Le cout en circuit est donc élevé, mais le gain en performance est là. Passons maintenant aux caches non-bloquants qui autorisent les défauts de cache secondaire. La solution la plus simple consiste à utiliser ===Les MSHR adressés implicitement=== Avec les '''MSHR adressés implicitement''', il est possible de fusionner plusieurs accès mémoire à une même ligne de cache, mais sous une condition très importante : ces accès lisent/écrivent des mots mémoire différents. Par exemple, imaginons qu'une ligne de cache contienne 8 mots mémoire de 64 bits. Si un premier accès mémoire lit le mot mémoire numéro 7 (dernier mot mémoire de la ligne), et le second accès le mot mémoire numéro 3, alors la fusion est possible. Mais si deux accès mémoire veulent lire/écrire le mot mémoire numéro 7, alors la fusion n'est pas possible et le processeur se bloque, un ''pipeline stall'' survient. Un MSHR adressé implicitement est un MSHR simple, qui contient naturellement un numéro de ligne de cache (tag) et un bit de validité, sauf que le champ de lecture/écriture est dupliqué. Les différents champs lecture/écriture d'un MSHR sont regroupés dans une mémoire RAM/cache qui contient autant d'entrées qu'il y a de mot mémoire dans une ligne de cache. Chaque entrée de la table est associée à un mot mémoire de la ligne de cache et stocke des informations sur celui-ci. Une entrée mémorise, au minimum : * Un champ de lecture/écriture qui contient soit la destination de la lecture, soit la donnée à écrire. * Un bit R/W permettant d'interpréter correctement le champ de lecture/écriture. * Un bit de validité pour chaque entrée de la table, qui dit si un défaut de cache antérieur accède déjà à ce mot mémoire. La fusion de deux défauts de cache est ainsi assez simple. Un défaut de cache primaire/secondaire configure l'entrée associée au mot mémoire lu/écrit. Si un défaut secondaire ultérieur lit/écrit un mot mémoire différent (dont le bit de validité de l'entrée dans la table est à zéro), pas de conflit : il configure une autre entrée, vide. Mais s'il lit/écrit un mot mémoire pour lequel l'entrée de la table est occupée, il y a conflit, le processeur est bloqué par un ''pipeline stall'', une bulle de pipeline. Avec cette organisation, le nombre de MSHR indique combien de lignes de cache peuvent être lues en même temps depuis la mémoire. Quant au nombre d'entrées par MSHR, il détermine combien d'accès mémoires qui ne se recouvrent pas peuvent avoir lieu en même temps. ===Les MSHR adressés explicitement=== Les '''MSHR adressés explicitement''' n'ont pas les contraintes des MSHR adressés implicitement. Avec eux, il est possible d'avoir plusieurs défauts de cache pointant vers la même ligne de cache, mais aussi vers le même mot mémoire. De tels défauts de cache apparaissent sur les processeurs à exécution dans le désordre, mais sont très rares, voire inexistants, sur les processeurs ''in-order''. L'idée est encore que chaque MSHR est associée à une table mémoire qui mémorise des entrées. Cette table n'est autre qu'une mémoire FIFO. Sauf que cette fois-ci, une entrée n'est pas associée à un mot mémoire. Une entrée est associée à un défaut de cache. Une entrée mémorise là encore la destination de la lecture ou la donnée de l'écriture suivi du bit R/W, ainsi que d'un bit de validité par entrée, mais aussi : la position du mot mémoire lu dans la ligne de cache. C'est cette dernière information qui n'était pas présente dans les MSHR adressés implicitement. De plus, le nombre d'entrée par MSHR n'est pas égal au nombre de mots mémoires dans une ligne de cache, mais peut être arbitrairement grand. L'avantage d'un tel cache est qu'il est capable de traité les défauts secondaires concernant un même mot mémoire, et ce, car les accès à la ligne de cache concernée se produisent dans l'ordre de génération des défauts (la table mémoire est une FIFO). ===Les MSHRs inversés=== Généralement, plus on veut supporter de défauts de cache, plus le nombre de MSHR et d'entrées augmente. Mais au-delà d'un certain nombre d'entrées et de MSHR, les MSHR adressés implicitement et explicitement ont tendance à bouffer un peu trop de circuits. Utiliser une organisation un peu moins gourmande en circuits est donc une nécessité. Cette organisation plus économe se base sur des MSHR inversés. Les MSHR inversés ne contiennent qu'une seule entrée, en quelque sorte : au lieu d’utiliser n MSHR de m entrées chacun, on va utiliser n × m MSHR inversés. La différence, c'est que plusieurs MSHR peuvent contenir un tag identique, contrairement aux MSHR adressés implicitement et explicitement. Lorsqu'un défaut de cache a lieu, chaque MSHR est vérifié. Si jamais aucun MSHR ne contient de tag identique à celui utilisé par le défaut, un MSHR vide est choisi pour stocker ce défaut, et une requête de lecture en mémoire est lancée. Dans le cas contraire, un MSHR est réservé au défaut de cache, mais la requête n'est pas lancée. Quand la donnée est disponible, les MSHR correspondant à la ligne qui vient d'être chargée vont être utilisés un par un pour résoudre les défauts de cache en attente. ===Les MSHR intégrés au cache=== Certains chercheurs ont remarqué que pendant qu'une ligne de cache est en train de subir un défaut de cache, celle-ci reste inutilisée, et son contenu est destiné à être perdu une fois le défaut de cache résolu. Ils se sont dit que, plutôt que d'utiliser des MSHR séparés, il vaudrait mieux utiliser la ligne de cache pour stocker les informations sur les défauts de cache en attente dans cette ligne de cache. Pour éviter tout problème, il faut rajouter un bit dans les bits de contrôle de la ligne de cache, qui sert à indiquer que la ligne de cache est occupée : un défaut de cache a eu lieu dans cette ligne, et elle stocke donc des informations pour résoudre les défauts de cache. ==L'entrelacement mémoire== Nous venons de voir comment une mémoire cache peut gérer plusieurs accès mémoire simultanés. Il se trouve que ce genre d'optimisation dépasse largement le cadre du cache. Les mémoires RAM ne sont pas en reste, elles aussi ! Il est possible de pipeliner l'accès à une mémoire RAM, en utilisant des techniques dites d''''entrelacement'''. Précisons cependant que cette optimisation n'est pas utilisée sur les mémoires SDRAM ou DDR modernes, du moins pas sans modifications majeures. Mais divers ordinateurs assez anciens, dont des superordinateurs, ont utilisé cette technique. La technique demande d'utiliser plusieurs mémoires séparées. Sur les anciens ordinateurs, les mémoires en question sont des chips mémoire, à savoir des circuits intégrés de DRAM. Pour faire plus général, nous allons utiliser le terme de '''banques''', ou encore de bancs mémoire. La différence est qu'il existe des mémoires multi-banque, qui regroupent plusieurs banques indépendantes dans un seul boitier, dans un seul chip mémoire. En clair, de telles mémoires regroupent plusieurs sous-mémoires dans un seul circuit intégré. Il est aussi possible d'utiliser plusieurs chips mémoire séparés, chacun ayant une banque. Reste à combiner ces banques pour former un pseudo-pipeline. ===Utiliser plusieurs banques sans entrelacement=== Sans optimisation particulière, les adresses sont réparties dans les banques comme indiqué ci-dessous. Il s'agit de ce qui s'appelle un arrangement horizontal, et nous avions vu celui-ci dans le chapiutre sur les mémoires SDRAM. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Avec cet arrangement, les bits de poids fort de l'adresse sont utilisées pour sélectionner la banque adéquate, et le reste de l'adresse est envoyé sur le bus d'adresse. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Adresse de banque !! Adresse dans la banque |- | Quelques bits de poids fort || Reste de l'adresse |} L'entrelacement change cette répartition, afin d'accélérer les accès mémoire. L'entrelacement classique vise à améliorer les accès à des adresses mémoires consécutives, mais des formes plus évoluées d'entrelacement visent à optimiser des accès mémoire arbitraires. Voyons-les dans l'ordre, du plus simple au plus complexe. ===L'entrelacement classique=== Avec l''''entrelacement classique''', des adresses consécutives sont réparties dans des banques consécutives. Précisons que cette attribution des adresses n'implique absolument pas la mémoire virtuelle ou n'importe quel mécanisme dans le processeur. La répartition décide que telle adresse mémoire va dans telle banque, à telle adresse dans la banque. Elle est donc le fait du contrôleur mémoire, donc en dehors du processeur (les contrôleurs mémoires n’étaient pas encore intégrés dans le processeur à l'époque). [[File:Répartition des adresses dans une mémoire interleaved.png|centre|vignette|upright=1.5|Répartition des adresses dans une mémoire interleaved.]] L'entrelacement simple permet d'accélérer les accès mémoire qui se font à des adresses consécutives. Avec l'entrelacement, chaque accès mémoire tombe dans une banque mémoire différente, ce qui fait qu'on peut démarrer un nouvel accès mémoire à chaque cycle d'horloge. Pendant qu'une banque est occupée par un accès mémoire, on démarre le suivant dans une autre banque, et ainsi de suite. Le tout se termine soit quand on a épuisé toutes les banques libres, soit quand l'accès en rafales se termine. Pas besoin d'attendre que la mémoire ait fini sa lecture/écriture avant de démarrer la lecture/écriture suivante. [[File:Pipemining mémoire.png|centre|vignette|upright=2.5|Pipelining mémoire]] L'animation suivante illustre bien le processus, avec quatres banques. [[File:Interleaving.gif|centre|vignette|upright=2.5|Pipelining mémoire]] L'entrelacement simple utilise les bits de poids faible pour sélectionner la banque, et les bits de poids fort pour la case mémoire. [[File:Adresse mémoire d'une mémoire entrelacée.png|centre|vignette|upright=2|Adresse mémoire d'une mémoire entrelacée]] Précisons que le temps d'accès mémoire ne change pas beaucoup avec l'entrelacement. Par contre, on peut faire plus d'accès mémoire simultanés. On peut démarrer un accès mémoire par cycle d'horloge, mais l'accès en lui-même prend plusieurs cycles. Les mémoires à entrelacement ont donc un débit supérieur aux mémoires qui ne l'utilisent pas. L'entrelacement classique pourrait être utilisé pour accélérer les transferts entre mémoire RAM et cache, qui se font en blocs d'adresses consécutives. Mais de nos jours, il n'est pas utilisé dans cette optique. Il faut dire qu'il est redondant avec le mode rafale des mémoires DRAM. Les deux font globalement la même chose et on voit mal comment utiliser les deux en même temps. Le mode rafale étant plus simple à implémenter, et plus léger niveau utilisation du bus de commande mémoire, il est préféré à l'entrelacement. Par contre, l'entrelacement a eu son heure de gloire sur d'anciens processeurs qui n'avaient pas de mémoire cache, alors qu'ils disposaient d'un pipeline. L'exemple typique est celui des nombreux ''processeurs vectoriels'', que nous n'avons pas encore abordé à ce stade du cours. De tels processeurs avaient un pipeline, une unité de calcul fortement pipelinée, et parfois même de l'exécution dans le désordre et du renommage de registres ! ===L'entrelacement par décalage=== L'entrelacement simple est très utile pour les accès en rafle ou équivalents. Par contre, il existe de rares situations où il n'est pas optimal. Une de ces situations est celle des accès en enjambée (en ''stride''), où l'on accède en série à des adresses sont séparées par N mots mémoires. [[File:Accès par enjambées.png|centre|vignette|upright=2|Accès par enjambées.]] De tels accès surviennent quand un logiciel accède à des structures de données spécifiques, à savoir des tableaux de structures, des matrices, ou d'autres structures de données dans le genre. Un cas classique est celui du parcours d'une matrice colonne par colonne. Les matrices de nombres sont mémorisées ligne par ligne, ce qui fait que parcourir une colonne demande de passer d'une ligne à la suivante et de faire des grands sauts en mémoire RAM, mais tous espacés par la même distance. Avec l'entrelacement simple, les accès en enjambée sont moins performants que les accès à des adresses consécutives, mais cela ne fonctionne pas trop mal. Le pire cas est celui où l'on a N banques et où les données sont justement placées toutes les N adresses. Des accès consécutifs vont tous tomber dans la même banque, on ne peut plus accéder à des banques différentes en parallèle. Mais en dehors de ce cas, on voit une amélioration par rapport à la situation sans entrelacement. Pour obtenir des performances maximales pour les accès en enjambées, il faut répartir les mots mémoires dans la mémoire autrement. L'organisation idéale est la suivante. : Dans les explications qui vont suivre, la variable N représente le nombre de banques, qui sont numérotées de 0 à N-1. On commence par organiser les N premières adresses comme une mémoire entrelacée simple : l'adresse 0 correspond à la banque 0, l'adresse 1 à la banque 1, etc. Pour le bloc suivant, on décale tout d'une adresse, à savoir qu'on commence à remplir les banques à partir de la seconde, non de la première. Une fois la fin du bloc atteinte, on finit de remplir le bloc en repartant du début du bloc. Le troisième bloc d'adresse subit le même traitement, sauf qu'on commence à remplir à partir de la seconde banque. Et on poursuit l’assignation des adresses en décalant d'un cran en plus à chaque bloc. Ainsi, chaque bloc verra ses adresses décalées d'un cran en plus comparé au bloc précédent. Si jamais le décalage dépasse la fin d'un bloc, alors on reprend au début. [[File:Mémoire interleaved par décalage.png|centre|vignette|upright=1.5|Mémoire entrelacée par décalage.]] En faisant cela, on remarque que les banques situées à N adresses d'intervalle sont différentes. Dans l'exemple du dessus, nous avons ajouté un décalage de 1 à chaque nouveau bloc à remplir. Mais on aurait tout aussi bien pu prendre un décalage de 2, 3, etc. Dans tous les cas, on obtient un entrelacement par décalage. Ce décalage est appelé le pas d'entrelacement, noté P. Le calcul de l'adresse à envoyer à la banque, ainsi que la banque à sélectionner se fait en utilisant les formules suivantes : * adresse à envoyer à la banque = adresse totale / N ; * numéro de la banque = (adresse + décalage) modulo N ; * décalage = (adresse totale * P) mod N. Avec cet entrelacement par décalage, on peut prouver que la bande passante maximale est atteinte si le nombre de banques est un nombre premier. Seulement, utiliser un nombre de banques premier peut créer des trous dans la mémoire, des mots mémoires inadressables. Malgré ce défaut, la technique a été utilisée sur quelques ordinateurs, avec l'exemple notable du superordinateur ''Burroughs Scientific Processor''. Pour éviter cela, il y a plusieurs solutions. Par exemple, on peut faire en sorte que N et la taille d'une banque soient premiers entre eux : ils ne doivent pas avoir de diviseur commun. Mais en pratique, elles n'ont pas vraiment été implémentées dans une vraie machine, et sont restées à l'état de recherche, aussi je les passe sous silence. ===L'entrelacement pseudo-aléatoire=== Une dernière méthode de répartition consiste à répartir les adresses dans les banques de manière "pseudo-aléatoire". L'idée exacte est de faire passer l'adresse dans une '''fonction de hachage''', plus ou moins complexe. Pour rappel, une fonction de hachage prend une entrée de grande taille et fournit en sortie un résultat de petite taille. Idéalement, elles doivent donner des résultats aussi différents que possible pour des entrées similaires, histoire de simuler une sorte de pseudo-aléatoire. Ici, une adresse est transformée en un numéro de banque. Une fonction de hachage simple ne fait que permuter des bits de l'adresse pour obtenir son résultat. Elle ne fait qu'échanger des bits de place avant de couper l'adresse en deux morceaux : un pour la sélection de la banque, et un autre pour la sélection de l'adresse dans la banque. Cette permutation est fixe, et ne change pas suivant l'adresse. Des fonctions de hachage plus complexes font des XOR bit à bit entre certains bits de l'adresse. ==Les contrôleurs mémoires SDRAM/DDR optimisés== Après avoir vu le cas des mémoires RAM, nous allons nous concentrer sur le cas particulier des mémoires SDRAM. Les mémoires SDRAM et DDR modernes sont capables de gérer plusieurs accès simultanés à la mémoire RAM, et leurs contrôleurs mémoire en font tout autant. C'est une différence majeure avec les mémoires asynchrones FPM/EDO, qui n'acceptaient qu'un seul accès mémoire à la fois. Leur contrôleur mémoire n'acceptait qu'un seul accès mémoire à la fois, c'était un contrôleur mémoire bloquant. Les contrôleurs mémoires des SDRAM sont eux non-bloquants et peuvent encaisser une dizaine d'accès mémoire à la fois. Peu de choses sont connues sur les contrôleurs de SDRAM/DDR modernes, les fabricants ne donnant que peu de détails dessus. Les rares simulateurs qui tentent de décrire leur fonctionnement, comme DRAM SIM I et II, sont particulièrement simples et ne vont pas dans le détail. Néanmoins, le peu qu'on sait est tout de même instructif. ===Rappel sur les SDRAM : tampon de ligne et commandes=== Les mémoires SDRAM sont des mémoires à tampon de ligne. Elles font un accès mémoire en deux étapes. La première étape recopie une ligne de N * 64 bits dans un tampon interne à la SDRAM. La seconde étape sélectionne une colonne, à savoir une donnée de 64 bits, qui est soit envoyée sur le bus de données pour une lecture, soit modifiée par une écriture. [[File:Mémoire à tampon de ligne.png|centre|vignette|upright=2|Mémoire à tampon de ligne]] Les deux étapes correspondent à deux commandes séparées : une commande ACT qui précise l'adresse de la ligne, et une commande READ ou WRITE qui précise l'adresse de la colonne. Les commandes ACT sont systématiquement précédées d'une commande PRECHARGE qui ferme la ligne précédente. Pour être plus précis, elle précharge les lignes de bits du plan mémoire à une tension particulière, ce qui les vide de leur contenu. Mais c'est un détail sans importance pour ce qui va suivre. Les SDRAM permettent de se passer de la première étape si des accès consécutifs se font dans la même ligne. Une fois activée, la ligne reste ouverte et on peut accéder plusieurs fois de suite dedans. On a alors juste à préciser l'adresse de colonne dedans. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | || bgcolor="#A0FFFF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || bgcolor="#A0FFFF" | READ (2) || bgcolor="#A0FFFF" | READ (3) || || || bgcolor="#A0FFFF" | READ (4) || bgcolor="#A0FFFF" | READ (5) || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | READ 1 || ||bgcolor="#A0FFFF" | READ 2 | bgcolor="#A0FFFF" | READ 3 || || || bgcolor="#A0FFFF" | READ 4 || bgcolor="#A0FFFF" | READ 5 |} ===La mise en attente des accès mémoire=== Un contrôleur de SDRAM peut accepter plusieurs accès mémoire en même temps. Mais cela ne signifie pas que le contrôleur sera prêt à les traiter immédiatement. Pour éviter tout problème de disponibilité, le contrôleur met en attente les accès mémoire que le processeur lui envoie, pour les exécuter dès que possible. Les accès mémoire sont mis en attente dans une mémoire FIFO, histoire de les exécuter dans leur ordre d'arrivée, notamment pour renvoyer les lectures dans l'ordre demandé. Il y a aussi une mémoire FIFO pour les données à écrire et une FIFO pour les données lues. Cette dernière sert au cas où le cache ne soit pas disponible quand on lui envoie la donnée lue. [[File:Module d'interface avec la mémoire.png|centre|vignette|upright=2.5|Contrôleur mémoire avec mise en attente des requêtes processeur et autres optimisations.]] Des optimisations sont possibles dès la mémoire FIFO. Par exemple, on peut regrouper plusieurs accès à des données consécutives en un seul accès en rafale. Le contrôleur mémoire analyse les requêtes mises en attente et détecte si certaines se font à des adresses consécutives. Si c'est le cas, il fusionne ces requêtes en une lecture/écriture en rafale. Une telle optimisation est appelée la '''combinaison de lecture''' pour les lectures, et la '''combinaison d'écriture''' pour les écritures. ===Le pipeline des mémoires SDRAM=== Les accès mémoire sont traduits par un séquenceur mémoire en une série de commandes mémoires, qui sont séparées par des délais mémoire de quelques cycles d'horloge. Les délais sont très précis, et sont à respecter à la lettre. Les SDRAM sont parfois capables de démarrer une commande avant que la précédente soit terminée. Mais le respecte des délais mémoire est très limitant, ce qui fait qu'on ne peut pas parler de réel pipeline, comme c'est le cas sur les processeurs. Parlons plutot de commandes anticipées. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | || bgcolor="#A0FFFF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || || || bgcolor="#FFA0FF" | READ (2) || || || || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 | bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 | |} Même si elle parait très limitée, cette possibilité est décuplée par la présence de plusieurs banques dans la mémoire SDRAM. Les chips mémoires de SDRAM sont composés de plusieurs banques, chacune étant une sorte de mini-mémoire miniature. Chaque banque a son propre décodeur, son propre tampon de ligne, ses propres multiplexeurs de colonne, sa logique de rafraichissement mémoire, etc. C'est comme si une SDRAM regroupait plusieurs mémoires séparées dans un même circuit intégré. Et cela permet de faire plusieurs accès mémoire en même temps, dans des banques différentes. Il est possible de lancer un accès mémoire dans deux banques en même temps, par exemple. Ou encore, on peut lancer un nouvel accès mémoire dans une banque si elle est inoccupée. La possibilité améliore grandement la performance de la SDRAM. Mais nous en reparlerons dans un chapitre ultérieur. {|class="wikitable" |+ Pipelining basique sur les SDRAM |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 |- ! Banque Numéro 1 | || || || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || || || |- ! Banque Numéro 2 | || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || |- ! Banque Numéro 3 | || || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || |} L'implémentation n'a rien de vraiment compliqué. Il suffit d'utiliser une mémoire FIFO par banque. Ainsi, les accès dans une même banque se feront en série, dans l'ordre d'arrivée, mais des accès à des banques différentes se feront dans le désordre. Cela ne pose pas de problème car les accès se font à des adresses différentes, ce qui fait qu'il n'y a pas de conflits majeurs. [[File:Gestion parallèle des banques.png|centre|vignette|upright=2.5|Gestion parallèle des banques.]] ===Le ré-ordonnancement des commandes mémoires=== Un contrôleur mémoire moderne est capable de changer l'ordre des requêtes mémoires, pour gagner en performances. En clair : il peut faire l'équivalent mémoire de l'exécution dans le désordre des processeurs haute performance. Une différence majeure est que le contrôleur mémoire ne remet pas les accès mémoire dans l'ordre avant de les envoyer au processeur. C'est le processeur qui remet en ordre les accès mémoire, dans sa ''load queue''. Pour cela, les requêtes mémoire sont "numérotées", à savoir qu'on leur attribue un identifiant binaire. Le contrôleur mémoire exécute les lectures/écritures dans le désordre, mais garde la trace de leur identifiant. Il sait dans quel ordre il exécute les accès mémoire et connait leur latence. Ce qui fait qu'il sait que la donnée lue à tel instant est associée à la requête mémoire numéro X. Pour les lectures, il renvoie la donnée lue avec l'identifiant. Le processeur sait alors à quelle lecture la donnée lue correspond et il se débrouille en interne pour gérer la situation. En clair : le processeur voit les accès mémoire dans le désordre, c'est lui qui les remet en ordre. Maintenant que ces précisions sont faites, posons cette question : quel est l'intérêt de faire les accès mémoire dans le désordre ? Accéder à des banques en parallèle est une réponse possible, mais le ré-ordonnancement est surtout utile si tous les accès mémoire atterrissent dans la même banque. L'exécution dans le désordre des lectures/écritures SDRAM vise à faire en sorte que des accès consécutifs se fassent dans une même ligne. Elle marche si plusieurs accès à une même ligne ne sont pas consécutifs, et qu'il y a des accès entre les deux. Après ré-ordonnancement, ces accès mémoire deviennent consécutifs, ils sont exécutés l'un à la suite de l'autre. Pour rendre le tout plus concret, voici un exemple. Imaginez que l'on sait les 3 accès mémoire suivants : * Une lecture ligne A ; * Une écriture ligne B ; * Une lecture ligne A. Sans réordonnancent, on doit émettre deux commandes PRECHARGE : une quand on passe de la ligne A à la ligne B, et une autre pour le passage inverse. pour éviter cela, le contrôleur mémoire peut retarder l'écriture, ou au contraire avancer la seconde lecture. le résultat est alors le suivant : * Lecture ligne A ; * Lecture ligne A ; * Une écriture ligne B. On a donc deux accès consécutifs à la même ligne, suivi par une écriture dans une autre ligne. La technique marche parce que l'on a un mix adéquat de lectures et d'écritures. Mais il existe des possibilités de réorganisation autres qui ne sont pas valides. Il faut par exemple prendre garde à éviter d'intervertir lectures et écritures à une même adresse, sous peine de problèmes. Encore une fois, il faut faire attention aux dépendances mémoires. Le controleur mémoire teste les dépendances mémoires avant d'envoyer des commandes à la mémoire DDR/SDRAM. ==L'entrelacement sur les mémoires SDRAM== L''''entrelacement''' fonctionne sur les mémoires SDRAM, mais il doit être fortement modifié. Concrètement, tout ce qui a étré dit plus est inapplicable pour les SDRAM, du fait de la présence du tampon de ligne. Des adresses consécutives atterrissent déjà dans la même ligne, ce qui fait qu'on n'a pas besoin d'entrelacement au niveau d'une ligne. Et ne parlons pas de ce la présence de rangées et de canaux mémoire ! Cependant, il peut y avoir un entrelacement lié à la présence des banques SDRAM. L'entrelacement est géré juste avant le séquenceur mémoire. Le '''circuit d'entrelacement''' intervertit certains bits de l'adresse lors des accès mémoires. On trouve aussi des mémoires FIFOs pour mettre en attente les requêtes mémoire provenant du processeur. Elles permettent de recevoir plusieurs accès mémoire consécutifs. Si vous vous demandez comment plusieurs accès mémoire consécutifs peuvent avoir lieu, sachez que c'est une bonne question. Pour le moment, nous allons dire que ces accès proviennent du processeur ou de la mémoire cache, sans expliquer comment. Le contrôleur mémoire reçoit une série d'accès mémoire assez rapprochés, qu'il doit exécuter au mieux. [[File:Controleur mémoire d'une SDRAM avec entrelacement.png|centre|vignette|upright=2.5|Contrôleur mémoire d'une SDRAM avec entrelacement.]] Pour gérer l'entrelacement, le contrôleur de SDRAM prend en entrée l'adresse envoyée par le processeur, et la découpe en plusieurs champs : un pour sélectionner la banque adéquate, un autre pour la ligne, un autre pour la colonne, un autre pour la rangée. Une solution simple désactive l'entrelacement. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de banque || Adresse de ligne || Adresse de colonne |} Si cette solution fonctionne bien si les accès mémoire sont assez aléatoires, en pratique, mieux vaut utiliser l'entrelacement. ===L'entrelacement de banques/lignes=== Une première méthode d'entrelacement est l''''entrelacement de banques'''. Elle répartit deux lignes consécutives dans deux banques différentes. Pour comprendre l'idée, prenons un exemple. Imaginons une mémoire avec deux banques et 4 lignes. Imaginons qu'on parcoure/balaye la mémoire RAM en partant des adresses basses. Sans entrelacement de ligne, les accès se feront comme suit, de gauche à droite : {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Banque numéro 1 !! colspan="4" | Banque numéro 2 |- | Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 || Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 |} Avec l'entrelacement de banques, les accès se feront comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 |- | Ligne 1 || Ligne 1 || Ligne 2 || Ligne 2 || Ligne 3 || Ligne 3 || Ligne 4 || Ligne 4 |} Avec N banques, la première ligne ira dans la première banque, la seconde ligne dans la seconde, et ainsi de suite jusqu'à la énième banque qui va dans la banque numéro N. Et la ligne N+1 repart dans la première banque. L'avantage est que passer d'une ligne à la suivante est assez rapide. Au lieu de devoir fermer la première ligne et ouvrir la suivante on ouvrira directement la ligne suivante dans une banque différente, ce qui sera plus rapide. La commande PRECHARGE pourra être envoyée à la seconde banque en avance. Pour cela, l'adresse est découpée comme suit. {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne (précise à l'octet) |} ===L'entrelacement de rangées=== Il est aussi possible de faire la même chose, mais avec les rangées. Pour simplifier fortement, une rangée est simplement un chip mémoire. En réalité, les rangées ne sont pas des chip mémoire, mais un ensemble de chips mémoire regroupés ensemble histoire d'atteindre les 64 bits du bus de données. Par exemple, une rangée peut combiner 8 chips mémoire avec un bus de données de 8 bits chacun pour obtenir les 64 bits du bus de données d'une SDRAM. Mais nous passons ce détail sous silence dans les explications qui vont suivre, par souci de simplification. Pour faire comprendre l'entrelacement de rangée, le mieux est d'utiliser un exemple, le même que précédemment. Sans entrelacement de rangée, on change de chip mémoire une fois qu'on a balayé toutes ses adresses. {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Rangée numéro 1 !! colspan="4" | Rangée numéro 2 |- | Banque 1 || Banque 2 || Banque 3 || Banque 4 || Banque 1 || Banque 2 || Banque 3 || Banque 4 |} Avec l'entrelacement de ligne, on change de rangée dès qu'on change de banque/ligne. {|class="wikitable" |+ Adresse mémoire |- ! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 |- | Banque 1 || Banque 1 || Banque 2 || Banque 2 || Banque 3 || Banque 3 || Banque 4 || Banque 4 |} En clair, quand on a épuisé toutes les banques dans une même rangée, on passe à la rangée suivante au lieu de rester dans la même rangée. Notons que dans l'exemple précédent, on a combiné l'entrelacement de rangée et de banque, mais on aurait pu utiliser l'entrelacement de rangée seul. Mais ce n'est pas le cas le plus courant en pratique. Toujours est-il qu'en combinant entrelacement de rangée et de ligne, le découpage de l'adresse est le suivant : {|class="wikitable" |+ Adresse mémoire |- | Adresse de ligne || Numéro de rangée || Adresse de banque || Adresse de colonne (précise à l'octet) |} D'autres méthodes d'entrelacement sont possibles, mais elles sont plus complexes. Chaque contrôleur mémoire fait un peu à sa sauce. Ils permettent en général de choisir entre plusieurs entrelacements. Au minimum, ils permettent de désactiver l'entrelacement, d'activer un entrelacement de ligne, et d'autres entrelacements plus complexes, dont certains sont propriétaires et tenus secrets par le fabricant. ===L'entrelacement avec le ''dual channel''=== [[File:Dual channel slots.jpg|vignette|Slots mémoires avec ''dual channel''.]] L'usage du ''dual channel'' complique encore l'entrelacement. Là encore, il y a deux grandes solutions : avec et sans entrelacement des canaux mémoire. Rappelons le principe : deux barrettes de RAM sont accédées en parallèle. Et pour cela, il faut utiliser l'entrelacement. Typiquement, chaque barrette mémoire fournit 64 bits, ce qui fait que l'on peut accéder à 128 bits d'un seul coup, par exemple avec un accès en rafale. Sans ''dual channel'', la première barrette correspond à la moitié haute de la RAM, la seconde barrette correspond à la moitié basse. Avec ''dual channel'', une forme spécifique d'entrelacement est activée. Concrétement, deux blocs de 64 bits sont placés dans des canaux mémoire séparés. Idem avec du triple ou quadruple canal, mais c'est alors trois ou quatre blocs de 64 bits qui sont dispersés dans des canaux consécutifs. Le découpage de l'adresse est alors le suivant : {|class="wikitable" |+ Adresse mémoire |- ! colspan="6" | Sans entrelacement |- | Numéro de canal/barrette || Reste de l'adresse || Position de l'octet dans un bloc de 64 bits |- | colspan="6" | |- ! colspan="6" | Avec entrelacement |- | Reste de l'adresse || Numéro de canal/barrette || Position de l'octet dans un bloc de 64 bits |} [[File:Décodage d'adresse avec dual channel.png|centre|vignette|upright=2|Décodage d'adresse avec dual channel.]] Les contrôleurs de SDRAM précédents sont assez basiques et ne représentent pas les contrôleurs les plus évolués. Ils étaient utilisés sur les anciens PC, à une époque où ils étaient encore sur la carte mère du processeur. Mais de nos jours, le contrôleur mémoire est intégré au processeur. Et il incorpore de nombreuses optimisations afin de gagner en performances. Malheureusement, ces optimisations sont elles-mêmes dépendantes des optimisations intégrées dans le processeur, ce qui fait qu'on ne peut pas en parler ici. Elles demandent que le contrôleur mémoire reçoive plusieurs accès mémoire simultanés, ce qui n'a aucun sens à ce stade du cours. Aussi, nous allons les passer sous silence pour le moment. Nous les verrons dans le chapitre sur le parallélisme mémoire. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=La désambiguïsation mémoire | prevText=La désambiguïsation mémoire | next=Les processeurs superscalaires | nextText=Les processeurs superscalaires }} </noinclude> 7xorwk4hql3dfe8hlvzm3jrm4qqgooq 764711 764710 2026-04-23T21:12:41Z Mewtow 31375 /* Rappel sur les SDRAM : tampon de ligne et commandes */ 764711 wikitext text/x-wiki Dans ce chapitre, nous allons voir les techniques qui permettent de gérer plusieurs accès mémoire simultanés directement au niveau du cache ou de la mémoire RAM. Par plusieurs accès mémoire simultanés, vous pensez sans doute à l'usage de cache multiports, voire de mémoires RAM multiport. C'est en effet une solution, mais ce n'est clairement pas la seule. Par exemple, il est possible de pipeliner l'accès au cache, voire à la mémoire RAM. Il existe de nombreuses techniques de '''parallélisme mémoire''', et nous allons les voir dans ce chapitre. Pour donner un exemple, il est possible de pipeliner les accès mémoire. Il est possible de pipeliner l'accès au cache et/ou l'accès à la mémoire, et nous verrons comment faire dans ce chapitre. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, qu'ils aient ou non un pipeline dynamique. Par contre, pipeliner la mémoire est une technique ancienne, aujourd'hui peu utilisée. Elle était utilisée sur des très vieux ordinateurs, qui avaient un pipeline mais pas de mémoire cache. Mais de nos jours, les mémoires SDRAM et DDR ne sont pas adaptées pour ça, il est très difficile de les pipeliner correctement. Le parallélisme mémoire est utile aussi bien sur des processeurs à émission dans l'ordre que dans le désordre. Par exemple, un processeur ''in-order'' avec des lectures non-bloquantes peut e, profiter. Il peut techniquement lancer une seconde lecture, après avoir rencontré une première lecture non-bloquante. En général, le nombre de lectures consécutives est cependant limité à moins d'une dizaine. À l'opposé, les processeurs sans lectures non-bloquantes profitent pas du parallélisme mémoire. À la rigueur, ils peuvent en profiter s'ils intègrent des techniques de préchargement. Pendant que le processeur exécute une lecture/écriture, il peut précharger une autre donnée en parallèle. Inutile de dire que sans pipeline processeur, le parallélisme mémoire ne sert pas à grand-chose. ==Le ''line fill buffer'' et les techniques associées== Lors d'un défaut de cache, le processeur doit attendre que toute la ligne de cache soit chargée avant d'être utilisable. Or, la taille d'une ligne de cache est supérieure à la largeur du bus mémoire, ce qui fait qu'une ligne de cache est chargée en plusieurs fois, morceaux par morceaux, mot mémoire par mot mémoire. Le chargement peut se faire directement dans le cache, mais ce n'est pas une solution très pratique. À la place, beaucoup de processeurs ajoutent une mémoire tampon entre la RAM et le cache, appelée le '''tampon de remplissage de ligne''' (''line-fill buffer'') dans les processeurs modernes. Lors d'un défaut de cache, le processeur charge la donnée de la RAM dans le tampon de remplissage de ligne, mot mémoire par mot mémoire. Une fois plein, le tampon de remplissage de ligne est recopié dans la ligne de cache. Le tampon de remplissage de ligne contient une ligne de cache, si ce n'est que certains bits de contrôle ne sont pas présents. Le tampon de remplissage de ligne contient un bit de validité pour chaque mot mémoire de la ligne de cache, qui indiquent si le mot mémoire a été chargé. Par exemple, prenons un processeur 64 bits, qui gère donc des mots mémoire de 8 octets, avec des lignes de cache 256 octets/32 mots mémoire. Le tampon de remplissage de ligne contiendra 32 bits de validité. Si le processeur a chargé les 6 premiers mots mémoire, les 6 premiers bits de validité seront mis à 1, les autres seront encore à zéro. Il faut noter que la disponibilité de la ligne complète se détermine assez facilement en faisant un ET logique entre tous les bits de validité. Le processeur sait ainsi quand la ligne de cache est disponible entièrement et donc quand la transférer dans le cache. La même chose existe avec une hiérarchie de cache, sauf que l'on trouve une mémoire tampon entre chaque niveau de cache. Il y a un tampon de remplissage de ligne entre le cache L1 et le cache L2, entre le cache L2 et le L3, etc. Si le cache ne gère qu'un seul défaut de cache à la fois, le ''fill line buffer'' est une mémoire très simple, qui ne mémorise qu'une seule ligne de cache. Et cela vaut aussi bien pour un cache bloquant que non-bloquant. Mais sur les caches capables de gérer plusieurs défauts simultanés, le tampon de remplissage de ligne est une mémoire de type FIFO ou LIFO, capable de mémoriser plusieurs lignes de cache. Notons que le tampon de remplissage de ligne est très utile pour implémenter certaines techniques, par exemple le contournement du cache. Nous avions vu dans le chapitre sur le cache que certains accès mémoire doivent contourner le cache, pour des raisons de cohérence des caches. C'est notamment nécessaire pour accéder aux périphériques, mais c'est aussi utile pour des raisons de performances dans des cas très spécifiques. Les accès qui contournent le cache se font directement dans le tampon de remplissage de ligne : le processeur écrit ou lit les données depuis ce tampon de remplissage de ligne, sans accéder au cache. ===L'''early restart'' et le ''critical word load''=== La présence du ''line buffer'' permet une optimisation assez intéressante, qui permet de réduire la latence des défauts de cache. L'optimisation consiste à lire un mot mémoire dans le tampon de remplissage de ligne, même si la ligne de cache complète n'a pas encore été chargée. Il existe deux manières de faire cela, qui portent les noms d'''early restart'' et de ''critical word load''. La première est la version la plus simple, la seconde est plus complexe mais plus performante. Avec la technique d''''''early restart''''', la ligne de cache est chargée normalement, en partant de son premier mot mémoire. Dès que le mot mémoire lu/écrit par le processeur est copié dans le ''line fill buffer'', il est envoyé au processeur immédiatement. Illustrons le tout par un exemple, où une ligne de cache fait 16 mots mémoire. Le processeur effectue une lecture, qui lit le 5ème mot mémoire. Sans ''early restart'', le processeur doit charger les 16 mots mémoire avant de faire la lecture dans le cache. Avec ''early restart'', le processeur reçoit la donnée dès que le 5ème mot mémoire est disponible. Le processeur doit attendre que les 4 premiers mots mémoire soient chargés, puis le 5ème mot mémoire arrive et est envoyé directement au processeur, il est lu directement depuis le ''line fill buffer''. Les 11 mots mémoire suivants sont ensuite chargés dans le cache pendant que le processeur fait des calculs dans son coin. Le ''critical word load'' est une optimisation de la technique précédente où le chargement de la ligne de cache commence directement à la donnée demandée par le processeur. Pour reprendre l'exemple précédent, où le processeur demande le 5ème mot mémoire sur 16, le ''critical word load'' charge le 5ème mot mémoire en premier et l'envoie au processeur, ce qui fait qu'il est chargé très rapidement. Pas besoin d'attendre que le processeur charge les 4 mots mémoire précédents comme avec l'''early restart''. Dans le détail, le processeur charge le 5ème mot mémoire en premier, puis charge les 11 suivants, et termine par les 4 mots mémoire du début. En clair, le ''critical word load'' commence par charger le mot mémoire lu, puis les blocs suivants, avant de revenir au début du bloc pour charger les blocs restants. Ainsi, la donnée demandée par le processeur sera la première disponible. Pour cela, l'organisation du tampon de remplissage de ligne est modifiée de manière à rendre cela possible. Il a une taille égale à une ligne de cache complète, qui contient elle-même plusieurs mots mémoire. Dans le ''line-fill buffer'', chaque mot mémoire est stocké avec un tag, qui indique l'adresse du mot mémoire stocké dans le ''line-fill buffer''. Le ''line-fill buffer'' est donc un cache un peu particulier, qui fonctionne comme un cache du point de vue du processeur, comme une mémoire FIFO pour les transferts avec le cache. Ainsi, un processeur qui veut lire dans le cache après un défaut peut accéder à la donnée directement depuis le tampon de remplissage de ligne, alors que la ligne de cache n'a pas encore été totalement recopiée en mémoire. ==Les caches pipelinés== Il est possible de pipeliner l'accès au cache, ce qui demande juste de rajouter des registres au bon endroits et quelques circuits de contrôle. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, même ceux avec un pipeline dynamique. Et l'implémentation peut se faire de plusieurs manières différentes. Il faut dire que découper une mémoire cache en plusieurs étapes peut se faire de plusieurs manière. Commençons par la plus simple. Beaucoup de processeurs des années 2000 avaient une fréquence peu élevée comparée aux standards d'aujourd'hui, ce qui fait que leur cache avait bien un temps d'accès d'un cycle d'horloge. Mais l'accès au cache se faisait en deux cycles d'horloge : un cycle pour calculer une adresse et un cycle pour l'accès mémoire proprement dit. Et ces deux étapes étaient pipelinées, à savoir que deux micro-opérations mémoire peuvent s'exécuter en même temps : une dans l'étage de calcul d'adresse, une autre dans le cache. L'unité mémoire était donc pipelinée, alors que l'accès au cache ne l'était pas. Les processeurs qui implémentaient cette technique regroupent les micro-architectures K5 et K6 d'AMD, les processeurs Intel de micro-architecture P6 (Pentium 2 et 3) et quelques autres. Il est aussi possible de pipeliner l'accès au cache lui-même. Avec un cache pipeliné, l'accès au cache ne se fait pas en un seul cycle, mais en plusieurs. Cependant, on peut lancer un nouvel accès au cache à chaque cycle d'horloge, comme un processeur pipeliné lance une nouvelle instruction à chaque cycle. L'implémentation est assez simple : il suffit d'ajouter des registres dans le cache. Pour cela, on profite que le cache est composé de plusieurs composants séparés, qui échangent des données dans un ordre bien précis, d'un composant à un autre. De plus, le trajet des informations dans un cache est linéaire, ce qui les rend parfois pour l'usage d'un pipeline. ===Le ''pipelining'' des caches ''direct-mapped''=== Voici ce qui se passe avec un cache directement adressé. Pour rappel ce genre de cache est conçu en combinant une mémoire RAM, généralement une SRAM, avec quelques circuits de comparaison et un MUX. L'accès au cache se fait globalement en deux étapes : la première lit la donnée et le ''tag'' dans la SRAM, et on utilise les deux dans une seconde étape. Nous avions vu il y a quelques chapitre comment pipeliner des mémoires, dans le chapitre sur les mémoires évoluées. Et bien ces méthodes peuvent s'utiliser pour la mémoire RAM interne au cache !L'idée est d'insérer un registre entre la sortie de la RAM et la suite du cache, pour en faire un cache pipéliné, à deux étages. Le premier étage lit dans la SRAM, le second fait le reste. L'implémentation sur les caches associatifs à plusieurs voies est globalement la même à quelques détails près. [[File:Cache directement adressé pipeliné - deux étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - deux étages]] Avec le circuit précédent, il est possible d'aller plus loin, cette fois en pipelinant l'accès à la mémoire RAM interne au cache. Pour comprendre comment, rappelons qu'une mémoire SRAM est composée d'un plan mémoire et d'un décodeur. L'accès à la mémoire demande d'abord que le décodeur fasse son travail pour sélectionner la case mémoire adéquate, puis ensuite la lecture ou écriture a lieu dans cette case mémoire. L'accès se fait donc en deux étapes successives séparées, on a juste à mettre un registre entre les deux. Il suffit donc de mettre un gros registre entre le décodeur et le plan mémoire. [[File:Cache directement adressé pipeliné - trois étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - trois étages]] Et on peut aller encore plus loin en découpant le décodeur en deux circuits séparés. En effet, rappelez-vous le chapitre sur les circuits de sélection : nous avions vu qu'il est possible de créer des décodeurs en assemblant des décodeurs plus petits, contrôlés par un circuit de prédécodage. Et bien on peut encore une ajouter un registre entre ce circuit de prédécodage et les petits décodeurs. : Théoriquement, toute l'adresse est fournie d'un seul coup au cache, la quasi-totalité des processeurs présentent l'adresse complète à un cache pipeliné. Mais le Pentium 4 fait autrement. Il faut noter que les premiers étages manipulent l'indice dans la SRAM, qui est dans les bits de poids faible, alors que les étages ultérieurs manipulent le ''tag'' qui est dans les bits de poids fort. Les concepteurs du Pentium 4 ont alors décidé de présenter les bits de poids faible lors du premier cycle d'accès au cache, puis ceux de poids fort au second cycle. Pour cela, l'ALU fonctionnait à une fréquence double de celle du processeur, tout comme le cache L1. Il n'y avait pas de pipeline proprement dit, mais cela réduisait grandement la latence d'accès au cache. ===Le ''pipelining'' des caches splittés/sériels=== Il est aussi possible de pipeliner un cache dits splittés, aussi appelés à accès sériel. Pour rappel, les caches à accès sériel vérifient si il y a succès ou défaut de cache, avant d'accéder aux lignes de cache en cas de succès. Ils font donc différemment des autres caches, qui accèdent à une ligne de cache, avant de déterminer s'il y a succès ou défaut en lisant le tag de la ligne de cache. Les caches sériels disposent de deux SRAM : une pour les tags des lignes de cache et une pour les données. Ils accèdent à la SRAM pour les tags, avant d’accéder à la SRAM des données en cas de succès de cache. Vu que l'accès se fait en deux étapes, une vérification des tags suivie de la lecture/écriture des données, il est facile à pipeliner. Pipeliner le cache permet de régler le problème des accès au cache L1, et elle est tout le temps utilisé sur les processeurs modernes. Mais que faire en cas de défaut de cache ? ==Les caches non bloquants== Un '''cache bloquant''' est un cache auquel le processeur ne peut pas accéder pendant un défaut de cache. Il faut attendre que la lecture ou écriture en RAM soit terminée avant de pouvoir utiliser de nouveau le cache. Un '''cache non bloquant''' n'a pas ce problème : on peut l'utiliser pendant un défaut de cache. Les caches non bloquants permettent de démarrer une nouvelle lecture ou écriture alors qu'une autre est en cours, ce qui permet d'exécuter plusieurs lectures ou écritures en même temps. ===Les ''Miss Handling Status Registers''=== Lors d'un défaut de cache, la mémoire RAM est consultée pendant le défaut de cache, mais le cache est inutilisé. Un défaut de cache n'utilise pas le cache, ce n'est qu'un accès en mémoire RAM, sur le bus mémoire (ou un accès aux niveaux de cache inférieurs, peu importe). Le cache en lui-même est laissé libre, rien n’empêche d'y accéder, il est en réalité intrinsèquement non-bloquant. Les caches, bloquants comme non-bloquants, sont en réalité composés d'une mémoire cache proprement dite, entourée de circuits qui servent d'interface entre le processeur et le cache lui-même. Et parmi les circuits tout autour, certains gèrent l'accès au cache lors d'un défaut de cache. Ils sont regroupés sous le terme de '''''Miss Handling Architecture''''' (MHA). La différence entre un cache bloquant et un cache non-bloquant est en réalité liée à l'implémentation de la MHA. Les caches bloquants coupent volontairement l'accès au cache lors d'un défaut, car les défauts sont plus simples à gérer ainsi. Le défaut de cache rapatrie une donnée depuis la RAM, qui sera écrite dans le cache. Et il ne faut pas qu'une tentative d'accès à cette donnée ait lieu avant qu'elle ne soit chargée. Mais ce blocage est général et touche tout le cache, alors que seule une ligne de cache est concernée par le défaut de cache. L'idée derrière un cache non-bloquant est que seule la ligne de cache est bloquée, mais les autres sont accessibles. L'idée est alors de mémoriser les lignes de cache concernées par le défaut de cache, afin d'en bloquer l'accès. À chaque accès, on vérifie si la ligne de cache est déjà réservée par un défaut de cache. La lecture/écriture est alors bloquée si c'est le cas, mais elle accepte les accès sinon. Pour cela, la ''Miss Handling Architecture'' contient des registres qui mémorisent des informations sur les défauts de cache en cours. Ils portent le nom de '''''miss status handling registers''''', que j’appellerais dorénavant MSHR, qui sont aussi appelés des '''''miss buffer'''''. Le contenu des MSHR varie beaucoup suivant le processeur, mais ces derniers stockent au minimum les informations suivantes : * Le numéro de la ligne de cache dans laquelle les données sont chargées. * Un bit de validité qui indique si le MSHR est vide ou pas, qui est mis à 0 quand le défaut de cache est résolu. * Un ou plusieurs champs de lecture/écriture, qui contiennent des informations sur la lecture/écriture. ** Pour une lecture, elle contient des informations sur la destination de la donnée, à savoir qui prévenir quand le défaut de cache est terminé. C'est parfois un nom/numéro de registre (celui dans lequel charger la donnée), mais c'est souvent le numéro de l'entrée dans le ''load/store queue''. ** Pour les écritures, elle contient la donnée à écrire, ou éventuellement un numéro de ''load/store queue'' où se trouve la donnée à écrire. Il faut noter que le bus mémoire ne peut gérer qu'un seul défaut de cache à la fois. Aussi, il est intéressant de regarder ce qui se passe lorsqu'un second défaut de cache survient, pendant qu'un premier est en cours. Dans ce cas, il y a deux réponses qui correspondent à deux types de caches non-bloquants, qui portent les noms barbares de caches de type succès après défaut et défaut après défaut. Sur le premier type, il ne peut pas y avoir plusieurs défauts de cache simultanés. Dès qu'un second défaut de cache survient, le cache stoppe son activité et on ne peut plus démarrer de nouvelle lecture/écriture, tant que le premier défaut de cache n'est pas résolu. Le second type est plus souple et autorise la survenue de plusieurs défauts de cache simultanés. Du moins, jusqu'à une certaine limite, car le cache ne peut supporter qu'un nombre limité d'accès mémoires simultanés (pipelinés). ===Les accès simultanés à une même ligne de cache=== Il arrive que le processeur fasse plusieurs accès mémoire simultanés à la même ligne de cache. Si la ligne de cache en question n'a pas encore été chargée dans le cache, alors on a plusieurs défauts de cache consécutifs pour la même ligne de cache, et le cache non-bloquant doit gérer la situation. Pour la suite, il va falloir faire une petite distinction entre les défauts primaires et secondaires. Imaginons qu'un défaut de cache ait lieu et demande à charger une donnée dans la ligne de cache numéro N. Il s'agit du premier défaut impliquant cette ligne de cache précise, ce qui lui vaut le nom de '''défaut de cache primaire'''. Mais par la suite, d'autres accès mémoire à la même ligne de cache ont lieu, alors que la ligne de cache n'est pas encore disponible. Dans ce cas, il s'agit de '''défauts de cache secondaires'''. Pour l'unité d'accès mémoire, les défauts de cache primaire et secondaire sont différents (ils prennent tous une entrée dans la ''load/store queue''). Mais pour le cache, ils ne correspondent qu'à un seul accès au cache : celui qui demande de charger la ligne de cache demandée. Les défauts de cache primaire et secondaire à la même ligne de cache se voient attribuer un MSHR unique. La gestion des défauts de cache secondaires dépend du cache non-bloquant. La solution la plus simple ne permet pas les défauts de cache secondaires. Le cache ne permet pas deux défauts de cache simultanés pour la même ligne de cache. Les autres solutions le permettent, en fusionnant des accès simultanés à la même ligne de cache en un seul au niveau des MSHR. Dans tous les cas, détecter les défauts de cache secondaires sont un problème qu'il faut détecter. La ''Miss Handling Architecture'' doit détecter les défauts de cache secondaire. Pour cela, elle procède comme suit. Lors de chaque défaut de cache, la MHA récupère le numéro de la ligne de cache associé. Il vérifie alors chaque MSHR pour vérifier s'il contient le numéro en question. S'il n'y a aucune correspondance dans les MSHR, alors c'est signe que le défaut de cache est un défaut primaire. Mais s'il y en a une, alors c'est un défaut secondaire. Évidemment, cela signifie que lors d'un défaut de cache, le numéro de ligne de cache est envoyé à tous les MSHR, pour comparaison. Les MSHR sont donc regroupés dans une mémoire associative, une sorte de mini-cache, faciliter l'implémentation. Lors d'un défaut de cache primaire, l'accès à un cache non-bloquant se fait comme suit : le processeur envoie une adresse au cache, accède à celui-ci, et détecte la survenue d'un défaut de cache. Il en profite alors pour attribuer une ligne de cache dans laquelle sera chargée ce défaut. L'attribution est très simple dans le cas des caches ''direct mapped'', ou associatifs par voie, pour lesquels l'attribution se fait assez simplement. Il mémorise alors cette information dans les MSHR, après avoir vérifié que le défaut de cache n'était pas un défaut secondaire. Lors des accès ultérieurs à une adresse proche, censée être dans la même ligne de cache, le processeur va encore une fois rencontrer un défaut de cache. Il va alors déterminer le numéro de la ligne de cache associée à l'adresse, et comparer ce numéro avec les MSHRs. Si un MSHR contient ce numéro, c'est signe que le défaut de cache est un défaut secondaire. La MHA réagit alors différemment selon le processeur considéré. Une première solution n'autorise pas les défauts de cache secondaires. Si l'un d'entre eux survient, le processeur est gelé par un ''pipeline stall'', une bulle de pipeline. Une autre solution fusionne les défauts de cache secondaires avec le défaut de cache primaire : tout cela ne correspond qu'à un seul défaut de cache pour lui. ===Les MSHR simples=== Un cache non-bloquant à '''MSHR simple''' contient juste plusieurs MSHR qui mémorisent juste un numéro de ligne de cache, un bit de validité, le champ de lecture/écriture, et l'adresse exacte de lecture/écriture. Avec cette organisation, il est possible d'avoir plusieurs défauts de cache séparés, mais à la condition que chaque défaut accède à une ligne de cache différente. Deux accès simultanés à une même ligne de cache ne sont pas possibles, les défauts de cache secondaires ne sont pas autorisés. Ainsi, chaque défaut de cache se voit attribuer son propre MSHR, chacun contient un numéro de ligne de cache différent. Pour comprendre pourquoi c'est impossible de gérer les défauts secondaires, il faut regarder le champ de lecture/écriture. Si on veut effectuer plusieurs écritures consécutives à la même adresse, le MSHR n'aura pas de quoi mémoriser les deux données à écrire. Il pourra mémoriser la première donnée à écrire, pas la seconde. Même chose lors d'une lecture : le champ lecture/écriture peut mémorisr la destination de la première lecture, pas de la seconde. L'avantage est que la MHA n'a besoin que des MSHR et de quelques circuits annexes. Les autres solutions rajoutent des circuits annexes pour gérer les défauts de cache secondaires, qui utilisent beaucoup de circuits. Le cout en circuit est donc élevé, mais le gain en performance est là. Passons maintenant aux caches non-bloquants qui autorisent les défauts de cache secondaire. La solution la plus simple consiste à utiliser ===Les MSHR adressés implicitement=== Avec les '''MSHR adressés implicitement''', il est possible de fusionner plusieurs accès mémoire à une même ligne de cache, mais sous une condition très importante : ces accès lisent/écrivent des mots mémoire différents. Par exemple, imaginons qu'une ligne de cache contienne 8 mots mémoire de 64 bits. Si un premier accès mémoire lit le mot mémoire numéro 7 (dernier mot mémoire de la ligne), et le second accès le mot mémoire numéro 3, alors la fusion est possible. Mais si deux accès mémoire veulent lire/écrire le mot mémoire numéro 7, alors la fusion n'est pas possible et le processeur se bloque, un ''pipeline stall'' survient. Un MSHR adressé implicitement est un MSHR simple, qui contient naturellement un numéro de ligne de cache (tag) et un bit de validité, sauf que le champ de lecture/écriture est dupliqué. Les différents champs lecture/écriture d'un MSHR sont regroupés dans une mémoire RAM/cache qui contient autant d'entrées qu'il y a de mot mémoire dans une ligne de cache. Chaque entrée de la table est associée à un mot mémoire de la ligne de cache et stocke des informations sur celui-ci. Une entrée mémorise, au minimum : * Un champ de lecture/écriture qui contient soit la destination de la lecture, soit la donnée à écrire. * Un bit R/W permettant d'interpréter correctement le champ de lecture/écriture. * Un bit de validité pour chaque entrée de la table, qui dit si un défaut de cache antérieur accède déjà à ce mot mémoire. La fusion de deux défauts de cache est ainsi assez simple. Un défaut de cache primaire/secondaire configure l'entrée associée au mot mémoire lu/écrit. Si un défaut secondaire ultérieur lit/écrit un mot mémoire différent (dont le bit de validité de l'entrée dans la table est à zéro), pas de conflit : il configure une autre entrée, vide. Mais s'il lit/écrit un mot mémoire pour lequel l'entrée de la table est occupée, il y a conflit, le processeur est bloqué par un ''pipeline stall'', une bulle de pipeline. Avec cette organisation, le nombre de MSHR indique combien de lignes de cache peuvent être lues en même temps depuis la mémoire. Quant au nombre d'entrées par MSHR, il détermine combien d'accès mémoires qui ne se recouvrent pas peuvent avoir lieu en même temps. ===Les MSHR adressés explicitement=== Les '''MSHR adressés explicitement''' n'ont pas les contraintes des MSHR adressés implicitement. Avec eux, il est possible d'avoir plusieurs défauts de cache pointant vers la même ligne de cache, mais aussi vers le même mot mémoire. De tels défauts de cache apparaissent sur les processeurs à exécution dans le désordre, mais sont très rares, voire inexistants, sur les processeurs ''in-order''. L'idée est encore que chaque MSHR est associée à une table mémoire qui mémorise des entrées. Cette table n'est autre qu'une mémoire FIFO. Sauf que cette fois-ci, une entrée n'est pas associée à un mot mémoire. Une entrée est associée à un défaut de cache. Une entrée mémorise là encore la destination de la lecture ou la donnée de l'écriture suivi du bit R/W, ainsi que d'un bit de validité par entrée, mais aussi : la position du mot mémoire lu dans la ligne de cache. C'est cette dernière information qui n'était pas présente dans les MSHR adressés implicitement. De plus, le nombre d'entrée par MSHR n'est pas égal au nombre de mots mémoires dans une ligne de cache, mais peut être arbitrairement grand. L'avantage d'un tel cache est qu'il est capable de traité les défauts secondaires concernant un même mot mémoire, et ce, car les accès à la ligne de cache concernée se produisent dans l'ordre de génération des défauts (la table mémoire est une FIFO). ===Les MSHRs inversés=== Généralement, plus on veut supporter de défauts de cache, plus le nombre de MSHR et d'entrées augmente. Mais au-delà d'un certain nombre d'entrées et de MSHR, les MSHR adressés implicitement et explicitement ont tendance à bouffer un peu trop de circuits. Utiliser une organisation un peu moins gourmande en circuits est donc une nécessité. Cette organisation plus économe se base sur des MSHR inversés. Les MSHR inversés ne contiennent qu'une seule entrée, en quelque sorte : au lieu d’utiliser n MSHR de m entrées chacun, on va utiliser n × m MSHR inversés. La différence, c'est que plusieurs MSHR peuvent contenir un tag identique, contrairement aux MSHR adressés implicitement et explicitement. Lorsqu'un défaut de cache a lieu, chaque MSHR est vérifié. Si jamais aucun MSHR ne contient de tag identique à celui utilisé par le défaut, un MSHR vide est choisi pour stocker ce défaut, et une requête de lecture en mémoire est lancée. Dans le cas contraire, un MSHR est réservé au défaut de cache, mais la requête n'est pas lancée. Quand la donnée est disponible, les MSHR correspondant à la ligne qui vient d'être chargée vont être utilisés un par un pour résoudre les défauts de cache en attente. ===Les MSHR intégrés au cache=== Certains chercheurs ont remarqué que pendant qu'une ligne de cache est en train de subir un défaut de cache, celle-ci reste inutilisée, et son contenu est destiné à être perdu une fois le défaut de cache résolu. Ils se sont dit que, plutôt que d'utiliser des MSHR séparés, il vaudrait mieux utiliser la ligne de cache pour stocker les informations sur les défauts de cache en attente dans cette ligne de cache. Pour éviter tout problème, il faut rajouter un bit dans les bits de contrôle de la ligne de cache, qui sert à indiquer que la ligne de cache est occupée : un défaut de cache a eu lieu dans cette ligne, et elle stocke donc des informations pour résoudre les défauts de cache. ==L'entrelacement mémoire== Nous venons de voir comment une mémoire cache peut gérer plusieurs accès mémoire simultanés. Il se trouve que ce genre d'optimisation dépasse largement le cadre du cache. Les mémoires RAM ne sont pas en reste, elles aussi ! Il est possible de pipeliner l'accès à une mémoire RAM, en utilisant des techniques dites d''''entrelacement'''. Précisons cependant que cette optimisation n'est pas utilisée sur les mémoires SDRAM ou DDR modernes, du moins pas sans modifications majeures. Mais divers ordinateurs assez anciens, dont des superordinateurs, ont utilisé cette technique. La technique demande d'utiliser plusieurs mémoires séparées. Sur les anciens ordinateurs, les mémoires en question sont des chips mémoire, à savoir des circuits intégrés de DRAM. Pour faire plus général, nous allons utiliser le terme de '''banques''', ou encore de bancs mémoire. La différence est qu'il existe des mémoires multi-banque, qui regroupent plusieurs banques indépendantes dans un seul boitier, dans un seul chip mémoire. En clair, de telles mémoires regroupent plusieurs sous-mémoires dans un seul circuit intégré. Il est aussi possible d'utiliser plusieurs chips mémoire séparés, chacun ayant une banque. Reste à combiner ces banques pour former un pseudo-pipeline. ===Utiliser plusieurs banques sans entrelacement=== Sans optimisation particulière, les adresses sont réparties dans les banques comme indiqué ci-dessous. Il s'agit de ce qui s'appelle un arrangement horizontal, et nous avions vu celui-ci dans le chapiutre sur les mémoires SDRAM. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Avec cet arrangement, les bits de poids fort de l'adresse sont utilisées pour sélectionner la banque adéquate, et le reste de l'adresse est envoyé sur le bus d'adresse. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Adresse de banque !! Adresse dans la banque |- | Quelques bits de poids fort || Reste de l'adresse |} L'entrelacement change cette répartition, afin d'accélérer les accès mémoire. L'entrelacement classique vise à améliorer les accès à des adresses mémoires consécutives, mais des formes plus évoluées d'entrelacement visent à optimiser des accès mémoire arbitraires. Voyons-les dans l'ordre, du plus simple au plus complexe. ===L'entrelacement classique=== Avec l''''entrelacement classique''', des adresses consécutives sont réparties dans des banques consécutives. Précisons que cette attribution des adresses n'implique absolument pas la mémoire virtuelle ou n'importe quel mécanisme dans le processeur. La répartition décide que telle adresse mémoire va dans telle banque, à telle adresse dans la banque. Elle est donc le fait du contrôleur mémoire, donc en dehors du processeur (les contrôleurs mémoires n’étaient pas encore intégrés dans le processeur à l'époque). [[File:Répartition des adresses dans une mémoire interleaved.png|centre|vignette|upright=1.5|Répartition des adresses dans une mémoire interleaved.]] L'entrelacement simple permet d'accélérer les accès mémoire qui se font à des adresses consécutives. Avec l'entrelacement, chaque accès mémoire tombe dans une banque mémoire différente, ce qui fait qu'on peut démarrer un nouvel accès mémoire à chaque cycle d'horloge. Pendant qu'une banque est occupée par un accès mémoire, on démarre le suivant dans une autre banque, et ainsi de suite. Le tout se termine soit quand on a épuisé toutes les banques libres, soit quand l'accès en rafales se termine. Pas besoin d'attendre que la mémoire ait fini sa lecture/écriture avant de démarrer la lecture/écriture suivante. [[File:Pipemining mémoire.png|centre|vignette|upright=2.5|Pipelining mémoire]] L'animation suivante illustre bien le processus, avec quatres banques. [[File:Interleaving.gif|centre|vignette|upright=2.5|Pipelining mémoire]] L'entrelacement simple utilise les bits de poids faible pour sélectionner la banque, et les bits de poids fort pour la case mémoire. [[File:Adresse mémoire d'une mémoire entrelacée.png|centre|vignette|upright=2|Adresse mémoire d'une mémoire entrelacée]] Précisons que le temps d'accès mémoire ne change pas beaucoup avec l'entrelacement. Par contre, on peut faire plus d'accès mémoire simultanés. On peut démarrer un accès mémoire par cycle d'horloge, mais l'accès en lui-même prend plusieurs cycles. Les mémoires à entrelacement ont donc un débit supérieur aux mémoires qui ne l'utilisent pas. L'entrelacement classique pourrait être utilisé pour accélérer les transferts entre mémoire RAM et cache, qui se font en blocs d'adresses consécutives. Mais de nos jours, il n'est pas utilisé dans cette optique. Il faut dire qu'il est redondant avec le mode rafale des mémoires DRAM. Les deux font globalement la même chose et on voit mal comment utiliser les deux en même temps. Le mode rafale étant plus simple à implémenter, et plus léger niveau utilisation du bus de commande mémoire, il est préféré à l'entrelacement. Par contre, l'entrelacement a eu son heure de gloire sur d'anciens processeurs qui n'avaient pas de mémoire cache, alors qu'ils disposaient d'un pipeline. L'exemple typique est celui des nombreux ''processeurs vectoriels'', que nous n'avons pas encore abordé à ce stade du cours. De tels processeurs avaient un pipeline, une unité de calcul fortement pipelinée, et parfois même de l'exécution dans le désordre et du renommage de registres ! ===L'entrelacement par décalage=== L'entrelacement simple est très utile pour les accès en rafle ou équivalents. Par contre, il existe de rares situations où il n'est pas optimal. Une de ces situations est celle des accès en enjambée (en ''stride''), où l'on accède en série à des adresses sont séparées par N mots mémoires. [[File:Accès par enjambées.png|centre|vignette|upright=2|Accès par enjambées.]] De tels accès surviennent quand un logiciel accède à des structures de données spécifiques, à savoir des tableaux de structures, des matrices, ou d'autres structures de données dans le genre. Un cas classique est celui du parcours d'une matrice colonne par colonne. Les matrices de nombres sont mémorisées ligne par ligne, ce qui fait que parcourir une colonne demande de passer d'une ligne à la suivante et de faire des grands sauts en mémoire RAM, mais tous espacés par la même distance. Avec l'entrelacement simple, les accès en enjambée sont moins performants que les accès à des adresses consécutives, mais cela ne fonctionne pas trop mal. Le pire cas est celui où l'on a N banques et où les données sont justement placées toutes les N adresses. Des accès consécutifs vont tous tomber dans la même banque, on ne peut plus accéder à des banques différentes en parallèle. Mais en dehors de ce cas, on voit une amélioration par rapport à la situation sans entrelacement. Pour obtenir des performances maximales pour les accès en enjambées, il faut répartir les mots mémoires dans la mémoire autrement. L'organisation idéale est la suivante. : Dans les explications qui vont suivre, la variable N représente le nombre de banques, qui sont numérotées de 0 à N-1. On commence par organiser les N premières adresses comme une mémoire entrelacée simple : l'adresse 0 correspond à la banque 0, l'adresse 1 à la banque 1, etc. Pour le bloc suivant, on décale tout d'une adresse, à savoir qu'on commence à remplir les banques à partir de la seconde, non de la première. Une fois la fin du bloc atteinte, on finit de remplir le bloc en repartant du début du bloc. Le troisième bloc d'adresse subit le même traitement, sauf qu'on commence à remplir à partir de la seconde banque. Et on poursuit l’assignation des adresses en décalant d'un cran en plus à chaque bloc. Ainsi, chaque bloc verra ses adresses décalées d'un cran en plus comparé au bloc précédent. Si jamais le décalage dépasse la fin d'un bloc, alors on reprend au début. [[File:Mémoire interleaved par décalage.png|centre|vignette|upright=1.5|Mémoire entrelacée par décalage.]] En faisant cela, on remarque que les banques situées à N adresses d'intervalle sont différentes. Dans l'exemple du dessus, nous avons ajouté un décalage de 1 à chaque nouveau bloc à remplir. Mais on aurait tout aussi bien pu prendre un décalage de 2, 3, etc. Dans tous les cas, on obtient un entrelacement par décalage. Ce décalage est appelé le pas d'entrelacement, noté P. Le calcul de l'adresse à envoyer à la banque, ainsi que la banque à sélectionner se fait en utilisant les formules suivantes : * adresse à envoyer à la banque = adresse totale / N ; * numéro de la banque = (adresse + décalage) modulo N ; * décalage = (adresse totale * P) mod N. Avec cet entrelacement par décalage, on peut prouver que la bande passante maximale est atteinte si le nombre de banques est un nombre premier. Seulement, utiliser un nombre de banques premier peut créer des trous dans la mémoire, des mots mémoires inadressables. Malgré ce défaut, la technique a été utilisée sur quelques ordinateurs, avec l'exemple notable du superordinateur ''Burroughs Scientific Processor''. Pour éviter cela, il y a plusieurs solutions. Par exemple, on peut faire en sorte que N et la taille d'une banque soient premiers entre eux : ils ne doivent pas avoir de diviseur commun. Mais en pratique, elles n'ont pas vraiment été implémentées dans une vraie machine, et sont restées à l'état de recherche, aussi je les passe sous silence. ===L'entrelacement pseudo-aléatoire=== Une dernière méthode de répartition consiste à répartir les adresses dans les banques de manière "pseudo-aléatoire". L'idée exacte est de faire passer l'adresse dans une '''fonction de hachage''', plus ou moins complexe. Pour rappel, une fonction de hachage prend une entrée de grande taille et fournit en sortie un résultat de petite taille. Idéalement, elles doivent donner des résultats aussi différents que possible pour des entrées similaires, histoire de simuler une sorte de pseudo-aléatoire. Ici, une adresse est transformée en un numéro de banque. Une fonction de hachage simple ne fait que permuter des bits de l'adresse pour obtenir son résultat. Elle ne fait qu'échanger des bits de place avant de couper l'adresse en deux morceaux : un pour la sélection de la banque, et un autre pour la sélection de l'adresse dans la banque. Cette permutation est fixe, et ne change pas suivant l'adresse. Des fonctions de hachage plus complexes font des XOR bit à bit entre certains bits de l'adresse. ==Les contrôleurs mémoires SDRAM/DDR optimisés== Après avoir vu le cas des mémoires RAM, nous allons nous concentrer sur le cas particulier des mémoires SDRAM. Les mémoires SDRAM et DDR modernes sont capables de gérer plusieurs accès simultanés à la mémoire RAM, et leurs contrôleurs mémoire en font tout autant. C'est une différence majeure avec les mémoires asynchrones FPM/EDO, qui n'acceptaient qu'un seul accès mémoire à la fois. Leur contrôleur mémoire n'acceptait qu'un seul accès mémoire à la fois, c'était un contrôleur mémoire bloquant. Les contrôleurs mémoires des SDRAM sont eux non-bloquants et peuvent encaisser une dizaine d'accès mémoire à la fois. Peu de choses sont connues sur les contrôleurs de SDRAM/DDR modernes, les fabricants ne donnant que peu de détails dessus. Les rares simulateurs qui tentent de décrire leur fonctionnement, comme DRAM SIM I et II, sont particulièrement simples et ne vont pas dans le détail. Néanmoins, le peu qu'on sait est tout de même instructif. ===Rappel sur les SDRAM : tampon de ligne et commandes=== Les mémoires SDRAM sont des mémoires à tampon de ligne. Elles font un accès mémoire en deux étapes. La première étape recopie une ligne de N * 64 bits dans un tampon interne à la SDRAM. La seconde étape sélectionne une colonne, à savoir une donnée de 64 bits, qui est soit envoyée sur le bus de données pour une lecture, soit modifiée par une écriture. [[File:Mémoire à tampon de ligne.png|centre|vignette|upright=2|Mémoire à tampon de ligne]] Les deux étapes correspondent à deux commandes séparées : une commande ACT qui précise l'adresse de la ligne, et une commande READ ou WRITE qui précise l'adresse de la colonne. Les commandes ACT sont systématiquement précédées d'une commande PRECHARGE qui ferme la ligne précédente. Pour être plus précis, elle précharge les lignes de bits du plan mémoire à une tension particulière, ce qui les vide de leur contenu. Mais c'est un détail sans importance pour ce qui va suivre. Les SDRAM permettent de se passer de la première étape si des accès consécutifs se font dans la même ligne. Une fois activée, la ligne reste ouverte et on peut accéder plusieurs fois de suite dedans. On a alors juste à préciser l'adresse de colonne dedans. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | || bgcolor="#A0FFFF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || bgcolor="#A0FFFF" | READ (2) || bgcolor="#A0FFFF" | READ (3) || || || bgcolor="#A0FFFF" | READ (4) || bgcolor="#A0FFFF" | READ (5) || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | Donnée READ 1 || ||bgcolor="#A0FFFF" | Donnée READ 2 | bgcolor="#A0FFFF" | Donnée READ 3 || || || bgcolor="#A0FFFF" | Donnée READ 4 || bgcolor="#A0FFFF" | Donnée READ 5 |} ===La mise en attente des accès mémoire=== Un contrôleur de SDRAM peut accepter plusieurs accès mémoire en même temps. Mais cela ne signifie pas que le contrôleur sera prêt à les traiter immédiatement. Pour éviter tout problème de disponibilité, le contrôleur met en attente les accès mémoire que le processeur lui envoie, pour les exécuter dès que possible. Les accès mémoire sont mis en attente dans une mémoire FIFO, histoire de les exécuter dans leur ordre d'arrivée, notamment pour renvoyer les lectures dans l'ordre demandé. Il y a aussi une mémoire FIFO pour les données à écrire et une FIFO pour les données lues. Cette dernière sert au cas où le cache ne soit pas disponible quand on lui envoie la donnée lue. [[File:Module d'interface avec la mémoire.png|centre|vignette|upright=2.5|Contrôleur mémoire avec mise en attente des requêtes processeur et autres optimisations.]] Des optimisations sont possibles dès la mémoire FIFO. Par exemple, on peut regrouper plusieurs accès à des données consécutives en un seul accès en rafale. Le contrôleur mémoire analyse les requêtes mises en attente et détecte si certaines se font à des adresses consécutives. Si c'est le cas, il fusionne ces requêtes en une lecture/écriture en rafale. Une telle optimisation est appelée la '''combinaison de lecture''' pour les lectures, et la '''combinaison d'écriture''' pour les écritures. ===Le pipeline des mémoires SDRAM=== Les accès mémoire sont traduits par un séquenceur mémoire en une série de commandes mémoires, qui sont séparées par des délais mémoire de quelques cycles d'horloge. Les délais sont très précis, et sont à respecter à la lettre. Les SDRAM sont parfois capables de démarrer une commande avant que la précédente soit terminée. Mais le respecte des délais mémoire est très limitant, ce qui fait qu'on ne peut pas parler de réel pipeline, comme c'est le cas sur les processeurs. Parlons plutot de commandes anticipées. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | || bgcolor="#A0FFFF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || || || bgcolor="#FFA0FF" | READ (2) || || || || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 | bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 | |} Même si elle parait très limitée, cette possibilité est décuplée par la présence de plusieurs banques dans la mémoire SDRAM. Les chips mémoires de SDRAM sont composés de plusieurs banques, chacune étant une sorte de mini-mémoire miniature. Chaque banque a son propre décodeur, son propre tampon de ligne, ses propres multiplexeurs de colonne, sa logique de rafraichissement mémoire, etc. C'est comme si une SDRAM regroupait plusieurs mémoires séparées dans un même circuit intégré. Et cela permet de faire plusieurs accès mémoire en même temps, dans des banques différentes. Il est possible de lancer un accès mémoire dans deux banques en même temps, par exemple. Ou encore, on peut lancer un nouvel accès mémoire dans une banque si elle est inoccupée. La possibilité améliore grandement la performance de la SDRAM. Mais nous en reparlerons dans un chapitre ultérieur. {|class="wikitable" |+ Pipelining basique sur les SDRAM |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 |- ! Banque Numéro 1 | || || || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || || || |- ! Banque Numéro 2 | || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || |- ! Banque Numéro 3 | || || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || |} L'implémentation n'a rien de vraiment compliqué. Il suffit d'utiliser une mémoire FIFO par banque. Ainsi, les accès dans une même banque se feront en série, dans l'ordre d'arrivée, mais des accès à des banques différentes se feront dans le désordre. Cela ne pose pas de problème car les accès se font à des adresses différentes, ce qui fait qu'il n'y a pas de conflits majeurs. [[File:Gestion parallèle des banques.png|centre|vignette|upright=2.5|Gestion parallèle des banques.]] ===Le ré-ordonnancement des commandes mémoires=== Un contrôleur mémoire moderne est capable de changer l'ordre des requêtes mémoires, pour gagner en performances. En clair : il peut faire l'équivalent mémoire de l'exécution dans le désordre des processeurs haute performance. Une différence majeure est que le contrôleur mémoire ne remet pas les accès mémoire dans l'ordre avant de les envoyer au processeur. C'est le processeur qui remet en ordre les accès mémoire, dans sa ''load queue''. Pour cela, les requêtes mémoire sont "numérotées", à savoir qu'on leur attribue un identifiant binaire. Le contrôleur mémoire exécute les lectures/écritures dans le désordre, mais garde la trace de leur identifiant. Il sait dans quel ordre il exécute les accès mémoire et connait leur latence. Ce qui fait qu'il sait que la donnée lue à tel instant est associée à la requête mémoire numéro X. Pour les lectures, il renvoie la donnée lue avec l'identifiant. Le processeur sait alors à quelle lecture la donnée lue correspond et il se débrouille en interne pour gérer la situation. En clair : le processeur voit les accès mémoire dans le désordre, c'est lui qui les remet en ordre. Maintenant que ces précisions sont faites, posons cette question : quel est l'intérêt de faire les accès mémoire dans le désordre ? Accéder à des banques en parallèle est une réponse possible, mais le ré-ordonnancement est surtout utile si tous les accès mémoire atterrissent dans la même banque. L'exécution dans le désordre des lectures/écritures SDRAM vise à faire en sorte que des accès consécutifs se fassent dans une même ligne. Elle marche si plusieurs accès à une même ligne ne sont pas consécutifs, et qu'il y a des accès entre les deux. Après ré-ordonnancement, ces accès mémoire deviennent consécutifs, ils sont exécutés l'un à la suite de l'autre. Pour rendre le tout plus concret, voici un exemple. Imaginez que l'on sait les 3 accès mémoire suivants : * Une lecture ligne A ; * Une écriture ligne B ; * Une lecture ligne A. Sans réordonnancent, on doit émettre deux commandes PRECHARGE : une quand on passe de la ligne A à la ligne B, et une autre pour le passage inverse. pour éviter cela, le contrôleur mémoire peut retarder l'écriture, ou au contraire avancer la seconde lecture. le résultat est alors le suivant : * Lecture ligne A ; * Lecture ligne A ; * Une écriture ligne B. On a donc deux accès consécutifs à la même ligne, suivi par une écriture dans une autre ligne. La technique marche parce que l'on a un mix adéquat de lectures et d'écritures. Mais il existe des possibilités de réorganisation autres qui ne sont pas valides. Il faut par exemple prendre garde à éviter d'intervertir lectures et écritures à une même adresse, sous peine de problèmes. Encore une fois, il faut faire attention aux dépendances mémoires. Le controleur mémoire teste les dépendances mémoires avant d'envoyer des commandes à la mémoire DDR/SDRAM. ==L'entrelacement sur les mémoires SDRAM== L''''entrelacement''' fonctionne sur les mémoires SDRAM, mais il doit être fortement modifié. Concrètement, tout ce qui a étré dit plus est inapplicable pour les SDRAM, du fait de la présence du tampon de ligne. Des adresses consécutives atterrissent déjà dans la même ligne, ce qui fait qu'on n'a pas besoin d'entrelacement au niveau d'une ligne. Et ne parlons pas de ce la présence de rangées et de canaux mémoire ! Cependant, il peut y avoir un entrelacement lié à la présence des banques SDRAM. L'entrelacement est géré juste avant le séquenceur mémoire. Le '''circuit d'entrelacement''' intervertit certains bits de l'adresse lors des accès mémoires. On trouve aussi des mémoires FIFOs pour mettre en attente les requêtes mémoire provenant du processeur. Elles permettent de recevoir plusieurs accès mémoire consécutifs. Si vous vous demandez comment plusieurs accès mémoire consécutifs peuvent avoir lieu, sachez que c'est une bonne question. Pour le moment, nous allons dire que ces accès proviennent du processeur ou de la mémoire cache, sans expliquer comment. Le contrôleur mémoire reçoit une série d'accès mémoire assez rapprochés, qu'il doit exécuter au mieux. [[File:Controleur mémoire d'une SDRAM avec entrelacement.png|centre|vignette|upright=2.5|Contrôleur mémoire d'une SDRAM avec entrelacement.]] Pour gérer l'entrelacement, le contrôleur de SDRAM prend en entrée l'adresse envoyée par le processeur, et la découpe en plusieurs champs : un pour sélectionner la banque adéquate, un autre pour la ligne, un autre pour la colonne, un autre pour la rangée. Une solution simple désactive l'entrelacement. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de banque || Adresse de ligne || Adresse de colonne |} Si cette solution fonctionne bien si les accès mémoire sont assez aléatoires, en pratique, mieux vaut utiliser l'entrelacement. ===L'entrelacement de banques/lignes=== Une première méthode d'entrelacement est l''''entrelacement de banques'''. Elle répartit deux lignes consécutives dans deux banques différentes. Pour comprendre l'idée, prenons un exemple. Imaginons une mémoire avec deux banques et 4 lignes. Imaginons qu'on parcoure/balaye la mémoire RAM en partant des adresses basses. Sans entrelacement de ligne, les accès se feront comme suit, de gauche à droite : {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Banque numéro 1 !! colspan="4" | Banque numéro 2 |- | Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 || Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 |} Avec l'entrelacement de banques, les accès se feront comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 |- | Ligne 1 || Ligne 1 || Ligne 2 || Ligne 2 || Ligne 3 || Ligne 3 || Ligne 4 || Ligne 4 |} Avec N banques, la première ligne ira dans la première banque, la seconde ligne dans la seconde, et ainsi de suite jusqu'à la énième banque qui va dans la banque numéro N. Et la ligne N+1 repart dans la première banque. L'avantage est que passer d'une ligne à la suivante est assez rapide. Au lieu de devoir fermer la première ligne et ouvrir la suivante on ouvrira directement la ligne suivante dans une banque différente, ce qui sera plus rapide. La commande PRECHARGE pourra être envoyée à la seconde banque en avance. Pour cela, l'adresse est découpée comme suit. {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne (précise à l'octet) |} ===L'entrelacement de rangées=== Il est aussi possible de faire la même chose, mais avec les rangées. Pour simplifier fortement, une rangée est simplement un chip mémoire. En réalité, les rangées ne sont pas des chip mémoire, mais un ensemble de chips mémoire regroupés ensemble histoire d'atteindre les 64 bits du bus de données. Par exemple, une rangée peut combiner 8 chips mémoire avec un bus de données de 8 bits chacun pour obtenir les 64 bits du bus de données d'une SDRAM. Mais nous passons ce détail sous silence dans les explications qui vont suivre, par souci de simplification. Pour faire comprendre l'entrelacement de rangée, le mieux est d'utiliser un exemple, le même que précédemment. Sans entrelacement de rangée, on change de chip mémoire une fois qu'on a balayé toutes ses adresses. {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Rangée numéro 1 !! colspan="4" | Rangée numéro 2 |- | Banque 1 || Banque 2 || Banque 3 || Banque 4 || Banque 1 || Banque 2 || Banque 3 || Banque 4 |} Avec l'entrelacement de ligne, on change de rangée dès qu'on change de banque/ligne. {|class="wikitable" |+ Adresse mémoire |- ! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 |- | Banque 1 || Banque 1 || Banque 2 || Banque 2 || Banque 3 || Banque 3 || Banque 4 || Banque 4 |} En clair, quand on a épuisé toutes les banques dans une même rangée, on passe à la rangée suivante au lieu de rester dans la même rangée. Notons que dans l'exemple précédent, on a combiné l'entrelacement de rangée et de banque, mais on aurait pu utiliser l'entrelacement de rangée seul. Mais ce n'est pas le cas le plus courant en pratique. Toujours est-il qu'en combinant entrelacement de rangée et de ligne, le découpage de l'adresse est le suivant : {|class="wikitable" |+ Adresse mémoire |- | Adresse de ligne || Numéro de rangée || Adresse de banque || Adresse de colonne (précise à l'octet) |} D'autres méthodes d'entrelacement sont possibles, mais elles sont plus complexes. Chaque contrôleur mémoire fait un peu à sa sauce. Ils permettent en général de choisir entre plusieurs entrelacements. Au minimum, ils permettent de désactiver l'entrelacement, d'activer un entrelacement de ligne, et d'autres entrelacements plus complexes, dont certains sont propriétaires et tenus secrets par le fabricant. ===L'entrelacement avec le ''dual channel''=== [[File:Dual channel slots.jpg|vignette|Slots mémoires avec ''dual channel''.]] L'usage du ''dual channel'' complique encore l'entrelacement. Là encore, il y a deux grandes solutions : avec et sans entrelacement des canaux mémoire. Rappelons le principe : deux barrettes de RAM sont accédées en parallèle. Et pour cela, il faut utiliser l'entrelacement. Typiquement, chaque barrette mémoire fournit 64 bits, ce qui fait que l'on peut accéder à 128 bits d'un seul coup, par exemple avec un accès en rafale. Sans ''dual channel'', la première barrette correspond à la moitié haute de la RAM, la seconde barrette correspond à la moitié basse. Avec ''dual channel'', une forme spécifique d'entrelacement est activée. Concrétement, deux blocs de 64 bits sont placés dans des canaux mémoire séparés. Idem avec du triple ou quadruple canal, mais c'est alors trois ou quatre blocs de 64 bits qui sont dispersés dans des canaux consécutifs. Le découpage de l'adresse est alors le suivant : {|class="wikitable" |+ Adresse mémoire |- ! colspan="6" | Sans entrelacement |- | Numéro de canal/barrette || Reste de l'adresse || Position de l'octet dans un bloc de 64 bits |- | colspan="6" | |- ! colspan="6" | Avec entrelacement |- | Reste de l'adresse || Numéro de canal/barrette || Position de l'octet dans un bloc de 64 bits |} [[File:Décodage d'adresse avec dual channel.png|centre|vignette|upright=2|Décodage d'adresse avec dual channel.]] Les contrôleurs de SDRAM précédents sont assez basiques et ne représentent pas les contrôleurs les plus évolués. Ils étaient utilisés sur les anciens PC, à une époque où ils étaient encore sur la carte mère du processeur. Mais de nos jours, le contrôleur mémoire est intégré au processeur. Et il incorpore de nombreuses optimisations afin de gagner en performances. Malheureusement, ces optimisations sont elles-mêmes dépendantes des optimisations intégrées dans le processeur, ce qui fait qu'on ne peut pas en parler ici. Elles demandent que le contrôleur mémoire reçoive plusieurs accès mémoire simultanés, ce qui n'a aucun sens à ce stade du cours. Aussi, nous allons les passer sous silence pour le moment. Nous les verrons dans le chapitre sur le parallélisme mémoire. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=La désambiguïsation mémoire | prevText=La désambiguïsation mémoire | next=Les processeurs superscalaires | nextText=Les processeurs superscalaires }} </noinclude> pte5crzzsa0zmaavbu91xbzark9e59u 764712 764711 2026-04-23T21:12:57Z Mewtow 31375 /* Rappel sur les SDRAM : tampon de ligne et commandes */ 764712 wikitext text/x-wiki Dans ce chapitre, nous allons voir les techniques qui permettent de gérer plusieurs accès mémoire simultanés directement au niveau du cache ou de la mémoire RAM. Par plusieurs accès mémoire simultanés, vous pensez sans doute à l'usage de cache multiports, voire de mémoires RAM multiport. C'est en effet une solution, mais ce n'est clairement pas la seule. Par exemple, il est possible de pipeliner l'accès au cache, voire à la mémoire RAM. Il existe de nombreuses techniques de '''parallélisme mémoire''', et nous allons les voir dans ce chapitre. Pour donner un exemple, il est possible de pipeliner les accès mémoire. Il est possible de pipeliner l'accès au cache et/ou l'accès à la mémoire, et nous verrons comment faire dans ce chapitre. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, qu'ils aient ou non un pipeline dynamique. Par contre, pipeliner la mémoire est une technique ancienne, aujourd'hui peu utilisée. Elle était utilisée sur des très vieux ordinateurs, qui avaient un pipeline mais pas de mémoire cache. Mais de nos jours, les mémoires SDRAM et DDR ne sont pas adaptées pour ça, il est très difficile de les pipeliner correctement. Le parallélisme mémoire est utile aussi bien sur des processeurs à émission dans l'ordre que dans le désordre. Par exemple, un processeur ''in-order'' avec des lectures non-bloquantes peut e, profiter. Il peut techniquement lancer une seconde lecture, après avoir rencontré une première lecture non-bloquante. En général, le nombre de lectures consécutives est cependant limité à moins d'une dizaine. À l'opposé, les processeurs sans lectures non-bloquantes profitent pas du parallélisme mémoire. À la rigueur, ils peuvent en profiter s'ils intègrent des techniques de préchargement. Pendant que le processeur exécute une lecture/écriture, il peut précharger une autre donnée en parallèle. Inutile de dire que sans pipeline processeur, le parallélisme mémoire ne sert pas à grand-chose. ==Le ''line fill buffer'' et les techniques associées== Lors d'un défaut de cache, le processeur doit attendre que toute la ligne de cache soit chargée avant d'être utilisable. Or, la taille d'une ligne de cache est supérieure à la largeur du bus mémoire, ce qui fait qu'une ligne de cache est chargée en plusieurs fois, morceaux par morceaux, mot mémoire par mot mémoire. Le chargement peut se faire directement dans le cache, mais ce n'est pas une solution très pratique. À la place, beaucoup de processeurs ajoutent une mémoire tampon entre la RAM et le cache, appelée le '''tampon de remplissage de ligne''' (''line-fill buffer'') dans les processeurs modernes. Lors d'un défaut de cache, le processeur charge la donnée de la RAM dans le tampon de remplissage de ligne, mot mémoire par mot mémoire. Une fois plein, le tampon de remplissage de ligne est recopié dans la ligne de cache. Le tampon de remplissage de ligne contient une ligne de cache, si ce n'est que certains bits de contrôle ne sont pas présents. Le tampon de remplissage de ligne contient un bit de validité pour chaque mot mémoire de la ligne de cache, qui indiquent si le mot mémoire a été chargé. Par exemple, prenons un processeur 64 bits, qui gère donc des mots mémoire de 8 octets, avec des lignes de cache 256 octets/32 mots mémoire. Le tampon de remplissage de ligne contiendra 32 bits de validité. Si le processeur a chargé les 6 premiers mots mémoire, les 6 premiers bits de validité seront mis à 1, les autres seront encore à zéro. Il faut noter que la disponibilité de la ligne complète se détermine assez facilement en faisant un ET logique entre tous les bits de validité. Le processeur sait ainsi quand la ligne de cache est disponible entièrement et donc quand la transférer dans le cache. La même chose existe avec une hiérarchie de cache, sauf que l'on trouve une mémoire tampon entre chaque niveau de cache. Il y a un tampon de remplissage de ligne entre le cache L1 et le cache L2, entre le cache L2 et le L3, etc. Si le cache ne gère qu'un seul défaut de cache à la fois, le ''fill line buffer'' est une mémoire très simple, qui ne mémorise qu'une seule ligne de cache. Et cela vaut aussi bien pour un cache bloquant que non-bloquant. Mais sur les caches capables de gérer plusieurs défauts simultanés, le tampon de remplissage de ligne est une mémoire de type FIFO ou LIFO, capable de mémoriser plusieurs lignes de cache. Notons que le tampon de remplissage de ligne est très utile pour implémenter certaines techniques, par exemple le contournement du cache. Nous avions vu dans le chapitre sur le cache que certains accès mémoire doivent contourner le cache, pour des raisons de cohérence des caches. C'est notamment nécessaire pour accéder aux périphériques, mais c'est aussi utile pour des raisons de performances dans des cas très spécifiques. Les accès qui contournent le cache se font directement dans le tampon de remplissage de ligne : le processeur écrit ou lit les données depuis ce tampon de remplissage de ligne, sans accéder au cache. ===L'''early restart'' et le ''critical word load''=== La présence du ''line buffer'' permet une optimisation assez intéressante, qui permet de réduire la latence des défauts de cache. L'optimisation consiste à lire un mot mémoire dans le tampon de remplissage de ligne, même si la ligne de cache complète n'a pas encore été chargée. Il existe deux manières de faire cela, qui portent les noms d'''early restart'' et de ''critical word load''. La première est la version la plus simple, la seconde est plus complexe mais plus performante. Avec la technique d''''''early restart''''', la ligne de cache est chargée normalement, en partant de son premier mot mémoire. Dès que le mot mémoire lu/écrit par le processeur est copié dans le ''line fill buffer'', il est envoyé au processeur immédiatement. Illustrons le tout par un exemple, où une ligne de cache fait 16 mots mémoire. Le processeur effectue une lecture, qui lit le 5ème mot mémoire. Sans ''early restart'', le processeur doit charger les 16 mots mémoire avant de faire la lecture dans le cache. Avec ''early restart'', le processeur reçoit la donnée dès que le 5ème mot mémoire est disponible. Le processeur doit attendre que les 4 premiers mots mémoire soient chargés, puis le 5ème mot mémoire arrive et est envoyé directement au processeur, il est lu directement depuis le ''line fill buffer''. Les 11 mots mémoire suivants sont ensuite chargés dans le cache pendant que le processeur fait des calculs dans son coin. Le ''critical word load'' est une optimisation de la technique précédente où le chargement de la ligne de cache commence directement à la donnée demandée par le processeur. Pour reprendre l'exemple précédent, où le processeur demande le 5ème mot mémoire sur 16, le ''critical word load'' charge le 5ème mot mémoire en premier et l'envoie au processeur, ce qui fait qu'il est chargé très rapidement. Pas besoin d'attendre que le processeur charge les 4 mots mémoire précédents comme avec l'''early restart''. Dans le détail, le processeur charge le 5ème mot mémoire en premier, puis charge les 11 suivants, et termine par les 4 mots mémoire du début. En clair, le ''critical word load'' commence par charger le mot mémoire lu, puis les blocs suivants, avant de revenir au début du bloc pour charger les blocs restants. Ainsi, la donnée demandée par le processeur sera la première disponible. Pour cela, l'organisation du tampon de remplissage de ligne est modifiée de manière à rendre cela possible. Il a une taille égale à une ligne de cache complète, qui contient elle-même plusieurs mots mémoire. Dans le ''line-fill buffer'', chaque mot mémoire est stocké avec un tag, qui indique l'adresse du mot mémoire stocké dans le ''line-fill buffer''. Le ''line-fill buffer'' est donc un cache un peu particulier, qui fonctionne comme un cache du point de vue du processeur, comme une mémoire FIFO pour les transferts avec le cache. Ainsi, un processeur qui veut lire dans le cache après un défaut peut accéder à la donnée directement depuis le tampon de remplissage de ligne, alors que la ligne de cache n'a pas encore été totalement recopiée en mémoire. ==Les caches pipelinés== Il est possible de pipeliner l'accès au cache, ce qui demande juste de rajouter des registres au bon endroits et quelques circuits de contrôle. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, même ceux avec un pipeline dynamique. Et l'implémentation peut se faire de plusieurs manières différentes. Il faut dire que découper une mémoire cache en plusieurs étapes peut se faire de plusieurs manière. Commençons par la plus simple. Beaucoup de processeurs des années 2000 avaient une fréquence peu élevée comparée aux standards d'aujourd'hui, ce qui fait que leur cache avait bien un temps d'accès d'un cycle d'horloge. Mais l'accès au cache se faisait en deux cycles d'horloge : un cycle pour calculer une adresse et un cycle pour l'accès mémoire proprement dit. Et ces deux étapes étaient pipelinées, à savoir que deux micro-opérations mémoire peuvent s'exécuter en même temps : une dans l'étage de calcul d'adresse, une autre dans le cache. L'unité mémoire était donc pipelinée, alors que l'accès au cache ne l'était pas. Les processeurs qui implémentaient cette technique regroupent les micro-architectures K5 et K6 d'AMD, les processeurs Intel de micro-architecture P6 (Pentium 2 et 3) et quelques autres. Il est aussi possible de pipeliner l'accès au cache lui-même. Avec un cache pipeliné, l'accès au cache ne se fait pas en un seul cycle, mais en plusieurs. Cependant, on peut lancer un nouvel accès au cache à chaque cycle d'horloge, comme un processeur pipeliné lance une nouvelle instruction à chaque cycle. L'implémentation est assez simple : il suffit d'ajouter des registres dans le cache. Pour cela, on profite que le cache est composé de plusieurs composants séparés, qui échangent des données dans un ordre bien précis, d'un composant à un autre. De plus, le trajet des informations dans un cache est linéaire, ce qui les rend parfois pour l'usage d'un pipeline. ===Le ''pipelining'' des caches ''direct-mapped''=== Voici ce qui se passe avec un cache directement adressé. Pour rappel ce genre de cache est conçu en combinant une mémoire RAM, généralement une SRAM, avec quelques circuits de comparaison et un MUX. L'accès au cache se fait globalement en deux étapes : la première lit la donnée et le ''tag'' dans la SRAM, et on utilise les deux dans une seconde étape. Nous avions vu il y a quelques chapitre comment pipeliner des mémoires, dans le chapitre sur les mémoires évoluées. Et bien ces méthodes peuvent s'utiliser pour la mémoire RAM interne au cache !L'idée est d'insérer un registre entre la sortie de la RAM et la suite du cache, pour en faire un cache pipéliné, à deux étages. Le premier étage lit dans la SRAM, le second fait le reste. L'implémentation sur les caches associatifs à plusieurs voies est globalement la même à quelques détails près. [[File:Cache directement adressé pipeliné - deux étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - deux étages]] Avec le circuit précédent, il est possible d'aller plus loin, cette fois en pipelinant l'accès à la mémoire RAM interne au cache. Pour comprendre comment, rappelons qu'une mémoire SRAM est composée d'un plan mémoire et d'un décodeur. L'accès à la mémoire demande d'abord que le décodeur fasse son travail pour sélectionner la case mémoire adéquate, puis ensuite la lecture ou écriture a lieu dans cette case mémoire. L'accès se fait donc en deux étapes successives séparées, on a juste à mettre un registre entre les deux. Il suffit donc de mettre un gros registre entre le décodeur et le plan mémoire. [[File:Cache directement adressé pipeliné - trois étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - trois étages]] Et on peut aller encore plus loin en découpant le décodeur en deux circuits séparés. En effet, rappelez-vous le chapitre sur les circuits de sélection : nous avions vu qu'il est possible de créer des décodeurs en assemblant des décodeurs plus petits, contrôlés par un circuit de prédécodage. Et bien on peut encore une ajouter un registre entre ce circuit de prédécodage et les petits décodeurs. : Théoriquement, toute l'adresse est fournie d'un seul coup au cache, la quasi-totalité des processeurs présentent l'adresse complète à un cache pipeliné. Mais le Pentium 4 fait autrement. Il faut noter que les premiers étages manipulent l'indice dans la SRAM, qui est dans les bits de poids faible, alors que les étages ultérieurs manipulent le ''tag'' qui est dans les bits de poids fort. Les concepteurs du Pentium 4 ont alors décidé de présenter les bits de poids faible lors du premier cycle d'accès au cache, puis ceux de poids fort au second cycle. Pour cela, l'ALU fonctionnait à une fréquence double de celle du processeur, tout comme le cache L1. Il n'y avait pas de pipeline proprement dit, mais cela réduisait grandement la latence d'accès au cache. ===Le ''pipelining'' des caches splittés/sériels=== Il est aussi possible de pipeliner un cache dits splittés, aussi appelés à accès sériel. Pour rappel, les caches à accès sériel vérifient si il y a succès ou défaut de cache, avant d'accéder aux lignes de cache en cas de succès. Ils font donc différemment des autres caches, qui accèdent à une ligne de cache, avant de déterminer s'il y a succès ou défaut en lisant le tag de la ligne de cache. Les caches sériels disposent de deux SRAM : une pour les tags des lignes de cache et une pour les données. Ils accèdent à la SRAM pour les tags, avant d’accéder à la SRAM des données en cas de succès de cache. Vu que l'accès se fait en deux étapes, une vérification des tags suivie de la lecture/écriture des données, il est facile à pipeliner. Pipeliner le cache permet de régler le problème des accès au cache L1, et elle est tout le temps utilisé sur les processeurs modernes. Mais que faire en cas de défaut de cache ? ==Les caches non bloquants== Un '''cache bloquant''' est un cache auquel le processeur ne peut pas accéder pendant un défaut de cache. Il faut attendre que la lecture ou écriture en RAM soit terminée avant de pouvoir utiliser de nouveau le cache. Un '''cache non bloquant''' n'a pas ce problème : on peut l'utiliser pendant un défaut de cache. Les caches non bloquants permettent de démarrer une nouvelle lecture ou écriture alors qu'une autre est en cours, ce qui permet d'exécuter plusieurs lectures ou écritures en même temps. ===Les ''Miss Handling Status Registers''=== Lors d'un défaut de cache, la mémoire RAM est consultée pendant le défaut de cache, mais le cache est inutilisé. Un défaut de cache n'utilise pas le cache, ce n'est qu'un accès en mémoire RAM, sur le bus mémoire (ou un accès aux niveaux de cache inférieurs, peu importe). Le cache en lui-même est laissé libre, rien n’empêche d'y accéder, il est en réalité intrinsèquement non-bloquant. Les caches, bloquants comme non-bloquants, sont en réalité composés d'une mémoire cache proprement dite, entourée de circuits qui servent d'interface entre le processeur et le cache lui-même. Et parmi les circuits tout autour, certains gèrent l'accès au cache lors d'un défaut de cache. Ils sont regroupés sous le terme de '''''Miss Handling Architecture''''' (MHA). La différence entre un cache bloquant et un cache non-bloquant est en réalité liée à l'implémentation de la MHA. Les caches bloquants coupent volontairement l'accès au cache lors d'un défaut, car les défauts sont plus simples à gérer ainsi. Le défaut de cache rapatrie une donnée depuis la RAM, qui sera écrite dans le cache. Et il ne faut pas qu'une tentative d'accès à cette donnée ait lieu avant qu'elle ne soit chargée. Mais ce blocage est général et touche tout le cache, alors que seule une ligne de cache est concernée par le défaut de cache. L'idée derrière un cache non-bloquant est que seule la ligne de cache est bloquée, mais les autres sont accessibles. L'idée est alors de mémoriser les lignes de cache concernées par le défaut de cache, afin d'en bloquer l'accès. À chaque accès, on vérifie si la ligne de cache est déjà réservée par un défaut de cache. La lecture/écriture est alors bloquée si c'est le cas, mais elle accepte les accès sinon. Pour cela, la ''Miss Handling Architecture'' contient des registres qui mémorisent des informations sur les défauts de cache en cours. Ils portent le nom de '''''miss status handling registers''''', que j’appellerais dorénavant MSHR, qui sont aussi appelés des '''''miss buffer'''''. Le contenu des MSHR varie beaucoup suivant le processeur, mais ces derniers stockent au minimum les informations suivantes : * Le numéro de la ligne de cache dans laquelle les données sont chargées. * Un bit de validité qui indique si le MSHR est vide ou pas, qui est mis à 0 quand le défaut de cache est résolu. * Un ou plusieurs champs de lecture/écriture, qui contiennent des informations sur la lecture/écriture. ** Pour une lecture, elle contient des informations sur la destination de la donnée, à savoir qui prévenir quand le défaut de cache est terminé. C'est parfois un nom/numéro de registre (celui dans lequel charger la donnée), mais c'est souvent le numéro de l'entrée dans le ''load/store queue''. ** Pour les écritures, elle contient la donnée à écrire, ou éventuellement un numéro de ''load/store queue'' où se trouve la donnée à écrire. Il faut noter que le bus mémoire ne peut gérer qu'un seul défaut de cache à la fois. Aussi, il est intéressant de regarder ce qui se passe lorsqu'un second défaut de cache survient, pendant qu'un premier est en cours. Dans ce cas, il y a deux réponses qui correspondent à deux types de caches non-bloquants, qui portent les noms barbares de caches de type succès après défaut et défaut après défaut. Sur le premier type, il ne peut pas y avoir plusieurs défauts de cache simultanés. Dès qu'un second défaut de cache survient, le cache stoppe son activité et on ne peut plus démarrer de nouvelle lecture/écriture, tant que le premier défaut de cache n'est pas résolu. Le second type est plus souple et autorise la survenue de plusieurs défauts de cache simultanés. Du moins, jusqu'à une certaine limite, car le cache ne peut supporter qu'un nombre limité d'accès mémoires simultanés (pipelinés). ===Les accès simultanés à une même ligne de cache=== Il arrive que le processeur fasse plusieurs accès mémoire simultanés à la même ligne de cache. Si la ligne de cache en question n'a pas encore été chargée dans le cache, alors on a plusieurs défauts de cache consécutifs pour la même ligne de cache, et le cache non-bloquant doit gérer la situation. Pour la suite, il va falloir faire une petite distinction entre les défauts primaires et secondaires. Imaginons qu'un défaut de cache ait lieu et demande à charger une donnée dans la ligne de cache numéro N. Il s'agit du premier défaut impliquant cette ligne de cache précise, ce qui lui vaut le nom de '''défaut de cache primaire'''. Mais par la suite, d'autres accès mémoire à la même ligne de cache ont lieu, alors que la ligne de cache n'est pas encore disponible. Dans ce cas, il s'agit de '''défauts de cache secondaires'''. Pour l'unité d'accès mémoire, les défauts de cache primaire et secondaire sont différents (ils prennent tous une entrée dans la ''load/store queue''). Mais pour le cache, ils ne correspondent qu'à un seul accès au cache : celui qui demande de charger la ligne de cache demandée. Les défauts de cache primaire et secondaire à la même ligne de cache se voient attribuer un MSHR unique. La gestion des défauts de cache secondaires dépend du cache non-bloquant. La solution la plus simple ne permet pas les défauts de cache secondaires. Le cache ne permet pas deux défauts de cache simultanés pour la même ligne de cache. Les autres solutions le permettent, en fusionnant des accès simultanés à la même ligne de cache en un seul au niveau des MSHR. Dans tous les cas, détecter les défauts de cache secondaires sont un problème qu'il faut détecter. La ''Miss Handling Architecture'' doit détecter les défauts de cache secondaire. Pour cela, elle procède comme suit. Lors de chaque défaut de cache, la MHA récupère le numéro de la ligne de cache associé. Il vérifie alors chaque MSHR pour vérifier s'il contient le numéro en question. S'il n'y a aucune correspondance dans les MSHR, alors c'est signe que le défaut de cache est un défaut primaire. Mais s'il y en a une, alors c'est un défaut secondaire. Évidemment, cela signifie que lors d'un défaut de cache, le numéro de ligne de cache est envoyé à tous les MSHR, pour comparaison. Les MSHR sont donc regroupés dans une mémoire associative, une sorte de mini-cache, faciliter l'implémentation. Lors d'un défaut de cache primaire, l'accès à un cache non-bloquant se fait comme suit : le processeur envoie une adresse au cache, accède à celui-ci, et détecte la survenue d'un défaut de cache. Il en profite alors pour attribuer une ligne de cache dans laquelle sera chargée ce défaut. L'attribution est très simple dans le cas des caches ''direct mapped'', ou associatifs par voie, pour lesquels l'attribution se fait assez simplement. Il mémorise alors cette information dans les MSHR, après avoir vérifié que le défaut de cache n'était pas un défaut secondaire. Lors des accès ultérieurs à une adresse proche, censée être dans la même ligne de cache, le processeur va encore une fois rencontrer un défaut de cache. Il va alors déterminer le numéro de la ligne de cache associée à l'adresse, et comparer ce numéro avec les MSHRs. Si un MSHR contient ce numéro, c'est signe que le défaut de cache est un défaut secondaire. La MHA réagit alors différemment selon le processeur considéré. Une première solution n'autorise pas les défauts de cache secondaires. Si l'un d'entre eux survient, le processeur est gelé par un ''pipeline stall'', une bulle de pipeline. Une autre solution fusionne les défauts de cache secondaires avec le défaut de cache primaire : tout cela ne correspond qu'à un seul défaut de cache pour lui. ===Les MSHR simples=== Un cache non-bloquant à '''MSHR simple''' contient juste plusieurs MSHR qui mémorisent juste un numéro de ligne de cache, un bit de validité, le champ de lecture/écriture, et l'adresse exacte de lecture/écriture. Avec cette organisation, il est possible d'avoir plusieurs défauts de cache séparés, mais à la condition que chaque défaut accède à une ligne de cache différente. Deux accès simultanés à une même ligne de cache ne sont pas possibles, les défauts de cache secondaires ne sont pas autorisés. Ainsi, chaque défaut de cache se voit attribuer son propre MSHR, chacun contient un numéro de ligne de cache différent. Pour comprendre pourquoi c'est impossible de gérer les défauts secondaires, il faut regarder le champ de lecture/écriture. Si on veut effectuer plusieurs écritures consécutives à la même adresse, le MSHR n'aura pas de quoi mémoriser les deux données à écrire. Il pourra mémoriser la première donnée à écrire, pas la seconde. Même chose lors d'une lecture : le champ lecture/écriture peut mémorisr la destination de la première lecture, pas de la seconde. L'avantage est que la MHA n'a besoin que des MSHR et de quelques circuits annexes. Les autres solutions rajoutent des circuits annexes pour gérer les défauts de cache secondaires, qui utilisent beaucoup de circuits. Le cout en circuit est donc élevé, mais le gain en performance est là. Passons maintenant aux caches non-bloquants qui autorisent les défauts de cache secondaire. La solution la plus simple consiste à utiliser ===Les MSHR adressés implicitement=== Avec les '''MSHR adressés implicitement''', il est possible de fusionner plusieurs accès mémoire à une même ligne de cache, mais sous une condition très importante : ces accès lisent/écrivent des mots mémoire différents. Par exemple, imaginons qu'une ligne de cache contienne 8 mots mémoire de 64 bits. Si un premier accès mémoire lit le mot mémoire numéro 7 (dernier mot mémoire de la ligne), et le second accès le mot mémoire numéro 3, alors la fusion est possible. Mais si deux accès mémoire veulent lire/écrire le mot mémoire numéro 7, alors la fusion n'est pas possible et le processeur se bloque, un ''pipeline stall'' survient. Un MSHR adressé implicitement est un MSHR simple, qui contient naturellement un numéro de ligne de cache (tag) et un bit de validité, sauf que le champ de lecture/écriture est dupliqué. Les différents champs lecture/écriture d'un MSHR sont regroupés dans une mémoire RAM/cache qui contient autant d'entrées qu'il y a de mot mémoire dans une ligne de cache. Chaque entrée de la table est associée à un mot mémoire de la ligne de cache et stocke des informations sur celui-ci. Une entrée mémorise, au minimum : * Un champ de lecture/écriture qui contient soit la destination de la lecture, soit la donnée à écrire. * Un bit R/W permettant d'interpréter correctement le champ de lecture/écriture. * Un bit de validité pour chaque entrée de la table, qui dit si un défaut de cache antérieur accède déjà à ce mot mémoire. La fusion de deux défauts de cache est ainsi assez simple. Un défaut de cache primaire/secondaire configure l'entrée associée au mot mémoire lu/écrit. Si un défaut secondaire ultérieur lit/écrit un mot mémoire différent (dont le bit de validité de l'entrée dans la table est à zéro), pas de conflit : il configure une autre entrée, vide. Mais s'il lit/écrit un mot mémoire pour lequel l'entrée de la table est occupée, il y a conflit, le processeur est bloqué par un ''pipeline stall'', une bulle de pipeline. Avec cette organisation, le nombre de MSHR indique combien de lignes de cache peuvent être lues en même temps depuis la mémoire. Quant au nombre d'entrées par MSHR, il détermine combien d'accès mémoires qui ne se recouvrent pas peuvent avoir lieu en même temps. ===Les MSHR adressés explicitement=== Les '''MSHR adressés explicitement''' n'ont pas les contraintes des MSHR adressés implicitement. Avec eux, il est possible d'avoir plusieurs défauts de cache pointant vers la même ligne de cache, mais aussi vers le même mot mémoire. De tels défauts de cache apparaissent sur les processeurs à exécution dans le désordre, mais sont très rares, voire inexistants, sur les processeurs ''in-order''. L'idée est encore que chaque MSHR est associée à une table mémoire qui mémorise des entrées. Cette table n'est autre qu'une mémoire FIFO. Sauf que cette fois-ci, une entrée n'est pas associée à un mot mémoire. Une entrée est associée à un défaut de cache. Une entrée mémorise là encore la destination de la lecture ou la donnée de l'écriture suivi du bit R/W, ainsi que d'un bit de validité par entrée, mais aussi : la position du mot mémoire lu dans la ligne de cache. C'est cette dernière information qui n'était pas présente dans les MSHR adressés implicitement. De plus, le nombre d'entrée par MSHR n'est pas égal au nombre de mots mémoires dans une ligne de cache, mais peut être arbitrairement grand. L'avantage d'un tel cache est qu'il est capable de traité les défauts secondaires concernant un même mot mémoire, et ce, car les accès à la ligne de cache concernée se produisent dans l'ordre de génération des défauts (la table mémoire est une FIFO). ===Les MSHRs inversés=== Généralement, plus on veut supporter de défauts de cache, plus le nombre de MSHR et d'entrées augmente. Mais au-delà d'un certain nombre d'entrées et de MSHR, les MSHR adressés implicitement et explicitement ont tendance à bouffer un peu trop de circuits. Utiliser une organisation un peu moins gourmande en circuits est donc une nécessité. Cette organisation plus économe se base sur des MSHR inversés. Les MSHR inversés ne contiennent qu'une seule entrée, en quelque sorte : au lieu d’utiliser n MSHR de m entrées chacun, on va utiliser n × m MSHR inversés. La différence, c'est que plusieurs MSHR peuvent contenir un tag identique, contrairement aux MSHR adressés implicitement et explicitement. Lorsqu'un défaut de cache a lieu, chaque MSHR est vérifié. Si jamais aucun MSHR ne contient de tag identique à celui utilisé par le défaut, un MSHR vide est choisi pour stocker ce défaut, et une requête de lecture en mémoire est lancée. Dans le cas contraire, un MSHR est réservé au défaut de cache, mais la requête n'est pas lancée. Quand la donnée est disponible, les MSHR correspondant à la ligne qui vient d'être chargée vont être utilisés un par un pour résoudre les défauts de cache en attente. ===Les MSHR intégrés au cache=== Certains chercheurs ont remarqué que pendant qu'une ligne de cache est en train de subir un défaut de cache, celle-ci reste inutilisée, et son contenu est destiné à être perdu une fois le défaut de cache résolu. Ils se sont dit que, plutôt que d'utiliser des MSHR séparés, il vaudrait mieux utiliser la ligne de cache pour stocker les informations sur les défauts de cache en attente dans cette ligne de cache. Pour éviter tout problème, il faut rajouter un bit dans les bits de contrôle de la ligne de cache, qui sert à indiquer que la ligne de cache est occupée : un défaut de cache a eu lieu dans cette ligne, et elle stocke donc des informations pour résoudre les défauts de cache. ==L'entrelacement mémoire== Nous venons de voir comment une mémoire cache peut gérer plusieurs accès mémoire simultanés. Il se trouve que ce genre d'optimisation dépasse largement le cadre du cache. Les mémoires RAM ne sont pas en reste, elles aussi ! Il est possible de pipeliner l'accès à une mémoire RAM, en utilisant des techniques dites d''''entrelacement'''. Précisons cependant que cette optimisation n'est pas utilisée sur les mémoires SDRAM ou DDR modernes, du moins pas sans modifications majeures. Mais divers ordinateurs assez anciens, dont des superordinateurs, ont utilisé cette technique. La technique demande d'utiliser plusieurs mémoires séparées. Sur les anciens ordinateurs, les mémoires en question sont des chips mémoire, à savoir des circuits intégrés de DRAM. Pour faire plus général, nous allons utiliser le terme de '''banques''', ou encore de bancs mémoire. La différence est qu'il existe des mémoires multi-banque, qui regroupent plusieurs banques indépendantes dans un seul boitier, dans un seul chip mémoire. En clair, de telles mémoires regroupent plusieurs sous-mémoires dans un seul circuit intégré. Il est aussi possible d'utiliser plusieurs chips mémoire séparés, chacun ayant une banque. Reste à combiner ces banques pour former un pseudo-pipeline. ===Utiliser plusieurs banques sans entrelacement=== Sans optimisation particulière, les adresses sont réparties dans les banques comme indiqué ci-dessous. Il s'agit de ce qui s'appelle un arrangement horizontal, et nous avions vu celui-ci dans le chapiutre sur les mémoires SDRAM. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Avec cet arrangement, les bits de poids fort de l'adresse sont utilisées pour sélectionner la banque adéquate, et le reste de l'adresse est envoyé sur le bus d'adresse. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Adresse de banque !! Adresse dans la banque |- | Quelques bits de poids fort || Reste de l'adresse |} L'entrelacement change cette répartition, afin d'accélérer les accès mémoire. L'entrelacement classique vise à améliorer les accès à des adresses mémoires consécutives, mais des formes plus évoluées d'entrelacement visent à optimiser des accès mémoire arbitraires. Voyons-les dans l'ordre, du plus simple au plus complexe. ===L'entrelacement classique=== Avec l''''entrelacement classique''', des adresses consécutives sont réparties dans des banques consécutives. Précisons que cette attribution des adresses n'implique absolument pas la mémoire virtuelle ou n'importe quel mécanisme dans le processeur. La répartition décide que telle adresse mémoire va dans telle banque, à telle adresse dans la banque. Elle est donc le fait du contrôleur mémoire, donc en dehors du processeur (les contrôleurs mémoires n’étaient pas encore intégrés dans le processeur à l'époque). [[File:Répartition des adresses dans une mémoire interleaved.png|centre|vignette|upright=1.5|Répartition des adresses dans une mémoire interleaved.]] L'entrelacement simple permet d'accélérer les accès mémoire qui se font à des adresses consécutives. Avec l'entrelacement, chaque accès mémoire tombe dans une banque mémoire différente, ce qui fait qu'on peut démarrer un nouvel accès mémoire à chaque cycle d'horloge. Pendant qu'une banque est occupée par un accès mémoire, on démarre le suivant dans une autre banque, et ainsi de suite. Le tout se termine soit quand on a épuisé toutes les banques libres, soit quand l'accès en rafales se termine. Pas besoin d'attendre que la mémoire ait fini sa lecture/écriture avant de démarrer la lecture/écriture suivante. [[File:Pipemining mémoire.png|centre|vignette|upright=2.5|Pipelining mémoire]] L'animation suivante illustre bien le processus, avec quatres banques. [[File:Interleaving.gif|centre|vignette|upright=2.5|Pipelining mémoire]] L'entrelacement simple utilise les bits de poids faible pour sélectionner la banque, et les bits de poids fort pour la case mémoire. [[File:Adresse mémoire d'une mémoire entrelacée.png|centre|vignette|upright=2|Adresse mémoire d'une mémoire entrelacée]] Précisons que le temps d'accès mémoire ne change pas beaucoup avec l'entrelacement. Par contre, on peut faire plus d'accès mémoire simultanés. On peut démarrer un accès mémoire par cycle d'horloge, mais l'accès en lui-même prend plusieurs cycles. Les mémoires à entrelacement ont donc un débit supérieur aux mémoires qui ne l'utilisent pas. L'entrelacement classique pourrait être utilisé pour accélérer les transferts entre mémoire RAM et cache, qui se font en blocs d'adresses consécutives. Mais de nos jours, il n'est pas utilisé dans cette optique. Il faut dire qu'il est redondant avec le mode rafale des mémoires DRAM. Les deux font globalement la même chose et on voit mal comment utiliser les deux en même temps. Le mode rafale étant plus simple à implémenter, et plus léger niveau utilisation du bus de commande mémoire, il est préféré à l'entrelacement. Par contre, l'entrelacement a eu son heure de gloire sur d'anciens processeurs qui n'avaient pas de mémoire cache, alors qu'ils disposaient d'un pipeline. L'exemple typique est celui des nombreux ''processeurs vectoriels'', que nous n'avons pas encore abordé à ce stade du cours. De tels processeurs avaient un pipeline, une unité de calcul fortement pipelinée, et parfois même de l'exécution dans le désordre et du renommage de registres ! ===L'entrelacement par décalage=== L'entrelacement simple est très utile pour les accès en rafle ou équivalents. Par contre, il existe de rares situations où il n'est pas optimal. Une de ces situations est celle des accès en enjambée (en ''stride''), où l'on accède en série à des adresses sont séparées par N mots mémoires. [[File:Accès par enjambées.png|centre|vignette|upright=2|Accès par enjambées.]] De tels accès surviennent quand un logiciel accède à des structures de données spécifiques, à savoir des tableaux de structures, des matrices, ou d'autres structures de données dans le genre. Un cas classique est celui du parcours d'une matrice colonne par colonne. Les matrices de nombres sont mémorisées ligne par ligne, ce qui fait que parcourir une colonne demande de passer d'une ligne à la suivante et de faire des grands sauts en mémoire RAM, mais tous espacés par la même distance. Avec l'entrelacement simple, les accès en enjambée sont moins performants que les accès à des adresses consécutives, mais cela ne fonctionne pas trop mal. Le pire cas est celui où l'on a N banques et où les données sont justement placées toutes les N adresses. Des accès consécutifs vont tous tomber dans la même banque, on ne peut plus accéder à des banques différentes en parallèle. Mais en dehors de ce cas, on voit une amélioration par rapport à la situation sans entrelacement. Pour obtenir des performances maximales pour les accès en enjambées, il faut répartir les mots mémoires dans la mémoire autrement. L'organisation idéale est la suivante. : Dans les explications qui vont suivre, la variable N représente le nombre de banques, qui sont numérotées de 0 à N-1. On commence par organiser les N premières adresses comme une mémoire entrelacée simple : l'adresse 0 correspond à la banque 0, l'adresse 1 à la banque 1, etc. Pour le bloc suivant, on décale tout d'une adresse, à savoir qu'on commence à remplir les banques à partir de la seconde, non de la première. Une fois la fin du bloc atteinte, on finit de remplir le bloc en repartant du début du bloc. Le troisième bloc d'adresse subit le même traitement, sauf qu'on commence à remplir à partir de la seconde banque. Et on poursuit l’assignation des adresses en décalant d'un cran en plus à chaque bloc. Ainsi, chaque bloc verra ses adresses décalées d'un cran en plus comparé au bloc précédent. Si jamais le décalage dépasse la fin d'un bloc, alors on reprend au début. [[File:Mémoire interleaved par décalage.png|centre|vignette|upright=1.5|Mémoire entrelacée par décalage.]] En faisant cela, on remarque que les banques situées à N adresses d'intervalle sont différentes. Dans l'exemple du dessus, nous avons ajouté un décalage de 1 à chaque nouveau bloc à remplir. Mais on aurait tout aussi bien pu prendre un décalage de 2, 3, etc. Dans tous les cas, on obtient un entrelacement par décalage. Ce décalage est appelé le pas d'entrelacement, noté P. Le calcul de l'adresse à envoyer à la banque, ainsi que la banque à sélectionner se fait en utilisant les formules suivantes : * adresse à envoyer à la banque = adresse totale / N ; * numéro de la banque = (adresse + décalage) modulo N ; * décalage = (adresse totale * P) mod N. Avec cet entrelacement par décalage, on peut prouver que la bande passante maximale est atteinte si le nombre de banques est un nombre premier. Seulement, utiliser un nombre de banques premier peut créer des trous dans la mémoire, des mots mémoires inadressables. Malgré ce défaut, la technique a été utilisée sur quelques ordinateurs, avec l'exemple notable du superordinateur ''Burroughs Scientific Processor''. Pour éviter cela, il y a plusieurs solutions. Par exemple, on peut faire en sorte que N et la taille d'une banque soient premiers entre eux : ils ne doivent pas avoir de diviseur commun. Mais en pratique, elles n'ont pas vraiment été implémentées dans une vraie machine, et sont restées à l'état de recherche, aussi je les passe sous silence. ===L'entrelacement pseudo-aléatoire=== Une dernière méthode de répartition consiste à répartir les adresses dans les banques de manière "pseudo-aléatoire". L'idée exacte est de faire passer l'adresse dans une '''fonction de hachage''', plus ou moins complexe. Pour rappel, une fonction de hachage prend une entrée de grande taille et fournit en sortie un résultat de petite taille. Idéalement, elles doivent donner des résultats aussi différents que possible pour des entrées similaires, histoire de simuler une sorte de pseudo-aléatoire. Ici, une adresse est transformée en un numéro de banque. Une fonction de hachage simple ne fait que permuter des bits de l'adresse pour obtenir son résultat. Elle ne fait qu'échanger des bits de place avant de couper l'adresse en deux morceaux : un pour la sélection de la banque, et un autre pour la sélection de l'adresse dans la banque. Cette permutation est fixe, et ne change pas suivant l'adresse. Des fonctions de hachage plus complexes font des XOR bit à bit entre certains bits de l'adresse. ==Les contrôleurs mémoires SDRAM/DDR optimisés== Après avoir vu le cas des mémoires RAM, nous allons nous concentrer sur le cas particulier des mémoires SDRAM. Les mémoires SDRAM et DDR modernes sont capables de gérer plusieurs accès simultanés à la mémoire RAM, et leurs contrôleurs mémoire en font tout autant. C'est une différence majeure avec les mémoires asynchrones FPM/EDO, qui n'acceptaient qu'un seul accès mémoire à la fois. Leur contrôleur mémoire n'acceptait qu'un seul accès mémoire à la fois, c'était un contrôleur mémoire bloquant. Les contrôleurs mémoires des SDRAM sont eux non-bloquants et peuvent encaisser une dizaine d'accès mémoire à la fois. Peu de choses sont connues sur les contrôleurs de SDRAM/DDR modernes, les fabricants ne donnant que peu de détails dessus. Les rares simulateurs qui tentent de décrire leur fonctionnement, comme DRAM SIM I et II, sont particulièrement simples et ne vont pas dans le détail. Néanmoins, le peu qu'on sait est tout de même instructif. ===Rappel sur les SDRAM : tampon de ligne et commandes=== Les mémoires SDRAM sont des mémoires à tampon de ligne. Elles font un accès mémoire en deux étapes. La première étape recopie une ligne de N * 64 bits dans un tampon interne à la SDRAM. La seconde étape sélectionne une colonne, à savoir une donnée de 64 bits, qui est soit envoyée sur le bus de données pour une lecture, soit modifiée par une écriture. [[File:Mémoire à tampon de ligne.png|centre|vignette|upright=2|Mémoire à tampon de ligne]] Les deux étapes correspondent à deux commandes séparées : une commande ACT qui précise l'adresse de la ligne, et une commande READ ou WRITE qui précise l'adresse de la colonne. Les commandes ACT sont systématiquement précédées d'une commande PRECHARGE qui ferme la ligne précédente. Pour être plus précis, elle précharge les lignes de bits du plan mémoire à une tension particulière, ce qui les vide de leur contenu. Mais c'est un détail sans importance pour ce qui va suivre. Les SDRAM permettent de se passer de la première étape si des accès consécutifs se font dans la même ligne. Une fois activée, la ligne reste ouverte et on peut accéder plusieurs fois de suite dedans. On a alors juste à préciser l'adresse de colonne dedans. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | || bgcolor="#FFA0FF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || bgcolor="#A0FFFF" | READ (2) || bgcolor="#A0FFFF" | READ (3) || || || bgcolor="#A0FFFF" | READ (4) || bgcolor="#A0FFFF" | READ (5) || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | Donnée READ 1 || ||bgcolor="#A0FFFF" | Donnée READ 2 | bgcolor="#A0FFFF" | Donnée READ 3 || || || bgcolor="#A0FFFF" | Donnée READ 4 || bgcolor="#A0FFFF" | Donnée READ 5 |} ===La mise en attente des accès mémoire=== Un contrôleur de SDRAM peut accepter plusieurs accès mémoire en même temps. Mais cela ne signifie pas que le contrôleur sera prêt à les traiter immédiatement. Pour éviter tout problème de disponibilité, le contrôleur met en attente les accès mémoire que le processeur lui envoie, pour les exécuter dès que possible. Les accès mémoire sont mis en attente dans une mémoire FIFO, histoire de les exécuter dans leur ordre d'arrivée, notamment pour renvoyer les lectures dans l'ordre demandé. Il y a aussi une mémoire FIFO pour les données à écrire et une FIFO pour les données lues. Cette dernière sert au cas où le cache ne soit pas disponible quand on lui envoie la donnée lue. [[File:Module d'interface avec la mémoire.png|centre|vignette|upright=2.5|Contrôleur mémoire avec mise en attente des requêtes processeur et autres optimisations.]] Des optimisations sont possibles dès la mémoire FIFO. Par exemple, on peut regrouper plusieurs accès à des données consécutives en un seul accès en rafale. Le contrôleur mémoire analyse les requêtes mises en attente et détecte si certaines se font à des adresses consécutives. Si c'est le cas, il fusionne ces requêtes en une lecture/écriture en rafale. Une telle optimisation est appelée la '''combinaison de lecture''' pour les lectures, et la '''combinaison d'écriture''' pour les écritures. ===Le pipeline des mémoires SDRAM=== Les accès mémoire sont traduits par un séquenceur mémoire en une série de commandes mémoires, qui sont séparées par des délais mémoire de quelques cycles d'horloge. Les délais sont très précis, et sont à respecter à la lettre. Les SDRAM sont parfois capables de démarrer une commande avant que la précédente soit terminée. Mais le respecte des délais mémoire est très limitant, ce qui fait qu'on ne peut pas parler de réel pipeline, comme c'est le cas sur les processeurs. Parlons plutot de commandes anticipées. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | || bgcolor="#A0FFFF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || || || bgcolor="#FFA0FF" | READ (2) || || || || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 | bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 | |} Même si elle parait très limitée, cette possibilité est décuplée par la présence de plusieurs banques dans la mémoire SDRAM. Les chips mémoires de SDRAM sont composés de plusieurs banques, chacune étant une sorte de mini-mémoire miniature. Chaque banque a son propre décodeur, son propre tampon de ligne, ses propres multiplexeurs de colonne, sa logique de rafraichissement mémoire, etc. C'est comme si une SDRAM regroupait plusieurs mémoires séparées dans un même circuit intégré. Et cela permet de faire plusieurs accès mémoire en même temps, dans des banques différentes. Il est possible de lancer un accès mémoire dans deux banques en même temps, par exemple. Ou encore, on peut lancer un nouvel accès mémoire dans une banque si elle est inoccupée. La possibilité améliore grandement la performance de la SDRAM. Mais nous en reparlerons dans un chapitre ultérieur. {|class="wikitable" |+ Pipelining basique sur les SDRAM |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 |- ! Banque Numéro 1 | || || || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || || || |- ! Banque Numéro 2 | || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || |- ! Banque Numéro 3 | || || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || |} L'implémentation n'a rien de vraiment compliqué. Il suffit d'utiliser une mémoire FIFO par banque. Ainsi, les accès dans une même banque se feront en série, dans l'ordre d'arrivée, mais des accès à des banques différentes se feront dans le désordre. Cela ne pose pas de problème car les accès se font à des adresses différentes, ce qui fait qu'il n'y a pas de conflits majeurs. [[File:Gestion parallèle des banques.png|centre|vignette|upright=2.5|Gestion parallèle des banques.]] ===Le ré-ordonnancement des commandes mémoires=== Un contrôleur mémoire moderne est capable de changer l'ordre des requêtes mémoires, pour gagner en performances. En clair : il peut faire l'équivalent mémoire de l'exécution dans le désordre des processeurs haute performance. Une différence majeure est que le contrôleur mémoire ne remet pas les accès mémoire dans l'ordre avant de les envoyer au processeur. C'est le processeur qui remet en ordre les accès mémoire, dans sa ''load queue''. Pour cela, les requêtes mémoire sont "numérotées", à savoir qu'on leur attribue un identifiant binaire. Le contrôleur mémoire exécute les lectures/écritures dans le désordre, mais garde la trace de leur identifiant. Il sait dans quel ordre il exécute les accès mémoire et connait leur latence. Ce qui fait qu'il sait que la donnée lue à tel instant est associée à la requête mémoire numéro X. Pour les lectures, il renvoie la donnée lue avec l'identifiant. Le processeur sait alors à quelle lecture la donnée lue correspond et il se débrouille en interne pour gérer la situation. En clair : le processeur voit les accès mémoire dans le désordre, c'est lui qui les remet en ordre. Maintenant que ces précisions sont faites, posons cette question : quel est l'intérêt de faire les accès mémoire dans le désordre ? Accéder à des banques en parallèle est une réponse possible, mais le ré-ordonnancement est surtout utile si tous les accès mémoire atterrissent dans la même banque. L'exécution dans le désordre des lectures/écritures SDRAM vise à faire en sorte que des accès consécutifs se fassent dans une même ligne. Elle marche si plusieurs accès à une même ligne ne sont pas consécutifs, et qu'il y a des accès entre les deux. Après ré-ordonnancement, ces accès mémoire deviennent consécutifs, ils sont exécutés l'un à la suite de l'autre. Pour rendre le tout plus concret, voici un exemple. Imaginez que l'on sait les 3 accès mémoire suivants : * Une lecture ligne A ; * Une écriture ligne B ; * Une lecture ligne A. Sans réordonnancent, on doit émettre deux commandes PRECHARGE : une quand on passe de la ligne A à la ligne B, et une autre pour le passage inverse. pour éviter cela, le contrôleur mémoire peut retarder l'écriture, ou au contraire avancer la seconde lecture. le résultat est alors le suivant : * Lecture ligne A ; * Lecture ligne A ; * Une écriture ligne B. On a donc deux accès consécutifs à la même ligne, suivi par une écriture dans une autre ligne. La technique marche parce que l'on a un mix adéquat de lectures et d'écritures. Mais il existe des possibilités de réorganisation autres qui ne sont pas valides. Il faut par exemple prendre garde à éviter d'intervertir lectures et écritures à une même adresse, sous peine de problèmes. Encore une fois, il faut faire attention aux dépendances mémoires. Le controleur mémoire teste les dépendances mémoires avant d'envoyer des commandes à la mémoire DDR/SDRAM. ==L'entrelacement sur les mémoires SDRAM== L''''entrelacement''' fonctionne sur les mémoires SDRAM, mais il doit être fortement modifié. Concrètement, tout ce qui a étré dit plus est inapplicable pour les SDRAM, du fait de la présence du tampon de ligne. Des adresses consécutives atterrissent déjà dans la même ligne, ce qui fait qu'on n'a pas besoin d'entrelacement au niveau d'une ligne. Et ne parlons pas de ce la présence de rangées et de canaux mémoire ! Cependant, il peut y avoir un entrelacement lié à la présence des banques SDRAM. L'entrelacement est géré juste avant le séquenceur mémoire. Le '''circuit d'entrelacement''' intervertit certains bits de l'adresse lors des accès mémoires. On trouve aussi des mémoires FIFOs pour mettre en attente les requêtes mémoire provenant du processeur. Elles permettent de recevoir plusieurs accès mémoire consécutifs. Si vous vous demandez comment plusieurs accès mémoire consécutifs peuvent avoir lieu, sachez que c'est une bonne question. Pour le moment, nous allons dire que ces accès proviennent du processeur ou de la mémoire cache, sans expliquer comment. Le contrôleur mémoire reçoit une série d'accès mémoire assez rapprochés, qu'il doit exécuter au mieux. [[File:Controleur mémoire d'une SDRAM avec entrelacement.png|centre|vignette|upright=2.5|Contrôleur mémoire d'une SDRAM avec entrelacement.]] Pour gérer l'entrelacement, le contrôleur de SDRAM prend en entrée l'adresse envoyée par le processeur, et la découpe en plusieurs champs : un pour sélectionner la banque adéquate, un autre pour la ligne, un autre pour la colonne, un autre pour la rangée. Une solution simple désactive l'entrelacement. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de banque || Adresse de ligne || Adresse de colonne |} Si cette solution fonctionne bien si les accès mémoire sont assez aléatoires, en pratique, mieux vaut utiliser l'entrelacement. ===L'entrelacement de banques/lignes=== Une première méthode d'entrelacement est l''''entrelacement de banques'''. Elle répartit deux lignes consécutives dans deux banques différentes. Pour comprendre l'idée, prenons un exemple. Imaginons une mémoire avec deux banques et 4 lignes. Imaginons qu'on parcoure/balaye la mémoire RAM en partant des adresses basses. Sans entrelacement de ligne, les accès se feront comme suit, de gauche à droite : {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Banque numéro 1 !! colspan="4" | Banque numéro 2 |- | Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 || Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 |} Avec l'entrelacement de banques, les accès se feront comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 |- | Ligne 1 || Ligne 1 || Ligne 2 || Ligne 2 || Ligne 3 || Ligne 3 || Ligne 4 || Ligne 4 |} Avec N banques, la première ligne ira dans la première banque, la seconde ligne dans la seconde, et ainsi de suite jusqu'à la énième banque qui va dans la banque numéro N. Et la ligne N+1 repart dans la première banque. L'avantage est que passer d'une ligne à la suivante est assez rapide. Au lieu de devoir fermer la première ligne et ouvrir la suivante on ouvrira directement la ligne suivante dans une banque différente, ce qui sera plus rapide. La commande PRECHARGE pourra être envoyée à la seconde banque en avance. Pour cela, l'adresse est découpée comme suit. {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne (précise à l'octet) |} ===L'entrelacement de rangées=== Il est aussi possible de faire la même chose, mais avec les rangées. Pour simplifier fortement, une rangée est simplement un chip mémoire. En réalité, les rangées ne sont pas des chip mémoire, mais un ensemble de chips mémoire regroupés ensemble histoire d'atteindre les 64 bits du bus de données. Par exemple, une rangée peut combiner 8 chips mémoire avec un bus de données de 8 bits chacun pour obtenir les 64 bits du bus de données d'une SDRAM. Mais nous passons ce détail sous silence dans les explications qui vont suivre, par souci de simplification. Pour faire comprendre l'entrelacement de rangée, le mieux est d'utiliser un exemple, le même que précédemment. Sans entrelacement de rangée, on change de chip mémoire une fois qu'on a balayé toutes ses adresses. {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Rangée numéro 1 !! colspan="4" | Rangée numéro 2 |- | Banque 1 || Banque 2 || Banque 3 || Banque 4 || Banque 1 || Banque 2 || Banque 3 || Banque 4 |} Avec l'entrelacement de ligne, on change de rangée dès qu'on change de banque/ligne. {|class="wikitable" |+ Adresse mémoire |- ! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 |- | Banque 1 || Banque 1 || Banque 2 || Banque 2 || Banque 3 || Banque 3 || Banque 4 || Banque 4 |} En clair, quand on a épuisé toutes les banques dans une même rangée, on passe à la rangée suivante au lieu de rester dans la même rangée. Notons que dans l'exemple précédent, on a combiné l'entrelacement de rangée et de banque, mais on aurait pu utiliser l'entrelacement de rangée seul. Mais ce n'est pas le cas le plus courant en pratique. Toujours est-il qu'en combinant entrelacement de rangée et de ligne, le découpage de l'adresse est le suivant : {|class="wikitable" |+ Adresse mémoire |- | Adresse de ligne || Numéro de rangée || Adresse de banque || Adresse de colonne (précise à l'octet) |} D'autres méthodes d'entrelacement sont possibles, mais elles sont plus complexes. Chaque contrôleur mémoire fait un peu à sa sauce. Ils permettent en général de choisir entre plusieurs entrelacements. Au minimum, ils permettent de désactiver l'entrelacement, d'activer un entrelacement de ligne, et d'autres entrelacements plus complexes, dont certains sont propriétaires et tenus secrets par le fabricant. ===L'entrelacement avec le ''dual channel''=== [[File:Dual channel slots.jpg|vignette|Slots mémoires avec ''dual channel''.]] L'usage du ''dual channel'' complique encore l'entrelacement. Là encore, il y a deux grandes solutions : avec et sans entrelacement des canaux mémoire. Rappelons le principe : deux barrettes de RAM sont accédées en parallèle. Et pour cela, il faut utiliser l'entrelacement. Typiquement, chaque barrette mémoire fournit 64 bits, ce qui fait que l'on peut accéder à 128 bits d'un seul coup, par exemple avec un accès en rafale. Sans ''dual channel'', la première barrette correspond à la moitié haute de la RAM, la seconde barrette correspond à la moitié basse. Avec ''dual channel'', une forme spécifique d'entrelacement est activée. Concrétement, deux blocs de 64 bits sont placés dans des canaux mémoire séparés. Idem avec du triple ou quadruple canal, mais c'est alors trois ou quatre blocs de 64 bits qui sont dispersés dans des canaux consécutifs. Le découpage de l'adresse est alors le suivant : {|class="wikitable" |+ Adresse mémoire |- ! colspan="6" | Sans entrelacement |- | Numéro de canal/barrette || Reste de l'adresse || Position de l'octet dans un bloc de 64 bits |- | colspan="6" | |- ! colspan="6" | Avec entrelacement |- | Reste de l'adresse || Numéro de canal/barrette || Position de l'octet dans un bloc de 64 bits |} [[File:Décodage d'adresse avec dual channel.png|centre|vignette|upright=2|Décodage d'adresse avec dual channel.]] Les contrôleurs de SDRAM précédents sont assez basiques et ne représentent pas les contrôleurs les plus évolués. Ils étaient utilisés sur les anciens PC, à une époque où ils étaient encore sur la carte mère du processeur. Mais de nos jours, le contrôleur mémoire est intégré au processeur. Et il incorpore de nombreuses optimisations afin de gagner en performances. Malheureusement, ces optimisations sont elles-mêmes dépendantes des optimisations intégrées dans le processeur, ce qui fait qu'on ne peut pas en parler ici. Elles demandent que le contrôleur mémoire reçoive plusieurs accès mémoire simultanés, ce qui n'a aucun sens à ce stade du cours. Aussi, nous allons les passer sous silence pour le moment. Nous les verrons dans le chapitre sur le parallélisme mémoire. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=La désambiguïsation mémoire | prevText=La désambiguïsation mémoire | next=Les processeurs superscalaires | nextText=Les processeurs superscalaires }} </noinclude> e1b3wecmpvurupeb2zebt3qswa3tfs5 764713 764712 2026-04-23T21:15:33Z Mewtow 31375 /* Les contrôleurs mémoires SDRAM/DDR optimisés */ 764713 wikitext text/x-wiki Dans ce chapitre, nous allons voir les techniques qui permettent de gérer plusieurs accès mémoire simultanés directement au niveau du cache ou de la mémoire RAM. Par plusieurs accès mémoire simultanés, vous pensez sans doute à l'usage de cache multiports, voire de mémoires RAM multiport. C'est en effet une solution, mais ce n'est clairement pas la seule. Par exemple, il est possible de pipeliner l'accès au cache, voire à la mémoire RAM. Il existe de nombreuses techniques de '''parallélisme mémoire''', et nous allons les voir dans ce chapitre. Pour donner un exemple, il est possible de pipeliner les accès mémoire. Il est possible de pipeliner l'accès au cache et/ou l'accès à la mémoire, et nous verrons comment faire dans ce chapitre. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, qu'ils aient ou non un pipeline dynamique. Par contre, pipeliner la mémoire est une technique ancienne, aujourd'hui peu utilisée. Elle était utilisée sur des très vieux ordinateurs, qui avaient un pipeline mais pas de mémoire cache. Mais de nos jours, les mémoires SDRAM et DDR ne sont pas adaptées pour ça, il est très difficile de les pipeliner correctement. Le parallélisme mémoire est utile aussi bien sur des processeurs à émission dans l'ordre que dans le désordre. Par exemple, un processeur ''in-order'' avec des lectures non-bloquantes peut e, profiter. Il peut techniquement lancer une seconde lecture, après avoir rencontré une première lecture non-bloquante. En général, le nombre de lectures consécutives est cependant limité à moins d'une dizaine. À l'opposé, les processeurs sans lectures non-bloquantes profitent pas du parallélisme mémoire. À la rigueur, ils peuvent en profiter s'ils intègrent des techniques de préchargement. Pendant que le processeur exécute une lecture/écriture, il peut précharger une autre donnée en parallèle. Inutile de dire que sans pipeline processeur, le parallélisme mémoire ne sert pas à grand-chose. ==Le ''line fill buffer'' et les techniques associées== Lors d'un défaut de cache, le processeur doit attendre que toute la ligne de cache soit chargée avant d'être utilisable. Or, la taille d'une ligne de cache est supérieure à la largeur du bus mémoire, ce qui fait qu'une ligne de cache est chargée en plusieurs fois, morceaux par morceaux, mot mémoire par mot mémoire. Le chargement peut se faire directement dans le cache, mais ce n'est pas une solution très pratique. À la place, beaucoup de processeurs ajoutent une mémoire tampon entre la RAM et le cache, appelée le '''tampon de remplissage de ligne''' (''line-fill buffer'') dans les processeurs modernes. Lors d'un défaut de cache, le processeur charge la donnée de la RAM dans le tampon de remplissage de ligne, mot mémoire par mot mémoire. Une fois plein, le tampon de remplissage de ligne est recopié dans la ligne de cache. Le tampon de remplissage de ligne contient une ligne de cache, si ce n'est que certains bits de contrôle ne sont pas présents. Le tampon de remplissage de ligne contient un bit de validité pour chaque mot mémoire de la ligne de cache, qui indiquent si le mot mémoire a été chargé. Par exemple, prenons un processeur 64 bits, qui gère donc des mots mémoire de 8 octets, avec des lignes de cache 256 octets/32 mots mémoire. Le tampon de remplissage de ligne contiendra 32 bits de validité. Si le processeur a chargé les 6 premiers mots mémoire, les 6 premiers bits de validité seront mis à 1, les autres seront encore à zéro. Il faut noter que la disponibilité de la ligne complète se détermine assez facilement en faisant un ET logique entre tous les bits de validité. Le processeur sait ainsi quand la ligne de cache est disponible entièrement et donc quand la transférer dans le cache. La même chose existe avec une hiérarchie de cache, sauf que l'on trouve une mémoire tampon entre chaque niveau de cache. Il y a un tampon de remplissage de ligne entre le cache L1 et le cache L2, entre le cache L2 et le L3, etc. Si le cache ne gère qu'un seul défaut de cache à la fois, le ''fill line buffer'' est une mémoire très simple, qui ne mémorise qu'une seule ligne de cache. Et cela vaut aussi bien pour un cache bloquant que non-bloquant. Mais sur les caches capables de gérer plusieurs défauts simultanés, le tampon de remplissage de ligne est une mémoire de type FIFO ou LIFO, capable de mémoriser plusieurs lignes de cache. Notons que le tampon de remplissage de ligne est très utile pour implémenter certaines techniques, par exemple le contournement du cache. Nous avions vu dans le chapitre sur le cache que certains accès mémoire doivent contourner le cache, pour des raisons de cohérence des caches. C'est notamment nécessaire pour accéder aux périphériques, mais c'est aussi utile pour des raisons de performances dans des cas très spécifiques. Les accès qui contournent le cache se font directement dans le tampon de remplissage de ligne : le processeur écrit ou lit les données depuis ce tampon de remplissage de ligne, sans accéder au cache. ===L'''early restart'' et le ''critical word load''=== La présence du ''line buffer'' permet une optimisation assez intéressante, qui permet de réduire la latence des défauts de cache. L'optimisation consiste à lire un mot mémoire dans le tampon de remplissage de ligne, même si la ligne de cache complète n'a pas encore été chargée. Il existe deux manières de faire cela, qui portent les noms d'''early restart'' et de ''critical word load''. La première est la version la plus simple, la seconde est plus complexe mais plus performante. Avec la technique d''''''early restart''''', la ligne de cache est chargée normalement, en partant de son premier mot mémoire. Dès que le mot mémoire lu/écrit par le processeur est copié dans le ''line fill buffer'', il est envoyé au processeur immédiatement. Illustrons le tout par un exemple, où une ligne de cache fait 16 mots mémoire. Le processeur effectue une lecture, qui lit le 5ème mot mémoire. Sans ''early restart'', le processeur doit charger les 16 mots mémoire avant de faire la lecture dans le cache. Avec ''early restart'', le processeur reçoit la donnée dès que le 5ème mot mémoire est disponible. Le processeur doit attendre que les 4 premiers mots mémoire soient chargés, puis le 5ème mot mémoire arrive et est envoyé directement au processeur, il est lu directement depuis le ''line fill buffer''. Les 11 mots mémoire suivants sont ensuite chargés dans le cache pendant que le processeur fait des calculs dans son coin. Le ''critical word load'' est une optimisation de la technique précédente où le chargement de la ligne de cache commence directement à la donnée demandée par le processeur. Pour reprendre l'exemple précédent, où le processeur demande le 5ème mot mémoire sur 16, le ''critical word load'' charge le 5ème mot mémoire en premier et l'envoie au processeur, ce qui fait qu'il est chargé très rapidement. Pas besoin d'attendre que le processeur charge les 4 mots mémoire précédents comme avec l'''early restart''. Dans le détail, le processeur charge le 5ème mot mémoire en premier, puis charge les 11 suivants, et termine par les 4 mots mémoire du début. En clair, le ''critical word load'' commence par charger le mot mémoire lu, puis les blocs suivants, avant de revenir au début du bloc pour charger les blocs restants. Ainsi, la donnée demandée par le processeur sera la première disponible. Pour cela, l'organisation du tampon de remplissage de ligne est modifiée de manière à rendre cela possible. Il a une taille égale à une ligne de cache complète, qui contient elle-même plusieurs mots mémoire. Dans le ''line-fill buffer'', chaque mot mémoire est stocké avec un tag, qui indique l'adresse du mot mémoire stocké dans le ''line-fill buffer''. Le ''line-fill buffer'' est donc un cache un peu particulier, qui fonctionne comme un cache du point de vue du processeur, comme une mémoire FIFO pour les transferts avec le cache. Ainsi, un processeur qui veut lire dans le cache après un défaut peut accéder à la donnée directement depuis le tampon de remplissage de ligne, alors que la ligne de cache n'a pas encore été totalement recopiée en mémoire. ==Les caches pipelinés== Il est possible de pipeliner l'accès au cache, ce qui demande juste de rajouter des registres au bon endroits et quelques circuits de contrôle. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, même ceux avec un pipeline dynamique. Et l'implémentation peut se faire de plusieurs manières différentes. Il faut dire que découper une mémoire cache en plusieurs étapes peut se faire de plusieurs manière. Commençons par la plus simple. Beaucoup de processeurs des années 2000 avaient une fréquence peu élevée comparée aux standards d'aujourd'hui, ce qui fait que leur cache avait bien un temps d'accès d'un cycle d'horloge. Mais l'accès au cache se faisait en deux cycles d'horloge : un cycle pour calculer une adresse et un cycle pour l'accès mémoire proprement dit. Et ces deux étapes étaient pipelinées, à savoir que deux micro-opérations mémoire peuvent s'exécuter en même temps : une dans l'étage de calcul d'adresse, une autre dans le cache. L'unité mémoire était donc pipelinée, alors que l'accès au cache ne l'était pas. Les processeurs qui implémentaient cette technique regroupent les micro-architectures K5 et K6 d'AMD, les processeurs Intel de micro-architecture P6 (Pentium 2 et 3) et quelques autres. Il est aussi possible de pipeliner l'accès au cache lui-même. Avec un cache pipeliné, l'accès au cache ne se fait pas en un seul cycle, mais en plusieurs. Cependant, on peut lancer un nouvel accès au cache à chaque cycle d'horloge, comme un processeur pipeliné lance une nouvelle instruction à chaque cycle. L'implémentation est assez simple : il suffit d'ajouter des registres dans le cache. Pour cela, on profite que le cache est composé de plusieurs composants séparés, qui échangent des données dans un ordre bien précis, d'un composant à un autre. De plus, le trajet des informations dans un cache est linéaire, ce qui les rend parfois pour l'usage d'un pipeline. ===Le ''pipelining'' des caches ''direct-mapped''=== Voici ce qui se passe avec un cache directement adressé. Pour rappel ce genre de cache est conçu en combinant une mémoire RAM, généralement une SRAM, avec quelques circuits de comparaison et un MUX. L'accès au cache se fait globalement en deux étapes : la première lit la donnée et le ''tag'' dans la SRAM, et on utilise les deux dans une seconde étape. Nous avions vu il y a quelques chapitre comment pipeliner des mémoires, dans le chapitre sur les mémoires évoluées. Et bien ces méthodes peuvent s'utiliser pour la mémoire RAM interne au cache !L'idée est d'insérer un registre entre la sortie de la RAM et la suite du cache, pour en faire un cache pipéliné, à deux étages. Le premier étage lit dans la SRAM, le second fait le reste. L'implémentation sur les caches associatifs à plusieurs voies est globalement la même à quelques détails près. [[File:Cache directement adressé pipeliné - deux étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - deux étages]] Avec le circuit précédent, il est possible d'aller plus loin, cette fois en pipelinant l'accès à la mémoire RAM interne au cache. Pour comprendre comment, rappelons qu'une mémoire SRAM est composée d'un plan mémoire et d'un décodeur. L'accès à la mémoire demande d'abord que le décodeur fasse son travail pour sélectionner la case mémoire adéquate, puis ensuite la lecture ou écriture a lieu dans cette case mémoire. L'accès se fait donc en deux étapes successives séparées, on a juste à mettre un registre entre les deux. Il suffit donc de mettre un gros registre entre le décodeur et le plan mémoire. [[File:Cache directement adressé pipeliné - trois étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - trois étages]] Et on peut aller encore plus loin en découpant le décodeur en deux circuits séparés. En effet, rappelez-vous le chapitre sur les circuits de sélection : nous avions vu qu'il est possible de créer des décodeurs en assemblant des décodeurs plus petits, contrôlés par un circuit de prédécodage. Et bien on peut encore une ajouter un registre entre ce circuit de prédécodage et les petits décodeurs. : Théoriquement, toute l'adresse est fournie d'un seul coup au cache, la quasi-totalité des processeurs présentent l'adresse complète à un cache pipeliné. Mais le Pentium 4 fait autrement. Il faut noter que les premiers étages manipulent l'indice dans la SRAM, qui est dans les bits de poids faible, alors que les étages ultérieurs manipulent le ''tag'' qui est dans les bits de poids fort. Les concepteurs du Pentium 4 ont alors décidé de présenter les bits de poids faible lors du premier cycle d'accès au cache, puis ceux de poids fort au second cycle. Pour cela, l'ALU fonctionnait à une fréquence double de celle du processeur, tout comme le cache L1. Il n'y avait pas de pipeline proprement dit, mais cela réduisait grandement la latence d'accès au cache. ===Le ''pipelining'' des caches splittés/sériels=== Il est aussi possible de pipeliner un cache dits splittés, aussi appelés à accès sériel. Pour rappel, les caches à accès sériel vérifient si il y a succès ou défaut de cache, avant d'accéder aux lignes de cache en cas de succès. Ils font donc différemment des autres caches, qui accèdent à une ligne de cache, avant de déterminer s'il y a succès ou défaut en lisant le tag de la ligne de cache. Les caches sériels disposent de deux SRAM : une pour les tags des lignes de cache et une pour les données. Ils accèdent à la SRAM pour les tags, avant d’accéder à la SRAM des données en cas de succès de cache. Vu que l'accès se fait en deux étapes, une vérification des tags suivie de la lecture/écriture des données, il est facile à pipeliner. Pipeliner le cache permet de régler le problème des accès au cache L1, et elle est tout le temps utilisé sur les processeurs modernes. Mais que faire en cas de défaut de cache ? ==Les caches non bloquants== Un '''cache bloquant''' est un cache auquel le processeur ne peut pas accéder pendant un défaut de cache. Il faut attendre que la lecture ou écriture en RAM soit terminée avant de pouvoir utiliser de nouveau le cache. Un '''cache non bloquant''' n'a pas ce problème : on peut l'utiliser pendant un défaut de cache. Les caches non bloquants permettent de démarrer une nouvelle lecture ou écriture alors qu'une autre est en cours, ce qui permet d'exécuter plusieurs lectures ou écritures en même temps. ===Les ''Miss Handling Status Registers''=== Lors d'un défaut de cache, la mémoire RAM est consultée pendant le défaut de cache, mais le cache est inutilisé. Un défaut de cache n'utilise pas le cache, ce n'est qu'un accès en mémoire RAM, sur le bus mémoire (ou un accès aux niveaux de cache inférieurs, peu importe). Le cache en lui-même est laissé libre, rien n’empêche d'y accéder, il est en réalité intrinsèquement non-bloquant. Les caches, bloquants comme non-bloquants, sont en réalité composés d'une mémoire cache proprement dite, entourée de circuits qui servent d'interface entre le processeur et le cache lui-même. Et parmi les circuits tout autour, certains gèrent l'accès au cache lors d'un défaut de cache. Ils sont regroupés sous le terme de '''''Miss Handling Architecture''''' (MHA). La différence entre un cache bloquant et un cache non-bloquant est en réalité liée à l'implémentation de la MHA. Les caches bloquants coupent volontairement l'accès au cache lors d'un défaut, car les défauts sont plus simples à gérer ainsi. Le défaut de cache rapatrie une donnée depuis la RAM, qui sera écrite dans le cache. Et il ne faut pas qu'une tentative d'accès à cette donnée ait lieu avant qu'elle ne soit chargée. Mais ce blocage est général et touche tout le cache, alors que seule une ligne de cache est concernée par le défaut de cache. L'idée derrière un cache non-bloquant est que seule la ligne de cache est bloquée, mais les autres sont accessibles. L'idée est alors de mémoriser les lignes de cache concernées par le défaut de cache, afin d'en bloquer l'accès. À chaque accès, on vérifie si la ligne de cache est déjà réservée par un défaut de cache. La lecture/écriture est alors bloquée si c'est le cas, mais elle accepte les accès sinon. Pour cela, la ''Miss Handling Architecture'' contient des registres qui mémorisent des informations sur les défauts de cache en cours. Ils portent le nom de '''''miss status handling registers''''', que j’appellerais dorénavant MSHR, qui sont aussi appelés des '''''miss buffer'''''. Le contenu des MSHR varie beaucoup suivant le processeur, mais ces derniers stockent au minimum les informations suivantes : * Le numéro de la ligne de cache dans laquelle les données sont chargées. * Un bit de validité qui indique si le MSHR est vide ou pas, qui est mis à 0 quand le défaut de cache est résolu. * Un ou plusieurs champs de lecture/écriture, qui contiennent des informations sur la lecture/écriture. ** Pour une lecture, elle contient des informations sur la destination de la donnée, à savoir qui prévenir quand le défaut de cache est terminé. C'est parfois un nom/numéro de registre (celui dans lequel charger la donnée), mais c'est souvent le numéro de l'entrée dans le ''load/store queue''. ** Pour les écritures, elle contient la donnée à écrire, ou éventuellement un numéro de ''load/store queue'' où se trouve la donnée à écrire. Il faut noter que le bus mémoire ne peut gérer qu'un seul défaut de cache à la fois. Aussi, il est intéressant de regarder ce qui se passe lorsqu'un second défaut de cache survient, pendant qu'un premier est en cours. Dans ce cas, il y a deux réponses qui correspondent à deux types de caches non-bloquants, qui portent les noms barbares de caches de type succès après défaut et défaut après défaut. Sur le premier type, il ne peut pas y avoir plusieurs défauts de cache simultanés. Dès qu'un second défaut de cache survient, le cache stoppe son activité et on ne peut plus démarrer de nouvelle lecture/écriture, tant que le premier défaut de cache n'est pas résolu. Le second type est plus souple et autorise la survenue de plusieurs défauts de cache simultanés. Du moins, jusqu'à une certaine limite, car le cache ne peut supporter qu'un nombre limité d'accès mémoires simultanés (pipelinés). ===Les accès simultanés à une même ligne de cache=== Il arrive que le processeur fasse plusieurs accès mémoire simultanés à la même ligne de cache. Si la ligne de cache en question n'a pas encore été chargée dans le cache, alors on a plusieurs défauts de cache consécutifs pour la même ligne de cache, et le cache non-bloquant doit gérer la situation. Pour la suite, il va falloir faire une petite distinction entre les défauts primaires et secondaires. Imaginons qu'un défaut de cache ait lieu et demande à charger une donnée dans la ligne de cache numéro N. Il s'agit du premier défaut impliquant cette ligne de cache précise, ce qui lui vaut le nom de '''défaut de cache primaire'''. Mais par la suite, d'autres accès mémoire à la même ligne de cache ont lieu, alors que la ligne de cache n'est pas encore disponible. Dans ce cas, il s'agit de '''défauts de cache secondaires'''. Pour l'unité d'accès mémoire, les défauts de cache primaire et secondaire sont différents (ils prennent tous une entrée dans la ''load/store queue''). Mais pour le cache, ils ne correspondent qu'à un seul accès au cache : celui qui demande de charger la ligne de cache demandée. Les défauts de cache primaire et secondaire à la même ligne de cache se voient attribuer un MSHR unique. La gestion des défauts de cache secondaires dépend du cache non-bloquant. La solution la plus simple ne permet pas les défauts de cache secondaires. Le cache ne permet pas deux défauts de cache simultanés pour la même ligne de cache. Les autres solutions le permettent, en fusionnant des accès simultanés à la même ligne de cache en un seul au niveau des MSHR. Dans tous les cas, détecter les défauts de cache secondaires sont un problème qu'il faut détecter. La ''Miss Handling Architecture'' doit détecter les défauts de cache secondaire. Pour cela, elle procède comme suit. Lors de chaque défaut de cache, la MHA récupère le numéro de la ligne de cache associé. Il vérifie alors chaque MSHR pour vérifier s'il contient le numéro en question. S'il n'y a aucune correspondance dans les MSHR, alors c'est signe que le défaut de cache est un défaut primaire. Mais s'il y en a une, alors c'est un défaut secondaire. Évidemment, cela signifie que lors d'un défaut de cache, le numéro de ligne de cache est envoyé à tous les MSHR, pour comparaison. Les MSHR sont donc regroupés dans une mémoire associative, une sorte de mini-cache, faciliter l'implémentation. Lors d'un défaut de cache primaire, l'accès à un cache non-bloquant se fait comme suit : le processeur envoie une adresse au cache, accède à celui-ci, et détecte la survenue d'un défaut de cache. Il en profite alors pour attribuer une ligne de cache dans laquelle sera chargée ce défaut. L'attribution est très simple dans le cas des caches ''direct mapped'', ou associatifs par voie, pour lesquels l'attribution se fait assez simplement. Il mémorise alors cette information dans les MSHR, après avoir vérifié que le défaut de cache n'était pas un défaut secondaire. Lors des accès ultérieurs à une adresse proche, censée être dans la même ligne de cache, le processeur va encore une fois rencontrer un défaut de cache. Il va alors déterminer le numéro de la ligne de cache associée à l'adresse, et comparer ce numéro avec les MSHRs. Si un MSHR contient ce numéro, c'est signe que le défaut de cache est un défaut secondaire. La MHA réagit alors différemment selon le processeur considéré. Une première solution n'autorise pas les défauts de cache secondaires. Si l'un d'entre eux survient, le processeur est gelé par un ''pipeline stall'', une bulle de pipeline. Une autre solution fusionne les défauts de cache secondaires avec le défaut de cache primaire : tout cela ne correspond qu'à un seul défaut de cache pour lui. ===Les MSHR simples=== Un cache non-bloquant à '''MSHR simple''' contient juste plusieurs MSHR qui mémorisent juste un numéro de ligne de cache, un bit de validité, le champ de lecture/écriture, et l'adresse exacte de lecture/écriture. Avec cette organisation, il est possible d'avoir plusieurs défauts de cache séparés, mais à la condition que chaque défaut accède à une ligne de cache différente. Deux accès simultanés à une même ligne de cache ne sont pas possibles, les défauts de cache secondaires ne sont pas autorisés. Ainsi, chaque défaut de cache se voit attribuer son propre MSHR, chacun contient un numéro de ligne de cache différent. Pour comprendre pourquoi c'est impossible de gérer les défauts secondaires, il faut regarder le champ de lecture/écriture. Si on veut effectuer plusieurs écritures consécutives à la même adresse, le MSHR n'aura pas de quoi mémoriser les deux données à écrire. Il pourra mémoriser la première donnée à écrire, pas la seconde. Même chose lors d'une lecture : le champ lecture/écriture peut mémorisr la destination de la première lecture, pas de la seconde. L'avantage est que la MHA n'a besoin que des MSHR et de quelques circuits annexes. Les autres solutions rajoutent des circuits annexes pour gérer les défauts de cache secondaires, qui utilisent beaucoup de circuits. Le cout en circuit est donc élevé, mais le gain en performance est là. Passons maintenant aux caches non-bloquants qui autorisent les défauts de cache secondaire. La solution la plus simple consiste à utiliser ===Les MSHR adressés implicitement=== Avec les '''MSHR adressés implicitement''', il est possible de fusionner plusieurs accès mémoire à une même ligne de cache, mais sous une condition très importante : ces accès lisent/écrivent des mots mémoire différents. Par exemple, imaginons qu'une ligne de cache contienne 8 mots mémoire de 64 bits. Si un premier accès mémoire lit le mot mémoire numéro 7 (dernier mot mémoire de la ligne), et le second accès le mot mémoire numéro 3, alors la fusion est possible. Mais si deux accès mémoire veulent lire/écrire le mot mémoire numéro 7, alors la fusion n'est pas possible et le processeur se bloque, un ''pipeline stall'' survient. Un MSHR adressé implicitement est un MSHR simple, qui contient naturellement un numéro de ligne de cache (tag) et un bit de validité, sauf que le champ de lecture/écriture est dupliqué. Les différents champs lecture/écriture d'un MSHR sont regroupés dans une mémoire RAM/cache qui contient autant d'entrées qu'il y a de mot mémoire dans une ligne de cache. Chaque entrée de la table est associée à un mot mémoire de la ligne de cache et stocke des informations sur celui-ci. Une entrée mémorise, au minimum : * Un champ de lecture/écriture qui contient soit la destination de la lecture, soit la donnée à écrire. * Un bit R/W permettant d'interpréter correctement le champ de lecture/écriture. * Un bit de validité pour chaque entrée de la table, qui dit si un défaut de cache antérieur accède déjà à ce mot mémoire. La fusion de deux défauts de cache est ainsi assez simple. Un défaut de cache primaire/secondaire configure l'entrée associée au mot mémoire lu/écrit. Si un défaut secondaire ultérieur lit/écrit un mot mémoire différent (dont le bit de validité de l'entrée dans la table est à zéro), pas de conflit : il configure une autre entrée, vide. Mais s'il lit/écrit un mot mémoire pour lequel l'entrée de la table est occupée, il y a conflit, le processeur est bloqué par un ''pipeline stall'', une bulle de pipeline. Avec cette organisation, le nombre de MSHR indique combien de lignes de cache peuvent être lues en même temps depuis la mémoire. Quant au nombre d'entrées par MSHR, il détermine combien d'accès mémoires qui ne se recouvrent pas peuvent avoir lieu en même temps. ===Les MSHR adressés explicitement=== Les '''MSHR adressés explicitement''' n'ont pas les contraintes des MSHR adressés implicitement. Avec eux, il est possible d'avoir plusieurs défauts de cache pointant vers la même ligne de cache, mais aussi vers le même mot mémoire. De tels défauts de cache apparaissent sur les processeurs à exécution dans le désordre, mais sont très rares, voire inexistants, sur les processeurs ''in-order''. L'idée est encore que chaque MSHR est associée à une table mémoire qui mémorise des entrées. Cette table n'est autre qu'une mémoire FIFO. Sauf que cette fois-ci, une entrée n'est pas associée à un mot mémoire. Une entrée est associée à un défaut de cache. Une entrée mémorise là encore la destination de la lecture ou la donnée de l'écriture suivi du bit R/W, ainsi que d'un bit de validité par entrée, mais aussi : la position du mot mémoire lu dans la ligne de cache. C'est cette dernière information qui n'était pas présente dans les MSHR adressés implicitement. De plus, le nombre d'entrée par MSHR n'est pas égal au nombre de mots mémoires dans une ligne de cache, mais peut être arbitrairement grand. L'avantage d'un tel cache est qu'il est capable de traité les défauts secondaires concernant un même mot mémoire, et ce, car les accès à la ligne de cache concernée se produisent dans l'ordre de génération des défauts (la table mémoire est une FIFO). ===Les MSHRs inversés=== Généralement, plus on veut supporter de défauts de cache, plus le nombre de MSHR et d'entrées augmente. Mais au-delà d'un certain nombre d'entrées et de MSHR, les MSHR adressés implicitement et explicitement ont tendance à bouffer un peu trop de circuits. Utiliser une organisation un peu moins gourmande en circuits est donc une nécessité. Cette organisation plus économe se base sur des MSHR inversés. Les MSHR inversés ne contiennent qu'une seule entrée, en quelque sorte : au lieu d’utiliser n MSHR de m entrées chacun, on va utiliser n × m MSHR inversés. La différence, c'est que plusieurs MSHR peuvent contenir un tag identique, contrairement aux MSHR adressés implicitement et explicitement. Lorsqu'un défaut de cache a lieu, chaque MSHR est vérifié. Si jamais aucun MSHR ne contient de tag identique à celui utilisé par le défaut, un MSHR vide est choisi pour stocker ce défaut, et une requête de lecture en mémoire est lancée. Dans le cas contraire, un MSHR est réservé au défaut de cache, mais la requête n'est pas lancée. Quand la donnée est disponible, les MSHR correspondant à la ligne qui vient d'être chargée vont être utilisés un par un pour résoudre les défauts de cache en attente. ===Les MSHR intégrés au cache=== Certains chercheurs ont remarqué que pendant qu'une ligne de cache est en train de subir un défaut de cache, celle-ci reste inutilisée, et son contenu est destiné à être perdu une fois le défaut de cache résolu. Ils se sont dit que, plutôt que d'utiliser des MSHR séparés, il vaudrait mieux utiliser la ligne de cache pour stocker les informations sur les défauts de cache en attente dans cette ligne de cache. Pour éviter tout problème, il faut rajouter un bit dans les bits de contrôle de la ligne de cache, qui sert à indiquer que la ligne de cache est occupée : un défaut de cache a eu lieu dans cette ligne, et elle stocke donc des informations pour résoudre les défauts de cache. ==L'entrelacement mémoire== Nous venons de voir comment une mémoire cache peut gérer plusieurs accès mémoire simultanés. Il se trouve que ce genre d'optimisation dépasse largement le cadre du cache. Les mémoires RAM ne sont pas en reste, elles aussi ! Il est possible de pipeliner l'accès à une mémoire RAM, en utilisant des techniques dites d''''entrelacement'''. Précisons cependant que cette optimisation n'est pas utilisée sur les mémoires SDRAM ou DDR modernes, du moins pas sans modifications majeures. Mais divers ordinateurs assez anciens, dont des superordinateurs, ont utilisé cette technique. La technique demande d'utiliser plusieurs mémoires séparées. Sur les anciens ordinateurs, les mémoires en question sont des chips mémoire, à savoir des circuits intégrés de DRAM. Pour faire plus général, nous allons utiliser le terme de '''banques''', ou encore de bancs mémoire. La différence est qu'il existe des mémoires multi-banque, qui regroupent plusieurs banques indépendantes dans un seul boitier, dans un seul chip mémoire. En clair, de telles mémoires regroupent plusieurs sous-mémoires dans un seul circuit intégré. Il est aussi possible d'utiliser plusieurs chips mémoire séparés, chacun ayant une banque. Reste à combiner ces banques pour former un pseudo-pipeline. ===Utiliser plusieurs banques sans entrelacement=== Sans optimisation particulière, les adresses sont réparties dans les banques comme indiqué ci-dessous. Il s'agit de ce qui s'appelle un arrangement horizontal, et nous avions vu celui-ci dans le chapiutre sur les mémoires SDRAM. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Avec cet arrangement, les bits de poids fort de l'adresse sont utilisées pour sélectionner la banque adéquate, et le reste de l'adresse est envoyé sur le bus d'adresse. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Adresse de banque !! Adresse dans la banque |- | Quelques bits de poids fort || Reste de l'adresse |} L'entrelacement change cette répartition, afin d'accélérer les accès mémoire. L'entrelacement classique vise à améliorer les accès à des adresses mémoires consécutives, mais des formes plus évoluées d'entrelacement visent à optimiser des accès mémoire arbitraires. Voyons-les dans l'ordre, du plus simple au plus complexe. ===L'entrelacement classique=== Avec l''''entrelacement classique''', des adresses consécutives sont réparties dans des banques consécutives. Précisons que cette attribution des adresses n'implique absolument pas la mémoire virtuelle ou n'importe quel mécanisme dans le processeur. La répartition décide que telle adresse mémoire va dans telle banque, à telle adresse dans la banque. Elle est donc le fait du contrôleur mémoire, donc en dehors du processeur (les contrôleurs mémoires n’étaient pas encore intégrés dans le processeur à l'époque). [[File:Répartition des adresses dans une mémoire interleaved.png|centre|vignette|upright=1.5|Répartition des adresses dans une mémoire interleaved.]] L'entrelacement simple permet d'accélérer les accès mémoire qui se font à des adresses consécutives. Avec l'entrelacement, chaque accès mémoire tombe dans une banque mémoire différente, ce qui fait qu'on peut démarrer un nouvel accès mémoire à chaque cycle d'horloge. Pendant qu'une banque est occupée par un accès mémoire, on démarre le suivant dans une autre banque, et ainsi de suite. Le tout se termine soit quand on a épuisé toutes les banques libres, soit quand l'accès en rafales se termine. Pas besoin d'attendre que la mémoire ait fini sa lecture/écriture avant de démarrer la lecture/écriture suivante. [[File:Pipemining mémoire.png|centre|vignette|upright=2.5|Pipelining mémoire]] L'animation suivante illustre bien le processus, avec quatres banques. [[File:Interleaving.gif|centre|vignette|upright=2.5|Pipelining mémoire]] L'entrelacement simple utilise les bits de poids faible pour sélectionner la banque, et les bits de poids fort pour la case mémoire. [[File:Adresse mémoire d'une mémoire entrelacée.png|centre|vignette|upright=2|Adresse mémoire d'une mémoire entrelacée]] Précisons que le temps d'accès mémoire ne change pas beaucoup avec l'entrelacement. Par contre, on peut faire plus d'accès mémoire simultanés. On peut démarrer un accès mémoire par cycle d'horloge, mais l'accès en lui-même prend plusieurs cycles. Les mémoires à entrelacement ont donc un débit supérieur aux mémoires qui ne l'utilisent pas. L'entrelacement classique pourrait être utilisé pour accélérer les transferts entre mémoire RAM et cache, qui se font en blocs d'adresses consécutives. Mais de nos jours, il n'est pas utilisé dans cette optique. Il faut dire qu'il est redondant avec le mode rafale des mémoires DRAM. Les deux font globalement la même chose et on voit mal comment utiliser les deux en même temps. Le mode rafale étant plus simple à implémenter, et plus léger niveau utilisation du bus de commande mémoire, il est préféré à l'entrelacement. Par contre, l'entrelacement a eu son heure de gloire sur d'anciens processeurs qui n'avaient pas de mémoire cache, alors qu'ils disposaient d'un pipeline. L'exemple typique est celui des nombreux ''processeurs vectoriels'', que nous n'avons pas encore abordé à ce stade du cours. De tels processeurs avaient un pipeline, une unité de calcul fortement pipelinée, et parfois même de l'exécution dans le désordre et du renommage de registres ! ===L'entrelacement par décalage=== L'entrelacement simple est très utile pour les accès en rafle ou équivalents. Par contre, il existe de rares situations où il n'est pas optimal. Une de ces situations est celle des accès en enjambée (en ''stride''), où l'on accède en série à des adresses sont séparées par N mots mémoires. [[File:Accès par enjambées.png|centre|vignette|upright=2|Accès par enjambées.]] De tels accès surviennent quand un logiciel accède à des structures de données spécifiques, à savoir des tableaux de structures, des matrices, ou d'autres structures de données dans le genre. Un cas classique est celui du parcours d'une matrice colonne par colonne. Les matrices de nombres sont mémorisées ligne par ligne, ce qui fait que parcourir une colonne demande de passer d'une ligne à la suivante et de faire des grands sauts en mémoire RAM, mais tous espacés par la même distance. Avec l'entrelacement simple, les accès en enjambée sont moins performants que les accès à des adresses consécutives, mais cela ne fonctionne pas trop mal. Le pire cas est celui où l'on a N banques et où les données sont justement placées toutes les N adresses. Des accès consécutifs vont tous tomber dans la même banque, on ne peut plus accéder à des banques différentes en parallèle. Mais en dehors de ce cas, on voit une amélioration par rapport à la situation sans entrelacement. Pour obtenir des performances maximales pour les accès en enjambées, il faut répartir les mots mémoires dans la mémoire autrement. L'organisation idéale est la suivante. : Dans les explications qui vont suivre, la variable N représente le nombre de banques, qui sont numérotées de 0 à N-1. On commence par organiser les N premières adresses comme une mémoire entrelacée simple : l'adresse 0 correspond à la banque 0, l'adresse 1 à la banque 1, etc. Pour le bloc suivant, on décale tout d'une adresse, à savoir qu'on commence à remplir les banques à partir de la seconde, non de la première. Une fois la fin du bloc atteinte, on finit de remplir le bloc en repartant du début du bloc. Le troisième bloc d'adresse subit le même traitement, sauf qu'on commence à remplir à partir de la seconde banque. Et on poursuit l’assignation des adresses en décalant d'un cran en plus à chaque bloc. Ainsi, chaque bloc verra ses adresses décalées d'un cran en plus comparé au bloc précédent. Si jamais le décalage dépasse la fin d'un bloc, alors on reprend au début. [[File:Mémoire interleaved par décalage.png|centre|vignette|upright=1.5|Mémoire entrelacée par décalage.]] En faisant cela, on remarque que les banques situées à N adresses d'intervalle sont différentes. Dans l'exemple du dessus, nous avons ajouté un décalage de 1 à chaque nouveau bloc à remplir. Mais on aurait tout aussi bien pu prendre un décalage de 2, 3, etc. Dans tous les cas, on obtient un entrelacement par décalage. Ce décalage est appelé le pas d'entrelacement, noté P. Le calcul de l'adresse à envoyer à la banque, ainsi que la banque à sélectionner se fait en utilisant les formules suivantes : * adresse à envoyer à la banque = adresse totale / N ; * numéro de la banque = (adresse + décalage) modulo N ; * décalage = (adresse totale * P) mod N. Avec cet entrelacement par décalage, on peut prouver que la bande passante maximale est atteinte si le nombre de banques est un nombre premier. Seulement, utiliser un nombre de banques premier peut créer des trous dans la mémoire, des mots mémoires inadressables. Malgré ce défaut, la technique a été utilisée sur quelques ordinateurs, avec l'exemple notable du superordinateur ''Burroughs Scientific Processor''. Pour éviter cela, il y a plusieurs solutions. Par exemple, on peut faire en sorte que N et la taille d'une banque soient premiers entre eux : ils ne doivent pas avoir de diviseur commun. Mais en pratique, elles n'ont pas vraiment été implémentées dans une vraie machine, et sont restées à l'état de recherche, aussi je les passe sous silence. ===L'entrelacement pseudo-aléatoire=== Une dernière méthode de répartition consiste à répartir les adresses dans les banques de manière "pseudo-aléatoire". L'idée exacte est de faire passer l'adresse dans une '''fonction de hachage''', plus ou moins complexe. Pour rappel, une fonction de hachage prend une entrée de grande taille et fournit en sortie un résultat de petite taille. Idéalement, elles doivent donner des résultats aussi différents que possible pour des entrées similaires, histoire de simuler une sorte de pseudo-aléatoire. Ici, une adresse est transformée en un numéro de banque. Une fonction de hachage simple ne fait que permuter des bits de l'adresse pour obtenir son résultat. Elle ne fait qu'échanger des bits de place avant de couper l'adresse en deux morceaux : un pour la sélection de la banque, et un autre pour la sélection de l'adresse dans la banque. Cette permutation est fixe, et ne change pas suivant l'adresse. Des fonctions de hachage plus complexes font des XOR bit à bit entre certains bits de l'adresse. ==Les contrôleurs mémoires SDRAM/DDR optimisés== Après avoir vu le cas des mémoires RAM, nous allons nous concentrer sur le cas particulier des mémoires SDRAM. Les mémoires SDRAM et DDR modernes sont capables de gérer plusieurs accès simultanés à la mémoire RAM, et leurs contrôleurs mémoire en font tout autant. C'est une différence majeure avec les mémoires asynchrones FPM/EDO, qui n'acceptaient qu'un seul accès mémoire à la fois. Leur contrôleur mémoire n'acceptait qu'un seul accès mémoire à la fois, c'était un contrôleur mémoire bloquant. Les contrôleurs mémoires des SDRAM sont eux non-bloquants et peuvent encaisser une dizaine d'accès mémoire à la fois. Peu de choses sont connues sur les contrôleurs de SDRAM/DDR modernes, les fabricants ne donnant que peu de détails dessus. Les rares simulateurs qui tentent de décrire leur fonctionnement, comme DRAM SIM I et II, sont particulièrement simples et ne vont pas dans le détail. Néanmoins, le peu qu'on sait est tout de même instructif. ===Rappel sur les SDRAM : tampon de ligne et commandes=== Les mémoires SDRAM sont des mémoires à tampon de ligne. Elles font un accès mémoire en deux étapes. La première étape recopie une ligne de N * 64 bits dans un tampon interne à la SDRAM. La seconde étape sélectionne une colonne, à savoir une donnée de 64 bits, qui est soit envoyée sur le bus de données pour une lecture, soit modifiée par une écriture. [[File:Mémoire à tampon de ligne.png|centre|vignette|upright=2|Mémoire à tampon de ligne]] Les accès mémoire sont traduits par un séquenceur mémoire en une série de commandes mémoires, qui sont séparées par des délais mémoire de quelques cycles d'horloge. Les délais sont très précis, et sont à respecter à la lettre. Un accès mémoire se fait en maximum trois commandes : une commande PRECHARGE qui ferme la ligne précédemment utilisée, une commande ACT qui précise l'adresse de la ligne, et une commande READ ou WRITE qui précise l'adresse de la colonne et éventuellement la donnée à écrire. : Pour être plus précis, la commande PRECHARGE précharge les lignes de bits du plan mémoire à une tension particulière, ce qui les vide de leur contenu. Mais c'est un détail sans importance pour ce qui va suivre. Les SDRAM permettent de se passer de la première étape si des accès consécutifs se font dans la même ligne. Une fois activée, la ligne reste ouverte et on peut accéder plusieurs fois de suite dedans. On a alors juste à préciser l'adresse de colonne dedans. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | || bgcolor="#FFA0FF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || bgcolor="#A0FFFF" | READ (2) || bgcolor="#A0FFFF" | READ (3) || || || bgcolor="#A0FFFF" | READ (4) || bgcolor="#A0FFFF" | READ (5) || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | Donnée READ 1 || ||bgcolor="#A0FFFF" | Donnée READ 2 | bgcolor="#A0FFFF" | Donnée READ 3 || || || bgcolor="#A0FFFF" | Donnée READ 4 || bgcolor="#A0FFFF" | Donnée READ 5 |} Les SDRAM sont parfois capables de démarrer une commande avant que la précédente soit terminée. Mais le respect des délais mémoire est très limitant, ce qui fait qu'on ne peut pas parler de réel pipeline, comme c'est le cas sur les processeurs. Parlons plutot de '''commandes anticipées'''. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | || bgcolor="#A0FFFF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || || || bgcolor="#FFA0FF" | READ (2) || || || || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 | bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 | |} ===La mise en attente des accès mémoire=== Un contrôleur de SDRAM peut accepter plusieurs accès mémoire en même temps. Mais cela ne signifie pas que le contrôleur sera prêt à les traiter immédiatement. Pour éviter tout problème de disponibilité, le contrôleur met en attente les accès mémoire que le processeur lui envoie, pour les exécuter dès que possible. Les accès mémoire sont mis en attente dans une mémoire FIFO, histoire de les exécuter dans leur ordre d'arrivée, notamment pour renvoyer les lectures dans l'ordre demandé. Il y a aussi une mémoire FIFO pour les données à écrire et une FIFO pour les données lues. Cette dernière sert au cas où le cache ne soit pas disponible quand on lui envoie la donnée lue. [[File:Module d'interface avec la mémoire.png|centre|vignette|upright=2.5|Contrôleur mémoire avec mise en attente des requêtes processeur et autres optimisations.]] Des optimisations sont possibles dès la mémoire FIFO. Par exemple, on peut regrouper plusieurs accès à des données consécutives en un seul accès en rafale. Le contrôleur mémoire analyse les requêtes mises en attente et détecte si certaines se font à des adresses consécutives. Si c'est le cas, il fusionne ces requêtes en une lecture/écriture en rafale. Une telle optimisation est appelée la '''combinaison de lecture''' pour les lectures, et la '''combinaison d'écriture''' pour les écritures. ===Le pipeline des mémoires SDRAM=== Les commandes anticipées permettent de faire un minimum de parallélisme mémoire. Même si elle parait très limitée, cette possibilité est décuplée par la présence de plusieurs banques dans la mémoire SDRAM. Les chips mémoires de SDRAM sont composés de plusieurs banques, chacune étant une sorte de mini-mémoire miniature. Chaque banque a son propre décodeur, son propre tampon de ligne, ses propres multiplexeurs de colonne, sa logique de rafraichissement mémoire, etc. C'est comme si une SDRAM regroupait plusieurs mémoires séparées dans un même circuit intégré. Et cela permet de faire plusieurs accès mémoire en même temps, dans des banques différentes. Il est possible de lancer un accès mémoire dans deux banques en même temps, par exemple. Ou encore, on peut lancer un nouvel accès mémoire dans une banque si elle est inoccupée. La possibilité améliore grandement la performance de la SDRAM. Mais nous en reparlerons dans un chapitre ultérieur. {|class="wikitable" |+ Pipelining basique sur les SDRAM |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 |- ! Banque Numéro 1 | || || || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || || || |- ! Banque Numéro 2 | || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || |- ! Banque Numéro 3 | || || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || |} L'implémentation n'a rien de vraiment compliqué. Il suffit d'utiliser une mémoire FIFO par banque. Ainsi, les accès dans une même banque se feront en série, dans l'ordre d'arrivée, mais des accès à des banques différentes se feront dans le désordre. Cela ne pose pas de problème car les accès se font à des adresses différentes, ce qui fait qu'il n'y a pas de conflits majeurs. [[File:Gestion parallèle des banques.png|centre|vignette|upright=2.5|Gestion parallèle des banques.]] ===Le ré-ordonnancement des commandes mémoires=== Un contrôleur mémoire moderne est capable de changer l'ordre des requêtes mémoires, pour gagner en performances. En clair : il peut faire l'équivalent mémoire de l'exécution dans le désordre des processeurs haute performance. Une différence majeure est que le contrôleur mémoire ne remet pas les accès mémoire dans l'ordre avant de les envoyer au processeur. C'est le processeur qui remet en ordre les accès mémoire, dans sa ''load queue''. Pour cela, les requêtes mémoire sont "numérotées", à savoir qu'on leur attribue un identifiant binaire. Le contrôleur mémoire exécute les lectures/écritures dans le désordre, mais garde la trace de leur identifiant. Il sait dans quel ordre il exécute les accès mémoire et connait leur latence. Ce qui fait qu'il sait que la donnée lue à tel instant est associée à la requête mémoire numéro X. Pour les lectures, il renvoie la donnée lue avec l'identifiant. Le processeur sait alors à quelle lecture la donnée lue correspond et il se débrouille en interne pour gérer la situation. En clair : le processeur voit les accès mémoire dans le désordre, c'est lui qui les remet en ordre. Maintenant que ces précisions sont faites, posons cette question : quel est l'intérêt de faire les accès mémoire dans le désordre ? Accéder à des banques en parallèle est une réponse possible, mais le ré-ordonnancement est surtout utile si tous les accès mémoire atterrissent dans la même banque. L'exécution dans le désordre des lectures/écritures SDRAM vise à faire en sorte que des accès consécutifs se fassent dans une même ligne. Elle marche si plusieurs accès à une même ligne ne sont pas consécutifs, et qu'il y a des accès entre les deux. Après ré-ordonnancement, ces accès mémoire deviennent consécutifs, ils sont exécutés l'un à la suite de l'autre. Pour rendre le tout plus concret, voici un exemple. Imaginez que l'on sait les 3 accès mémoire suivants : * Une lecture ligne A ; * Une écriture ligne B ; * Une lecture ligne A. Sans réordonnancent, on doit émettre deux commandes PRECHARGE : une quand on passe de la ligne A à la ligne B, et une autre pour le passage inverse. pour éviter cela, le contrôleur mémoire peut retarder l'écriture, ou au contraire avancer la seconde lecture. le résultat est alors le suivant : * Lecture ligne A ; * Lecture ligne A ; * Une écriture ligne B. On a donc deux accès consécutifs à la même ligne, suivi par une écriture dans une autre ligne. La technique marche parce que l'on a un mix adéquat de lectures et d'écritures. Mais il existe des possibilités de réorganisation autres qui ne sont pas valides. Il faut par exemple prendre garde à éviter d'intervertir lectures et écritures à une même adresse, sous peine de problèmes. Encore une fois, il faut faire attention aux dépendances mémoires. Le controleur mémoire teste les dépendances mémoires avant d'envoyer des commandes à la mémoire DDR/SDRAM. ==L'entrelacement sur les mémoires SDRAM== L''''entrelacement''' fonctionne sur les mémoires SDRAM, mais il doit être fortement modifié. Concrètement, tout ce qui a étré dit plus est inapplicable pour les SDRAM, du fait de la présence du tampon de ligne. Des adresses consécutives atterrissent déjà dans la même ligne, ce qui fait qu'on n'a pas besoin d'entrelacement au niveau d'une ligne. Et ne parlons pas de ce la présence de rangées et de canaux mémoire ! Cependant, il peut y avoir un entrelacement lié à la présence des banques SDRAM. L'entrelacement est géré juste avant le séquenceur mémoire. Le '''circuit d'entrelacement''' intervertit certains bits de l'adresse lors des accès mémoires. On trouve aussi des mémoires FIFOs pour mettre en attente les requêtes mémoire provenant du processeur. Elles permettent de recevoir plusieurs accès mémoire consécutifs. Si vous vous demandez comment plusieurs accès mémoire consécutifs peuvent avoir lieu, sachez que c'est une bonne question. Pour le moment, nous allons dire que ces accès proviennent du processeur ou de la mémoire cache, sans expliquer comment. Le contrôleur mémoire reçoit une série d'accès mémoire assez rapprochés, qu'il doit exécuter au mieux. [[File:Controleur mémoire d'une SDRAM avec entrelacement.png|centre|vignette|upright=2.5|Contrôleur mémoire d'une SDRAM avec entrelacement.]] Pour gérer l'entrelacement, le contrôleur de SDRAM prend en entrée l'adresse envoyée par le processeur, et la découpe en plusieurs champs : un pour sélectionner la banque adéquate, un autre pour la ligne, un autre pour la colonne, un autre pour la rangée. Une solution simple désactive l'entrelacement. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de banque || Adresse de ligne || Adresse de colonne |} Si cette solution fonctionne bien si les accès mémoire sont assez aléatoires, en pratique, mieux vaut utiliser l'entrelacement. ===L'entrelacement de banques/lignes=== Une première méthode d'entrelacement est l''''entrelacement de banques'''. Elle répartit deux lignes consécutives dans deux banques différentes. Pour comprendre l'idée, prenons un exemple. Imaginons une mémoire avec deux banques et 4 lignes. Imaginons qu'on parcoure/balaye la mémoire RAM en partant des adresses basses. Sans entrelacement de ligne, les accès se feront comme suit, de gauche à droite : {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Banque numéro 1 !! colspan="4" | Banque numéro 2 |- | Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 || Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 |} Avec l'entrelacement de banques, les accès se feront comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 |- | Ligne 1 || Ligne 1 || Ligne 2 || Ligne 2 || Ligne 3 || Ligne 3 || Ligne 4 || Ligne 4 |} Avec N banques, la première ligne ira dans la première banque, la seconde ligne dans la seconde, et ainsi de suite jusqu'à la énième banque qui va dans la banque numéro N. Et la ligne N+1 repart dans la première banque. L'avantage est que passer d'une ligne à la suivante est assez rapide. Au lieu de devoir fermer la première ligne et ouvrir la suivante on ouvrira directement la ligne suivante dans une banque différente, ce qui sera plus rapide. La commande PRECHARGE pourra être envoyée à la seconde banque en avance. Pour cela, l'adresse est découpée comme suit. {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne (précise à l'octet) |} ===L'entrelacement de rangées=== Il est aussi possible de faire la même chose, mais avec les rangées. Pour simplifier fortement, une rangée est simplement un chip mémoire. En réalité, les rangées ne sont pas des chip mémoire, mais un ensemble de chips mémoire regroupés ensemble histoire d'atteindre les 64 bits du bus de données. Par exemple, une rangée peut combiner 8 chips mémoire avec un bus de données de 8 bits chacun pour obtenir les 64 bits du bus de données d'une SDRAM. Mais nous passons ce détail sous silence dans les explications qui vont suivre, par souci de simplification. Pour faire comprendre l'entrelacement de rangée, le mieux est d'utiliser un exemple, le même que précédemment. Sans entrelacement de rangée, on change de chip mémoire une fois qu'on a balayé toutes ses adresses. {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Rangée numéro 1 !! colspan="4" | Rangée numéro 2 |- | Banque 1 || Banque 2 || Banque 3 || Banque 4 || Banque 1 || Banque 2 || Banque 3 || Banque 4 |} Avec l'entrelacement de ligne, on change de rangée dès qu'on change de banque/ligne. {|class="wikitable" |+ Adresse mémoire |- ! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 |- | Banque 1 || Banque 1 || Banque 2 || Banque 2 || Banque 3 || Banque 3 || Banque 4 || Banque 4 |} En clair, quand on a épuisé toutes les banques dans une même rangée, on passe à la rangée suivante au lieu de rester dans la même rangée. Notons que dans l'exemple précédent, on a combiné l'entrelacement de rangée et de banque, mais on aurait pu utiliser l'entrelacement de rangée seul. Mais ce n'est pas le cas le plus courant en pratique. Toujours est-il qu'en combinant entrelacement de rangée et de ligne, le découpage de l'adresse est le suivant : {|class="wikitable" |+ Adresse mémoire |- | Adresse de ligne || Numéro de rangée || Adresse de banque || Adresse de colonne (précise à l'octet) |} D'autres méthodes d'entrelacement sont possibles, mais elles sont plus complexes. Chaque contrôleur mémoire fait un peu à sa sauce. Ils permettent en général de choisir entre plusieurs entrelacements. Au minimum, ils permettent de désactiver l'entrelacement, d'activer un entrelacement de ligne, et d'autres entrelacements plus complexes, dont certains sont propriétaires et tenus secrets par le fabricant. ===L'entrelacement avec le ''dual channel''=== [[File:Dual channel slots.jpg|vignette|Slots mémoires avec ''dual channel''.]] L'usage du ''dual channel'' complique encore l'entrelacement. Là encore, il y a deux grandes solutions : avec et sans entrelacement des canaux mémoire. Rappelons le principe : deux barrettes de RAM sont accédées en parallèle. Et pour cela, il faut utiliser l'entrelacement. Typiquement, chaque barrette mémoire fournit 64 bits, ce qui fait que l'on peut accéder à 128 bits d'un seul coup, par exemple avec un accès en rafale. Sans ''dual channel'', la première barrette correspond à la moitié haute de la RAM, la seconde barrette correspond à la moitié basse. Avec ''dual channel'', une forme spécifique d'entrelacement est activée. Concrétement, deux blocs de 64 bits sont placés dans des canaux mémoire séparés. Idem avec du triple ou quadruple canal, mais c'est alors trois ou quatre blocs de 64 bits qui sont dispersés dans des canaux consécutifs. Le découpage de l'adresse est alors le suivant : {|class="wikitable" |+ Adresse mémoire |- ! colspan="6" | Sans entrelacement |- | Numéro de canal/barrette || Reste de l'adresse || Position de l'octet dans un bloc de 64 bits |- | colspan="6" | |- ! colspan="6" | Avec entrelacement |- | Reste de l'adresse || Numéro de canal/barrette || Position de l'octet dans un bloc de 64 bits |} [[File:Décodage d'adresse avec dual channel.png|centre|vignette|upright=2|Décodage d'adresse avec dual channel.]] Les contrôleurs de SDRAM précédents sont assez basiques et ne représentent pas les contrôleurs les plus évolués. Ils étaient utilisés sur les anciens PC, à une époque où ils étaient encore sur la carte mère du processeur. Mais de nos jours, le contrôleur mémoire est intégré au processeur. Et il incorpore de nombreuses optimisations afin de gagner en performances. Malheureusement, ces optimisations sont elles-mêmes dépendantes des optimisations intégrées dans le processeur, ce qui fait qu'on ne peut pas en parler ici. Elles demandent que le contrôleur mémoire reçoive plusieurs accès mémoire simultanés, ce qui n'a aucun sens à ce stade du cours. Aussi, nous allons les passer sous silence pour le moment. Nous les verrons dans le chapitre sur le parallélisme mémoire. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=La désambiguïsation mémoire | prevText=La désambiguïsation mémoire | next=Les processeurs superscalaires | nextText=Les processeurs superscalaires }} </noinclude> fbiho6g5ruddsjf3ons8feupdxvy7bp 764714 764713 2026-04-23T21:15:49Z Mewtow 31375 /* Rappel sur les SDRAM : tampon de ligne et commandes */ 764714 wikitext text/x-wiki Dans ce chapitre, nous allons voir les techniques qui permettent de gérer plusieurs accès mémoire simultanés directement au niveau du cache ou de la mémoire RAM. Par plusieurs accès mémoire simultanés, vous pensez sans doute à l'usage de cache multiports, voire de mémoires RAM multiport. C'est en effet une solution, mais ce n'est clairement pas la seule. Par exemple, il est possible de pipeliner l'accès au cache, voire à la mémoire RAM. Il existe de nombreuses techniques de '''parallélisme mémoire''', et nous allons les voir dans ce chapitre. Pour donner un exemple, il est possible de pipeliner les accès mémoire. Il est possible de pipeliner l'accès au cache et/ou l'accès à la mémoire, et nous verrons comment faire dans ce chapitre. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, qu'ils aient ou non un pipeline dynamique. Par contre, pipeliner la mémoire est une technique ancienne, aujourd'hui peu utilisée. Elle était utilisée sur des très vieux ordinateurs, qui avaient un pipeline mais pas de mémoire cache. Mais de nos jours, les mémoires SDRAM et DDR ne sont pas adaptées pour ça, il est très difficile de les pipeliner correctement. Le parallélisme mémoire est utile aussi bien sur des processeurs à émission dans l'ordre que dans le désordre. Par exemple, un processeur ''in-order'' avec des lectures non-bloquantes peut e, profiter. Il peut techniquement lancer une seconde lecture, après avoir rencontré une première lecture non-bloquante. En général, le nombre de lectures consécutives est cependant limité à moins d'une dizaine. À l'opposé, les processeurs sans lectures non-bloquantes profitent pas du parallélisme mémoire. À la rigueur, ils peuvent en profiter s'ils intègrent des techniques de préchargement. Pendant que le processeur exécute une lecture/écriture, il peut précharger une autre donnée en parallèle. Inutile de dire que sans pipeline processeur, le parallélisme mémoire ne sert pas à grand-chose. ==Le ''line fill buffer'' et les techniques associées== Lors d'un défaut de cache, le processeur doit attendre que toute la ligne de cache soit chargée avant d'être utilisable. Or, la taille d'une ligne de cache est supérieure à la largeur du bus mémoire, ce qui fait qu'une ligne de cache est chargée en plusieurs fois, morceaux par morceaux, mot mémoire par mot mémoire. Le chargement peut se faire directement dans le cache, mais ce n'est pas une solution très pratique. À la place, beaucoup de processeurs ajoutent une mémoire tampon entre la RAM et le cache, appelée le '''tampon de remplissage de ligne''' (''line-fill buffer'') dans les processeurs modernes. Lors d'un défaut de cache, le processeur charge la donnée de la RAM dans le tampon de remplissage de ligne, mot mémoire par mot mémoire. Une fois plein, le tampon de remplissage de ligne est recopié dans la ligne de cache. Le tampon de remplissage de ligne contient une ligne de cache, si ce n'est que certains bits de contrôle ne sont pas présents. Le tampon de remplissage de ligne contient un bit de validité pour chaque mot mémoire de la ligne de cache, qui indiquent si le mot mémoire a été chargé. Par exemple, prenons un processeur 64 bits, qui gère donc des mots mémoire de 8 octets, avec des lignes de cache 256 octets/32 mots mémoire. Le tampon de remplissage de ligne contiendra 32 bits de validité. Si le processeur a chargé les 6 premiers mots mémoire, les 6 premiers bits de validité seront mis à 1, les autres seront encore à zéro. Il faut noter que la disponibilité de la ligne complète se détermine assez facilement en faisant un ET logique entre tous les bits de validité. Le processeur sait ainsi quand la ligne de cache est disponible entièrement et donc quand la transférer dans le cache. La même chose existe avec une hiérarchie de cache, sauf que l'on trouve une mémoire tampon entre chaque niveau de cache. Il y a un tampon de remplissage de ligne entre le cache L1 et le cache L2, entre le cache L2 et le L3, etc. Si le cache ne gère qu'un seul défaut de cache à la fois, le ''fill line buffer'' est une mémoire très simple, qui ne mémorise qu'une seule ligne de cache. Et cela vaut aussi bien pour un cache bloquant que non-bloquant. Mais sur les caches capables de gérer plusieurs défauts simultanés, le tampon de remplissage de ligne est une mémoire de type FIFO ou LIFO, capable de mémoriser plusieurs lignes de cache. Notons que le tampon de remplissage de ligne est très utile pour implémenter certaines techniques, par exemple le contournement du cache. Nous avions vu dans le chapitre sur le cache que certains accès mémoire doivent contourner le cache, pour des raisons de cohérence des caches. C'est notamment nécessaire pour accéder aux périphériques, mais c'est aussi utile pour des raisons de performances dans des cas très spécifiques. Les accès qui contournent le cache se font directement dans le tampon de remplissage de ligne : le processeur écrit ou lit les données depuis ce tampon de remplissage de ligne, sans accéder au cache. ===L'''early restart'' et le ''critical word load''=== La présence du ''line buffer'' permet une optimisation assez intéressante, qui permet de réduire la latence des défauts de cache. L'optimisation consiste à lire un mot mémoire dans le tampon de remplissage de ligne, même si la ligne de cache complète n'a pas encore été chargée. Il existe deux manières de faire cela, qui portent les noms d'''early restart'' et de ''critical word load''. La première est la version la plus simple, la seconde est plus complexe mais plus performante. Avec la technique d''''''early restart''''', la ligne de cache est chargée normalement, en partant de son premier mot mémoire. Dès que le mot mémoire lu/écrit par le processeur est copié dans le ''line fill buffer'', il est envoyé au processeur immédiatement. Illustrons le tout par un exemple, où une ligne de cache fait 16 mots mémoire. Le processeur effectue une lecture, qui lit le 5ème mot mémoire. Sans ''early restart'', le processeur doit charger les 16 mots mémoire avant de faire la lecture dans le cache. Avec ''early restart'', le processeur reçoit la donnée dès que le 5ème mot mémoire est disponible. Le processeur doit attendre que les 4 premiers mots mémoire soient chargés, puis le 5ème mot mémoire arrive et est envoyé directement au processeur, il est lu directement depuis le ''line fill buffer''. Les 11 mots mémoire suivants sont ensuite chargés dans le cache pendant que le processeur fait des calculs dans son coin. Le ''critical word load'' est une optimisation de la technique précédente où le chargement de la ligne de cache commence directement à la donnée demandée par le processeur. Pour reprendre l'exemple précédent, où le processeur demande le 5ème mot mémoire sur 16, le ''critical word load'' charge le 5ème mot mémoire en premier et l'envoie au processeur, ce qui fait qu'il est chargé très rapidement. Pas besoin d'attendre que le processeur charge les 4 mots mémoire précédents comme avec l'''early restart''. Dans le détail, le processeur charge le 5ème mot mémoire en premier, puis charge les 11 suivants, et termine par les 4 mots mémoire du début. En clair, le ''critical word load'' commence par charger le mot mémoire lu, puis les blocs suivants, avant de revenir au début du bloc pour charger les blocs restants. Ainsi, la donnée demandée par le processeur sera la première disponible. Pour cela, l'organisation du tampon de remplissage de ligne est modifiée de manière à rendre cela possible. Il a une taille égale à une ligne de cache complète, qui contient elle-même plusieurs mots mémoire. Dans le ''line-fill buffer'', chaque mot mémoire est stocké avec un tag, qui indique l'adresse du mot mémoire stocké dans le ''line-fill buffer''. Le ''line-fill buffer'' est donc un cache un peu particulier, qui fonctionne comme un cache du point de vue du processeur, comme une mémoire FIFO pour les transferts avec le cache. Ainsi, un processeur qui veut lire dans le cache après un défaut peut accéder à la donnée directement depuis le tampon de remplissage de ligne, alors que la ligne de cache n'a pas encore été totalement recopiée en mémoire. ==Les caches pipelinés== Il est possible de pipeliner l'accès au cache, ce qui demande juste de rajouter des registres au bon endroits et quelques circuits de contrôle. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, même ceux avec un pipeline dynamique. Et l'implémentation peut se faire de plusieurs manières différentes. Il faut dire que découper une mémoire cache en plusieurs étapes peut se faire de plusieurs manière. Commençons par la plus simple. Beaucoup de processeurs des années 2000 avaient une fréquence peu élevée comparée aux standards d'aujourd'hui, ce qui fait que leur cache avait bien un temps d'accès d'un cycle d'horloge. Mais l'accès au cache se faisait en deux cycles d'horloge : un cycle pour calculer une adresse et un cycle pour l'accès mémoire proprement dit. Et ces deux étapes étaient pipelinées, à savoir que deux micro-opérations mémoire peuvent s'exécuter en même temps : une dans l'étage de calcul d'adresse, une autre dans le cache. L'unité mémoire était donc pipelinée, alors que l'accès au cache ne l'était pas. Les processeurs qui implémentaient cette technique regroupent les micro-architectures K5 et K6 d'AMD, les processeurs Intel de micro-architecture P6 (Pentium 2 et 3) et quelques autres. Il est aussi possible de pipeliner l'accès au cache lui-même. Avec un cache pipeliné, l'accès au cache ne se fait pas en un seul cycle, mais en plusieurs. Cependant, on peut lancer un nouvel accès au cache à chaque cycle d'horloge, comme un processeur pipeliné lance une nouvelle instruction à chaque cycle. L'implémentation est assez simple : il suffit d'ajouter des registres dans le cache. Pour cela, on profite que le cache est composé de plusieurs composants séparés, qui échangent des données dans un ordre bien précis, d'un composant à un autre. De plus, le trajet des informations dans un cache est linéaire, ce qui les rend parfois pour l'usage d'un pipeline. ===Le ''pipelining'' des caches ''direct-mapped''=== Voici ce qui se passe avec un cache directement adressé. Pour rappel ce genre de cache est conçu en combinant une mémoire RAM, généralement une SRAM, avec quelques circuits de comparaison et un MUX. L'accès au cache se fait globalement en deux étapes : la première lit la donnée et le ''tag'' dans la SRAM, et on utilise les deux dans une seconde étape. Nous avions vu il y a quelques chapitre comment pipeliner des mémoires, dans le chapitre sur les mémoires évoluées. Et bien ces méthodes peuvent s'utiliser pour la mémoire RAM interne au cache !L'idée est d'insérer un registre entre la sortie de la RAM et la suite du cache, pour en faire un cache pipéliné, à deux étages. Le premier étage lit dans la SRAM, le second fait le reste. L'implémentation sur les caches associatifs à plusieurs voies est globalement la même à quelques détails près. [[File:Cache directement adressé pipeliné - deux étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - deux étages]] Avec le circuit précédent, il est possible d'aller plus loin, cette fois en pipelinant l'accès à la mémoire RAM interne au cache. Pour comprendre comment, rappelons qu'une mémoire SRAM est composée d'un plan mémoire et d'un décodeur. L'accès à la mémoire demande d'abord que le décodeur fasse son travail pour sélectionner la case mémoire adéquate, puis ensuite la lecture ou écriture a lieu dans cette case mémoire. L'accès se fait donc en deux étapes successives séparées, on a juste à mettre un registre entre les deux. Il suffit donc de mettre un gros registre entre le décodeur et le plan mémoire. [[File:Cache directement adressé pipeliné - trois étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - trois étages]] Et on peut aller encore plus loin en découpant le décodeur en deux circuits séparés. En effet, rappelez-vous le chapitre sur les circuits de sélection : nous avions vu qu'il est possible de créer des décodeurs en assemblant des décodeurs plus petits, contrôlés par un circuit de prédécodage. Et bien on peut encore une ajouter un registre entre ce circuit de prédécodage et les petits décodeurs. : Théoriquement, toute l'adresse est fournie d'un seul coup au cache, la quasi-totalité des processeurs présentent l'adresse complète à un cache pipeliné. Mais le Pentium 4 fait autrement. Il faut noter que les premiers étages manipulent l'indice dans la SRAM, qui est dans les bits de poids faible, alors que les étages ultérieurs manipulent le ''tag'' qui est dans les bits de poids fort. Les concepteurs du Pentium 4 ont alors décidé de présenter les bits de poids faible lors du premier cycle d'accès au cache, puis ceux de poids fort au second cycle. Pour cela, l'ALU fonctionnait à une fréquence double de celle du processeur, tout comme le cache L1. Il n'y avait pas de pipeline proprement dit, mais cela réduisait grandement la latence d'accès au cache. ===Le ''pipelining'' des caches splittés/sériels=== Il est aussi possible de pipeliner un cache dits splittés, aussi appelés à accès sériel. Pour rappel, les caches à accès sériel vérifient si il y a succès ou défaut de cache, avant d'accéder aux lignes de cache en cas de succès. Ils font donc différemment des autres caches, qui accèdent à une ligne de cache, avant de déterminer s'il y a succès ou défaut en lisant le tag de la ligne de cache. Les caches sériels disposent de deux SRAM : une pour les tags des lignes de cache et une pour les données. Ils accèdent à la SRAM pour les tags, avant d’accéder à la SRAM des données en cas de succès de cache. Vu que l'accès se fait en deux étapes, une vérification des tags suivie de la lecture/écriture des données, il est facile à pipeliner. Pipeliner le cache permet de régler le problème des accès au cache L1, et elle est tout le temps utilisé sur les processeurs modernes. Mais que faire en cas de défaut de cache ? ==Les caches non bloquants== Un '''cache bloquant''' est un cache auquel le processeur ne peut pas accéder pendant un défaut de cache. Il faut attendre que la lecture ou écriture en RAM soit terminée avant de pouvoir utiliser de nouveau le cache. Un '''cache non bloquant''' n'a pas ce problème : on peut l'utiliser pendant un défaut de cache. Les caches non bloquants permettent de démarrer une nouvelle lecture ou écriture alors qu'une autre est en cours, ce qui permet d'exécuter plusieurs lectures ou écritures en même temps. ===Les ''Miss Handling Status Registers''=== Lors d'un défaut de cache, la mémoire RAM est consultée pendant le défaut de cache, mais le cache est inutilisé. Un défaut de cache n'utilise pas le cache, ce n'est qu'un accès en mémoire RAM, sur le bus mémoire (ou un accès aux niveaux de cache inférieurs, peu importe). Le cache en lui-même est laissé libre, rien n’empêche d'y accéder, il est en réalité intrinsèquement non-bloquant. Les caches, bloquants comme non-bloquants, sont en réalité composés d'une mémoire cache proprement dite, entourée de circuits qui servent d'interface entre le processeur et le cache lui-même. Et parmi les circuits tout autour, certains gèrent l'accès au cache lors d'un défaut de cache. Ils sont regroupés sous le terme de '''''Miss Handling Architecture''''' (MHA). La différence entre un cache bloquant et un cache non-bloquant est en réalité liée à l'implémentation de la MHA. Les caches bloquants coupent volontairement l'accès au cache lors d'un défaut, car les défauts sont plus simples à gérer ainsi. Le défaut de cache rapatrie une donnée depuis la RAM, qui sera écrite dans le cache. Et il ne faut pas qu'une tentative d'accès à cette donnée ait lieu avant qu'elle ne soit chargée. Mais ce blocage est général et touche tout le cache, alors que seule une ligne de cache est concernée par le défaut de cache. L'idée derrière un cache non-bloquant est que seule la ligne de cache est bloquée, mais les autres sont accessibles. L'idée est alors de mémoriser les lignes de cache concernées par le défaut de cache, afin d'en bloquer l'accès. À chaque accès, on vérifie si la ligne de cache est déjà réservée par un défaut de cache. La lecture/écriture est alors bloquée si c'est le cas, mais elle accepte les accès sinon. Pour cela, la ''Miss Handling Architecture'' contient des registres qui mémorisent des informations sur les défauts de cache en cours. Ils portent le nom de '''''miss status handling registers''''', que j’appellerais dorénavant MSHR, qui sont aussi appelés des '''''miss buffer'''''. Le contenu des MSHR varie beaucoup suivant le processeur, mais ces derniers stockent au minimum les informations suivantes : * Le numéro de la ligne de cache dans laquelle les données sont chargées. * Un bit de validité qui indique si le MSHR est vide ou pas, qui est mis à 0 quand le défaut de cache est résolu. * Un ou plusieurs champs de lecture/écriture, qui contiennent des informations sur la lecture/écriture. ** Pour une lecture, elle contient des informations sur la destination de la donnée, à savoir qui prévenir quand le défaut de cache est terminé. C'est parfois un nom/numéro de registre (celui dans lequel charger la donnée), mais c'est souvent le numéro de l'entrée dans le ''load/store queue''. ** Pour les écritures, elle contient la donnée à écrire, ou éventuellement un numéro de ''load/store queue'' où se trouve la donnée à écrire. Il faut noter que le bus mémoire ne peut gérer qu'un seul défaut de cache à la fois. Aussi, il est intéressant de regarder ce qui se passe lorsqu'un second défaut de cache survient, pendant qu'un premier est en cours. Dans ce cas, il y a deux réponses qui correspondent à deux types de caches non-bloquants, qui portent les noms barbares de caches de type succès après défaut et défaut après défaut. Sur le premier type, il ne peut pas y avoir plusieurs défauts de cache simultanés. Dès qu'un second défaut de cache survient, le cache stoppe son activité et on ne peut plus démarrer de nouvelle lecture/écriture, tant que le premier défaut de cache n'est pas résolu. Le second type est plus souple et autorise la survenue de plusieurs défauts de cache simultanés. Du moins, jusqu'à une certaine limite, car le cache ne peut supporter qu'un nombre limité d'accès mémoires simultanés (pipelinés). ===Les accès simultanés à une même ligne de cache=== Il arrive que le processeur fasse plusieurs accès mémoire simultanés à la même ligne de cache. Si la ligne de cache en question n'a pas encore été chargée dans le cache, alors on a plusieurs défauts de cache consécutifs pour la même ligne de cache, et le cache non-bloquant doit gérer la situation. Pour la suite, il va falloir faire une petite distinction entre les défauts primaires et secondaires. Imaginons qu'un défaut de cache ait lieu et demande à charger une donnée dans la ligne de cache numéro N. Il s'agit du premier défaut impliquant cette ligne de cache précise, ce qui lui vaut le nom de '''défaut de cache primaire'''. Mais par la suite, d'autres accès mémoire à la même ligne de cache ont lieu, alors que la ligne de cache n'est pas encore disponible. Dans ce cas, il s'agit de '''défauts de cache secondaires'''. Pour l'unité d'accès mémoire, les défauts de cache primaire et secondaire sont différents (ils prennent tous une entrée dans la ''load/store queue''). Mais pour le cache, ils ne correspondent qu'à un seul accès au cache : celui qui demande de charger la ligne de cache demandée. Les défauts de cache primaire et secondaire à la même ligne de cache se voient attribuer un MSHR unique. La gestion des défauts de cache secondaires dépend du cache non-bloquant. La solution la plus simple ne permet pas les défauts de cache secondaires. Le cache ne permet pas deux défauts de cache simultanés pour la même ligne de cache. Les autres solutions le permettent, en fusionnant des accès simultanés à la même ligne de cache en un seul au niveau des MSHR. Dans tous les cas, détecter les défauts de cache secondaires sont un problème qu'il faut détecter. La ''Miss Handling Architecture'' doit détecter les défauts de cache secondaire. Pour cela, elle procède comme suit. Lors de chaque défaut de cache, la MHA récupère le numéro de la ligne de cache associé. Il vérifie alors chaque MSHR pour vérifier s'il contient le numéro en question. S'il n'y a aucune correspondance dans les MSHR, alors c'est signe que le défaut de cache est un défaut primaire. Mais s'il y en a une, alors c'est un défaut secondaire. Évidemment, cela signifie que lors d'un défaut de cache, le numéro de ligne de cache est envoyé à tous les MSHR, pour comparaison. Les MSHR sont donc regroupés dans une mémoire associative, une sorte de mini-cache, faciliter l'implémentation. Lors d'un défaut de cache primaire, l'accès à un cache non-bloquant se fait comme suit : le processeur envoie une adresse au cache, accède à celui-ci, et détecte la survenue d'un défaut de cache. Il en profite alors pour attribuer une ligne de cache dans laquelle sera chargée ce défaut. L'attribution est très simple dans le cas des caches ''direct mapped'', ou associatifs par voie, pour lesquels l'attribution se fait assez simplement. Il mémorise alors cette information dans les MSHR, après avoir vérifié que le défaut de cache n'était pas un défaut secondaire. Lors des accès ultérieurs à une adresse proche, censée être dans la même ligne de cache, le processeur va encore une fois rencontrer un défaut de cache. Il va alors déterminer le numéro de la ligne de cache associée à l'adresse, et comparer ce numéro avec les MSHRs. Si un MSHR contient ce numéro, c'est signe que le défaut de cache est un défaut secondaire. La MHA réagit alors différemment selon le processeur considéré. Une première solution n'autorise pas les défauts de cache secondaires. Si l'un d'entre eux survient, le processeur est gelé par un ''pipeline stall'', une bulle de pipeline. Une autre solution fusionne les défauts de cache secondaires avec le défaut de cache primaire : tout cela ne correspond qu'à un seul défaut de cache pour lui. ===Les MSHR simples=== Un cache non-bloquant à '''MSHR simple''' contient juste plusieurs MSHR qui mémorisent juste un numéro de ligne de cache, un bit de validité, le champ de lecture/écriture, et l'adresse exacte de lecture/écriture. Avec cette organisation, il est possible d'avoir plusieurs défauts de cache séparés, mais à la condition que chaque défaut accède à une ligne de cache différente. Deux accès simultanés à une même ligne de cache ne sont pas possibles, les défauts de cache secondaires ne sont pas autorisés. Ainsi, chaque défaut de cache se voit attribuer son propre MSHR, chacun contient un numéro de ligne de cache différent. Pour comprendre pourquoi c'est impossible de gérer les défauts secondaires, il faut regarder le champ de lecture/écriture. Si on veut effectuer plusieurs écritures consécutives à la même adresse, le MSHR n'aura pas de quoi mémoriser les deux données à écrire. Il pourra mémoriser la première donnée à écrire, pas la seconde. Même chose lors d'une lecture : le champ lecture/écriture peut mémorisr la destination de la première lecture, pas de la seconde. L'avantage est que la MHA n'a besoin que des MSHR et de quelques circuits annexes. Les autres solutions rajoutent des circuits annexes pour gérer les défauts de cache secondaires, qui utilisent beaucoup de circuits. Le cout en circuit est donc élevé, mais le gain en performance est là. Passons maintenant aux caches non-bloquants qui autorisent les défauts de cache secondaire. La solution la plus simple consiste à utiliser ===Les MSHR adressés implicitement=== Avec les '''MSHR adressés implicitement''', il est possible de fusionner plusieurs accès mémoire à une même ligne de cache, mais sous une condition très importante : ces accès lisent/écrivent des mots mémoire différents. Par exemple, imaginons qu'une ligne de cache contienne 8 mots mémoire de 64 bits. Si un premier accès mémoire lit le mot mémoire numéro 7 (dernier mot mémoire de la ligne), et le second accès le mot mémoire numéro 3, alors la fusion est possible. Mais si deux accès mémoire veulent lire/écrire le mot mémoire numéro 7, alors la fusion n'est pas possible et le processeur se bloque, un ''pipeline stall'' survient. Un MSHR adressé implicitement est un MSHR simple, qui contient naturellement un numéro de ligne de cache (tag) et un bit de validité, sauf que le champ de lecture/écriture est dupliqué. Les différents champs lecture/écriture d'un MSHR sont regroupés dans une mémoire RAM/cache qui contient autant d'entrées qu'il y a de mot mémoire dans une ligne de cache. Chaque entrée de la table est associée à un mot mémoire de la ligne de cache et stocke des informations sur celui-ci. Une entrée mémorise, au minimum : * Un champ de lecture/écriture qui contient soit la destination de la lecture, soit la donnée à écrire. * Un bit R/W permettant d'interpréter correctement le champ de lecture/écriture. * Un bit de validité pour chaque entrée de la table, qui dit si un défaut de cache antérieur accède déjà à ce mot mémoire. La fusion de deux défauts de cache est ainsi assez simple. Un défaut de cache primaire/secondaire configure l'entrée associée au mot mémoire lu/écrit. Si un défaut secondaire ultérieur lit/écrit un mot mémoire différent (dont le bit de validité de l'entrée dans la table est à zéro), pas de conflit : il configure une autre entrée, vide. Mais s'il lit/écrit un mot mémoire pour lequel l'entrée de la table est occupée, il y a conflit, le processeur est bloqué par un ''pipeline stall'', une bulle de pipeline. Avec cette organisation, le nombre de MSHR indique combien de lignes de cache peuvent être lues en même temps depuis la mémoire. Quant au nombre d'entrées par MSHR, il détermine combien d'accès mémoires qui ne se recouvrent pas peuvent avoir lieu en même temps. ===Les MSHR adressés explicitement=== Les '''MSHR adressés explicitement''' n'ont pas les contraintes des MSHR adressés implicitement. Avec eux, il est possible d'avoir plusieurs défauts de cache pointant vers la même ligne de cache, mais aussi vers le même mot mémoire. De tels défauts de cache apparaissent sur les processeurs à exécution dans le désordre, mais sont très rares, voire inexistants, sur les processeurs ''in-order''. L'idée est encore que chaque MSHR est associée à une table mémoire qui mémorise des entrées. Cette table n'est autre qu'une mémoire FIFO. Sauf que cette fois-ci, une entrée n'est pas associée à un mot mémoire. Une entrée est associée à un défaut de cache. Une entrée mémorise là encore la destination de la lecture ou la donnée de l'écriture suivi du bit R/W, ainsi que d'un bit de validité par entrée, mais aussi : la position du mot mémoire lu dans la ligne de cache. C'est cette dernière information qui n'était pas présente dans les MSHR adressés implicitement. De plus, le nombre d'entrée par MSHR n'est pas égal au nombre de mots mémoires dans une ligne de cache, mais peut être arbitrairement grand. L'avantage d'un tel cache est qu'il est capable de traité les défauts secondaires concernant un même mot mémoire, et ce, car les accès à la ligne de cache concernée se produisent dans l'ordre de génération des défauts (la table mémoire est une FIFO). ===Les MSHRs inversés=== Généralement, plus on veut supporter de défauts de cache, plus le nombre de MSHR et d'entrées augmente. Mais au-delà d'un certain nombre d'entrées et de MSHR, les MSHR adressés implicitement et explicitement ont tendance à bouffer un peu trop de circuits. Utiliser une organisation un peu moins gourmande en circuits est donc une nécessité. Cette organisation plus économe se base sur des MSHR inversés. Les MSHR inversés ne contiennent qu'une seule entrée, en quelque sorte : au lieu d’utiliser n MSHR de m entrées chacun, on va utiliser n × m MSHR inversés. La différence, c'est que plusieurs MSHR peuvent contenir un tag identique, contrairement aux MSHR adressés implicitement et explicitement. Lorsqu'un défaut de cache a lieu, chaque MSHR est vérifié. Si jamais aucun MSHR ne contient de tag identique à celui utilisé par le défaut, un MSHR vide est choisi pour stocker ce défaut, et une requête de lecture en mémoire est lancée. Dans le cas contraire, un MSHR est réservé au défaut de cache, mais la requête n'est pas lancée. Quand la donnée est disponible, les MSHR correspondant à la ligne qui vient d'être chargée vont être utilisés un par un pour résoudre les défauts de cache en attente. ===Les MSHR intégrés au cache=== Certains chercheurs ont remarqué que pendant qu'une ligne de cache est en train de subir un défaut de cache, celle-ci reste inutilisée, et son contenu est destiné à être perdu une fois le défaut de cache résolu. Ils se sont dit que, plutôt que d'utiliser des MSHR séparés, il vaudrait mieux utiliser la ligne de cache pour stocker les informations sur les défauts de cache en attente dans cette ligne de cache. Pour éviter tout problème, il faut rajouter un bit dans les bits de contrôle de la ligne de cache, qui sert à indiquer que la ligne de cache est occupée : un défaut de cache a eu lieu dans cette ligne, et elle stocke donc des informations pour résoudre les défauts de cache. ==L'entrelacement mémoire== Nous venons de voir comment une mémoire cache peut gérer plusieurs accès mémoire simultanés. Il se trouve que ce genre d'optimisation dépasse largement le cadre du cache. Les mémoires RAM ne sont pas en reste, elles aussi ! Il est possible de pipeliner l'accès à une mémoire RAM, en utilisant des techniques dites d''''entrelacement'''. Précisons cependant que cette optimisation n'est pas utilisée sur les mémoires SDRAM ou DDR modernes, du moins pas sans modifications majeures. Mais divers ordinateurs assez anciens, dont des superordinateurs, ont utilisé cette technique. La technique demande d'utiliser plusieurs mémoires séparées. Sur les anciens ordinateurs, les mémoires en question sont des chips mémoire, à savoir des circuits intégrés de DRAM. Pour faire plus général, nous allons utiliser le terme de '''banques''', ou encore de bancs mémoire. La différence est qu'il existe des mémoires multi-banque, qui regroupent plusieurs banques indépendantes dans un seul boitier, dans un seul chip mémoire. En clair, de telles mémoires regroupent plusieurs sous-mémoires dans un seul circuit intégré. Il est aussi possible d'utiliser plusieurs chips mémoire séparés, chacun ayant une banque. Reste à combiner ces banques pour former un pseudo-pipeline. ===Utiliser plusieurs banques sans entrelacement=== Sans optimisation particulière, les adresses sont réparties dans les banques comme indiqué ci-dessous. Il s'agit de ce qui s'appelle un arrangement horizontal, et nous avions vu celui-ci dans le chapiutre sur les mémoires SDRAM. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Avec cet arrangement, les bits de poids fort de l'adresse sont utilisées pour sélectionner la banque adéquate, et le reste de l'adresse est envoyé sur le bus d'adresse. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Adresse de banque !! Adresse dans la banque |- | Quelques bits de poids fort || Reste de l'adresse |} L'entrelacement change cette répartition, afin d'accélérer les accès mémoire. L'entrelacement classique vise à améliorer les accès à des adresses mémoires consécutives, mais des formes plus évoluées d'entrelacement visent à optimiser des accès mémoire arbitraires. Voyons-les dans l'ordre, du plus simple au plus complexe. ===L'entrelacement classique=== Avec l''''entrelacement classique''', des adresses consécutives sont réparties dans des banques consécutives. Précisons que cette attribution des adresses n'implique absolument pas la mémoire virtuelle ou n'importe quel mécanisme dans le processeur. La répartition décide que telle adresse mémoire va dans telle banque, à telle adresse dans la banque. Elle est donc le fait du contrôleur mémoire, donc en dehors du processeur (les contrôleurs mémoires n’étaient pas encore intégrés dans le processeur à l'époque). [[File:Répartition des adresses dans une mémoire interleaved.png|centre|vignette|upright=1.5|Répartition des adresses dans une mémoire interleaved.]] L'entrelacement simple permet d'accélérer les accès mémoire qui se font à des adresses consécutives. Avec l'entrelacement, chaque accès mémoire tombe dans une banque mémoire différente, ce qui fait qu'on peut démarrer un nouvel accès mémoire à chaque cycle d'horloge. Pendant qu'une banque est occupée par un accès mémoire, on démarre le suivant dans une autre banque, et ainsi de suite. Le tout se termine soit quand on a épuisé toutes les banques libres, soit quand l'accès en rafales se termine. Pas besoin d'attendre que la mémoire ait fini sa lecture/écriture avant de démarrer la lecture/écriture suivante. [[File:Pipemining mémoire.png|centre|vignette|upright=2.5|Pipelining mémoire]] L'animation suivante illustre bien le processus, avec quatres banques. [[File:Interleaving.gif|centre|vignette|upright=2.5|Pipelining mémoire]] L'entrelacement simple utilise les bits de poids faible pour sélectionner la banque, et les bits de poids fort pour la case mémoire. [[File:Adresse mémoire d'une mémoire entrelacée.png|centre|vignette|upright=2|Adresse mémoire d'une mémoire entrelacée]] Précisons que le temps d'accès mémoire ne change pas beaucoup avec l'entrelacement. Par contre, on peut faire plus d'accès mémoire simultanés. On peut démarrer un accès mémoire par cycle d'horloge, mais l'accès en lui-même prend plusieurs cycles. Les mémoires à entrelacement ont donc un débit supérieur aux mémoires qui ne l'utilisent pas. L'entrelacement classique pourrait être utilisé pour accélérer les transferts entre mémoire RAM et cache, qui se font en blocs d'adresses consécutives. Mais de nos jours, il n'est pas utilisé dans cette optique. Il faut dire qu'il est redondant avec le mode rafale des mémoires DRAM. Les deux font globalement la même chose et on voit mal comment utiliser les deux en même temps. Le mode rafale étant plus simple à implémenter, et plus léger niveau utilisation du bus de commande mémoire, il est préféré à l'entrelacement. Par contre, l'entrelacement a eu son heure de gloire sur d'anciens processeurs qui n'avaient pas de mémoire cache, alors qu'ils disposaient d'un pipeline. L'exemple typique est celui des nombreux ''processeurs vectoriels'', que nous n'avons pas encore abordé à ce stade du cours. De tels processeurs avaient un pipeline, une unité de calcul fortement pipelinée, et parfois même de l'exécution dans le désordre et du renommage de registres ! ===L'entrelacement par décalage=== L'entrelacement simple est très utile pour les accès en rafle ou équivalents. Par contre, il existe de rares situations où il n'est pas optimal. Une de ces situations est celle des accès en enjambée (en ''stride''), où l'on accède en série à des adresses sont séparées par N mots mémoires. [[File:Accès par enjambées.png|centre|vignette|upright=2|Accès par enjambées.]] De tels accès surviennent quand un logiciel accède à des structures de données spécifiques, à savoir des tableaux de structures, des matrices, ou d'autres structures de données dans le genre. Un cas classique est celui du parcours d'une matrice colonne par colonne. Les matrices de nombres sont mémorisées ligne par ligne, ce qui fait que parcourir une colonne demande de passer d'une ligne à la suivante et de faire des grands sauts en mémoire RAM, mais tous espacés par la même distance. Avec l'entrelacement simple, les accès en enjambée sont moins performants que les accès à des adresses consécutives, mais cela ne fonctionne pas trop mal. Le pire cas est celui où l'on a N banques et où les données sont justement placées toutes les N adresses. Des accès consécutifs vont tous tomber dans la même banque, on ne peut plus accéder à des banques différentes en parallèle. Mais en dehors de ce cas, on voit une amélioration par rapport à la situation sans entrelacement. Pour obtenir des performances maximales pour les accès en enjambées, il faut répartir les mots mémoires dans la mémoire autrement. L'organisation idéale est la suivante. : Dans les explications qui vont suivre, la variable N représente le nombre de banques, qui sont numérotées de 0 à N-1. On commence par organiser les N premières adresses comme une mémoire entrelacée simple : l'adresse 0 correspond à la banque 0, l'adresse 1 à la banque 1, etc. Pour le bloc suivant, on décale tout d'une adresse, à savoir qu'on commence à remplir les banques à partir de la seconde, non de la première. Une fois la fin du bloc atteinte, on finit de remplir le bloc en repartant du début du bloc. Le troisième bloc d'adresse subit le même traitement, sauf qu'on commence à remplir à partir de la seconde banque. Et on poursuit l’assignation des adresses en décalant d'un cran en plus à chaque bloc. Ainsi, chaque bloc verra ses adresses décalées d'un cran en plus comparé au bloc précédent. Si jamais le décalage dépasse la fin d'un bloc, alors on reprend au début. [[File:Mémoire interleaved par décalage.png|centre|vignette|upright=1.5|Mémoire entrelacée par décalage.]] En faisant cela, on remarque que les banques situées à N adresses d'intervalle sont différentes. Dans l'exemple du dessus, nous avons ajouté un décalage de 1 à chaque nouveau bloc à remplir. Mais on aurait tout aussi bien pu prendre un décalage de 2, 3, etc. Dans tous les cas, on obtient un entrelacement par décalage. Ce décalage est appelé le pas d'entrelacement, noté P. Le calcul de l'adresse à envoyer à la banque, ainsi que la banque à sélectionner se fait en utilisant les formules suivantes : * adresse à envoyer à la banque = adresse totale / N ; * numéro de la banque = (adresse + décalage) modulo N ; * décalage = (adresse totale * P) mod N. Avec cet entrelacement par décalage, on peut prouver que la bande passante maximale est atteinte si le nombre de banques est un nombre premier. Seulement, utiliser un nombre de banques premier peut créer des trous dans la mémoire, des mots mémoires inadressables. Malgré ce défaut, la technique a été utilisée sur quelques ordinateurs, avec l'exemple notable du superordinateur ''Burroughs Scientific Processor''. Pour éviter cela, il y a plusieurs solutions. Par exemple, on peut faire en sorte que N et la taille d'une banque soient premiers entre eux : ils ne doivent pas avoir de diviseur commun. Mais en pratique, elles n'ont pas vraiment été implémentées dans une vraie machine, et sont restées à l'état de recherche, aussi je les passe sous silence. ===L'entrelacement pseudo-aléatoire=== Une dernière méthode de répartition consiste à répartir les adresses dans les banques de manière "pseudo-aléatoire". L'idée exacte est de faire passer l'adresse dans une '''fonction de hachage''', plus ou moins complexe. Pour rappel, une fonction de hachage prend une entrée de grande taille et fournit en sortie un résultat de petite taille. Idéalement, elles doivent donner des résultats aussi différents que possible pour des entrées similaires, histoire de simuler une sorte de pseudo-aléatoire. Ici, une adresse est transformée en un numéro de banque. Une fonction de hachage simple ne fait que permuter des bits de l'adresse pour obtenir son résultat. Elle ne fait qu'échanger des bits de place avant de couper l'adresse en deux morceaux : un pour la sélection de la banque, et un autre pour la sélection de l'adresse dans la banque. Cette permutation est fixe, et ne change pas suivant l'adresse. Des fonctions de hachage plus complexes font des XOR bit à bit entre certains bits de l'adresse. ==Les contrôleurs mémoires SDRAM/DDR optimisés== Après avoir vu le cas des mémoires RAM, nous allons nous concentrer sur le cas particulier des mémoires SDRAM. Les mémoires SDRAM et DDR modernes sont capables de gérer plusieurs accès simultanés à la mémoire RAM, et leurs contrôleurs mémoire en font tout autant. C'est une différence majeure avec les mémoires asynchrones FPM/EDO, qui n'acceptaient qu'un seul accès mémoire à la fois. Leur contrôleur mémoire n'acceptait qu'un seul accès mémoire à la fois, c'était un contrôleur mémoire bloquant. Les contrôleurs mémoires des SDRAM sont eux non-bloquants et peuvent encaisser une dizaine d'accès mémoire à la fois. Peu de choses sont connues sur les contrôleurs de SDRAM/DDR modernes, les fabricants ne donnant que peu de détails dessus. Les rares simulateurs qui tentent de décrire leur fonctionnement, comme DRAM SIM I et II, sont particulièrement simples et ne vont pas dans le détail. Néanmoins, le peu qu'on sait est tout de même instructif. ===Rappel sur les SDRAM : tampon de ligne et commandes=== Les mémoires SDRAM sont des mémoires à tampon de ligne. Elles font un accès mémoire en deux étapes. La première étape recopie une ligne de N * 64 bits dans un tampon interne à la SDRAM. La seconde étape sélectionne une colonne, à savoir une donnée de 64 bits, qui est soit envoyée sur le bus de données pour une lecture, soit modifiée par une écriture. [[File:Mémoire à tampon de ligne.png|centre|vignette|upright=2|Mémoire à tampon de ligne]] Les accès mémoire sont traduits par un séquenceur mémoire en une série de commandes mémoires, qui sont séparées par des délais mémoire de quelques cycles d'horloge. Les délais sont très précis, et sont à respecter à la lettre. Un accès mémoire se fait en maximum trois commandes : une commande PRECHARGE qui ferme la ligne précédemment utilisée, une commande ACT qui précise l'adresse de la ligne, et une commande READ ou WRITE qui précise l'adresse de la colonne et éventuellement la donnée à écrire. : Pour être plus précis, la commande PRECHARGE précharge les lignes de bits du plan mémoire à une tension particulière, ce qui les vide de leur contenu. Mais c'est un détail sans importance pour ce qui va suivre. Les SDRAM permettent de se passer de la première étape si des accès consécutifs se font dans la même ligne. Une fois activée, la ligne reste ouverte et on peut accéder plusieurs fois de suite dedans. On a alors juste à préciser l'adresse de colonne dedans. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | bgcolor="#FFA0FF" | PRECHARGE || bgcolor="#FFA0FF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || bgcolor="#A0FFFF" | READ (2) || bgcolor="#A0FFFF" | READ (3) || || || bgcolor="#A0FFFF" | READ (4) || bgcolor="#A0FFFF" | READ (5) || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | Donnée READ 1 || ||bgcolor="#A0FFFF" | Donnée READ 2 | bgcolor="#A0FFFF" | Donnée READ 3 || || || bgcolor="#A0FFFF" | Donnée READ 4 || bgcolor="#A0FFFF" | Donnée READ 5 |} Les SDRAM sont parfois capables de démarrer une commande avant que la précédente soit terminée. Mais le respect des délais mémoire est très limitant, ce qui fait qu'on ne peut pas parler de réel pipeline, comme c'est le cas sur les processeurs. Parlons plutot de '''commandes anticipées'''. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | || bgcolor="#A0FFFF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || || || bgcolor="#FFA0FF" | READ (2) || || || || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 | bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 | |} ===La mise en attente des accès mémoire=== Un contrôleur de SDRAM peut accepter plusieurs accès mémoire en même temps. Mais cela ne signifie pas que le contrôleur sera prêt à les traiter immédiatement. Pour éviter tout problème de disponibilité, le contrôleur met en attente les accès mémoire que le processeur lui envoie, pour les exécuter dès que possible. Les accès mémoire sont mis en attente dans une mémoire FIFO, histoire de les exécuter dans leur ordre d'arrivée, notamment pour renvoyer les lectures dans l'ordre demandé. Il y a aussi une mémoire FIFO pour les données à écrire et une FIFO pour les données lues. Cette dernière sert au cas où le cache ne soit pas disponible quand on lui envoie la donnée lue. [[File:Module d'interface avec la mémoire.png|centre|vignette|upright=2.5|Contrôleur mémoire avec mise en attente des requêtes processeur et autres optimisations.]] Des optimisations sont possibles dès la mémoire FIFO. Par exemple, on peut regrouper plusieurs accès à des données consécutives en un seul accès en rafale. Le contrôleur mémoire analyse les requêtes mises en attente et détecte si certaines se font à des adresses consécutives. Si c'est le cas, il fusionne ces requêtes en une lecture/écriture en rafale. Une telle optimisation est appelée la '''combinaison de lecture''' pour les lectures, et la '''combinaison d'écriture''' pour les écritures. ===Le pipeline des mémoires SDRAM=== Les commandes anticipées permettent de faire un minimum de parallélisme mémoire. Même si elle parait très limitée, cette possibilité est décuplée par la présence de plusieurs banques dans la mémoire SDRAM. Les chips mémoires de SDRAM sont composés de plusieurs banques, chacune étant une sorte de mini-mémoire miniature. Chaque banque a son propre décodeur, son propre tampon de ligne, ses propres multiplexeurs de colonne, sa logique de rafraichissement mémoire, etc. C'est comme si une SDRAM regroupait plusieurs mémoires séparées dans un même circuit intégré. Et cela permet de faire plusieurs accès mémoire en même temps, dans des banques différentes. Il est possible de lancer un accès mémoire dans deux banques en même temps, par exemple. Ou encore, on peut lancer un nouvel accès mémoire dans une banque si elle est inoccupée. La possibilité améliore grandement la performance de la SDRAM. Mais nous en reparlerons dans un chapitre ultérieur. {|class="wikitable" |+ Pipelining basique sur les SDRAM |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 |- ! Banque Numéro 1 | || || || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || || || |- ! Banque Numéro 2 | || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || |- ! Banque Numéro 3 | || || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || |} L'implémentation n'a rien de vraiment compliqué. Il suffit d'utiliser une mémoire FIFO par banque. Ainsi, les accès dans une même banque se feront en série, dans l'ordre d'arrivée, mais des accès à des banques différentes se feront dans le désordre. Cela ne pose pas de problème car les accès se font à des adresses différentes, ce qui fait qu'il n'y a pas de conflits majeurs. [[File:Gestion parallèle des banques.png|centre|vignette|upright=2.5|Gestion parallèle des banques.]] ===Le ré-ordonnancement des commandes mémoires=== Un contrôleur mémoire moderne est capable de changer l'ordre des requêtes mémoires, pour gagner en performances. En clair : il peut faire l'équivalent mémoire de l'exécution dans le désordre des processeurs haute performance. Une différence majeure est que le contrôleur mémoire ne remet pas les accès mémoire dans l'ordre avant de les envoyer au processeur. C'est le processeur qui remet en ordre les accès mémoire, dans sa ''load queue''. Pour cela, les requêtes mémoire sont "numérotées", à savoir qu'on leur attribue un identifiant binaire. Le contrôleur mémoire exécute les lectures/écritures dans le désordre, mais garde la trace de leur identifiant. Il sait dans quel ordre il exécute les accès mémoire et connait leur latence. Ce qui fait qu'il sait que la donnée lue à tel instant est associée à la requête mémoire numéro X. Pour les lectures, il renvoie la donnée lue avec l'identifiant. Le processeur sait alors à quelle lecture la donnée lue correspond et il se débrouille en interne pour gérer la situation. En clair : le processeur voit les accès mémoire dans le désordre, c'est lui qui les remet en ordre. Maintenant que ces précisions sont faites, posons cette question : quel est l'intérêt de faire les accès mémoire dans le désordre ? Accéder à des banques en parallèle est une réponse possible, mais le ré-ordonnancement est surtout utile si tous les accès mémoire atterrissent dans la même banque. L'exécution dans le désordre des lectures/écritures SDRAM vise à faire en sorte que des accès consécutifs se fassent dans une même ligne. Elle marche si plusieurs accès à une même ligne ne sont pas consécutifs, et qu'il y a des accès entre les deux. Après ré-ordonnancement, ces accès mémoire deviennent consécutifs, ils sont exécutés l'un à la suite de l'autre. Pour rendre le tout plus concret, voici un exemple. Imaginez que l'on sait les 3 accès mémoire suivants : * Une lecture ligne A ; * Une écriture ligne B ; * Une lecture ligne A. Sans réordonnancent, on doit émettre deux commandes PRECHARGE : une quand on passe de la ligne A à la ligne B, et une autre pour le passage inverse. pour éviter cela, le contrôleur mémoire peut retarder l'écriture, ou au contraire avancer la seconde lecture. le résultat est alors le suivant : * Lecture ligne A ; * Lecture ligne A ; * Une écriture ligne B. On a donc deux accès consécutifs à la même ligne, suivi par une écriture dans une autre ligne. La technique marche parce que l'on a un mix adéquat de lectures et d'écritures. Mais il existe des possibilités de réorganisation autres qui ne sont pas valides. Il faut par exemple prendre garde à éviter d'intervertir lectures et écritures à une même adresse, sous peine de problèmes. Encore une fois, il faut faire attention aux dépendances mémoires. Le controleur mémoire teste les dépendances mémoires avant d'envoyer des commandes à la mémoire DDR/SDRAM. ==L'entrelacement sur les mémoires SDRAM== L''''entrelacement''' fonctionne sur les mémoires SDRAM, mais il doit être fortement modifié. Concrètement, tout ce qui a étré dit plus est inapplicable pour les SDRAM, du fait de la présence du tampon de ligne. Des adresses consécutives atterrissent déjà dans la même ligne, ce qui fait qu'on n'a pas besoin d'entrelacement au niveau d'une ligne. Et ne parlons pas de ce la présence de rangées et de canaux mémoire ! Cependant, il peut y avoir un entrelacement lié à la présence des banques SDRAM. L'entrelacement est géré juste avant le séquenceur mémoire. Le '''circuit d'entrelacement''' intervertit certains bits de l'adresse lors des accès mémoires. On trouve aussi des mémoires FIFOs pour mettre en attente les requêtes mémoire provenant du processeur. Elles permettent de recevoir plusieurs accès mémoire consécutifs. Si vous vous demandez comment plusieurs accès mémoire consécutifs peuvent avoir lieu, sachez que c'est une bonne question. Pour le moment, nous allons dire que ces accès proviennent du processeur ou de la mémoire cache, sans expliquer comment. Le contrôleur mémoire reçoit une série d'accès mémoire assez rapprochés, qu'il doit exécuter au mieux. [[File:Controleur mémoire d'une SDRAM avec entrelacement.png|centre|vignette|upright=2.5|Contrôleur mémoire d'une SDRAM avec entrelacement.]] Pour gérer l'entrelacement, le contrôleur de SDRAM prend en entrée l'adresse envoyée par le processeur, et la découpe en plusieurs champs : un pour sélectionner la banque adéquate, un autre pour la ligne, un autre pour la colonne, un autre pour la rangée. Une solution simple désactive l'entrelacement. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de banque || Adresse de ligne || Adresse de colonne |} Si cette solution fonctionne bien si les accès mémoire sont assez aléatoires, en pratique, mieux vaut utiliser l'entrelacement. ===L'entrelacement de banques/lignes=== Une première méthode d'entrelacement est l''''entrelacement de banques'''. Elle répartit deux lignes consécutives dans deux banques différentes. Pour comprendre l'idée, prenons un exemple. Imaginons une mémoire avec deux banques et 4 lignes. Imaginons qu'on parcoure/balaye la mémoire RAM en partant des adresses basses. Sans entrelacement de ligne, les accès se feront comme suit, de gauche à droite : {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Banque numéro 1 !! colspan="4" | Banque numéro 2 |- | Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 || Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 |} Avec l'entrelacement de banques, les accès se feront comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 |- | Ligne 1 || Ligne 1 || Ligne 2 || Ligne 2 || Ligne 3 || Ligne 3 || Ligne 4 || Ligne 4 |} Avec N banques, la première ligne ira dans la première banque, la seconde ligne dans la seconde, et ainsi de suite jusqu'à la énième banque qui va dans la banque numéro N. Et la ligne N+1 repart dans la première banque. L'avantage est que passer d'une ligne à la suivante est assez rapide. Au lieu de devoir fermer la première ligne et ouvrir la suivante on ouvrira directement la ligne suivante dans une banque différente, ce qui sera plus rapide. La commande PRECHARGE pourra être envoyée à la seconde banque en avance. Pour cela, l'adresse est découpée comme suit. {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne (précise à l'octet) |} ===L'entrelacement de rangées=== Il est aussi possible de faire la même chose, mais avec les rangées. Pour simplifier fortement, une rangée est simplement un chip mémoire. En réalité, les rangées ne sont pas des chip mémoire, mais un ensemble de chips mémoire regroupés ensemble histoire d'atteindre les 64 bits du bus de données. Par exemple, une rangée peut combiner 8 chips mémoire avec un bus de données de 8 bits chacun pour obtenir les 64 bits du bus de données d'une SDRAM. Mais nous passons ce détail sous silence dans les explications qui vont suivre, par souci de simplification. Pour faire comprendre l'entrelacement de rangée, le mieux est d'utiliser un exemple, le même que précédemment. Sans entrelacement de rangée, on change de chip mémoire une fois qu'on a balayé toutes ses adresses. {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Rangée numéro 1 !! colspan="4" | Rangée numéro 2 |- | Banque 1 || Banque 2 || Banque 3 || Banque 4 || Banque 1 || Banque 2 || Banque 3 || Banque 4 |} Avec l'entrelacement de ligne, on change de rangée dès qu'on change de banque/ligne. {|class="wikitable" |+ Adresse mémoire |- ! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 |- | Banque 1 || Banque 1 || Banque 2 || Banque 2 || Banque 3 || Banque 3 || Banque 4 || Banque 4 |} En clair, quand on a épuisé toutes les banques dans une même rangée, on passe à la rangée suivante au lieu de rester dans la même rangée. Notons que dans l'exemple précédent, on a combiné l'entrelacement de rangée et de banque, mais on aurait pu utiliser l'entrelacement de rangée seul. Mais ce n'est pas le cas le plus courant en pratique. Toujours est-il qu'en combinant entrelacement de rangée et de ligne, le découpage de l'adresse est le suivant : {|class="wikitable" |+ Adresse mémoire |- | Adresse de ligne || Numéro de rangée || Adresse de banque || Adresse de colonne (précise à l'octet) |} D'autres méthodes d'entrelacement sont possibles, mais elles sont plus complexes. Chaque contrôleur mémoire fait un peu à sa sauce. Ils permettent en général de choisir entre plusieurs entrelacements. Au minimum, ils permettent de désactiver l'entrelacement, d'activer un entrelacement de ligne, et d'autres entrelacements plus complexes, dont certains sont propriétaires et tenus secrets par le fabricant. ===L'entrelacement avec le ''dual channel''=== [[File:Dual channel slots.jpg|vignette|Slots mémoires avec ''dual channel''.]] L'usage du ''dual channel'' complique encore l'entrelacement. Là encore, il y a deux grandes solutions : avec et sans entrelacement des canaux mémoire. Rappelons le principe : deux barrettes de RAM sont accédées en parallèle. Et pour cela, il faut utiliser l'entrelacement. Typiquement, chaque barrette mémoire fournit 64 bits, ce qui fait que l'on peut accéder à 128 bits d'un seul coup, par exemple avec un accès en rafale. Sans ''dual channel'', la première barrette correspond à la moitié haute de la RAM, la seconde barrette correspond à la moitié basse. Avec ''dual channel'', une forme spécifique d'entrelacement est activée. Concrétement, deux blocs de 64 bits sont placés dans des canaux mémoire séparés. Idem avec du triple ou quadruple canal, mais c'est alors trois ou quatre blocs de 64 bits qui sont dispersés dans des canaux consécutifs. Le découpage de l'adresse est alors le suivant : {|class="wikitable" |+ Adresse mémoire |- ! colspan="6" | Sans entrelacement |- | Numéro de canal/barrette || Reste de l'adresse || Position de l'octet dans un bloc de 64 bits |- | colspan="6" | |- ! colspan="6" | Avec entrelacement |- | Reste de l'adresse || Numéro de canal/barrette || Position de l'octet dans un bloc de 64 bits |} [[File:Décodage d'adresse avec dual channel.png|centre|vignette|upright=2|Décodage d'adresse avec dual channel.]] Les contrôleurs de SDRAM précédents sont assez basiques et ne représentent pas les contrôleurs les plus évolués. Ils étaient utilisés sur les anciens PC, à une époque où ils étaient encore sur la carte mère du processeur. Mais de nos jours, le contrôleur mémoire est intégré au processeur. Et il incorpore de nombreuses optimisations afin de gagner en performances. Malheureusement, ces optimisations sont elles-mêmes dépendantes des optimisations intégrées dans le processeur, ce qui fait qu'on ne peut pas en parler ici. Elles demandent que le contrôleur mémoire reçoive plusieurs accès mémoire simultanés, ce qui n'a aucun sens à ce stade du cours. Aussi, nous allons les passer sous silence pour le moment. Nous les verrons dans le chapitre sur le parallélisme mémoire. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=La désambiguïsation mémoire | prevText=La désambiguïsation mémoire | next=Les processeurs superscalaires | nextText=Les processeurs superscalaires }} </noinclude> kkjrfhpxi3zgxrg8fdgjrahbnslp54s 764715 764714 2026-04-23T21:16:03Z Mewtow 31375 /* Rappel sur les SDRAM : tampon de ligne et commandes */ 764715 wikitext text/x-wiki Dans ce chapitre, nous allons voir les techniques qui permettent de gérer plusieurs accès mémoire simultanés directement au niveau du cache ou de la mémoire RAM. Par plusieurs accès mémoire simultanés, vous pensez sans doute à l'usage de cache multiports, voire de mémoires RAM multiport. C'est en effet une solution, mais ce n'est clairement pas la seule. Par exemple, il est possible de pipeliner l'accès au cache, voire à la mémoire RAM. Il existe de nombreuses techniques de '''parallélisme mémoire''', et nous allons les voir dans ce chapitre. Pour donner un exemple, il est possible de pipeliner les accès mémoire. Il est possible de pipeliner l'accès au cache et/ou l'accès à la mémoire, et nous verrons comment faire dans ce chapitre. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, qu'ils aient ou non un pipeline dynamique. Par contre, pipeliner la mémoire est une technique ancienne, aujourd'hui peu utilisée. Elle était utilisée sur des très vieux ordinateurs, qui avaient un pipeline mais pas de mémoire cache. Mais de nos jours, les mémoires SDRAM et DDR ne sont pas adaptées pour ça, il est très difficile de les pipeliner correctement. Le parallélisme mémoire est utile aussi bien sur des processeurs à émission dans l'ordre que dans le désordre. Par exemple, un processeur ''in-order'' avec des lectures non-bloquantes peut e, profiter. Il peut techniquement lancer une seconde lecture, après avoir rencontré une première lecture non-bloquante. En général, le nombre de lectures consécutives est cependant limité à moins d'une dizaine. À l'opposé, les processeurs sans lectures non-bloquantes profitent pas du parallélisme mémoire. À la rigueur, ils peuvent en profiter s'ils intègrent des techniques de préchargement. Pendant que le processeur exécute une lecture/écriture, il peut précharger une autre donnée en parallèle. Inutile de dire que sans pipeline processeur, le parallélisme mémoire ne sert pas à grand-chose. ==Le ''line fill buffer'' et les techniques associées== Lors d'un défaut de cache, le processeur doit attendre que toute la ligne de cache soit chargée avant d'être utilisable. Or, la taille d'une ligne de cache est supérieure à la largeur du bus mémoire, ce qui fait qu'une ligne de cache est chargée en plusieurs fois, morceaux par morceaux, mot mémoire par mot mémoire. Le chargement peut se faire directement dans le cache, mais ce n'est pas une solution très pratique. À la place, beaucoup de processeurs ajoutent une mémoire tampon entre la RAM et le cache, appelée le '''tampon de remplissage de ligne''' (''line-fill buffer'') dans les processeurs modernes. Lors d'un défaut de cache, le processeur charge la donnée de la RAM dans le tampon de remplissage de ligne, mot mémoire par mot mémoire. Une fois plein, le tampon de remplissage de ligne est recopié dans la ligne de cache. Le tampon de remplissage de ligne contient une ligne de cache, si ce n'est que certains bits de contrôle ne sont pas présents. Le tampon de remplissage de ligne contient un bit de validité pour chaque mot mémoire de la ligne de cache, qui indiquent si le mot mémoire a été chargé. Par exemple, prenons un processeur 64 bits, qui gère donc des mots mémoire de 8 octets, avec des lignes de cache 256 octets/32 mots mémoire. Le tampon de remplissage de ligne contiendra 32 bits de validité. Si le processeur a chargé les 6 premiers mots mémoire, les 6 premiers bits de validité seront mis à 1, les autres seront encore à zéro. Il faut noter que la disponibilité de la ligne complète se détermine assez facilement en faisant un ET logique entre tous les bits de validité. Le processeur sait ainsi quand la ligne de cache est disponible entièrement et donc quand la transférer dans le cache. La même chose existe avec une hiérarchie de cache, sauf que l'on trouve une mémoire tampon entre chaque niveau de cache. Il y a un tampon de remplissage de ligne entre le cache L1 et le cache L2, entre le cache L2 et le L3, etc. Si le cache ne gère qu'un seul défaut de cache à la fois, le ''fill line buffer'' est une mémoire très simple, qui ne mémorise qu'une seule ligne de cache. Et cela vaut aussi bien pour un cache bloquant que non-bloquant. Mais sur les caches capables de gérer plusieurs défauts simultanés, le tampon de remplissage de ligne est une mémoire de type FIFO ou LIFO, capable de mémoriser plusieurs lignes de cache. Notons que le tampon de remplissage de ligne est très utile pour implémenter certaines techniques, par exemple le contournement du cache. Nous avions vu dans le chapitre sur le cache que certains accès mémoire doivent contourner le cache, pour des raisons de cohérence des caches. C'est notamment nécessaire pour accéder aux périphériques, mais c'est aussi utile pour des raisons de performances dans des cas très spécifiques. Les accès qui contournent le cache se font directement dans le tampon de remplissage de ligne : le processeur écrit ou lit les données depuis ce tampon de remplissage de ligne, sans accéder au cache. ===L'''early restart'' et le ''critical word load''=== La présence du ''line buffer'' permet une optimisation assez intéressante, qui permet de réduire la latence des défauts de cache. L'optimisation consiste à lire un mot mémoire dans le tampon de remplissage de ligne, même si la ligne de cache complète n'a pas encore été chargée. Il existe deux manières de faire cela, qui portent les noms d'''early restart'' et de ''critical word load''. La première est la version la plus simple, la seconde est plus complexe mais plus performante. Avec la technique d''''''early restart''''', la ligne de cache est chargée normalement, en partant de son premier mot mémoire. Dès que le mot mémoire lu/écrit par le processeur est copié dans le ''line fill buffer'', il est envoyé au processeur immédiatement. Illustrons le tout par un exemple, où une ligne de cache fait 16 mots mémoire. Le processeur effectue une lecture, qui lit le 5ème mot mémoire. Sans ''early restart'', le processeur doit charger les 16 mots mémoire avant de faire la lecture dans le cache. Avec ''early restart'', le processeur reçoit la donnée dès que le 5ème mot mémoire est disponible. Le processeur doit attendre que les 4 premiers mots mémoire soient chargés, puis le 5ème mot mémoire arrive et est envoyé directement au processeur, il est lu directement depuis le ''line fill buffer''. Les 11 mots mémoire suivants sont ensuite chargés dans le cache pendant que le processeur fait des calculs dans son coin. Le ''critical word load'' est une optimisation de la technique précédente où le chargement de la ligne de cache commence directement à la donnée demandée par le processeur. Pour reprendre l'exemple précédent, où le processeur demande le 5ème mot mémoire sur 16, le ''critical word load'' charge le 5ème mot mémoire en premier et l'envoie au processeur, ce qui fait qu'il est chargé très rapidement. Pas besoin d'attendre que le processeur charge les 4 mots mémoire précédents comme avec l'''early restart''. Dans le détail, le processeur charge le 5ème mot mémoire en premier, puis charge les 11 suivants, et termine par les 4 mots mémoire du début. En clair, le ''critical word load'' commence par charger le mot mémoire lu, puis les blocs suivants, avant de revenir au début du bloc pour charger les blocs restants. Ainsi, la donnée demandée par le processeur sera la première disponible. Pour cela, l'organisation du tampon de remplissage de ligne est modifiée de manière à rendre cela possible. Il a une taille égale à une ligne de cache complète, qui contient elle-même plusieurs mots mémoire. Dans le ''line-fill buffer'', chaque mot mémoire est stocké avec un tag, qui indique l'adresse du mot mémoire stocké dans le ''line-fill buffer''. Le ''line-fill buffer'' est donc un cache un peu particulier, qui fonctionne comme un cache du point de vue du processeur, comme une mémoire FIFO pour les transferts avec le cache. Ainsi, un processeur qui veut lire dans le cache après un défaut peut accéder à la donnée directement depuis le tampon de remplissage de ligne, alors que la ligne de cache n'a pas encore été totalement recopiée en mémoire. ==Les caches pipelinés== Il est possible de pipeliner l'accès au cache, ce qui demande juste de rajouter des registres au bon endroits et quelques circuits de contrôle. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, même ceux avec un pipeline dynamique. Et l'implémentation peut se faire de plusieurs manières différentes. Il faut dire que découper une mémoire cache en plusieurs étapes peut se faire de plusieurs manière. Commençons par la plus simple. Beaucoup de processeurs des années 2000 avaient une fréquence peu élevée comparée aux standards d'aujourd'hui, ce qui fait que leur cache avait bien un temps d'accès d'un cycle d'horloge. Mais l'accès au cache se faisait en deux cycles d'horloge : un cycle pour calculer une adresse et un cycle pour l'accès mémoire proprement dit. Et ces deux étapes étaient pipelinées, à savoir que deux micro-opérations mémoire peuvent s'exécuter en même temps : une dans l'étage de calcul d'adresse, une autre dans le cache. L'unité mémoire était donc pipelinée, alors que l'accès au cache ne l'était pas. Les processeurs qui implémentaient cette technique regroupent les micro-architectures K5 et K6 d'AMD, les processeurs Intel de micro-architecture P6 (Pentium 2 et 3) et quelques autres. Il est aussi possible de pipeliner l'accès au cache lui-même. Avec un cache pipeliné, l'accès au cache ne se fait pas en un seul cycle, mais en plusieurs. Cependant, on peut lancer un nouvel accès au cache à chaque cycle d'horloge, comme un processeur pipeliné lance une nouvelle instruction à chaque cycle. L'implémentation est assez simple : il suffit d'ajouter des registres dans le cache. Pour cela, on profite que le cache est composé de plusieurs composants séparés, qui échangent des données dans un ordre bien précis, d'un composant à un autre. De plus, le trajet des informations dans un cache est linéaire, ce qui les rend parfois pour l'usage d'un pipeline. ===Le ''pipelining'' des caches ''direct-mapped''=== Voici ce qui se passe avec un cache directement adressé. Pour rappel ce genre de cache est conçu en combinant une mémoire RAM, généralement une SRAM, avec quelques circuits de comparaison et un MUX. L'accès au cache se fait globalement en deux étapes : la première lit la donnée et le ''tag'' dans la SRAM, et on utilise les deux dans une seconde étape. Nous avions vu il y a quelques chapitre comment pipeliner des mémoires, dans le chapitre sur les mémoires évoluées. Et bien ces méthodes peuvent s'utiliser pour la mémoire RAM interne au cache !L'idée est d'insérer un registre entre la sortie de la RAM et la suite du cache, pour en faire un cache pipéliné, à deux étages. Le premier étage lit dans la SRAM, le second fait le reste. L'implémentation sur les caches associatifs à plusieurs voies est globalement la même à quelques détails près. [[File:Cache directement adressé pipeliné - deux étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - deux étages]] Avec le circuit précédent, il est possible d'aller plus loin, cette fois en pipelinant l'accès à la mémoire RAM interne au cache. Pour comprendre comment, rappelons qu'une mémoire SRAM est composée d'un plan mémoire et d'un décodeur. L'accès à la mémoire demande d'abord que le décodeur fasse son travail pour sélectionner la case mémoire adéquate, puis ensuite la lecture ou écriture a lieu dans cette case mémoire. L'accès se fait donc en deux étapes successives séparées, on a juste à mettre un registre entre les deux. Il suffit donc de mettre un gros registre entre le décodeur et le plan mémoire. [[File:Cache directement adressé pipeliné - trois étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - trois étages]] Et on peut aller encore plus loin en découpant le décodeur en deux circuits séparés. En effet, rappelez-vous le chapitre sur les circuits de sélection : nous avions vu qu'il est possible de créer des décodeurs en assemblant des décodeurs plus petits, contrôlés par un circuit de prédécodage. Et bien on peut encore une ajouter un registre entre ce circuit de prédécodage et les petits décodeurs. : Théoriquement, toute l'adresse est fournie d'un seul coup au cache, la quasi-totalité des processeurs présentent l'adresse complète à un cache pipeliné. Mais le Pentium 4 fait autrement. Il faut noter que les premiers étages manipulent l'indice dans la SRAM, qui est dans les bits de poids faible, alors que les étages ultérieurs manipulent le ''tag'' qui est dans les bits de poids fort. Les concepteurs du Pentium 4 ont alors décidé de présenter les bits de poids faible lors du premier cycle d'accès au cache, puis ceux de poids fort au second cycle. Pour cela, l'ALU fonctionnait à une fréquence double de celle du processeur, tout comme le cache L1. Il n'y avait pas de pipeline proprement dit, mais cela réduisait grandement la latence d'accès au cache. ===Le ''pipelining'' des caches splittés/sériels=== Il est aussi possible de pipeliner un cache dits splittés, aussi appelés à accès sériel. Pour rappel, les caches à accès sériel vérifient si il y a succès ou défaut de cache, avant d'accéder aux lignes de cache en cas de succès. Ils font donc différemment des autres caches, qui accèdent à une ligne de cache, avant de déterminer s'il y a succès ou défaut en lisant le tag de la ligne de cache. Les caches sériels disposent de deux SRAM : une pour les tags des lignes de cache et une pour les données. Ils accèdent à la SRAM pour les tags, avant d’accéder à la SRAM des données en cas de succès de cache. Vu que l'accès se fait en deux étapes, une vérification des tags suivie de la lecture/écriture des données, il est facile à pipeliner. Pipeliner le cache permet de régler le problème des accès au cache L1, et elle est tout le temps utilisé sur les processeurs modernes. Mais que faire en cas de défaut de cache ? ==Les caches non bloquants== Un '''cache bloquant''' est un cache auquel le processeur ne peut pas accéder pendant un défaut de cache. Il faut attendre que la lecture ou écriture en RAM soit terminée avant de pouvoir utiliser de nouveau le cache. Un '''cache non bloquant''' n'a pas ce problème : on peut l'utiliser pendant un défaut de cache. Les caches non bloquants permettent de démarrer une nouvelle lecture ou écriture alors qu'une autre est en cours, ce qui permet d'exécuter plusieurs lectures ou écritures en même temps. ===Les ''Miss Handling Status Registers''=== Lors d'un défaut de cache, la mémoire RAM est consultée pendant le défaut de cache, mais le cache est inutilisé. Un défaut de cache n'utilise pas le cache, ce n'est qu'un accès en mémoire RAM, sur le bus mémoire (ou un accès aux niveaux de cache inférieurs, peu importe). Le cache en lui-même est laissé libre, rien n’empêche d'y accéder, il est en réalité intrinsèquement non-bloquant. Les caches, bloquants comme non-bloquants, sont en réalité composés d'une mémoire cache proprement dite, entourée de circuits qui servent d'interface entre le processeur et le cache lui-même. Et parmi les circuits tout autour, certains gèrent l'accès au cache lors d'un défaut de cache. Ils sont regroupés sous le terme de '''''Miss Handling Architecture''''' (MHA). La différence entre un cache bloquant et un cache non-bloquant est en réalité liée à l'implémentation de la MHA. Les caches bloquants coupent volontairement l'accès au cache lors d'un défaut, car les défauts sont plus simples à gérer ainsi. Le défaut de cache rapatrie une donnée depuis la RAM, qui sera écrite dans le cache. Et il ne faut pas qu'une tentative d'accès à cette donnée ait lieu avant qu'elle ne soit chargée. Mais ce blocage est général et touche tout le cache, alors que seule une ligne de cache est concernée par le défaut de cache. L'idée derrière un cache non-bloquant est que seule la ligne de cache est bloquée, mais les autres sont accessibles. L'idée est alors de mémoriser les lignes de cache concernées par le défaut de cache, afin d'en bloquer l'accès. À chaque accès, on vérifie si la ligne de cache est déjà réservée par un défaut de cache. La lecture/écriture est alors bloquée si c'est le cas, mais elle accepte les accès sinon. Pour cela, la ''Miss Handling Architecture'' contient des registres qui mémorisent des informations sur les défauts de cache en cours. Ils portent le nom de '''''miss status handling registers''''', que j’appellerais dorénavant MSHR, qui sont aussi appelés des '''''miss buffer'''''. Le contenu des MSHR varie beaucoup suivant le processeur, mais ces derniers stockent au minimum les informations suivantes : * Le numéro de la ligne de cache dans laquelle les données sont chargées. * Un bit de validité qui indique si le MSHR est vide ou pas, qui est mis à 0 quand le défaut de cache est résolu. * Un ou plusieurs champs de lecture/écriture, qui contiennent des informations sur la lecture/écriture. ** Pour une lecture, elle contient des informations sur la destination de la donnée, à savoir qui prévenir quand le défaut de cache est terminé. C'est parfois un nom/numéro de registre (celui dans lequel charger la donnée), mais c'est souvent le numéro de l'entrée dans le ''load/store queue''. ** Pour les écritures, elle contient la donnée à écrire, ou éventuellement un numéro de ''load/store queue'' où se trouve la donnée à écrire. Il faut noter que le bus mémoire ne peut gérer qu'un seul défaut de cache à la fois. Aussi, il est intéressant de regarder ce qui se passe lorsqu'un second défaut de cache survient, pendant qu'un premier est en cours. Dans ce cas, il y a deux réponses qui correspondent à deux types de caches non-bloquants, qui portent les noms barbares de caches de type succès après défaut et défaut après défaut. Sur le premier type, il ne peut pas y avoir plusieurs défauts de cache simultanés. Dès qu'un second défaut de cache survient, le cache stoppe son activité et on ne peut plus démarrer de nouvelle lecture/écriture, tant que le premier défaut de cache n'est pas résolu. Le second type est plus souple et autorise la survenue de plusieurs défauts de cache simultanés. Du moins, jusqu'à une certaine limite, car le cache ne peut supporter qu'un nombre limité d'accès mémoires simultanés (pipelinés). ===Les accès simultanés à une même ligne de cache=== Il arrive que le processeur fasse plusieurs accès mémoire simultanés à la même ligne de cache. Si la ligne de cache en question n'a pas encore été chargée dans le cache, alors on a plusieurs défauts de cache consécutifs pour la même ligne de cache, et le cache non-bloquant doit gérer la situation. Pour la suite, il va falloir faire une petite distinction entre les défauts primaires et secondaires. Imaginons qu'un défaut de cache ait lieu et demande à charger une donnée dans la ligne de cache numéro N. Il s'agit du premier défaut impliquant cette ligne de cache précise, ce qui lui vaut le nom de '''défaut de cache primaire'''. Mais par la suite, d'autres accès mémoire à la même ligne de cache ont lieu, alors que la ligne de cache n'est pas encore disponible. Dans ce cas, il s'agit de '''défauts de cache secondaires'''. Pour l'unité d'accès mémoire, les défauts de cache primaire et secondaire sont différents (ils prennent tous une entrée dans la ''load/store queue''). Mais pour le cache, ils ne correspondent qu'à un seul accès au cache : celui qui demande de charger la ligne de cache demandée. Les défauts de cache primaire et secondaire à la même ligne de cache se voient attribuer un MSHR unique. La gestion des défauts de cache secondaires dépend du cache non-bloquant. La solution la plus simple ne permet pas les défauts de cache secondaires. Le cache ne permet pas deux défauts de cache simultanés pour la même ligne de cache. Les autres solutions le permettent, en fusionnant des accès simultanés à la même ligne de cache en un seul au niveau des MSHR. Dans tous les cas, détecter les défauts de cache secondaires sont un problème qu'il faut détecter. La ''Miss Handling Architecture'' doit détecter les défauts de cache secondaire. Pour cela, elle procède comme suit. Lors de chaque défaut de cache, la MHA récupère le numéro de la ligne de cache associé. Il vérifie alors chaque MSHR pour vérifier s'il contient le numéro en question. S'il n'y a aucune correspondance dans les MSHR, alors c'est signe que le défaut de cache est un défaut primaire. Mais s'il y en a une, alors c'est un défaut secondaire. Évidemment, cela signifie que lors d'un défaut de cache, le numéro de ligne de cache est envoyé à tous les MSHR, pour comparaison. Les MSHR sont donc regroupés dans une mémoire associative, une sorte de mini-cache, faciliter l'implémentation. Lors d'un défaut de cache primaire, l'accès à un cache non-bloquant se fait comme suit : le processeur envoie une adresse au cache, accède à celui-ci, et détecte la survenue d'un défaut de cache. Il en profite alors pour attribuer une ligne de cache dans laquelle sera chargée ce défaut. L'attribution est très simple dans le cas des caches ''direct mapped'', ou associatifs par voie, pour lesquels l'attribution se fait assez simplement. Il mémorise alors cette information dans les MSHR, après avoir vérifié que le défaut de cache n'était pas un défaut secondaire. Lors des accès ultérieurs à une adresse proche, censée être dans la même ligne de cache, le processeur va encore une fois rencontrer un défaut de cache. Il va alors déterminer le numéro de la ligne de cache associée à l'adresse, et comparer ce numéro avec les MSHRs. Si un MSHR contient ce numéro, c'est signe que le défaut de cache est un défaut secondaire. La MHA réagit alors différemment selon le processeur considéré. Une première solution n'autorise pas les défauts de cache secondaires. Si l'un d'entre eux survient, le processeur est gelé par un ''pipeline stall'', une bulle de pipeline. Une autre solution fusionne les défauts de cache secondaires avec le défaut de cache primaire : tout cela ne correspond qu'à un seul défaut de cache pour lui. ===Les MSHR simples=== Un cache non-bloquant à '''MSHR simple''' contient juste plusieurs MSHR qui mémorisent juste un numéro de ligne de cache, un bit de validité, le champ de lecture/écriture, et l'adresse exacte de lecture/écriture. Avec cette organisation, il est possible d'avoir plusieurs défauts de cache séparés, mais à la condition que chaque défaut accède à une ligne de cache différente. Deux accès simultanés à une même ligne de cache ne sont pas possibles, les défauts de cache secondaires ne sont pas autorisés. Ainsi, chaque défaut de cache se voit attribuer son propre MSHR, chacun contient un numéro de ligne de cache différent. Pour comprendre pourquoi c'est impossible de gérer les défauts secondaires, il faut regarder le champ de lecture/écriture. Si on veut effectuer plusieurs écritures consécutives à la même adresse, le MSHR n'aura pas de quoi mémoriser les deux données à écrire. Il pourra mémoriser la première donnée à écrire, pas la seconde. Même chose lors d'une lecture : le champ lecture/écriture peut mémorisr la destination de la première lecture, pas de la seconde. L'avantage est que la MHA n'a besoin que des MSHR et de quelques circuits annexes. Les autres solutions rajoutent des circuits annexes pour gérer les défauts de cache secondaires, qui utilisent beaucoup de circuits. Le cout en circuit est donc élevé, mais le gain en performance est là. Passons maintenant aux caches non-bloquants qui autorisent les défauts de cache secondaire. La solution la plus simple consiste à utiliser ===Les MSHR adressés implicitement=== Avec les '''MSHR adressés implicitement''', il est possible de fusionner plusieurs accès mémoire à une même ligne de cache, mais sous une condition très importante : ces accès lisent/écrivent des mots mémoire différents. Par exemple, imaginons qu'une ligne de cache contienne 8 mots mémoire de 64 bits. Si un premier accès mémoire lit le mot mémoire numéro 7 (dernier mot mémoire de la ligne), et le second accès le mot mémoire numéro 3, alors la fusion est possible. Mais si deux accès mémoire veulent lire/écrire le mot mémoire numéro 7, alors la fusion n'est pas possible et le processeur se bloque, un ''pipeline stall'' survient. Un MSHR adressé implicitement est un MSHR simple, qui contient naturellement un numéro de ligne de cache (tag) et un bit de validité, sauf que le champ de lecture/écriture est dupliqué. Les différents champs lecture/écriture d'un MSHR sont regroupés dans une mémoire RAM/cache qui contient autant d'entrées qu'il y a de mot mémoire dans une ligne de cache. Chaque entrée de la table est associée à un mot mémoire de la ligne de cache et stocke des informations sur celui-ci. Une entrée mémorise, au minimum : * Un champ de lecture/écriture qui contient soit la destination de la lecture, soit la donnée à écrire. * Un bit R/W permettant d'interpréter correctement le champ de lecture/écriture. * Un bit de validité pour chaque entrée de la table, qui dit si un défaut de cache antérieur accède déjà à ce mot mémoire. La fusion de deux défauts de cache est ainsi assez simple. Un défaut de cache primaire/secondaire configure l'entrée associée au mot mémoire lu/écrit. Si un défaut secondaire ultérieur lit/écrit un mot mémoire différent (dont le bit de validité de l'entrée dans la table est à zéro), pas de conflit : il configure une autre entrée, vide. Mais s'il lit/écrit un mot mémoire pour lequel l'entrée de la table est occupée, il y a conflit, le processeur est bloqué par un ''pipeline stall'', une bulle de pipeline. Avec cette organisation, le nombre de MSHR indique combien de lignes de cache peuvent être lues en même temps depuis la mémoire. Quant au nombre d'entrées par MSHR, il détermine combien d'accès mémoires qui ne se recouvrent pas peuvent avoir lieu en même temps. ===Les MSHR adressés explicitement=== Les '''MSHR adressés explicitement''' n'ont pas les contraintes des MSHR adressés implicitement. Avec eux, il est possible d'avoir plusieurs défauts de cache pointant vers la même ligne de cache, mais aussi vers le même mot mémoire. De tels défauts de cache apparaissent sur les processeurs à exécution dans le désordre, mais sont très rares, voire inexistants, sur les processeurs ''in-order''. L'idée est encore que chaque MSHR est associée à une table mémoire qui mémorise des entrées. Cette table n'est autre qu'une mémoire FIFO. Sauf que cette fois-ci, une entrée n'est pas associée à un mot mémoire. Une entrée est associée à un défaut de cache. Une entrée mémorise là encore la destination de la lecture ou la donnée de l'écriture suivi du bit R/W, ainsi que d'un bit de validité par entrée, mais aussi : la position du mot mémoire lu dans la ligne de cache. C'est cette dernière information qui n'était pas présente dans les MSHR adressés implicitement. De plus, le nombre d'entrée par MSHR n'est pas égal au nombre de mots mémoires dans une ligne de cache, mais peut être arbitrairement grand. L'avantage d'un tel cache est qu'il est capable de traité les défauts secondaires concernant un même mot mémoire, et ce, car les accès à la ligne de cache concernée se produisent dans l'ordre de génération des défauts (la table mémoire est une FIFO). ===Les MSHRs inversés=== Généralement, plus on veut supporter de défauts de cache, plus le nombre de MSHR et d'entrées augmente. Mais au-delà d'un certain nombre d'entrées et de MSHR, les MSHR adressés implicitement et explicitement ont tendance à bouffer un peu trop de circuits. Utiliser une organisation un peu moins gourmande en circuits est donc une nécessité. Cette organisation plus économe se base sur des MSHR inversés. Les MSHR inversés ne contiennent qu'une seule entrée, en quelque sorte : au lieu d’utiliser n MSHR de m entrées chacun, on va utiliser n × m MSHR inversés. La différence, c'est que plusieurs MSHR peuvent contenir un tag identique, contrairement aux MSHR adressés implicitement et explicitement. Lorsqu'un défaut de cache a lieu, chaque MSHR est vérifié. Si jamais aucun MSHR ne contient de tag identique à celui utilisé par le défaut, un MSHR vide est choisi pour stocker ce défaut, et une requête de lecture en mémoire est lancée. Dans le cas contraire, un MSHR est réservé au défaut de cache, mais la requête n'est pas lancée. Quand la donnée est disponible, les MSHR correspondant à la ligne qui vient d'être chargée vont être utilisés un par un pour résoudre les défauts de cache en attente. ===Les MSHR intégrés au cache=== Certains chercheurs ont remarqué que pendant qu'une ligne de cache est en train de subir un défaut de cache, celle-ci reste inutilisée, et son contenu est destiné à être perdu une fois le défaut de cache résolu. Ils se sont dit que, plutôt que d'utiliser des MSHR séparés, il vaudrait mieux utiliser la ligne de cache pour stocker les informations sur les défauts de cache en attente dans cette ligne de cache. Pour éviter tout problème, il faut rajouter un bit dans les bits de contrôle de la ligne de cache, qui sert à indiquer que la ligne de cache est occupée : un défaut de cache a eu lieu dans cette ligne, et elle stocke donc des informations pour résoudre les défauts de cache. ==L'entrelacement mémoire== Nous venons de voir comment une mémoire cache peut gérer plusieurs accès mémoire simultanés. Il se trouve que ce genre d'optimisation dépasse largement le cadre du cache. Les mémoires RAM ne sont pas en reste, elles aussi ! Il est possible de pipeliner l'accès à une mémoire RAM, en utilisant des techniques dites d''''entrelacement'''. Précisons cependant que cette optimisation n'est pas utilisée sur les mémoires SDRAM ou DDR modernes, du moins pas sans modifications majeures. Mais divers ordinateurs assez anciens, dont des superordinateurs, ont utilisé cette technique. La technique demande d'utiliser plusieurs mémoires séparées. Sur les anciens ordinateurs, les mémoires en question sont des chips mémoire, à savoir des circuits intégrés de DRAM. Pour faire plus général, nous allons utiliser le terme de '''banques''', ou encore de bancs mémoire. La différence est qu'il existe des mémoires multi-banque, qui regroupent plusieurs banques indépendantes dans un seul boitier, dans un seul chip mémoire. En clair, de telles mémoires regroupent plusieurs sous-mémoires dans un seul circuit intégré. Il est aussi possible d'utiliser plusieurs chips mémoire séparés, chacun ayant une banque. Reste à combiner ces banques pour former un pseudo-pipeline. ===Utiliser plusieurs banques sans entrelacement=== Sans optimisation particulière, les adresses sont réparties dans les banques comme indiqué ci-dessous. Il s'agit de ce qui s'appelle un arrangement horizontal, et nous avions vu celui-ci dans le chapiutre sur les mémoires SDRAM. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Avec cet arrangement, les bits de poids fort de l'adresse sont utilisées pour sélectionner la banque adéquate, et le reste de l'adresse est envoyé sur le bus d'adresse. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Adresse de banque !! Adresse dans la banque |- | Quelques bits de poids fort || Reste de l'adresse |} L'entrelacement change cette répartition, afin d'accélérer les accès mémoire. L'entrelacement classique vise à améliorer les accès à des adresses mémoires consécutives, mais des formes plus évoluées d'entrelacement visent à optimiser des accès mémoire arbitraires. Voyons-les dans l'ordre, du plus simple au plus complexe. ===L'entrelacement classique=== Avec l''''entrelacement classique''', des adresses consécutives sont réparties dans des banques consécutives. Précisons que cette attribution des adresses n'implique absolument pas la mémoire virtuelle ou n'importe quel mécanisme dans le processeur. La répartition décide que telle adresse mémoire va dans telle banque, à telle adresse dans la banque. Elle est donc le fait du contrôleur mémoire, donc en dehors du processeur (les contrôleurs mémoires n’étaient pas encore intégrés dans le processeur à l'époque). [[File:Répartition des adresses dans une mémoire interleaved.png|centre|vignette|upright=1.5|Répartition des adresses dans une mémoire interleaved.]] L'entrelacement simple permet d'accélérer les accès mémoire qui se font à des adresses consécutives. Avec l'entrelacement, chaque accès mémoire tombe dans une banque mémoire différente, ce qui fait qu'on peut démarrer un nouvel accès mémoire à chaque cycle d'horloge. Pendant qu'une banque est occupée par un accès mémoire, on démarre le suivant dans une autre banque, et ainsi de suite. Le tout se termine soit quand on a épuisé toutes les banques libres, soit quand l'accès en rafales se termine. Pas besoin d'attendre que la mémoire ait fini sa lecture/écriture avant de démarrer la lecture/écriture suivante. [[File:Pipemining mémoire.png|centre|vignette|upright=2.5|Pipelining mémoire]] L'animation suivante illustre bien le processus, avec quatres banques. [[File:Interleaving.gif|centre|vignette|upright=2.5|Pipelining mémoire]] L'entrelacement simple utilise les bits de poids faible pour sélectionner la banque, et les bits de poids fort pour la case mémoire. [[File:Adresse mémoire d'une mémoire entrelacée.png|centre|vignette|upright=2|Adresse mémoire d'une mémoire entrelacée]] Précisons que le temps d'accès mémoire ne change pas beaucoup avec l'entrelacement. Par contre, on peut faire plus d'accès mémoire simultanés. On peut démarrer un accès mémoire par cycle d'horloge, mais l'accès en lui-même prend plusieurs cycles. Les mémoires à entrelacement ont donc un débit supérieur aux mémoires qui ne l'utilisent pas. L'entrelacement classique pourrait être utilisé pour accélérer les transferts entre mémoire RAM et cache, qui se font en blocs d'adresses consécutives. Mais de nos jours, il n'est pas utilisé dans cette optique. Il faut dire qu'il est redondant avec le mode rafale des mémoires DRAM. Les deux font globalement la même chose et on voit mal comment utiliser les deux en même temps. Le mode rafale étant plus simple à implémenter, et plus léger niveau utilisation du bus de commande mémoire, il est préféré à l'entrelacement. Par contre, l'entrelacement a eu son heure de gloire sur d'anciens processeurs qui n'avaient pas de mémoire cache, alors qu'ils disposaient d'un pipeline. L'exemple typique est celui des nombreux ''processeurs vectoriels'', que nous n'avons pas encore abordé à ce stade du cours. De tels processeurs avaient un pipeline, une unité de calcul fortement pipelinée, et parfois même de l'exécution dans le désordre et du renommage de registres ! ===L'entrelacement par décalage=== L'entrelacement simple est très utile pour les accès en rafle ou équivalents. Par contre, il existe de rares situations où il n'est pas optimal. Une de ces situations est celle des accès en enjambée (en ''stride''), où l'on accède en série à des adresses sont séparées par N mots mémoires. [[File:Accès par enjambées.png|centre|vignette|upright=2|Accès par enjambées.]] De tels accès surviennent quand un logiciel accède à des structures de données spécifiques, à savoir des tableaux de structures, des matrices, ou d'autres structures de données dans le genre. Un cas classique est celui du parcours d'une matrice colonne par colonne. Les matrices de nombres sont mémorisées ligne par ligne, ce qui fait que parcourir une colonne demande de passer d'une ligne à la suivante et de faire des grands sauts en mémoire RAM, mais tous espacés par la même distance. Avec l'entrelacement simple, les accès en enjambée sont moins performants que les accès à des adresses consécutives, mais cela ne fonctionne pas trop mal. Le pire cas est celui où l'on a N banques et où les données sont justement placées toutes les N adresses. Des accès consécutifs vont tous tomber dans la même banque, on ne peut plus accéder à des banques différentes en parallèle. Mais en dehors de ce cas, on voit une amélioration par rapport à la situation sans entrelacement. Pour obtenir des performances maximales pour les accès en enjambées, il faut répartir les mots mémoires dans la mémoire autrement. L'organisation idéale est la suivante. : Dans les explications qui vont suivre, la variable N représente le nombre de banques, qui sont numérotées de 0 à N-1. On commence par organiser les N premières adresses comme une mémoire entrelacée simple : l'adresse 0 correspond à la banque 0, l'adresse 1 à la banque 1, etc. Pour le bloc suivant, on décale tout d'une adresse, à savoir qu'on commence à remplir les banques à partir de la seconde, non de la première. Une fois la fin du bloc atteinte, on finit de remplir le bloc en repartant du début du bloc. Le troisième bloc d'adresse subit le même traitement, sauf qu'on commence à remplir à partir de la seconde banque. Et on poursuit l’assignation des adresses en décalant d'un cran en plus à chaque bloc. Ainsi, chaque bloc verra ses adresses décalées d'un cran en plus comparé au bloc précédent. Si jamais le décalage dépasse la fin d'un bloc, alors on reprend au début. [[File:Mémoire interleaved par décalage.png|centre|vignette|upright=1.5|Mémoire entrelacée par décalage.]] En faisant cela, on remarque que les banques situées à N adresses d'intervalle sont différentes. Dans l'exemple du dessus, nous avons ajouté un décalage de 1 à chaque nouveau bloc à remplir. Mais on aurait tout aussi bien pu prendre un décalage de 2, 3, etc. Dans tous les cas, on obtient un entrelacement par décalage. Ce décalage est appelé le pas d'entrelacement, noté P. Le calcul de l'adresse à envoyer à la banque, ainsi que la banque à sélectionner se fait en utilisant les formules suivantes : * adresse à envoyer à la banque = adresse totale / N ; * numéro de la banque = (adresse + décalage) modulo N ; * décalage = (adresse totale * P) mod N. Avec cet entrelacement par décalage, on peut prouver que la bande passante maximale est atteinte si le nombre de banques est un nombre premier. Seulement, utiliser un nombre de banques premier peut créer des trous dans la mémoire, des mots mémoires inadressables. Malgré ce défaut, la technique a été utilisée sur quelques ordinateurs, avec l'exemple notable du superordinateur ''Burroughs Scientific Processor''. Pour éviter cela, il y a plusieurs solutions. Par exemple, on peut faire en sorte que N et la taille d'une banque soient premiers entre eux : ils ne doivent pas avoir de diviseur commun. Mais en pratique, elles n'ont pas vraiment été implémentées dans une vraie machine, et sont restées à l'état de recherche, aussi je les passe sous silence. ===L'entrelacement pseudo-aléatoire=== Une dernière méthode de répartition consiste à répartir les adresses dans les banques de manière "pseudo-aléatoire". L'idée exacte est de faire passer l'adresse dans une '''fonction de hachage''', plus ou moins complexe. Pour rappel, une fonction de hachage prend une entrée de grande taille et fournit en sortie un résultat de petite taille. Idéalement, elles doivent donner des résultats aussi différents que possible pour des entrées similaires, histoire de simuler une sorte de pseudo-aléatoire. Ici, une adresse est transformée en un numéro de banque. Une fonction de hachage simple ne fait que permuter des bits de l'adresse pour obtenir son résultat. Elle ne fait qu'échanger des bits de place avant de couper l'adresse en deux morceaux : un pour la sélection de la banque, et un autre pour la sélection de l'adresse dans la banque. Cette permutation est fixe, et ne change pas suivant l'adresse. Des fonctions de hachage plus complexes font des XOR bit à bit entre certains bits de l'adresse. ==Les contrôleurs mémoires SDRAM/DDR optimisés== Après avoir vu le cas des mémoires RAM, nous allons nous concentrer sur le cas particulier des mémoires SDRAM. Les mémoires SDRAM et DDR modernes sont capables de gérer plusieurs accès simultanés à la mémoire RAM, et leurs contrôleurs mémoire en font tout autant. C'est une différence majeure avec les mémoires asynchrones FPM/EDO, qui n'acceptaient qu'un seul accès mémoire à la fois. Leur contrôleur mémoire n'acceptait qu'un seul accès mémoire à la fois, c'était un contrôleur mémoire bloquant. Les contrôleurs mémoires des SDRAM sont eux non-bloquants et peuvent encaisser une dizaine d'accès mémoire à la fois. Peu de choses sont connues sur les contrôleurs de SDRAM/DDR modernes, les fabricants ne donnant que peu de détails dessus. Les rares simulateurs qui tentent de décrire leur fonctionnement, comme DRAM SIM I et II, sont particulièrement simples et ne vont pas dans le détail. Néanmoins, le peu qu'on sait est tout de même instructif. ===Rappel sur les SDRAM : tampon de ligne et commandes=== Les mémoires SDRAM sont des mémoires à tampon de ligne. Elles font un accès mémoire en deux étapes. La première étape recopie une ligne de N * 64 bits dans un tampon interne à la SDRAM. La seconde étape sélectionne une colonne, à savoir une donnée de 64 bits, qui est soit envoyée sur le bus de données pour une lecture, soit modifiée par une écriture. [[File:Mémoire à tampon de ligne.png|centre|vignette|upright=2|Mémoire à tampon de ligne]] Les accès mémoire sont traduits par un séquenceur mémoire en une série de commandes mémoires, qui sont séparées par des délais mémoire de quelques cycles d'horloge. Les délais sont très précis, et sont à respecter à la lettre. Une lecture ou une écriture se fait en maximum trois commandes : une commande PRECHARGE qui ferme la ligne précédemment utilisée, une commande ACT qui précise l'adresse de la ligne, et une commande READ ou WRITE qui précise l'adresse de la colonne et éventuellement la donnée à écrire. : Pour être plus précis, la commande PRECHARGE précharge les lignes de bits du plan mémoire à une tension particulière, ce qui les vide de leur contenu. Mais c'est un détail sans importance pour ce qui va suivre. Les SDRAM permettent de se passer de la première étape si des accès consécutifs se font dans la même ligne. Une fois activée, la ligne reste ouverte et on peut accéder plusieurs fois de suite dedans. On a alors juste à préciser l'adresse de colonne dedans. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | bgcolor="#FFA0FF" | PRECHARGE || bgcolor="#FFA0FF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || bgcolor="#A0FFFF" | READ (2) || bgcolor="#A0FFFF" | READ (3) || || || bgcolor="#A0FFFF" | READ (4) || bgcolor="#A0FFFF" | READ (5) || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | Donnée READ 1 || ||bgcolor="#A0FFFF" | Donnée READ 2 | bgcolor="#A0FFFF" | Donnée READ 3 || || || bgcolor="#A0FFFF" | Donnée READ 4 || bgcolor="#A0FFFF" | Donnée READ 5 |} Les SDRAM sont parfois capables de démarrer une commande avant que la précédente soit terminée. Mais le respect des délais mémoire est très limitant, ce qui fait qu'on ne peut pas parler de réel pipeline, comme c'est le cas sur les processeurs. Parlons plutot de '''commandes anticipées'''. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | || bgcolor="#A0FFFF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || || || bgcolor="#FFA0FF" | READ (2) || || || || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 | bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 | |} ===La mise en attente des accès mémoire=== Un contrôleur de SDRAM peut accepter plusieurs accès mémoire en même temps. Mais cela ne signifie pas que le contrôleur sera prêt à les traiter immédiatement. Pour éviter tout problème de disponibilité, le contrôleur met en attente les accès mémoire que le processeur lui envoie, pour les exécuter dès que possible. Les accès mémoire sont mis en attente dans une mémoire FIFO, histoire de les exécuter dans leur ordre d'arrivée, notamment pour renvoyer les lectures dans l'ordre demandé. Il y a aussi une mémoire FIFO pour les données à écrire et une FIFO pour les données lues. Cette dernière sert au cas où le cache ne soit pas disponible quand on lui envoie la donnée lue. [[File:Module d'interface avec la mémoire.png|centre|vignette|upright=2.5|Contrôleur mémoire avec mise en attente des requêtes processeur et autres optimisations.]] Des optimisations sont possibles dès la mémoire FIFO. Par exemple, on peut regrouper plusieurs accès à des données consécutives en un seul accès en rafale. Le contrôleur mémoire analyse les requêtes mises en attente et détecte si certaines se font à des adresses consécutives. Si c'est le cas, il fusionne ces requêtes en une lecture/écriture en rafale. Une telle optimisation est appelée la '''combinaison de lecture''' pour les lectures, et la '''combinaison d'écriture''' pour les écritures. ===Le pipeline des mémoires SDRAM=== Les commandes anticipées permettent de faire un minimum de parallélisme mémoire. Même si elle parait très limitée, cette possibilité est décuplée par la présence de plusieurs banques dans la mémoire SDRAM. Les chips mémoires de SDRAM sont composés de plusieurs banques, chacune étant une sorte de mini-mémoire miniature. Chaque banque a son propre décodeur, son propre tampon de ligne, ses propres multiplexeurs de colonne, sa logique de rafraichissement mémoire, etc. C'est comme si une SDRAM regroupait plusieurs mémoires séparées dans un même circuit intégré. Et cela permet de faire plusieurs accès mémoire en même temps, dans des banques différentes. Il est possible de lancer un accès mémoire dans deux banques en même temps, par exemple. Ou encore, on peut lancer un nouvel accès mémoire dans une banque si elle est inoccupée. La possibilité améliore grandement la performance de la SDRAM. Mais nous en reparlerons dans un chapitre ultérieur. {|class="wikitable" |+ Pipelining basique sur les SDRAM |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 |- ! Banque Numéro 1 | || || || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || || || |- ! Banque Numéro 2 | || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || |- ! Banque Numéro 3 | || || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || |} L'implémentation n'a rien de vraiment compliqué. Il suffit d'utiliser une mémoire FIFO par banque. Ainsi, les accès dans une même banque se feront en série, dans l'ordre d'arrivée, mais des accès à des banques différentes se feront dans le désordre. Cela ne pose pas de problème car les accès se font à des adresses différentes, ce qui fait qu'il n'y a pas de conflits majeurs. [[File:Gestion parallèle des banques.png|centre|vignette|upright=2.5|Gestion parallèle des banques.]] ===Le ré-ordonnancement des commandes mémoires=== Un contrôleur mémoire moderne est capable de changer l'ordre des requêtes mémoires, pour gagner en performances. En clair : il peut faire l'équivalent mémoire de l'exécution dans le désordre des processeurs haute performance. Une différence majeure est que le contrôleur mémoire ne remet pas les accès mémoire dans l'ordre avant de les envoyer au processeur. C'est le processeur qui remet en ordre les accès mémoire, dans sa ''load queue''. Pour cela, les requêtes mémoire sont "numérotées", à savoir qu'on leur attribue un identifiant binaire. Le contrôleur mémoire exécute les lectures/écritures dans le désordre, mais garde la trace de leur identifiant. Il sait dans quel ordre il exécute les accès mémoire et connait leur latence. Ce qui fait qu'il sait que la donnée lue à tel instant est associée à la requête mémoire numéro X. Pour les lectures, il renvoie la donnée lue avec l'identifiant. Le processeur sait alors à quelle lecture la donnée lue correspond et il se débrouille en interne pour gérer la situation. En clair : le processeur voit les accès mémoire dans le désordre, c'est lui qui les remet en ordre. Maintenant que ces précisions sont faites, posons cette question : quel est l'intérêt de faire les accès mémoire dans le désordre ? Accéder à des banques en parallèle est une réponse possible, mais le ré-ordonnancement est surtout utile si tous les accès mémoire atterrissent dans la même banque. L'exécution dans le désordre des lectures/écritures SDRAM vise à faire en sorte que des accès consécutifs se fassent dans une même ligne. Elle marche si plusieurs accès à une même ligne ne sont pas consécutifs, et qu'il y a des accès entre les deux. Après ré-ordonnancement, ces accès mémoire deviennent consécutifs, ils sont exécutés l'un à la suite de l'autre. Pour rendre le tout plus concret, voici un exemple. Imaginez que l'on sait les 3 accès mémoire suivants : * Une lecture ligne A ; * Une écriture ligne B ; * Une lecture ligne A. Sans réordonnancent, on doit émettre deux commandes PRECHARGE : une quand on passe de la ligne A à la ligne B, et une autre pour le passage inverse. pour éviter cela, le contrôleur mémoire peut retarder l'écriture, ou au contraire avancer la seconde lecture. le résultat est alors le suivant : * Lecture ligne A ; * Lecture ligne A ; * Une écriture ligne B. On a donc deux accès consécutifs à la même ligne, suivi par une écriture dans une autre ligne. La technique marche parce que l'on a un mix adéquat de lectures et d'écritures. Mais il existe des possibilités de réorganisation autres qui ne sont pas valides. Il faut par exemple prendre garde à éviter d'intervertir lectures et écritures à une même adresse, sous peine de problèmes. Encore une fois, il faut faire attention aux dépendances mémoires. Le controleur mémoire teste les dépendances mémoires avant d'envoyer des commandes à la mémoire DDR/SDRAM. ==L'entrelacement sur les mémoires SDRAM== L''''entrelacement''' fonctionne sur les mémoires SDRAM, mais il doit être fortement modifié. Concrètement, tout ce qui a étré dit plus est inapplicable pour les SDRAM, du fait de la présence du tampon de ligne. Des adresses consécutives atterrissent déjà dans la même ligne, ce qui fait qu'on n'a pas besoin d'entrelacement au niveau d'une ligne. Et ne parlons pas de ce la présence de rangées et de canaux mémoire ! Cependant, il peut y avoir un entrelacement lié à la présence des banques SDRAM. L'entrelacement est géré juste avant le séquenceur mémoire. Le '''circuit d'entrelacement''' intervertit certains bits de l'adresse lors des accès mémoires. On trouve aussi des mémoires FIFOs pour mettre en attente les requêtes mémoire provenant du processeur. Elles permettent de recevoir plusieurs accès mémoire consécutifs. Si vous vous demandez comment plusieurs accès mémoire consécutifs peuvent avoir lieu, sachez que c'est une bonne question. Pour le moment, nous allons dire que ces accès proviennent du processeur ou de la mémoire cache, sans expliquer comment. Le contrôleur mémoire reçoit une série d'accès mémoire assez rapprochés, qu'il doit exécuter au mieux. [[File:Controleur mémoire d'une SDRAM avec entrelacement.png|centre|vignette|upright=2.5|Contrôleur mémoire d'une SDRAM avec entrelacement.]] Pour gérer l'entrelacement, le contrôleur de SDRAM prend en entrée l'adresse envoyée par le processeur, et la découpe en plusieurs champs : un pour sélectionner la banque adéquate, un autre pour la ligne, un autre pour la colonne, un autre pour la rangée. Une solution simple désactive l'entrelacement. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de banque || Adresse de ligne || Adresse de colonne |} Si cette solution fonctionne bien si les accès mémoire sont assez aléatoires, en pratique, mieux vaut utiliser l'entrelacement. ===L'entrelacement de banques/lignes=== Une première méthode d'entrelacement est l''''entrelacement de banques'''. Elle répartit deux lignes consécutives dans deux banques différentes. Pour comprendre l'idée, prenons un exemple. Imaginons une mémoire avec deux banques et 4 lignes. Imaginons qu'on parcoure/balaye la mémoire RAM en partant des adresses basses. Sans entrelacement de ligne, les accès se feront comme suit, de gauche à droite : {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Banque numéro 1 !! colspan="4" | Banque numéro 2 |- | Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 || Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 |} Avec l'entrelacement de banques, les accès se feront comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 |- | Ligne 1 || Ligne 1 || Ligne 2 || Ligne 2 || Ligne 3 || Ligne 3 || Ligne 4 || Ligne 4 |} Avec N banques, la première ligne ira dans la première banque, la seconde ligne dans la seconde, et ainsi de suite jusqu'à la énième banque qui va dans la banque numéro N. Et la ligne N+1 repart dans la première banque. L'avantage est que passer d'une ligne à la suivante est assez rapide. Au lieu de devoir fermer la première ligne et ouvrir la suivante on ouvrira directement la ligne suivante dans une banque différente, ce qui sera plus rapide. La commande PRECHARGE pourra être envoyée à la seconde banque en avance. Pour cela, l'adresse est découpée comme suit. {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne (précise à l'octet) |} ===L'entrelacement de rangées=== Il est aussi possible de faire la même chose, mais avec les rangées. Pour simplifier fortement, une rangée est simplement un chip mémoire. En réalité, les rangées ne sont pas des chip mémoire, mais un ensemble de chips mémoire regroupés ensemble histoire d'atteindre les 64 bits du bus de données. Par exemple, une rangée peut combiner 8 chips mémoire avec un bus de données de 8 bits chacun pour obtenir les 64 bits du bus de données d'une SDRAM. Mais nous passons ce détail sous silence dans les explications qui vont suivre, par souci de simplification. Pour faire comprendre l'entrelacement de rangée, le mieux est d'utiliser un exemple, le même que précédemment. Sans entrelacement de rangée, on change de chip mémoire une fois qu'on a balayé toutes ses adresses. {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Rangée numéro 1 !! colspan="4" | Rangée numéro 2 |- | Banque 1 || Banque 2 || Banque 3 || Banque 4 || Banque 1 || Banque 2 || Banque 3 || Banque 4 |} Avec l'entrelacement de ligne, on change de rangée dès qu'on change de banque/ligne. {|class="wikitable" |+ Adresse mémoire |- ! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 |- | Banque 1 || Banque 1 || Banque 2 || Banque 2 || Banque 3 || Banque 3 || Banque 4 || Banque 4 |} En clair, quand on a épuisé toutes les banques dans une même rangée, on passe à la rangée suivante au lieu de rester dans la même rangée. Notons que dans l'exemple précédent, on a combiné l'entrelacement de rangée et de banque, mais on aurait pu utiliser l'entrelacement de rangée seul. Mais ce n'est pas le cas le plus courant en pratique. Toujours est-il qu'en combinant entrelacement de rangée et de ligne, le découpage de l'adresse est le suivant : {|class="wikitable" |+ Adresse mémoire |- | Adresse de ligne || Numéro de rangée || Adresse de banque || Adresse de colonne (précise à l'octet) |} D'autres méthodes d'entrelacement sont possibles, mais elles sont plus complexes. Chaque contrôleur mémoire fait un peu à sa sauce. Ils permettent en général de choisir entre plusieurs entrelacements. Au minimum, ils permettent de désactiver l'entrelacement, d'activer un entrelacement de ligne, et d'autres entrelacements plus complexes, dont certains sont propriétaires et tenus secrets par le fabricant. ===L'entrelacement avec le ''dual channel''=== [[File:Dual channel slots.jpg|vignette|Slots mémoires avec ''dual channel''.]] L'usage du ''dual channel'' complique encore l'entrelacement. Là encore, il y a deux grandes solutions : avec et sans entrelacement des canaux mémoire. Rappelons le principe : deux barrettes de RAM sont accédées en parallèle. Et pour cela, il faut utiliser l'entrelacement. Typiquement, chaque barrette mémoire fournit 64 bits, ce qui fait que l'on peut accéder à 128 bits d'un seul coup, par exemple avec un accès en rafale. Sans ''dual channel'', la première barrette correspond à la moitié haute de la RAM, la seconde barrette correspond à la moitié basse. Avec ''dual channel'', une forme spécifique d'entrelacement est activée. Concrétement, deux blocs de 64 bits sont placés dans des canaux mémoire séparés. Idem avec du triple ou quadruple canal, mais c'est alors trois ou quatre blocs de 64 bits qui sont dispersés dans des canaux consécutifs. Le découpage de l'adresse est alors le suivant : {|class="wikitable" |+ Adresse mémoire |- ! colspan="6" | Sans entrelacement |- | Numéro de canal/barrette || Reste de l'adresse || Position de l'octet dans un bloc de 64 bits |- | colspan="6" | |- ! colspan="6" | Avec entrelacement |- | Reste de l'adresse || Numéro de canal/barrette || Position de l'octet dans un bloc de 64 bits |} [[File:Décodage d'adresse avec dual channel.png|centre|vignette|upright=2|Décodage d'adresse avec dual channel.]] Les contrôleurs de SDRAM précédents sont assez basiques et ne représentent pas les contrôleurs les plus évolués. Ils étaient utilisés sur les anciens PC, à une époque où ils étaient encore sur la carte mère du processeur. Mais de nos jours, le contrôleur mémoire est intégré au processeur. Et il incorpore de nombreuses optimisations afin de gagner en performances. Malheureusement, ces optimisations sont elles-mêmes dépendantes des optimisations intégrées dans le processeur, ce qui fait qu'on ne peut pas en parler ici. Elles demandent que le contrôleur mémoire reçoive plusieurs accès mémoire simultanés, ce qui n'a aucun sens à ce stade du cours. Aussi, nous allons les passer sous silence pour le moment. Nous les verrons dans le chapitre sur le parallélisme mémoire. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=La désambiguïsation mémoire | prevText=La désambiguïsation mémoire | next=Les processeurs superscalaires | nextText=Les processeurs superscalaires }} </noinclude> g37jkwqt593fs5hc3lx1mfpkmex7epg 764716 764715 2026-04-23T21:16:33Z Mewtow 31375 /* Le pipeline des mémoires SDRAM */ 764716 wikitext text/x-wiki Dans ce chapitre, nous allons voir les techniques qui permettent de gérer plusieurs accès mémoire simultanés directement au niveau du cache ou de la mémoire RAM. Par plusieurs accès mémoire simultanés, vous pensez sans doute à l'usage de cache multiports, voire de mémoires RAM multiport. C'est en effet une solution, mais ce n'est clairement pas la seule. Par exemple, il est possible de pipeliner l'accès au cache, voire à la mémoire RAM. Il existe de nombreuses techniques de '''parallélisme mémoire''', et nous allons les voir dans ce chapitre. Pour donner un exemple, il est possible de pipeliner les accès mémoire. Il est possible de pipeliner l'accès au cache et/ou l'accès à la mémoire, et nous verrons comment faire dans ce chapitre. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, qu'ils aient ou non un pipeline dynamique. Par contre, pipeliner la mémoire est une technique ancienne, aujourd'hui peu utilisée. Elle était utilisée sur des très vieux ordinateurs, qui avaient un pipeline mais pas de mémoire cache. Mais de nos jours, les mémoires SDRAM et DDR ne sont pas adaptées pour ça, il est très difficile de les pipeliner correctement. Le parallélisme mémoire est utile aussi bien sur des processeurs à émission dans l'ordre que dans le désordre. Par exemple, un processeur ''in-order'' avec des lectures non-bloquantes peut e, profiter. Il peut techniquement lancer une seconde lecture, après avoir rencontré une première lecture non-bloquante. En général, le nombre de lectures consécutives est cependant limité à moins d'une dizaine. À l'opposé, les processeurs sans lectures non-bloquantes profitent pas du parallélisme mémoire. À la rigueur, ils peuvent en profiter s'ils intègrent des techniques de préchargement. Pendant que le processeur exécute une lecture/écriture, il peut précharger une autre donnée en parallèle. Inutile de dire que sans pipeline processeur, le parallélisme mémoire ne sert pas à grand-chose. ==Le ''line fill buffer'' et les techniques associées== Lors d'un défaut de cache, le processeur doit attendre que toute la ligne de cache soit chargée avant d'être utilisable. Or, la taille d'une ligne de cache est supérieure à la largeur du bus mémoire, ce qui fait qu'une ligne de cache est chargée en plusieurs fois, morceaux par morceaux, mot mémoire par mot mémoire. Le chargement peut se faire directement dans le cache, mais ce n'est pas une solution très pratique. À la place, beaucoup de processeurs ajoutent une mémoire tampon entre la RAM et le cache, appelée le '''tampon de remplissage de ligne''' (''line-fill buffer'') dans les processeurs modernes. Lors d'un défaut de cache, le processeur charge la donnée de la RAM dans le tampon de remplissage de ligne, mot mémoire par mot mémoire. Une fois plein, le tampon de remplissage de ligne est recopié dans la ligne de cache. Le tampon de remplissage de ligne contient une ligne de cache, si ce n'est que certains bits de contrôle ne sont pas présents. Le tampon de remplissage de ligne contient un bit de validité pour chaque mot mémoire de la ligne de cache, qui indiquent si le mot mémoire a été chargé. Par exemple, prenons un processeur 64 bits, qui gère donc des mots mémoire de 8 octets, avec des lignes de cache 256 octets/32 mots mémoire. Le tampon de remplissage de ligne contiendra 32 bits de validité. Si le processeur a chargé les 6 premiers mots mémoire, les 6 premiers bits de validité seront mis à 1, les autres seront encore à zéro. Il faut noter que la disponibilité de la ligne complète se détermine assez facilement en faisant un ET logique entre tous les bits de validité. Le processeur sait ainsi quand la ligne de cache est disponible entièrement et donc quand la transférer dans le cache. La même chose existe avec une hiérarchie de cache, sauf que l'on trouve une mémoire tampon entre chaque niveau de cache. Il y a un tampon de remplissage de ligne entre le cache L1 et le cache L2, entre le cache L2 et le L3, etc. Si le cache ne gère qu'un seul défaut de cache à la fois, le ''fill line buffer'' est une mémoire très simple, qui ne mémorise qu'une seule ligne de cache. Et cela vaut aussi bien pour un cache bloquant que non-bloquant. Mais sur les caches capables de gérer plusieurs défauts simultanés, le tampon de remplissage de ligne est une mémoire de type FIFO ou LIFO, capable de mémoriser plusieurs lignes de cache. Notons que le tampon de remplissage de ligne est très utile pour implémenter certaines techniques, par exemple le contournement du cache. Nous avions vu dans le chapitre sur le cache que certains accès mémoire doivent contourner le cache, pour des raisons de cohérence des caches. C'est notamment nécessaire pour accéder aux périphériques, mais c'est aussi utile pour des raisons de performances dans des cas très spécifiques. Les accès qui contournent le cache se font directement dans le tampon de remplissage de ligne : le processeur écrit ou lit les données depuis ce tampon de remplissage de ligne, sans accéder au cache. ===L'''early restart'' et le ''critical word load''=== La présence du ''line buffer'' permet une optimisation assez intéressante, qui permet de réduire la latence des défauts de cache. L'optimisation consiste à lire un mot mémoire dans le tampon de remplissage de ligne, même si la ligne de cache complète n'a pas encore été chargée. Il existe deux manières de faire cela, qui portent les noms d'''early restart'' et de ''critical word load''. La première est la version la plus simple, la seconde est plus complexe mais plus performante. Avec la technique d''''''early restart''''', la ligne de cache est chargée normalement, en partant de son premier mot mémoire. Dès que le mot mémoire lu/écrit par le processeur est copié dans le ''line fill buffer'', il est envoyé au processeur immédiatement. Illustrons le tout par un exemple, où une ligne de cache fait 16 mots mémoire. Le processeur effectue une lecture, qui lit le 5ème mot mémoire. Sans ''early restart'', le processeur doit charger les 16 mots mémoire avant de faire la lecture dans le cache. Avec ''early restart'', le processeur reçoit la donnée dès que le 5ème mot mémoire est disponible. Le processeur doit attendre que les 4 premiers mots mémoire soient chargés, puis le 5ème mot mémoire arrive et est envoyé directement au processeur, il est lu directement depuis le ''line fill buffer''. Les 11 mots mémoire suivants sont ensuite chargés dans le cache pendant que le processeur fait des calculs dans son coin. Le ''critical word load'' est une optimisation de la technique précédente où le chargement de la ligne de cache commence directement à la donnée demandée par le processeur. Pour reprendre l'exemple précédent, où le processeur demande le 5ème mot mémoire sur 16, le ''critical word load'' charge le 5ème mot mémoire en premier et l'envoie au processeur, ce qui fait qu'il est chargé très rapidement. Pas besoin d'attendre que le processeur charge les 4 mots mémoire précédents comme avec l'''early restart''. Dans le détail, le processeur charge le 5ème mot mémoire en premier, puis charge les 11 suivants, et termine par les 4 mots mémoire du début. En clair, le ''critical word load'' commence par charger le mot mémoire lu, puis les blocs suivants, avant de revenir au début du bloc pour charger les blocs restants. Ainsi, la donnée demandée par le processeur sera la première disponible. Pour cela, l'organisation du tampon de remplissage de ligne est modifiée de manière à rendre cela possible. Il a une taille égale à une ligne de cache complète, qui contient elle-même plusieurs mots mémoire. Dans le ''line-fill buffer'', chaque mot mémoire est stocké avec un tag, qui indique l'adresse du mot mémoire stocké dans le ''line-fill buffer''. Le ''line-fill buffer'' est donc un cache un peu particulier, qui fonctionne comme un cache du point de vue du processeur, comme une mémoire FIFO pour les transferts avec le cache. Ainsi, un processeur qui veut lire dans le cache après un défaut peut accéder à la donnée directement depuis le tampon de remplissage de ligne, alors que la ligne de cache n'a pas encore été totalement recopiée en mémoire. ==Les caches pipelinés== Il est possible de pipeliner l'accès au cache, ce qui demande juste de rajouter des registres au bon endroits et quelques circuits de contrôle. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, même ceux avec un pipeline dynamique. Et l'implémentation peut se faire de plusieurs manières différentes. Il faut dire que découper une mémoire cache en plusieurs étapes peut se faire de plusieurs manière. Commençons par la plus simple. Beaucoup de processeurs des années 2000 avaient une fréquence peu élevée comparée aux standards d'aujourd'hui, ce qui fait que leur cache avait bien un temps d'accès d'un cycle d'horloge. Mais l'accès au cache se faisait en deux cycles d'horloge : un cycle pour calculer une adresse et un cycle pour l'accès mémoire proprement dit. Et ces deux étapes étaient pipelinées, à savoir que deux micro-opérations mémoire peuvent s'exécuter en même temps : une dans l'étage de calcul d'adresse, une autre dans le cache. L'unité mémoire était donc pipelinée, alors que l'accès au cache ne l'était pas. Les processeurs qui implémentaient cette technique regroupent les micro-architectures K5 et K6 d'AMD, les processeurs Intel de micro-architecture P6 (Pentium 2 et 3) et quelques autres. Il est aussi possible de pipeliner l'accès au cache lui-même. Avec un cache pipeliné, l'accès au cache ne se fait pas en un seul cycle, mais en plusieurs. Cependant, on peut lancer un nouvel accès au cache à chaque cycle d'horloge, comme un processeur pipeliné lance une nouvelle instruction à chaque cycle. L'implémentation est assez simple : il suffit d'ajouter des registres dans le cache. Pour cela, on profite que le cache est composé de plusieurs composants séparés, qui échangent des données dans un ordre bien précis, d'un composant à un autre. De plus, le trajet des informations dans un cache est linéaire, ce qui les rend parfois pour l'usage d'un pipeline. ===Le ''pipelining'' des caches ''direct-mapped''=== Voici ce qui se passe avec un cache directement adressé. Pour rappel ce genre de cache est conçu en combinant une mémoire RAM, généralement une SRAM, avec quelques circuits de comparaison et un MUX. L'accès au cache se fait globalement en deux étapes : la première lit la donnée et le ''tag'' dans la SRAM, et on utilise les deux dans une seconde étape. Nous avions vu il y a quelques chapitre comment pipeliner des mémoires, dans le chapitre sur les mémoires évoluées. Et bien ces méthodes peuvent s'utiliser pour la mémoire RAM interne au cache !L'idée est d'insérer un registre entre la sortie de la RAM et la suite du cache, pour en faire un cache pipéliné, à deux étages. Le premier étage lit dans la SRAM, le second fait le reste. L'implémentation sur les caches associatifs à plusieurs voies est globalement la même à quelques détails près. [[File:Cache directement adressé pipeliné - deux étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - deux étages]] Avec le circuit précédent, il est possible d'aller plus loin, cette fois en pipelinant l'accès à la mémoire RAM interne au cache. Pour comprendre comment, rappelons qu'une mémoire SRAM est composée d'un plan mémoire et d'un décodeur. L'accès à la mémoire demande d'abord que le décodeur fasse son travail pour sélectionner la case mémoire adéquate, puis ensuite la lecture ou écriture a lieu dans cette case mémoire. L'accès se fait donc en deux étapes successives séparées, on a juste à mettre un registre entre les deux. Il suffit donc de mettre un gros registre entre le décodeur et le plan mémoire. [[File:Cache directement adressé pipeliné - trois étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - trois étages]] Et on peut aller encore plus loin en découpant le décodeur en deux circuits séparés. En effet, rappelez-vous le chapitre sur les circuits de sélection : nous avions vu qu'il est possible de créer des décodeurs en assemblant des décodeurs plus petits, contrôlés par un circuit de prédécodage. Et bien on peut encore une ajouter un registre entre ce circuit de prédécodage et les petits décodeurs. : Théoriquement, toute l'adresse est fournie d'un seul coup au cache, la quasi-totalité des processeurs présentent l'adresse complète à un cache pipeliné. Mais le Pentium 4 fait autrement. Il faut noter que les premiers étages manipulent l'indice dans la SRAM, qui est dans les bits de poids faible, alors que les étages ultérieurs manipulent le ''tag'' qui est dans les bits de poids fort. Les concepteurs du Pentium 4 ont alors décidé de présenter les bits de poids faible lors du premier cycle d'accès au cache, puis ceux de poids fort au second cycle. Pour cela, l'ALU fonctionnait à une fréquence double de celle du processeur, tout comme le cache L1. Il n'y avait pas de pipeline proprement dit, mais cela réduisait grandement la latence d'accès au cache. ===Le ''pipelining'' des caches splittés/sériels=== Il est aussi possible de pipeliner un cache dits splittés, aussi appelés à accès sériel. Pour rappel, les caches à accès sériel vérifient si il y a succès ou défaut de cache, avant d'accéder aux lignes de cache en cas de succès. Ils font donc différemment des autres caches, qui accèdent à une ligne de cache, avant de déterminer s'il y a succès ou défaut en lisant le tag de la ligne de cache. Les caches sériels disposent de deux SRAM : une pour les tags des lignes de cache et une pour les données. Ils accèdent à la SRAM pour les tags, avant d’accéder à la SRAM des données en cas de succès de cache. Vu que l'accès se fait en deux étapes, une vérification des tags suivie de la lecture/écriture des données, il est facile à pipeliner. Pipeliner le cache permet de régler le problème des accès au cache L1, et elle est tout le temps utilisé sur les processeurs modernes. Mais que faire en cas de défaut de cache ? ==Les caches non bloquants== Un '''cache bloquant''' est un cache auquel le processeur ne peut pas accéder pendant un défaut de cache. Il faut attendre que la lecture ou écriture en RAM soit terminée avant de pouvoir utiliser de nouveau le cache. Un '''cache non bloquant''' n'a pas ce problème : on peut l'utiliser pendant un défaut de cache. Les caches non bloquants permettent de démarrer une nouvelle lecture ou écriture alors qu'une autre est en cours, ce qui permet d'exécuter plusieurs lectures ou écritures en même temps. ===Les ''Miss Handling Status Registers''=== Lors d'un défaut de cache, la mémoire RAM est consultée pendant le défaut de cache, mais le cache est inutilisé. Un défaut de cache n'utilise pas le cache, ce n'est qu'un accès en mémoire RAM, sur le bus mémoire (ou un accès aux niveaux de cache inférieurs, peu importe). Le cache en lui-même est laissé libre, rien n’empêche d'y accéder, il est en réalité intrinsèquement non-bloquant. Les caches, bloquants comme non-bloquants, sont en réalité composés d'une mémoire cache proprement dite, entourée de circuits qui servent d'interface entre le processeur et le cache lui-même. Et parmi les circuits tout autour, certains gèrent l'accès au cache lors d'un défaut de cache. Ils sont regroupés sous le terme de '''''Miss Handling Architecture''''' (MHA). La différence entre un cache bloquant et un cache non-bloquant est en réalité liée à l'implémentation de la MHA. Les caches bloquants coupent volontairement l'accès au cache lors d'un défaut, car les défauts sont plus simples à gérer ainsi. Le défaut de cache rapatrie une donnée depuis la RAM, qui sera écrite dans le cache. Et il ne faut pas qu'une tentative d'accès à cette donnée ait lieu avant qu'elle ne soit chargée. Mais ce blocage est général et touche tout le cache, alors que seule une ligne de cache est concernée par le défaut de cache. L'idée derrière un cache non-bloquant est que seule la ligne de cache est bloquée, mais les autres sont accessibles. L'idée est alors de mémoriser les lignes de cache concernées par le défaut de cache, afin d'en bloquer l'accès. À chaque accès, on vérifie si la ligne de cache est déjà réservée par un défaut de cache. La lecture/écriture est alors bloquée si c'est le cas, mais elle accepte les accès sinon. Pour cela, la ''Miss Handling Architecture'' contient des registres qui mémorisent des informations sur les défauts de cache en cours. Ils portent le nom de '''''miss status handling registers''''', que j’appellerais dorénavant MSHR, qui sont aussi appelés des '''''miss buffer'''''. Le contenu des MSHR varie beaucoup suivant le processeur, mais ces derniers stockent au minimum les informations suivantes : * Le numéro de la ligne de cache dans laquelle les données sont chargées. * Un bit de validité qui indique si le MSHR est vide ou pas, qui est mis à 0 quand le défaut de cache est résolu. * Un ou plusieurs champs de lecture/écriture, qui contiennent des informations sur la lecture/écriture. ** Pour une lecture, elle contient des informations sur la destination de la donnée, à savoir qui prévenir quand le défaut de cache est terminé. C'est parfois un nom/numéro de registre (celui dans lequel charger la donnée), mais c'est souvent le numéro de l'entrée dans le ''load/store queue''. ** Pour les écritures, elle contient la donnée à écrire, ou éventuellement un numéro de ''load/store queue'' où se trouve la donnée à écrire. Il faut noter que le bus mémoire ne peut gérer qu'un seul défaut de cache à la fois. Aussi, il est intéressant de regarder ce qui se passe lorsqu'un second défaut de cache survient, pendant qu'un premier est en cours. Dans ce cas, il y a deux réponses qui correspondent à deux types de caches non-bloquants, qui portent les noms barbares de caches de type succès après défaut et défaut après défaut. Sur le premier type, il ne peut pas y avoir plusieurs défauts de cache simultanés. Dès qu'un second défaut de cache survient, le cache stoppe son activité et on ne peut plus démarrer de nouvelle lecture/écriture, tant que le premier défaut de cache n'est pas résolu. Le second type est plus souple et autorise la survenue de plusieurs défauts de cache simultanés. Du moins, jusqu'à une certaine limite, car le cache ne peut supporter qu'un nombre limité d'accès mémoires simultanés (pipelinés). ===Les accès simultanés à une même ligne de cache=== Il arrive que le processeur fasse plusieurs accès mémoire simultanés à la même ligne de cache. Si la ligne de cache en question n'a pas encore été chargée dans le cache, alors on a plusieurs défauts de cache consécutifs pour la même ligne de cache, et le cache non-bloquant doit gérer la situation. Pour la suite, il va falloir faire une petite distinction entre les défauts primaires et secondaires. Imaginons qu'un défaut de cache ait lieu et demande à charger une donnée dans la ligne de cache numéro N. Il s'agit du premier défaut impliquant cette ligne de cache précise, ce qui lui vaut le nom de '''défaut de cache primaire'''. Mais par la suite, d'autres accès mémoire à la même ligne de cache ont lieu, alors que la ligne de cache n'est pas encore disponible. Dans ce cas, il s'agit de '''défauts de cache secondaires'''. Pour l'unité d'accès mémoire, les défauts de cache primaire et secondaire sont différents (ils prennent tous une entrée dans la ''load/store queue''). Mais pour le cache, ils ne correspondent qu'à un seul accès au cache : celui qui demande de charger la ligne de cache demandée. Les défauts de cache primaire et secondaire à la même ligne de cache se voient attribuer un MSHR unique. La gestion des défauts de cache secondaires dépend du cache non-bloquant. La solution la plus simple ne permet pas les défauts de cache secondaires. Le cache ne permet pas deux défauts de cache simultanés pour la même ligne de cache. Les autres solutions le permettent, en fusionnant des accès simultanés à la même ligne de cache en un seul au niveau des MSHR. Dans tous les cas, détecter les défauts de cache secondaires sont un problème qu'il faut détecter. La ''Miss Handling Architecture'' doit détecter les défauts de cache secondaire. Pour cela, elle procède comme suit. Lors de chaque défaut de cache, la MHA récupère le numéro de la ligne de cache associé. Il vérifie alors chaque MSHR pour vérifier s'il contient le numéro en question. S'il n'y a aucune correspondance dans les MSHR, alors c'est signe que le défaut de cache est un défaut primaire. Mais s'il y en a une, alors c'est un défaut secondaire. Évidemment, cela signifie que lors d'un défaut de cache, le numéro de ligne de cache est envoyé à tous les MSHR, pour comparaison. Les MSHR sont donc regroupés dans une mémoire associative, une sorte de mini-cache, faciliter l'implémentation. Lors d'un défaut de cache primaire, l'accès à un cache non-bloquant se fait comme suit : le processeur envoie une adresse au cache, accède à celui-ci, et détecte la survenue d'un défaut de cache. Il en profite alors pour attribuer une ligne de cache dans laquelle sera chargée ce défaut. L'attribution est très simple dans le cas des caches ''direct mapped'', ou associatifs par voie, pour lesquels l'attribution se fait assez simplement. Il mémorise alors cette information dans les MSHR, après avoir vérifié que le défaut de cache n'était pas un défaut secondaire. Lors des accès ultérieurs à une adresse proche, censée être dans la même ligne de cache, le processeur va encore une fois rencontrer un défaut de cache. Il va alors déterminer le numéro de la ligne de cache associée à l'adresse, et comparer ce numéro avec les MSHRs. Si un MSHR contient ce numéro, c'est signe que le défaut de cache est un défaut secondaire. La MHA réagit alors différemment selon le processeur considéré. Une première solution n'autorise pas les défauts de cache secondaires. Si l'un d'entre eux survient, le processeur est gelé par un ''pipeline stall'', une bulle de pipeline. Une autre solution fusionne les défauts de cache secondaires avec le défaut de cache primaire : tout cela ne correspond qu'à un seul défaut de cache pour lui. ===Les MSHR simples=== Un cache non-bloquant à '''MSHR simple''' contient juste plusieurs MSHR qui mémorisent juste un numéro de ligne de cache, un bit de validité, le champ de lecture/écriture, et l'adresse exacte de lecture/écriture. Avec cette organisation, il est possible d'avoir plusieurs défauts de cache séparés, mais à la condition que chaque défaut accède à une ligne de cache différente. Deux accès simultanés à une même ligne de cache ne sont pas possibles, les défauts de cache secondaires ne sont pas autorisés. Ainsi, chaque défaut de cache se voit attribuer son propre MSHR, chacun contient un numéro de ligne de cache différent. Pour comprendre pourquoi c'est impossible de gérer les défauts secondaires, il faut regarder le champ de lecture/écriture. Si on veut effectuer plusieurs écritures consécutives à la même adresse, le MSHR n'aura pas de quoi mémoriser les deux données à écrire. Il pourra mémoriser la première donnée à écrire, pas la seconde. Même chose lors d'une lecture : le champ lecture/écriture peut mémorisr la destination de la première lecture, pas de la seconde. L'avantage est que la MHA n'a besoin que des MSHR et de quelques circuits annexes. Les autres solutions rajoutent des circuits annexes pour gérer les défauts de cache secondaires, qui utilisent beaucoup de circuits. Le cout en circuit est donc élevé, mais le gain en performance est là. Passons maintenant aux caches non-bloquants qui autorisent les défauts de cache secondaire. La solution la plus simple consiste à utiliser ===Les MSHR adressés implicitement=== Avec les '''MSHR adressés implicitement''', il est possible de fusionner plusieurs accès mémoire à une même ligne de cache, mais sous une condition très importante : ces accès lisent/écrivent des mots mémoire différents. Par exemple, imaginons qu'une ligne de cache contienne 8 mots mémoire de 64 bits. Si un premier accès mémoire lit le mot mémoire numéro 7 (dernier mot mémoire de la ligne), et le second accès le mot mémoire numéro 3, alors la fusion est possible. Mais si deux accès mémoire veulent lire/écrire le mot mémoire numéro 7, alors la fusion n'est pas possible et le processeur se bloque, un ''pipeline stall'' survient. Un MSHR adressé implicitement est un MSHR simple, qui contient naturellement un numéro de ligne de cache (tag) et un bit de validité, sauf que le champ de lecture/écriture est dupliqué. Les différents champs lecture/écriture d'un MSHR sont regroupés dans une mémoire RAM/cache qui contient autant d'entrées qu'il y a de mot mémoire dans une ligne de cache. Chaque entrée de la table est associée à un mot mémoire de la ligne de cache et stocke des informations sur celui-ci. Une entrée mémorise, au minimum : * Un champ de lecture/écriture qui contient soit la destination de la lecture, soit la donnée à écrire. * Un bit R/W permettant d'interpréter correctement le champ de lecture/écriture. * Un bit de validité pour chaque entrée de la table, qui dit si un défaut de cache antérieur accède déjà à ce mot mémoire. La fusion de deux défauts de cache est ainsi assez simple. Un défaut de cache primaire/secondaire configure l'entrée associée au mot mémoire lu/écrit. Si un défaut secondaire ultérieur lit/écrit un mot mémoire différent (dont le bit de validité de l'entrée dans la table est à zéro), pas de conflit : il configure une autre entrée, vide. Mais s'il lit/écrit un mot mémoire pour lequel l'entrée de la table est occupée, il y a conflit, le processeur est bloqué par un ''pipeline stall'', une bulle de pipeline. Avec cette organisation, le nombre de MSHR indique combien de lignes de cache peuvent être lues en même temps depuis la mémoire. Quant au nombre d'entrées par MSHR, il détermine combien d'accès mémoires qui ne se recouvrent pas peuvent avoir lieu en même temps. ===Les MSHR adressés explicitement=== Les '''MSHR adressés explicitement''' n'ont pas les contraintes des MSHR adressés implicitement. Avec eux, il est possible d'avoir plusieurs défauts de cache pointant vers la même ligne de cache, mais aussi vers le même mot mémoire. De tels défauts de cache apparaissent sur les processeurs à exécution dans le désordre, mais sont très rares, voire inexistants, sur les processeurs ''in-order''. L'idée est encore que chaque MSHR est associée à une table mémoire qui mémorise des entrées. Cette table n'est autre qu'une mémoire FIFO. Sauf que cette fois-ci, une entrée n'est pas associée à un mot mémoire. Une entrée est associée à un défaut de cache. Une entrée mémorise là encore la destination de la lecture ou la donnée de l'écriture suivi du bit R/W, ainsi que d'un bit de validité par entrée, mais aussi : la position du mot mémoire lu dans la ligne de cache. C'est cette dernière information qui n'était pas présente dans les MSHR adressés implicitement. De plus, le nombre d'entrée par MSHR n'est pas égal au nombre de mots mémoires dans une ligne de cache, mais peut être arbitrairement grand. L'avantage d'un tel cache est qu'il est capable de traité les défauts secondaires concernant un même mot mémoire, et ce, car les accès à la ligne de cache concernée se produisent dans l'ordre de génération des défauts (la table mémoire est une FIFO). ===Les MSHRs inversés=== Généralement, plus on veut supporter de défauts de cache, plus le nombre de MSHR et d'entrées augmente. Mais au-delà d'un certain nombre d'entrées et de MSHR, les MSHR adressés implicitement et explicitement ont tendance à bouffer un peu trop de circuits. Utiliser une organisation un peu moins gourmande en circuits est donc une nécessité. Cette organisation plus économe se base sur des MSHR inversés. Les MSHR inversés ne contiennent qu'une seule entrée, en quelque sorte : au lieu d’utiliser n MSHR de m entrées chacun, on va utiliser n × m MSHR inversés. La différence, c'est que plusieurs MSHR peuvent contenir un tag identique, contrairement aux MSHR adressés implicitement et explicitement. Lorsqu'un défaut de cache a lieu, chaque MSHR est vérifié. Si jamais aucun MSHR ne contient de tag identique à celui utilisé par le défaut, un MSHR vide est choisi pour stocker ce défaut, et une requête de lecture en mémoire est lancée. Dans le cas contraire, un MSHR est réservé au défaut de cache, mais la requête n'est pas lancée. Quand la donnée est disponible, les MSHR correspondant à la ligne qui vient d'être chargée vont être utilisés un par un pour résoudre les défauts de cache en attente. ===Les MSHR intégrés au cache=== Certains chercheurs ont remarqué que pendant qu'une ligne de cache est en train de subir un défaut de cache, celle-ci reste inutilisée, et son contenu est destiné à être perdu une fois le défaut de cache résolu. Ils se sont dit que, plutôt que d'utiliser des MSHR séparés, il vaudrait mieux utiliser la ligne de cache pour stocker les informations sur les défauts de cache en attente dans cette ligne de cache. Pour éviter tout problème, il faut rajouter un bit dans les bits de contrôle de la ligne de cache, qui sert à indiquer que la ligne de cache est occupée : un défaut de cache a eu lieu dans cette ligne, et elle stocke donc des informations pour résoudre les défauts de cache. ==L'entrelacement mémoire== Nous venons de voir comment une mémoire cache peut gérer plusieurs accès mémoire simultanés. Il se trouve que ce genre d'optimisation dépasse largement le cadre du cache. Les mémoires RAM ne sont pas en reste, elles aussi ! Il est possible de pipeliner l'accès à une mémoire RAM, en utilisant des techniques dites d''''entrelacement'''. Précisons cependant que cette optimisation n'est pas utilisée sur les mémoires SDRAM ou DDR modernes, du moins pas sans modifications majeures. Mais divers ordinateurs assez anciens, dont des superordinateurs, ont utilisé cette technique. La technique demande d'utiliser plusieurs mémoires séparées. Sur les anciens ordinateurs, les mémoires en question sont des chips mémoire, à savoir des circuits intégrés de DRAM. Pour faire plus général, nous allons utiliser le terme de '''banques''', ou encore de bancs mémoire. La différence est qu'il existe des mémoires multi-banque, qui regroupent plusieurs banques indépendantes dans un seul boitier, dans un seul chip mémoire. En clair, de telles mémoires regroupent plusieurs sous-mémoires dans un seul circuit intégré. Il est aussi possible d'utiliser plusieurs chips mémoire séparés, chacun ayant une banque. Reste à combiner ces banques pour former un pseudo-pipeline. ===Utiliser plusieurs banques sans entrelacement=== Sans optimisation particulière, les adresses sont réparties dans les banques comme indiqué ci-dessous. Il s'agit de ce qui s'appelle un arrangement horizontal, et nous avions vu celui-ci dans le chapiutre sur les mémoires SDRAM. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Avec cet arrangement, les bits de poids fort de l'adresse sont utilisées pour sélectionner la banque adéquate, et le reste de l'adresse est envoyé sur le bus d'adresse. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Adresse de banque !! Adresse dans la banque |- | Quelques bits de poids fort || Reste de l'adresse |} L'entrelacement change cette répartition, afin d'accélérer les accès mémoire. L'entrelacement classique vise à améliorer les accès à des adresses mémoires consécutives, mais des formes plus évoluées d'entrelacement visent à optimiser des accès mémoire arbitraires. Voyons-les dans l'ordre, du plus simple au plus complexe. ===L'entrelacement classique=== Avec l''''entrelacement classique''', des adresses consécutives sont réparties dans des banques consécutives. Précisons que cette attribution des adresses n'implique absolument pas la mémoire virtuelle ou n'importe quel mécanisme dans le processeur. La répartition décide que telle adresse mémoire va dans telle banque, à telle adresse dans la banque. Elle est donc le fait du contrôleur mémoire, donc en dehors du processeur (les contrôleurs mémoires n’étaient pas encore intégrés dans le processeur à l'époque). [[File:Répartition des adresses dans une mémoire interleaved.png|centre|vignette|upright=1.5|Répartition des adresses dans une mémoire interleaved.]] L'entrelacement simple permet d'accélérer les accès mémoire qui se font à des adresses consécutives. Avec l'entrelacement, chaque accès mémoire tombe dans une banque mémoire différente, ce qui fait qu'on peut démarrer un nouvel accès mémoire à chaque cycle d'horloge. Pendant qu'une banque est occupée par un accès mémoire, on démarre le suivant dans une autre banque, et ainsi de suite. Le tout se termine soit quand on a épuisé toutes les banques libres, soit quand l'accès en rafales se termine. Pas besoin d'attendre que la mémoire ait fini sa lecture/écriture avant de démarrer la lecture/écriture suivante. [[File:Pipemining mémoire.png|centre|vignette|upright=2.5|Pipelining mémoire]] L'animation suivante illustre bien le processus, avec quatres banques. [[File:Interleaving.gif|centre|vignette|upright=2.5|Pipelining mémoire]] L'entrelacement simple utilise les bits de poids faible pour sélectionner la banque, et les bits de poids fort pour la case mémoire. [[File:Adresse mémoire d'une mémoire entrelacée.png|centre|vignette|upright=2|Adresse mémoire d'une mémoire entrelacée]] Précisons que le temps d'accès mémoire ne change pas beaucoup avec l'entrelacement. Par contre, on peut faire plus d'accès mémoire simultanés. On peut démarrer un accès mémoire par cycle d'horloge, mais l'accès en lui-même prend plusieurs cycles. Les mémoires à entrelacement ont donc un débit supérieur aux mémoires qui ne l'utilisent pas. L'entrelacement classique pourrait être utilisé pour accélérer les transferts entre mémoire RAM et cache, qui se font en blocs d'adresses consécutives. Mais de nos jours, il n'est pas utilisé dans cette optique. Il faut dire qu'il est redondant avec le mode rafale des mémoires DRAM. Les deux font globalement la même chose et on voit mal comment utiliser les deux en même temps. Le mode rafale étant plus simple à implémenter, et plus léger niveau utilisation du bus de commande mémoire, il est préféré à l'entrelacement. Par contre, l'entrelacement a eu son heure de gloire sur d'anciens processeurs qui n'avaient pas de mémoire cache, alors qu'ils disposaient d'un pipeline. L'exemple typique est celui des nombreux ''processeurs vectoriels'', que nous n'avons pas encore abordé à ce stade du cours. De tels processeurs avaient un pipeline, une unité de calcul fortement pipelinée, et parfois même de l'exécution dans le désordre et du renommage de registres ! ===L'entrelacement par décalage=== L'entrelacement simple est très utile pour les accès en rafle ou équivalents. Par contre, il existe de rares situations où il n'est pas optimal. Une de ces situations est celle des accès en enjambée (en ''stride''), où l'on accède en série à des adresses sont séparées par N mots mémoires. [[File:Accès par enjambées.png|centre|vignette|upright=2|Accès par enjambées.]] De tels accès surviennent quand un logiciel accède à des structures de données spécifiques, à savoir des tableaux de structures, des matrices, ou d'autres structures de données dans le genre. Un cas classique est celui du parcours d'une matrice colonne par colonne. Les matrices de nombres sont mémorisées ligne par ligne, ce qui fait que parcourir une colonne demande de passer d'une ligne à la suivante et de faire des grands sauts en mémoire RAM, mais tous espacés par la même distance. Avec l'entrelacement simple, les accès en enjambée sont moins performants que les accès à des adresses consécutives, mais cela ne fonctionne pas trop mal. Le pire cas est celui où l'on a N banques et où les données sont justement placées toutes les N adresses. Des accès consécutifs vont tous tomber dans la même banque, on ne peut plus accéder à des banques différentes en parallèle. Mais en dehors de ce cas, on voit une amélioration par rapport à la situation sans entrelacement. Pour obtenir des performances maximales pour les accès en enjambées, il faut répartir les mots mémoires dans la mémoire autrement. L'organisation idéale est la suivante. : Dans les explications qui vont suivre, la variable N représente le nombre de banques, qui sont numérotées de 0 à N-1. On commence par organiser les N premières adresses comme une mémoire entrelacée simple : l'adresse 0 correspond à la banque 0, l'adresse 1 à la banque 1, etc. Pour le bloc suivant, on décale tout d'une adresse, à savoir qu'on commence à remplir les banques à partir de la seconde, non de la première. Une fois la fin du bloc atteinte, on finit de remplir le bloc en repartant du début du bloc. Le troisième bloc d'adresse subit le même traitement, sauf qu'on commence à remplir à partir de la seconde banque. Et on poursuit l’assignation des adresses en décalant d'un cran en plus à chaque bloc. Ainsi, chaque bloc verra ses adresses décalées d'un cran en plus comparé au bloc précédent. Si jamais le décalage dépasse la fin d'un bloc, alors on reprend au début. [[File:Mémoire interleaved par décalage.png|centre|vignette|upright=1.5|Mémoire entrelacée par décalage.]] En faisant cela, on remarque que les banques situées à N adresses d'intervalle sont différentes. Dans l'exemple du dessus, nous avons ajouté un décalage de 1 à chaque nouveau bloc à remplir. Mais on aurait tout aussi bien pu prendre un décalage de 2, 3, etc. Dans tous les cas, on obtient un entrelacement par décalage. Ce décalage est appelé le pas d'entrelacement, noté P. Le calcul de l'adresse à envoyer à la banque, ainsi que la banque à sélectionner se fait en utilisant les formules suivantes : * adresse à envoyer à la banque = adresse totale / N ; * numéro de la banque = (adresse + décalage) modulo N ; * décalage = (adresse totale * P) mod N. Avec cet entrelacement par décalage, on peut prouver que la bande passante maximale est atteinte si le nombre de banques est un nombre premier. Seulement, utiliser un nombre de banques premier peut créer des trous dans la mémoire, des mots mémoires inadressables. Malgré ce défaut, la technique a été utilisée sur quelques ordinateurs, avec l'exemple notable du superordinateur ''Burroughs Scientific Processor''. Pour éviter cela, il y a plusieurs solutions. Par exemple, on peut faire en sorte que N et la taille d'une banque soient premiers entre eux : ils ne doivent pas avoir de diviseur commun. Mais en pratique, elles n'ont pas vraiment été implémentées dans une vraie machine, et sont restées à l'état de recherche, aussi je les passe sous silence. ===L'entrelacement pseudo-aléatoire=== Une dernière méthode de répartition consiste à répartir les adresses dans les banques de manière "pseudo-aléatoire". L'idée exacte est de faire passer l'adresse dans une '''fonction de hachage''', plus ou moins complexe. Pour rappel, une fonction de hachage prend une entrée de grande taille et fournit en sortie un résultat de petite taille. Idéalement, elles doivent donner des résultats aussi différents que possible pour des entrées similaires, histoire de simuler une sorte de pseudo-aléatoire. Ici, une adresse est transformée en un numéro de banque. Une fonction de hachage simple ne fait que permuter des bits de l'adresse pour obtenir son résultat. Elle ne fait qu'échanger des bits de place avant de couper l'adresse en deux morceaux : un pour la sélection de la banque, et un autre pour la sélection de l'adresse dans la banque. Cette permutation est fixe, et ne change pas suivant l'adresse. Des fonctions de hachage plus complexes font des XOR bit à bit entre certains bits de l'adresse. ==Les contrôleurs mémoires SDRAM/DDR optimisés== Après avoir vu le cas des mémoires RAM, nous allons nous concentrer sur le cas particulier des mémoires SDRAM. Les mémoires SDRAM et DDR modernes sont capables de gérer plusieurs accès simultanés à la mémoire RAM, et leurs contrôleurs mémoire en font tout autant. C'est une différence majeure avec les mémoires asynchrones FPM/EDO, qui n'acceptaient qu'un seul accès mémoire à la fois. Leur contrôleur mémoire n'acceptait qu'un seul accès mémoire à la fois, c'était un contrôleur mémoire bloquant. Les contrôleurs mémoires des SDRAM sont eux non-bloquants et peuvent encaisser une dizaine d'accès mémoire à la fois. Peu de choses sont connues sur les contrôleurs de SDRAM/DDR modernes, les fabricants ne donnant que peu de détails dessus. Les rares simulateurs qui tentent de décrire leur fonctionnement, comme DRAM SIM I et II, sont particulièrement simples et ne vont pas dans le détail. Néanmoins, le peu qu'on sait est tout de même instructif. ===Rappel sur les SDRAM : tampon de ligne et commandes=== Les mémoires SDRAM sont des mémoires à tampon de ligne. Elles font un accès mémoire en deux étapes. La première étape recopie une ligne de N * 64 bits dans un tampon interne à la SDRAM. La seconde étape sélectionne une colonne, à savoir une donnée de 64 bits, qui est soit envoyée sur le bus de données pour une lecture, soit modifiée par une écriture. [[File:Mémoire à tampon de ligne.png|centre|vignette|upright=2|Mémoire à tampon de ligne]] Les accès mémoire sont traduits par un séquenceur mémoire en une série de commandes mémoires, qui sont séparées par des délais mémoire de quelques cycles d'horloge. Les délais sont très précis, et sont à respecter à la lettre. Une lecture ou une écriture se fait en maximum trois commandes : une commande PRECHARGE qui ferme la ligne précédemment utilisée, une commande ACT qui précise l'adresse de la ligne, et une commande READ ou WRITE qui précise l'adresse de la colonne et éventuellement la donnée à écrire. : Pour être plus précis, la commande PRECHARGE précharge les lignes de bits du plan mémoire à une tension particulière, ce qui les vide de leur contenu. Mais c'est un détail sans importance pour ce qui va suivre. Les SDRAM permettent de se passer de la première étape si des accès consécutifs se font dans la même ligne. Une fois activée, la ligne reste ouverte et on peut accéder plusieurs fois de suite dedans. On a alors juste à préciser l'adresse de colonne dedans. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | bgcolor="#FFA0FF" | PRECHARGE || bgcolor="#FFA0FF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || bgcolor="#A0FFFF" | READ (2) || bgcolor="#A0FFFF" | READ (3) || || || bgcolor="#A0FFFF" | READ (4) || bgcolor="#A0FFFF" | READ (5) || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | Donnée READ 1 || ||bgcolor="#A0FFFF" | Donnée READ 2 | bgcolor="#A0FFFF" | Donnée READ 3 || || || bgcolor="#A0FFFF" | Donnée READ 4 || bgcolor="#A0FFFF" | Donnée READ 5 |} Les SDRAM sont parfois capables de démarrer une commande avant que la précédente soit terminée. Mais le respect des délais mémoire est très limitant, ce qui fait qu'on ne peut pas parler de réel pipeline, comme c'est le cas sur les processeurs. Parlons plutot de '''commandes anticipées'''. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | || bgcolor="#A0FFFF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || || || bgcolor="#FFA0FF" | READ (2) || || || || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 | bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 | |} ===La mise en attente des accès mémoire=== Un contrôleur de SDRAM peut accepter plusieurs accès mémoire en même temps. Mais cela ne signifie pas que le contrôleur sera prêt à les traiter immédiatement. Pour éviter tout problème de disponibilité, le contrôleur met en attente les accès mémoire que le processeur lui envoie, pour les exécuter dès que possible. Les accès mémoire sont mis en attente dans une mémoire FIFO, histoire de les exécuter dans leur ordre d'arrivée, notamment pour renvoyer les lectures dans l'ordre demandé. Il y a aussi une mémoire FIFO pour les données à écrire et une FIFO pour les données lues. Cette dernière sert au cas où le cache ne soit pas disponible quand on lui envoie la donnée lue. [[File:Module d'interface avec la mémoire.png|centre|vignette|upright=2.5|Contrôleur mémoire avec mise en attente des requêtes processeur et autres optimisations.]] Des optimisations sont possibles dès la mémoire FIFO. Par exemple, on peut regrouper plusieurs accès à des données consécutives en un seul accès en rafale. Le contrôleur mémoire analyse les requêtes mises en attente et détecte si certaines se font à des adresses consécutives. Si c'est le cas, il fusionne ces requêtes en une lecture/écriture en rafale. Une telle optimisation est appelée la '''combinaison de lecture''' pour les lectures, et la '''combinaison d'écriture''' pour les écritures. ===Les optimisations liées aux commandes anticipées=== Les commandes anticipées permettent de faire un minimum de parallélisme mémoire. Même si elle parait très limitée, cette possibilité est décuplée par la présence de plusieurs banques dans la mémoire SDRAM. Les chips mémoires de SDRAM sont composés de plusieurs banques, chacune étant une sorte de mini-mémoire miniature. Chaque banque a son propre décodeur, son propre tampon de ligne, ses propres multiplexeurs de colonne, sa logique de rafraichissement mémoire, etc. C'est comme si une SDRAM regroupait plusieurs mémoires séparées dans un même circuit intégré. Et cela permet de faire plusieurs accès mémoire en même temps, dans des banques différentes. Il est possible de lancer un accès mémoire dans deux banques en même temps, par exemple. Ou encore, on peut lancer un nouvel accès mémoire dans une banque si elle est inoccupée. La possibilité améliore grandement la performance de la SDRAM. Mais nous en reparlerons dans un chapitre ultérieur. {|class="wikitable" |+ Pipelining basique sur les SDRAM |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 |- ! Banque Numéro 1 | || || || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || || || |- ! Banque Numéro 2 | || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || |- ! Banque Numéro 3 | || || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || |} L'implémentation n'a rien de vraiment compliqué. Il suffit d'utiliser une mémoire FIFO par banque. Ainsi, les accès dans une même banque se feront en série, dans l'ordre d'arrivée, mais des accès à des banques différentes se feront dans le désordre. Cela ne pose pas de problème car les accès se font à des adresses différentes, ce qui fait qu'il n'y a pas de conflits majeurs. [[File:Gestion parallèle des banques.png|centre|vignette|upright=2.5|Gestion parallèle des banques.]] ===Le ré-ordonnancement des commandes mémoires=== Un contrôleur mémoire moderne est capable de changer l'ordre des requêtes mémoires, pour gagner en performances. En clair : il peut faire l'équivalent mémoire de l'exécution dans le désordre des processeurs haute performance. Une différence majeure est que le contrôleur mémoire ne remet pas les accès mémoire dans l'ordre avant de les envoyer au processeur. C'est le processeur qui remet en ordre les accès mémoire, dans sa ''load queue''. Pour cela, les requêtes mémoire sont "numérotées", à savoir qu'on leur attribue un identifiant binaire. Le contrôleur mémoire exécute les lectures/écritures dans le désordre, mais garde la trace de leur identifiant. Il sait dans quel ordre il exécute les accès mémoire et connait leur latence. Ce qui fait qu'il sait que la donnée lue à tel instant est associée à la requête mémoire numéro X. Pour les lectures, il renvoie la donnée lue avec l'identifiant. Le processeur sait alors à quelle lecture la donnée lue correspond et il se débrouille en interne pour gérer la situation. En clair : le processeur voit les accès mémoire dans le désordre, c'est lui qui les remet en ordre. Maintenant que ces précisions sont faites, posons cette question : quel est l'intérêt de faire les accès mémoire dans le désordre ? Accéder à des banques en parallèle est une réponse possible, mais le ré-ordonnancement est surtout utile si tous les accès mémoire atterrissent dans la même banque. L'exécution dans le désordre des lectures/écritures SDRAM vise à faire en sorte que des accès consécutifs se fassent dans une même ligne. Elle marche si plusieurs accès à une même ligne ne sont pas consécutifs, et qu'il y a des accès entre les deux. Après ré-ordonnancement, ces accès mémoire deviennent consécutifs, ils sont exécutés l'un à la suite de l'autre. Pour rendre le tout plus concret, voici un exemple. Imaginez que l'on sait les 3 accès mémoire suivants : * Une lecture ligne A ; * Une écriture ligne B ; * Une lecture ligne A. Sans réordonnancent, on doit émettre deux commandes PRECHARGE : une quand on passe de la ligne A à la ligne B, et une autre pour le passage inverse. pour éviter cela, le contrôleur mémoire peut retarder l'écriture, ou au contraire avancer la seconde lecture. le résultat est alors le suivant : * Lecture ligne A ; * Lecture ligne A ; * Une écriture ligne B. On a donc deux accès consécutifs à la même ligne, suivi par une écriture dans une autre ligne. La technique marche parce que l'on a un mix adéquat de lectures et d'écritures. Mais il existe des possibilités de réorganisation autres qui ne sont pas valides. Il faut par exemple prendre garde à éviter d'intervertir lectures et écritures à une même adresse, sous peine de problèmes. Encore une fois, il faut faire attention aux dépendances mémoires. Le controleur mémoire teste les dépendances mémoires avant d'envoyer des commandes à la mémoire DDR/SDRAM. ==L'entrelacement sur les mémoires SDRAM== L''''entrelacement''' fonctionne sur les mémoires SDRAM, mais il doit être fortement modifié. Concrètement, tout ce qui a étré dit plus est inapplicable pour les SDRAM, du fait de la présence du tampon de ligne. Des adresses consécutives atterrissent déjà dans la même ligne, ce qui fait qu'on n'a pas besoin d'entrelacement au niveau d'une ligne. Et ne parlons pas de ce la présence de rangées et de canaux mémoire ! Cependant, il peut y avoir un entrelacement lié à la présence des banques SDRAM. L'entrelacement est géré juste avant le séquenceur mémoire. Le '''circuit d'entrelacement''' intervertit certains bits de l'adresse lors des accès mémoires. On trouve aussi des mémoires FIFOs pour mettre en attente les requêtes mémoire provenant du processeur. Elles permettent de recevoir plusieurs accès mémoire consécutifs. Si vous vous demandez comment plusieurs accès mémoire consécutifs peuvent avoir lieu, sachez que c'est une bonne question. Pour le moment, nous allons dire que ces accès proviennent du processeur ou de la mémoire cache, sans expliquer comment. Le contrôleur mémoire reçoit une série d'accès mémoire assez rapprochés, qu'il doit exécuter au mieux. [[File:Controleur mémoire d'une SDRAM avec entrelacement.png|centre|vignette|upright=2.5|Contrôleur mémoire d'une SDRAM avec entrelacement.]] Pour gérer l'entrelacement, le contrôleur de SDRAM prend en entrée l'adresse envoyée par le processeur, et la découpe en plusieurs champs : un pour sélectionner la banque adéquate, un autre pour la ligne, un autre pour la colonne, un autre pour la rangée. Une solution simple désactive l'entrelacement. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de banque || Adresse de ligne || Adresse de colonne |} Si cette solution fonctionne bien si les accès mémoire sont assez aléatoires, en pratique, mieux vaut utiliser l'entrelacement. ===L'entrelacement de banques/lignes=== Une première méthode d'entrelacement est l''''entrelacement de banques'''. Elle répartit deux lignes consécutives dans deux banques différentes. Pour comprendre l'idée, prenons un exemple. Imaginons une mémoire avec deux banques et 4 lignes. Imaginons qu'on parcoure/balaye la mémoire RAM en partant des adresses basses. Sans entrelacement de ligne, les accès se feront comme suit, de gauche à droite : {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Banque numéro 1 !! colspan="4" | Banque numéro 2 |- | Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 || Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 |} Avec l'entrelacement de banques, les accès se feront comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 |- | Ligne 1 || Ligne 1 || Ligne 2 || Ligne 2 || Ligne 3 || Ligne 3 || Ligne 4 || Ligne 4 |} Avec N banques, la première ligne ira dans la première banque, la seconde ligne dans la seconde, et ainsi de suite jusqu'à la énième banque qui va dans la banque numéro N. Et la ligne N+1 repart dans la première banque. L'avantage est que passer d'une ligne à la suivante est assez rapide. Au lieu de devoir fermer la première ligne et ouvrir la suivante on ouvrira directement la ligne suivante dans une banque différente, ce qui sera plus rapide. La commande PRECHARGE pourra être envoyée à la seconde banque en avance. Pour cela, l'adresse est découpée comme suit. {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne (précise à l'octet) |} ===L'entrelacement de rangées=== Il est aussi possible de faire la même chose, mais avec les rangées. Pour simplifier fortement, une rangée est simplement un chip mémoire. En réalité, les rangées ne sont pas des chip mémoire, mais un ensemble de chips mémoire regroupés ensemble histoire d'atteindre les 64 bits du bus de données. Par exemple, une rangée peut combiner 8 chips mémoire avec un bus de données de 8 bits chacun pour obtenir les 64 bits du bus de données d'une SDRAM. Mais nous passons ce détail sous silence dans les explications qui vont suivre, par souci de simplification. Pour faire comprendre l'entrelacement de rangée, le mieux est d'utiliser un exemple, le même que précédemment. Sans entrelacement de rangée, on change de chip mémoire une fois qu'on a balayé toutes ses adresses. {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Rangée numéro 1 !! colspan="4" | Rangée numéro 2 |- | Banque 1 || Banque 2 || Banque 3 || Banque 4 || Banque 1 || Banque 2 || Banque 3 || Banque 4 |} Avec l'entrelacement de ligne, on change de rangée dès qu'on change de banque/ligne. {|class="wikitable" |+ Adresse mémoire |- ! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 |- | Banque 1 || Banque 1 || Banque 2 || Banque 2 || Banque 3 || Banque 3 || Banque 4 || Banque 4 |} En clair, quand on a épuisé toutes les banques dans une même rangée, on passe à la rangée suivante au lieu de rester dans la même rangée. Notons que dans l'exemple précédent, on a combiné l'entrelacement de rangée et de banque, mais on aurait pu utiliser l'entrelacement de rangée seul. Mais ce n'est pas le cas le plus courant en pratique. Toujours est-il qu'en combinant entrelacement de rangée et de ligne, le découpage de l'adresse est le suivant : {|class="wikitable" |+ Adresse mémoire |- | Adresse de ligne || Numéro de rangée || Adresse de banque || Adresse de colonne (précise à l'octet) |} D'autres méthodes d'entrelacement sont possibles, mais elles sont plus complexes. Chaque contrôleur mémoire fait un peu à sa sauce. Ils permettent en général de choisir entre plusieurs entrelacements. Au minimum, ils permettent de désactiver l'entrelacement, d'activer un entrelacement de ligne, et d'autres entrelacements plus complexes, dont certains sont propriétaires et tenus secrets par le fabricant. ===L'entrelacement avec le ''dual channel''=== [[File:Dual channel slots.jpg|vignette|Slots mémoires avec ''dual channel''.]] L'usage du ''dual channel'' complique encore l'entrelacement. Là encore, il y a deux grandes solutions : avec et sans entrelacement des canaux mémoire. Rappelons le principe : deux barrettes de RAM sont accédées en parallèle. Et pour cela, il faut utiliser l'entrelacement. Typiquement, chaque barrette mémoire fournit 64 bits, ce qui fait que l'on peut accéder à 128 bits d'un seul coup, par exemple avec un accès en rafale. Sans ''dual channel'', la première barrette correspond à la moitié haute de la RAM, la seconde barrette correspond à la moitié basse. Avec ''dual channel'', une forme spécifique d'entrelacement est activée. Concrétement, deux blocs de 64 bits sont placés dans des canaux mémoire séparés. Idem avec du triple ou quadruple canal, mais c'est alors trois ou quatre blocs de 64 bits qui sont dispersés dans des canaux consécutifs. Le découpage de l'adresse est alors le suivant : {|class="wikitable" |+ Adresse mémoire |- ! colspan="6" | Sans entrelacement |- | Numéro de canal/barrette || Reste de l'adresse || Position de l'octet dans un bloc de 64 bits |- | colspan="6" | |- ! colspan="6" | Avec entrelacement |- | Reste de l'adresse || Numéro de canal/barrette || Position de l'octet dans un bloc de 64 bits |} [[File:Décodage d'adresse avec dual channel.png|centre|vignette|upright=2|Décodage d'adresse avec dual channel.]] Les contrôleurs de SDRAM précédents sont assez basiques et ne représentent pas les contrôleurs les plus évolués. Ils étaient utilisés sur les anciens PC, à une époque où ils étaient encore sur la carte mère du processeur. Mais de nos jours, le contrôleur mémoire est intégré au processeur. Et il incorpore de nombreuses optimisations afin de gagner en performances. Malheureusement, ces optimisations sont elles-mêmes dépendantes des optimisations intégrées dans le processeur, ce qui fait qu'on ne peut pas en parler ici. Elles demandent que le contrôleur mémoire reçoive plusieurs accès mémoire simultanés, ce qui n'a aucun sens à ce stade du cours. Aussi, nous allons les passer sous silence pour le moment. Nous les verrons dans le chapitre sur le parallélisme mémoire. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=La désambiguïsation mémoire | prevText=La désambiguïsation mémoire | next=Les processeurs superscalaires | nextText=Les processeurs superscalaires }} </noinclude> 82vp2uptzy7kawce0kd3e64hqzsmuuf 764717 764716 2026-04-23T21:17:42Z Mewtow 31375 /* Le ré-ordonnancement des commandes mémoires */ 764717 wikitext text/x-wiki Dans ce chapitre, nous allons voir les techniques qui permettent de gérer plusieurs accès mémoire simultanés directement au niveau du cache ou de la mémoire RAM. Par plusieurs accès mémoire simultanés, vous pensez sans doute à l'usage de cache multiports, voire de mémoires RAM multiport. C'est en effet une solution, mais ce n'est clairement pas la seule. Par exemple, il est possible de pipeliner l'accès au cache, voire à la mémoire RAM. Il existe de nombreuses techniques de '''parallélisme mémoire''', et nous allons les voir dans ce chapitre. Pour donner un exemple, il est possible de pipeliner les accès mémoire. Il est possible de pipeliner l'accès au cache et/ou l'accès à la mémoire, et nous verrons comment faire dans ce chapitre. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, qu'ils aient ou non un pipeline dynamique. Par contre, pipeliner la mémoire est une technique ancienne, aujourd'hui peu utilisée. Elle était utilisée sur des très vieux ordinateurs, qui avaient un pipeline mais pas de mémoire cache. Mais de nos jours, les mémoires SDRAM et DDR ne sont pas adaptées pour ça, il est très difficile de les pipeliner correctement. Le parallélisme mémoire est utile aussi bien sur des processeurs à émission dans l'ordre que dans le désordre. Par exemple, un processeur ''in-order'' avec des lectures non-bloquantes peut e, profiter. Il peut techniquement lancer une seconde lecture, après avoir rencontré une première lecture non-bloquante. En général, le nombre de lectures consécutives est cependant limité à moins d'une dizaine. À l'opposé, les processeurs sans lectures non-bloquantes profitent pas du parallélisme mémoire. À la rigueur, ils peuvent en profiter s'ils intègrent des techniques de préchargement. Pendant que le processeur exécute une lecture/écriture, il peut précharger une autre donnée en parallèle. Inutile de dire que sans pipeline processeur, le parallélisme mémoire ne sert pas à grand-chose. ==Le ''line fill buffer'' et les techniques associées== Lors d'un défaut de cache, le processeur doit attendre que toute la ligne de cache soit chargée avant d'être utilisable. Or, la taille d'une ligne de cache est supérieure à la largeur du bus mémoire, ce qui fait qu'une ligne de cache est chargée en plusieurs fois, morceaux par morceaux, mot mémoire par mot mémoire. Le chargement peut se faire directement dans le cache, mais ce n'est pas une solution très pratique. À la place, beaucoup de processeurs ajoutent une mémoire tampon entre la RAM et le cache, appelée le '''tampon de remplissage de ligne''' (''line-fill buffer'') dans les processeurs modernes. Lors d'un défaut de cache, le processeur charge la donnée de la RAM dans le tampon de remplissage de ligne, mot mémoire par mot mémoire. Une fois plein, le tampon de remplissage de ligne est recopié dans la ligne de cache. Le tampon de remplissage de ligne contient une ligne de cache, si ce n'est que certains bits de contrôle ne sont pas présents. Le tampon de remplissage de ligne contient un bit de validité pour chaque mot mémoire de la ligne de cache, qui indiquent si le mot mémoire a été chargé. Par exemple, prenons un processeur 64 bits, qui gère donc des mots mémoire de 8 octets, avec des lignes de cache 256 octets/32 mots mémoire. Le tampon de remplissage de ligne contiendra 32 bits de validité. Si le processeur a chargé les 6 premiers mots mémoire, les 6 premiers bits de validité seront mis à 1, les autres seront encore à zéro. Il faut noter que la disponibilité de la ligne complète se détermine assez facilement en faisant un ET logique entre tous les bits de validité. Le processeur sait ainsi quand la ligne de cache est disponible entièrement et donc quand la transférer dans le cache. La même chose existe avec une hiérarchie de cache, sauf que l'on trouve une mémoire tampon entre chaque niveau de cache. Il y a un tampon de remplissage de ligne entre le cache L1 et le cache L2, entre le cache L2 et le L3, etc. Si le cache ne gère qu'un seul défaut de cache à la fois, le ''fill line buffer'' est une mémoire très simple, qui ne mémorise qu'une seule ligne de cache. Et cela vaut aussi bien pour un cache bloquant que non-bloquant. Mais sur les caches capables de gérer plusieurs défauts simultanés, le tampon de remplissage de ligne est une mémoire de type FIFO ou LIFO, capable de mémoriser plusieurs lignes de cache. Notons que le tampon de remplissage de ligne est très utile pour implémenter certaines techniques, par exemple le contournement du cache. Nous avions vu dans le chapitre sur le cache que certains accès mémoire doivent contourner le cache, pour des raisons de cohérence des caches. C'est notamment nécessaire pour accéder aux périphériques, mais c'est aussi utile pour des raisons de performances dans des cas très spécifiques. Les accès qui contournent le cache se font directement dans le tampon de remplissage de ligne : le processeur écrit ou lit les données depuis ce tampon de remplissage de ligne, sans accéder au cache. ===L'''early restart'' et le ''critical word load''=== La présence du ''line buffer'' permet une optimisation assez intéressante, qui permet de réduire la latence des défauts de cache. L'optimisation consiste à lire un mot mémoire dans le tampon de remplissage de ligne, même si la ligne de cache complète n'a pas encore été chargée. Il existe deux manières de faire cela, qui portent les noms d'''early restart'' et de ''critical word load''. La première est la version la plus simple, la seconde est plus complexe mais plus performante. Avec la technique d''''''early restart''''', la ligne de cache est chargée normalement, en partant de son premier mot mémoire. Dès que le mot mémoire lu/écrit par le processeur est copié dans le ''line fill buffer'', il est envoyé au processeur immédiatement. Illustrons le tout par un exemple, où une ligne de cache fait 16 mots mémoire. Le processeur effectue une lecture, qui lit le 5ème mot mémoire. Sans ''early restart'', le processeur doit charger les 16 mots mémoire avant de faire la lecture dans le cache. Avec ''early restart'', le processeur reçoit la donnée dès que le 5ème mot mémoire est disponible. Le processeur doit attendre que les 4 premiers mots mémoire soient chargés, puis le 5ème mot mémoire arrive et est envoyé directement au processeur, il est lu directement depuis le ''line fill buffer''. Les 11 mots mémoire suivants sont ensuite chargés dans le cache pendant que le processeur fait des calculs dans son coin. Le ''critical word load'' est une optimisation de la technique précédente où le chargement de la ligne de cache commence directement à la donnée demandée par le processeur. Pour reprendre l'exemple précédent, où le processeur demande le 5ème mot mémoire sur 16, le ''critical word load'' charge le 5ème mot mémoire en premier et l'envoie au processeur, ce qui fait qu'il est chargé très rapidement. Pas besoin d'attendre que le processeur charge les 4 mots mémoire précédents comme avec l'''early restart''. Dans le détail, le processeur charge le 5ème mot mémoire en premier, puis charge les 11 suivants, et termine par les 4 mots mémoire du début. En clair, le ''critical word load'' commence par charger le mot mémoire lu, puis les blocs suivants, avant de revenir au début du bloc pour charger les blocs restants. Ainsi, la donnée demandée par le processeur sera la première disponible. Pour cela, l'organisation du tampon de remplissage de ligne est modifiée de manière à rendre cela possible. Il a une taille égale à une ligne de cache complète, qui contient elle-même plusieurs mots mémoire. Dans le ''line-fill buffer'', chaque mot mémoire est stocké avec un tag, qui indique l'adresse du mot mémoire stocké dans le ''line-fill buffer''. Le ''line-fill buffer'' est donc un cache un peu particulier, qui fonctionne comme un cache du point de vue du processeur, comme une mémoire FIFO pour les transferts avec le cache. Ainsi, un processeur qui veut lire dans le cache après un défaut peut accéder à la donnée directement depuis le tampon de remplissage de ligne, alors que la ligne de cache n'a pas encore été totalement recopiée en mémoire. ==Les caches pipelinés== Il est possible de pipeliner l'accès au cache, ce qui demande juste de rajouter des registres au bon endroits et quelques circuits de contrôle. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, même ceux avec un pipeline dynamique. Et l'implémentation peut se faire de plusieurs manières différentes. Il faut dire que découper une mémoire cache en plusieurs étapes peut se faire de plusieurs manière. Commençons par la plus simple. Beaucoup de processeurs des années 2000 avaient une fréquence peu élevée comparée aux standards d'aujourd'hui, ce qui fait que leur cache avait bien un temps d'accès d'un cycle d'horloge. Mais l'accès au cache se faisait en deux cycles d'horloge : un cycle pour calculer une adresse et un cycle pour l'accès mémoire proprement dit. Et ces deux étapes étaient pipelinées, à savoir que deux micro-opérations mémoire peuvent s'exécuter en même temps : une dans l'étage de calcul d'adresse, une autre dans le cache. L'unité mémoire était donc pipelinée, alors que l'accès au cache ne l'était pas. Les processeurs qui implémentaient cette technique regroupent les micro-architectures K5 et K6 d'AMD, les processeurs Intel de micro-architecture P6 (Pentium 2 et 3) et quelques autres. Il est aussi possible de pipeliner l'accès au cache lui-même. Avec un cache pipeliné, l'accès au cache ne se fait pas en un seul cycle, mais en plusieurs. Cependant, on peut lancer un nouvel accès au cache à chaque cycle d'horloge, comme un processeur pipeliné lance une nouvelle instruction à chaque cycle. L'implémentation est assez simple : il suffit d'ajouter des registres dans le cache. Pour cela, on profite que le cache est composé de plusieurs composants séparés, qui échangent des données dans un ordre bien précis, d'un composant à un autre. De plus, le trajet des informations dans un cache est linéaire, ce qui les rend parfois pour l'usage d'un pipeline. ===Le ''pipelining'' des caches ''direct-mapped''=== Voici ce qui se passe avec un cache directement adressé. Pour rappel ce genre de cache est conçu en combinant une mémoire RAM, généralement une SRAM, avec quelques circuits de comparaison et un MUX. L'accès au cache se fait globalement en deux étapes : la première lit la donnée et le ''tag'' dans la SRAM, et on utilise les deux dans une seconde étape. Nous avions vu il y a quelques chapitre comment pipeliner des mémoires, dans le chapitre sur les mémoires évoluées. Et bien ces méthodes peuvent s'utiliser pour la mémoire RAM interne au cache !L'idée est d'insérer un registre entre la sortie de la RAM et la suite du cache, pour en faire un cache pipéliné, à deux étages. Le premier étage lit dans la SRAM, le second fait le reste. L'implémentation sur les caches associatifs à plusieurs voies est globalement la même à quelques détails près. [[File:Cache directement adressé pipeliné - deux étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - deux étages]] Avec le circuit précédent, il est possible d'aller plus loin, cette fois en pipelinant l'accès à la mémoire RAM interne au cache. Pour comprendre comment, rappelons qu'une mémoire SRAM est composée d'un plan mémoire et d'un décodeur. L'accès à la mémoire demande d'abord que le décodeur fasse son travail pour sélectionner la case mémoire adéquate, puis ensuite la lecture ou écriture a lieu dans cette case mémoire. L'accès se fait donc en deux étapes successives séparées, on a juste à mettre un registre entre les deux. Il suffit donc de mettre un gros registre entre le décodeur et le plan mémoire. [[File:Cache directement adressé pipeliné - trois étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - trois étages]] Et on peut aller encore plus loin en découpant le décodeur en deux circuits séparés. En effet, rappelez-vous le chapitre sur les circuits de sélection : nous avions vu qu'il est possible de créer des décodeurs en assemblant des décodeurs plus petits, contrôlés par un circuit de prédécodage. Et bien on peut encore une ajouter un registre entre ce circuit de prédécodage et les petits décodeurs. : Théoriquement, toute l'adresse est fournie d'un seul coup au cache, la quasi-totalité des processeurs présentent l'adresse complète à un cache pipeliné. Mais le Pentium 4 fait autrement. Il faut noter que les premiers étages manipulent l'indice dans la SRAM, qui est dans les bits de poids faible, alors que les étages ultérieurs manipulent le ''tag'' qui est dans les bits de poids fort. Les concepteurs du Pentium 4 ont alors décidé de présenter les bits de poids faible lors du premier cycle d'accès au cache, puis ceux de poids fort au second cycle. Pour cela, l'ALU fonctionnait à une fréquence double de celle du processeur, tout comme le cache L1. Il n'y avait pas de pipeline proprement dit, mais cela réduisait grandement la latence d'accès au cache. ===Le ''pipelining'' des caches splittés/sériels=== Il est aussi possible de pipeliner un cache dits splittés, aussi appelés à accès sériel. Pour rappel, les caches à accès sériel vérifient si il y a succès ou défaut de cache, avant d'accéder aux lignes de cache en cas de succès. Ils font donc différemment des autres caches, qui accèdent à une ligne de cache, avant de déterminer s'il y a succès ou défaut en lisant le tag de la ligne de cache. Les caches sériels disposent de deux SRAM : une pour les tags des lignes de cache et une pour les données. Ils accèdent à la SRAM pour les tags, avant d’accéder à la SRAM des données en cas de succès de cache. Vu que l'accès se fait en deux étapes, une vérification des tags suivie de la lecture/écriture des données, il est facile à pipeliner. Pipeliner le cache permet de régler le problème des accès au cache L1, et elle est tout le temps utilisé sur les processeurs modernes. Mais que faire en cas de défaut de cache ? ==Les caches non bloquants== Un '''cache bloquant''' est un cache auquel le processeur ne peut pas accéder pendant un défaut de cache. Il faut attendre que la lecture ou écriture en RAM soit terminée avant de pouvoir utiliser de nouveau le cache. Un '''cache non bloquant''' n'a pas ce problème : on peut l'utiliser pendant un défaut de cache. Les caches non bloquants permettent de démarrer une nouvelle lecture ou écriture alors qu'une autre est en cours, ce qui permet d'exécuter plusieurs lectures ou écritures en même temps. ===Les ''Miss Handling Status Registers''=== Lors d'un défaut de cache, la mémoire RAM est consultée pendant le défaut de cache, mais le cache est inutilisé. Un défaut de cache n'utilise pas le cache, ce n'est qu'un accès en mémoire RAM, sur le bus mémoire (ou un accès aux niveaux de cache inférieurs, peu importe). Le cache en lui-même est laissé libre, rien n’empêche d'y accéder, il est en réalité intrinsèquement non-bloquant. Les caches, bloquants comme non-bloquants, sont en réalité composés d'une mémoire cache proprement dite, entourée de circuits qui servent d'interface entre le processeur et le cache lui-même. Et parmi les circuits tout autour, certains gèrent l'accès au cache lors d'un défaut de cache. Ils sont regroupés sous le terme de '''''Miss Handling Architecture''''' (MHA). La différence entre un cache bloquant et un cache non-bloquant est en réalité liée à l'implémentation de la MHA. Les caches bloquants coupent volontairement l'accès au cache lors d'un défaut, car les défauts sont plus simples à gérer ainsi. Le défaut de cache rapatrie une donnée depuis la RAM, qui sera écrite dans le cache. Et il ne faut pas qu'une tentative d'accès à cette donnée ait lieu avant qu'elle ne soit chargée. Mais ce blocage est général et touche tout le cache, alors que seule une ligne de cache est concernée par le défaut de cache. L'idée derrière un cache non-bloquant est que seule la ligne de cache est bloquée, mais les autres sont accessibles. L'idée est alors de mémoriser les lignes de cache concernées par le défaut de cache, afin d'en bloquer l'accès. À chaque accès, on vérifie si la ligne de cache est déjà réservée par un défaut de cache. La lecture/écriture est alors bloquée si c'est le cas, mais elle accepte les accès sinon. Pour cela, la ''Miss Handling Architecture'' contient des registres qui mémorisent des informations sur les défauts de cache en cours. Ils portent le nom de '''''miss status handling registers''''', que j’appellerais dorénavant MSHR, qui sont aussi appelés des '''''miss buffer'''''. Le contenu des MSHR varie beaucoup suivant le processeur, mais ces derniers stockent au minimum les informations suivantes : * Le numéro de la ligne de cache dans laquelle les données sont chargées. * Un bit de validité qui indique si le MSHR est vide ou pas, qui est mis à 0 quand le défaut de cache est résolu. * Un ou plusieurs champs de lecture/écriture, qui contiennent des informations sur la lecture/écriture. ** Pour une lecture, elle contient des informations sur la destination de la donnée, à savoir qui prévenir quand le défaut de cache est terminé. C'est parfois un nom/numéro de registre (celui dans lequel charger la donnée), mais c'est souvent le numéro de l'entrée dans le ''load/store queue''. ** Pour les écritures, elle contient la donnée à écrire, ou éventuellement un numéro de ''load/store queue'' où se trouve la donnée à écrire. Il faut noter que le bus mémoire ne peut gérer qu'un seul défaut de cache à la fois. Aussi, il est intéressant de regarder ce qui se passe lorsqu'un second défaut de cache survient, pendant qu'un premier est en cours. Dans ce cas, il y a deux réponses qui correspondent à deux types de caches non-bloquants, qui portent les noms barbares de caches de type succès après défaut et défaut après défaut. Sur le premier type, il ne peut pas y avoir plusieurs défauts de cache simultanés. Dès qu'un second défaut de cache survient, le cache stoppe son activité et on ne peut plus démarrer de nouvelle lecture/écriture, tant que le premier défaut de cache n'est pas résolu. Le second type est plus souple et autorise la survenue de plusieurs défauts de cache simultanés. Du moins, jusqu'à une certaine limite, car le cache ne peut supporter qu'un nombre limité d'accès mémoires simultanés (pipelinés). ===Les accès simultanés à une même ligne de cache=== Il arrive que le processeur fasse plusieurs accès mémoire simultanés à la même ligne de cache. Si la ligne de cache en question n'a pas encore été chargée dans le cache, alors on a plusieurs défauts de cache consécutifs pour la même ligne de cache, et le cache non-bloquant doit gérer la situation. Pour la suite, il va falloir faire une petite distinction entre les défauts primaires et secondaires. Imaginons qu'un défaut de cache ait lieu et demande à charger une donnée dans la ligne de cache numéro N. Il s'agit du premier défaut impliquant cette ligne de cache précise, ce qui lui vaut le nom de '''défaut de cache primaire'''. Mais par la suite, d'autres accès mémoire à la même ligne de cache ont lieu, alors que la ligne de cache n'est pas encore disponible. Dans ce cas, il s'agit de '''défauts de cache secondaires'''. Pour l'unité d'accès mémoire, les défauts de cache primaire et secondaire sont différents (ils prennent tous une entrée dans la ''load/store queue''). Mais pour le cache, ils ne correspondent qu'à un seul accès au cache : celui qui demande de charger la ligne de cache demandée. Les défauts de cache primaire et secondaire à la même ligne de cache se voient attribuer un MSHR unique. La gestion des défauts de cache secondaires dépend du cache non-bloquant. La solution la plus simple ne permet pas les défauts de cache secondaires. Le cache ne permet pas deux défauts de cache simultanés pour la même ligne de cache. Les autres solutions le permettent, en fusionnant des accès simultanés à la même ligne de cache en un seul au niveau des MSHR. Dans tous les cas, détecter les défauts de cache secondaires sont un problème qu'il faut détecter. La ''Miss Handling Architecture'' doit détecter les défauts de cache secondaire. Pour cela, elle procède comme suit. Lors de chaque défaut de cache, la MHA récupère le numéro de la ligne de cache associé. Il vérifie alors chaque MSHR pour vérifier s'il contient le numéro en question. S'il n'y a aucune correspondance dans les MSHR, alors c'est signe que le défaut de cache est un défaut primaire. Mais s'il y en a une, alors c'est un défaut secondaire. Évidemment, cela signifie que lors d'un défaut de cache, le numéro de ligne de cache est envoyé à tous les MSHR, pour comparaison. Les MSHR sont donc regroupés dans une mémoire associative, une sorte de mini-cache, faciliter l'implémentation. Lors d'un défaut de cache primaire, l'accès à un cache non-bloquant se fait comme suit : le processeur envoie une adresse au cache, accède à celui-ci, et détecte la survenue d'un défaut de cache. Il en profite alors pour attribuer une ligne de cache dans laquelle sera chargée ce défaut. L'attribution est très simple dans le cas des caches ''direct mapped'', ou associatifs par voie, pour lesquels l'attribution se fait assez simplement. Il mémorise alors cette information dans les MSHR, après avoir vérifié que le défaut de cache n'était pas un défaut secondaire. Lors des accès ultérieurs à une adresse proche, censée être dans la même ligne de cache, le processeur va encore une fois rencontrer un défaut de cache. Il va alors déterminer le numéro de la ligne de cache associée à l'adresse, et comparer ce numéro avec les MSHRs. Si un MSHR contient ce numéro, c'est signe que le défaut de cache est un défaut secondaire. La MHA réagit alors différemment selon le processeur considéré. Une première solution n'autorise pas les défauts de cache secondaires. Si l'un d'entre eux survient, le processeur est gelé par un ''pipeline stall'', une bulle de pipeline. Une autre solution fusionne les défauts de cache secondaires avec le défaut de cache primaire : tout cela ne correspond qu'à un seul défaut de cache pour lui. ===Les MSHR simples=== Un cache non-bloquant à '''MSHR simple''' contient juste plusieurs MSHR qui mémorisent juste un numéro de ligne de cache, un bit de validité, le champ de lecture/écriture, et l'adresse exacte de lecture/écriture. Avec cette organisation, il est possible d'avoir plusieurs défauts de cache séparés, mais à la condition que chaque défaut accède à une ligne de cache différente. Deux accès simultanés à une même ligne de cache ne sont pas possibles, les défauts de cache secondaires ne sont pas autorisés. Ainsi, chaque défaut de cache se voit attribuer son propre MSHR, chacun contient un numéro de ligne de cache différent. Pour comprendre pourquoi c'est impossible de gérer les défauts secondaires, il faut regarder le champ de lecture/écriture. Si on veut effectuer plusieurs écritures consécutives à la même adresse, le MSHR n'aura pas de quoi mémoriser les deux données à écrire. Il pourra mémoriser la première donnée à écrire, pas la seconde. Même chose lors d'une lecture : le champ lecture/écriture peut mémorisr la destination de la première lecture, pas de la seconde. L'avantage est que la MHA n'a besoin que des MSHR et de quelques circuits annexes. Les autres solutions rajoutent des circuits annexes pour gérer les défauts de cache secondaires, qui utilisent beaucoup de circuits. Le cout en circuit est donc élevé, mais le gain en performance est là. Passons maintenant aux caches non-bloquants qui autorisent les défauts de cache secondaire. La solution la plus simple consiste à utiliser ===Les MSHR adressés implicitement=== Avec les '''MSHR adressés implicitement''', il est possible de fusionner plusieurs accès mémoire à une même ligne de cache, mais sous une condition très importante : ces accès lisent/écrivent des mots mémoire différents. Par exemple, imaginons qu'une ligne de cache contienne 8 mots mémoire de 64 bits. Si un premier accès mémoire lit le mot mémoire numéro 7 (dernier mot mémoire de la ligne), et le second accès le mot mémoire numéro 3, alors la fusion est possible. Mais si deux accès mémoire veulent lire/écrire le mot mémoire numéro 7, alors la fusion n'est pas possible et le processeur se bloque, un ''pipeline stall'' survient. Un MSHR adressé implicitement est un MSHR simple, qui contient naturellement un numéro de ligne de cache (tag) et un bit de validité, sauf que le champ de lecture/écriture est dupliqué. Les différents champs lecture/écriture d'un MSHR sont regroupés dans une mémoire RAM/cache qui contient autant d'entrées qu'il y a de mot mémoire dans une ligne de cache. Chaque entrée de la table est associée à un mot mémoire de la ligne de cache et stocke des informations sur celui-ci. Une entrée mémorise, au minimum : * Un champ de lecture/écriture qui contient soit la destination de la lecture, soit la donnée à écrire. * Un bit R/W permettant d'interpréter correctement le champ de lecture/écriture. * Un bit de validité pour chaque entrée de la table, qui dit si un défaut de cache antérieur accède déjà à ce mot mémoire. La fusion de deux défauts de cache est ainsi assez simple. Un défaut de cache primaire/secondaire configure l'entrée associée au mot mémoire lu/écrit. Si un défaut secondaire ultérieur lit/écrit un mot mémoire différent (dont le bit de validité de l'entrée dans la table est à zéro), pas de conflit : il configure une autre entrée, vide. Mais s'il lit/écrit un mot mémoire pour lequel l'entrée de la table est occupée, il y a conflit, le processeur est bloqué par un ''pipeline stall'', une bulle de pipeline. Avec cette organisation, le nombre de MSHR indique combien de lignes de cache peuvent être lues en même temps depuis la mémoire. Quant au nombre d'entrées par MSHR, il détermine combien d'accès mémoires qui ne se recouvrent pas peuvent avoir lieu en même temps. ===Les MSHR adressés explicitement=== Les '''MSHR adressés explicitement''' n'ont pas les contraintes des MSHR adressés implicitement. Avec eux, il est possible d'avoir plusieurs défauts de cache pointant vers la même ligne de cache, mais aussi vers le même mot mémoire. De tels défauts de cache apparaissent sur les processeurs à exécution dans le désordre, mais sont très rares, voire inexistants, sur les processeurs ''in-order''. L'idée est encore que chaque MSHR est associée à une table mémoire qui mémorise des entrées. Cette table n'est autre qu'une mémoire FIFO. Sauf que cette fois-ci, une entrée n'est pas associée à un mot mémoire. Une entrée est associée à un défaut de cache. Une entrée mémorise là encore la destination de la lecture ou la donnée de l'écriture suivi du bit R/W, ainsi que d'un bit de validité par entrée, mais aussi : la position du mot mémoire lu dans la ligne de cache. C'est cette dernière information qui n'était pas présente dans les MSHR adressés implicitement. De plus, le nombre d'entrée par MSHR n'est pas égal au nombre de mots mémoires dans une ligne de cache, mais peut être arbitrairement grand. L'avantage d'un tel cache est qu'il est capable de traité les défauts secondaires concernant un même mot mémoire, et ce, car les accès à la ligne de cache concernée se produisent dans l'ordre de génération des défauts (la table mémoire est une FIFO). ===Les MSHRs inversés=== Généralement, plus on veut supporter de défauts de cache, plus le nombre de MSHR et d'entrées augmente. Mais au-delà d'un certain nombre d'entrées et de MSHR, les MSHR adressés implicitement et explicitement ont tendance à bouffer un peu trop de circuits. Utiliser une organisation un peu moins gourmande en circuits est donc une nécessité. Cette organisation plus économe se base sur des MSHR inversés. Les MSHR inversés ne contiennent qu'une seule entrée, en quelque sorte : au lieu d’utiliser n MSHR de m entrées chacun, on va utiliser n × m MSHR inversés. La différence, c'est que plusieurs MSHR peuvent contenir un tag identique, contrairement aux MSHR adressés implicitement et explicitement. Lorsqu'un défaut de cache a lieu, chaque MSHR est vérifié. Si jamais aucun MSHR ne contient de tag identique à celui utilisé par le défaut, un MSHR vide est choisi pour stocker ce défaut, et une requête de lecture en mémoire est lancée. Dans le cas contraire, un MSHR est réservé au défaut de cache, mais la requête n'est pas lancée. Quand la donnée est disponible, les MSHR correspondant à la ligne qui vient d'être chargée vont être utilisés un par un pour résoudre les défauts de cache en attente. ===Les MSHR intégrés au cache=== Certains chercheurs ont remarqué que pendant qu'une ligne de cache est en train de subir un défaut de cache, celle-ci reste inutilisée, et son contenu est destiné à être perdu une fois le défaut de cache résolu. Ils se sont dit que, plutôt que d'utiliser des MSHR séparés, il vaudrait mieux utiliser la ligne de cache pour stocker les informations sur les défauts de cache en attente dans cette ligne de cache. Pour éviter tout problème, il faut rajouter un bit dans les bits de contrôle de la ligne de cache, qui sert à indiquer que la ligne de cache est occupée : un défaut de cache a eu lieu dans cette ligne, et elle stocke donc des informations pour résoudre les défauts de cache. ==L'entrelacement mémoire== Nous venons de voir comment une mémoire cache peut gérer plusieurs accès mémoire simultanés. Il se trouve que ce genre d'optimisation dépasse largement le cadre du cache. Les mémoires RAM ne sont pas en reste, elles aussi ! Il est possible de pipeliner l'accès à une mémoire RAM, en utilisant des techniques dites d''''entrelacement'''. Précisons cependant que cette optimisation n'est pas utilisée sur les mémoires SDRAM ou DDR modernes, du moins pas sans modifications majeures. Mais divers ordinateurs assez anciens, dont des superordinateurs, ont utilisé cette technique. La technique demande d'utiliser plusieurs mémoires séparées. Sur les anciens ordinateurs, les mémoires en question sont des chips mémoire, à savoir des circuits intégrés de DRAM. Pour faire plus général, nous allons utiliser le terme de '''banques''', ou encore de bancs mémoire. La différence est qu'il existe des mémoires multi-banque, qui regroupent plusieurs banques indépendantes dans un seul boitier, dans un seul chip mémoire. En clair, de telles mémoires regroupent plusieurs sous-mémoires dans un seul circuit intégré. Il est aussi possible d'utiliser plusieurs chips mémoire séparés, chacun ayant une banque. Reste à combiner ces banques pour former un pseudo-pipeline. ===Utiliser plusieurs banques sans entrelacement=== Sans optimisation particulière, les adresses sont réparties dans les banques comme indiqué ci-dessous. Il s'agit de ce qui s'appelle un arrangement horizontal, et nous avions vu celui-ci dans le chapiutre sur les mémoires SDRAM. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Avec cet arrangement, les bits de poids fort de l'adresse sont utilisées pour sélectionner la banque adéquate, et le reste de l'adresse est envoyé sur le bus d'adresse. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Adresse de banque !! Adresse dans la banque |- | Quelques bits de poids fort || Reste de l'adresse |} L'entrelacement change cette répartition, afin d'accélérer les accès mémoire. L'entrelacement classique vise à améliorer les accès à des adresses mémoires consécutives, mais des formes plus évoluées d'entrelacement visent à optimiser des accès mémoire arbitraires. Voyons-les dans l'ordre, du plus simple au plus complexe. ===L'entrelacement classique=== Avec l''''entrelacement classique''', des adresses consécutives sont réparties dans des banques consécutives. Précisons que cette attribution des adresses n'implique absolument pas la mémoire virtuelle ou n'importe quel mécanisme dans le processeur. La répartition décide que telle adresse mémoire va dans telle banque, à telle adresse dans la banque. Elle est donc le fait du contrôleur mémoire, donc en dehors du processeur (les contrôleurs mémoires n’étaient pas encore intégrés dans le processeur à l'époque). [[File:Répartition des adresses dans une mémoire interleaved.png|centre|vignette|upright=1.5|Répartition des adresses dans une mémoire interleaved.]] L'entrelacement simple permet d'accélérer les accès mémoire qui se font à des adresses consécutives. Avec l'entrelacement, chaque accès mémoire tombe dans une banque mémoire différente, ce qui fait qu'on peut démarrer un nouvel accès mémoire à chaque cycle d'horloge. Pendant qu'une banque est occupée par un accès mémoire, on démarre le suivant dans une autre banque, et ainsi de suite. Le tout se termine soit quand on a épuisé toutes les banques libres, soit quand l'accès en rafales se termine. Pas besoin d'attendre que la mémoire ait fini sa lecture/écriture avant de démarrer la lecture/écriture suivante. [[File:Pipemining mémoire.png|centre|vignette|upright=2.5|Pipelining mémoire]] L'animation suivante illustre bien le processus, avec quatres banques. [[File:Interleaving.gif|centre|vignette|upright=2.5|Pipelining mémoire]] L'entrelacement simple utilise les bits de poids faible pour sélectionner la banque, et les bits de poids fort pour la case mémoire. [[File:Adresse mémoire d'une mémoire entrelacée.png|centre|vignette|upright=2|Adresse mémoire d'une mémoire entrelacée]] Précisons que le temps d'accès mémoire ne change pas beaucoup avec l'entrelacement. Par contre, on peut faire plus d'accès mémoire simultanés. On peut démarrer un accès mémoire par cycle d'horloge, mais l'accès en lui-même prend plusieurs cycles. Les mémoires à entrelacement ont donc un débit supérieur aux mémoires qui ne l'utilisent pas. L'entrelacement classique pourrait être utilisé pour accélérer les transferts entre mémoire RAM et cache, qui se font en blocs d'adresses consécutives. Mais de nos jours, il n'est pas utilisé dans cette optique. Il faut dire qu'il est redondant avec le mode rafale des mémoires DRAM. Les deux font globalement la même chose et on voit mal comment utiliser les deux en même temps. Le mode rafale étant plus simple à implémenter, et plus léger niveau utilisation du bus de commande mémoire, il est préféré à l'entrelacement. Par contre, l'entrelacement a eu son heure de gloire sur d'anciens processeurs qui n'avaient pas de mémoire cache, alors qu'ils disposaient d'un pipeline. L'exemple typique est celui des nombreux ''processeurs vectoriels'', que nous n'avons pas encore abordé à ce stade du cours. De tels processeurs avaient un pipeline, une unité de calcul fortement pipelinée, et parfois même de l'exécution dans le désordre et du renommage de registres ! ===L'entrelacement par décalage=== L'entrelacement simple est très utile pour les accès en rafle ou équivalents. Par contre, il existe de rares situations où il n'est pas optimal. Une de ces situations est celle des accès en enjambée (en ''stride''), où l'on accède en série à des adresses sont séparées par N mots mémoires. [[File:Accès par enjambées.png|centre|vignette|upright=2|Accès par enjambées.]] De tels accès surviennent quand un logiciel accède à des structures de données spécifiques, à savoir des tableaux de structures, des matrices, ou d'autres structures de données dans le genre. Un cas classique est celui du parcours d'une matrice colonne par colonne. Les matrices de nombres sont mémorisées ligne par ligne, ce qui fait que parcourir une colonne demande de passer d'une ligne à la suivante et de faire des grands sauts en mémoire RAM, mais tous espacés par la même distance. Avec l'entrelacement simple, les accès en enjambée sont moins performants que les accès à des adresses consécutives, mais cela ne fonctionne pas trop mal. Le pire cas est celui où l'on a N banques et où les données sont justement placées toutes les N adresses. Des accès consécutifs vont tous tomber dans la même banque, on ne peut plus accéder à des banques différentes en parallèle. Mais en dehors de ce cas, on voit une amélioration par rapport à la situation sans entrelacement. Pour obtenir des performances maximales pour les accès en enjambées, il faut répartir les mots mémoires dans la mémoire autrement. L'organisation idéale est la suivante. : Dans les explications qui vont suivre, la variable N représente le nombre de banques, qui sont numérotées de 0 à N-1. On commence par organiser les N premières adresses comme une mémoire entrelacée simple : l'adresse 0 correspond à la banque 0, l'adresse 1 à la banque 1, etc. Pour le bloc suivant, on décale tout d'une adresse, à savoir qu'on commence à remplir les banques à partir de la seconde, non de la première. Une fois la fin du bloc atteinte, on finit de remplir le bloc en repartant du début du bloc. Le troisième bloc d'adresse subit le même traitement, sauf qu'on commence à remplir à partir de la seconde banque. Et on poursuit l’assignation des adresses en décalant d'un cran en plus à chaque bloc. Ainsi, chaque bloc verra ses adresses décalées d'un cran en plus comparé au bloc précédent. Si jamais le décalage dépasse la fin d'un bloc, alors on reprend au début. [[File:Mémoire interleaved par décalage.png|centre|vignette|upright=1.5|Mémoire entrelacée par décalage.]] En faisant cela, on remarque que les banques situées à N adresses d'intervalle sont différentes. Dans l'exemple du dessus, nous avons ajouté un décalage de 1 à chaque nouveau bloc à remplir. Mais on aurait tout aussi bien pu prendre un décalage de 2, 3, etc. Dans tous les cas, on obtient un entrelacement par décalage. Ce décalage est appelé le pas d'entrelacement, noté P. Le calcul de l'adresse à envoyer à la banque, ainsi que la banque à sélectionner se fait en utilisant les formules suivantes : * adresse à envoyer à la banque = adresse totale / N ; * numéro de la banque = (adresse + décalage) modulo N ; * décalage = (adresse totale * P) mod N. Avec cet entrelacement par décalage, on peut prouver que la bande passante maximale est atteinte si le nombre de banques est un nombre premier. Seulement, utiliser un nombre de banques premier peut créer des trous dans la mémoire, des mots mémoires inadressables. Malgré ce défaut, la technique a été utilisée sur quelques ordinateurs, avec l'exemple notable du superordinateur ''Burroughs Scientific Processor''. Pour éviter cela, il y a plusieurs solutions. Par exemple, on peut faire en sorte que N et la taille d'une banque soient premiers entre eux : ils ne doivent pas avoir de diviseur commun. Mais en pratique, elles n'ont pas vraiment été implémentées dans une vraie machine, et sont restées à l'état de recherche, aussi je les passe sous silence. ===L'entrelacement pseudo-aléatoire=== Une dernière méthode de répartition consiste à répartir les adresses dans les banques de manière "pseudo-aléatoire". L'idée exacte est de faire passer l'adresse dans une '''fonction de hachage''', plus ou moins complexe. Pour rappel, une fonction de hachage prend une entrée de grande taille et fournit en sortie un résultat de petite taille. Idéalement, elles doivent donner des résultats aussi différents que possible pour des entrées similaires, histoire de simuler une sorte de pseudo-aléatoire. Ici, une adresse est transformée en un numéro de banque. Une fonction de hachage simple ne fait que permuter des bits de l'adresse pour obtenir son résultat. Elle ne fait qu'échanger des bits de place avant de couper l'adresse en deux morceaux : un pour la sélection de la banque, et un autre pour la sélection de l'adresse dans la banque. Cette permutation est fixe, et ne change pas suivant l'adresse. Des fonctions de hachage plus complexes font des XOR bit à bit entre certains bits de l'adresse. ==Les contrôleurs mémoires SDRAM/DDR optimisés== Après avoir vu le cas des mémoires RAM, nous allons nous concentrer sur le cas particulier des mémoires SDRAM. Les mémoires SDRAM et DDR modernes sont capables de gérer plusieurs accès simultanés à la mémoire RAM, et leurs contrôleurs mémoire en font tout autant. C'est une différence majeure avec les mémoires asynchrones FPM/EDO, qui n'acceptaient qu'un seul accès mémoire à la fois. Leur contrôleur mémoire n'acceptait qu'un seul accès mémoire à la fois, c'était un contrôleur mémoire bloquant. Les contrôleurs mémoires des SDRAM sont eux non-bloquants et peuvent encaisser une dizaine d'accès mémoire à la fois. Peu de choses sont connues sur les contrôleurs de SDRAM/DDR modernes, les fabricants ne donnant que peu de détails dessus. Les rares simulateurs qui tentent de décrire leur fonctionnement, comme DRAM SIM I et II, sont particulièrement simples et ne vont pas dans le détail. Néanmoins, le peu qu'on sait est tout de même instructif. ===Rappel sur les SDRAM : tampon de ligne et commandes=== Les mémoires SDRAM sont des mémoires à tampon de ligne. Elles font un accès mémoire en deux étapes. La première étape recopie une ligne de N * 64 bits dans un tampon interne à la SDRAM. La seconde étape sélectionne une colonne, à savoir une donnée de 64 bits, qui est soit envoyée sur le bus de données pour une lecture, soit modifiée par une écriture. [[File:Mémoire à tampon de ligne.png|centre|vignette|upright=2|Mémoire à tampon de ligne]] Les accès mémoire sont traduits par un séquenceur mémoire en une série de commandes mémoires, qui sont séparées par des délais mémoire de quelques cycles d'horloge. Les délais sont très précis, et sont à respecter à la lettre. Une lecture ou une écriture se fait en maximum trois commandes : une commande PRECHARGE qui ferme la ligne précédemment utilisée, une commande ACT qui précise l'adresse de la ligne, et une commande READ ou WRITE qui précise l'adresse de la colonne et éventuellement la donnée à écrire. : Pour être plus précis, la commande PRECHARGE précharge les lignes de bits du plan mémoire à une tension particulière, ce qui les vide de leur contenu. Mais c'est un détail sans importance pour ce qui va suivre. Les SDRAM permettent de se passer de la première étape si des accès consécutifs se font dans la même ligne. Une fois activée, la ligne reste ouverte et on peut accéder plusieurs fois de suite dedans. On a alors juste à préciser l'adresse de colonne dedans. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | bgcolor="#FFA0FF" | PRECHARGE || bgcolor="#FFA0FF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || bgcolor="#A0FFFF" | READ (2) || bgcolor="#A0FFFF" | READ (3) || || || bgcolor="#A0FFFF" | READ (4) || bgcolor="#A0FFFF" | READ (5) || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | Donnée READ 1 || ||bgcolor="#A0FFFF" | Donnée READ 2 | bgcolor="#A0FFFF" | Donnée READ 3 || || || bgcolor="#A0FFFF" | Donnée READ 4 || bgcolor="#A0FFFF" | Donnée READ 5 |} Les SDRAM sont parfois capables de démarrer une commande avant que la précédente soit terminée. Mais le respect des délais mémoire est très limitant, ce qui fait qu'on ne peut pas parler de réel pipeline, comme c'est le cas sur les processeurs. Parlons plutot de '''commandes anticipées'''. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | || bgcolor="#A0FFFF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || || || bgcolor="#FFA0FF" | READ (2) || || || || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 | bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 | |} ===La mise en attente des accès mémoire=== Un contrôleur de SDRAM peut accepter plusieurs accès mémoire en même temps. Mais cela ne signifie pas que le contrôleur sera prêt à les traiter immédiatement. Pour éviter tout problème de disponibilité, le contrôleur met en attente les accès mémoire que le processeur lui envoie, pour les exécuter dès que possible. Les accès mémoire sont mis en attente dans une mémoire FIFO, histoire de les exécuter dans leur ordre d'arrivée, notamment pour renvoyer les lectures dans l'ordre demandé. Il y a aussi une mémoire FIFO pour les données à écrire et une FIFO pour les données lues. Cette dernière sert au cas où le cache ne soit pas disponible quand on lui envoie la donnée lue. [[File:Module d'interface avec la mémoire.png|centre|vignette|upright=2.5|Contrôleur mémoire avec mise en attente des requêtes processeur et autres optimisations.]] Des optimisations sont possibles dès la mémoire FIFO. Par exemple, on peut regrouper plusieurs accès à des données consécutives en un seul accès en rafale. Le contrôleur mémoire analyse les requêtes mises en attente et détecte si certaines se font à des adresses consécutives. Si c'est le cas, il fusionne ces requêtes en une lecture/écriture en rafale. Une telle optimisation est appelée la '''combinaison de lecture''' pour les lectures, et la '''combinaison d'écriture''' pour les écritures. ===Les optimisations liées aux commandes anticipées=== Les commandes anticipées permettent de faire un minimum de parallélisme mémoire. Même si elle parait très limitée, cette possibilité est décuplée par la présence de plusieurs banques dans la mémoire SDRAM. Les chips mémoires de SDRAM sont composés de plusieurs banques, chacune étant une sorte de mini-mémoire miniature. Chaque banque a son propre décodeur, son propre tampon de ligne, ses propres multiplexeurs de colonne, sa logique de rafraichissement mémoire, etc. C'est comme si une SDRAM regroupait plusieurs mémoires séparées dans un même circuit intégré. Et cela permet de faire plusieurs accès mémoire en même temps, dans des banques différentes. Il est possible de lancer un accès mémoire dans deux banques en même temps, par exemple. Ou encore, on peut lancer un nouvel accès mémoire dans une banque si elle est inoccupée. La possibilité améliore grandement la performance de la SDRAM. Mais nous en reparlerons dans un chapitre ultérieur. {|class="wikitable" |+ Pipelining basique sur les SDRAM |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 |- ! Banque Numéro 1 | || || || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || || || |- ! Banque Numéro 2 | || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || |- ! Banque Numéro 3 | || || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || |} L'implémentation n'a rien de vraiment compliqué. Il suffit d'utiliser une mémoire FIFO par banque. Ainsi, les accès dans une même banque se feront en série, dans l'ordre d'arrivée, mais des accès à des banques différentes se feront dans le désordre. Cela ne pose pas de problème car les accès se font à des adresses différentes, ce qui fait qu'il n'y a pas de conflits majeurs. [[File:Gestion parallèle des banques.png|centre|vignette|upright=2.5|Gestion parallèle des banques.]] ===Le ré-ordonnancement des commandes mémoires=== Un contrôleur mémoire moderne est capable de changer l'ordre des requêtes mémoires, pour gagner en performances. En clair : il peut faire l'équivalent mémoire de l'exécution dans le désordre des processeurs haute performance. Une différence majeure est que le contrôleur mémoire ne remet pas les accès mémoire dans l'ordre avant de les envoyer au processeur. C'est le processeur qui remet en ordre les accès mémoire, dans sa ''load queue''. Pour cela, les requêtes mémoire sont "numérotées", à savoir qu'on leur attribue un identifiant binaire. Le contrôleur mémoire exécute les lectures/écritures dans le désordre, mais garde la trace de leur identifiant. Il sait dans quel ordre il exécute les accès mémoire et connait leur latence. Ce qui fait qu'il sait que la donnée lue à tel instant est associée à la requête mémoire numéro X. Pour les lectures, il renvoie la donnée lue avec l'identifiant. Le processeur sait alors à quelle lecture la donnée lue correspond et il se débrouille en interne pour gérer la situation. En clair : le processeur voit les accès mémoire dans le désordre, c'est lui qui les remet en ordre. Maintenant que ces précisions sont faites, posons cette question : quel est l'intérêt de faire les accès mémoire dans le désordre ? Accéder à des banques en parallèle est une réponse possible, mais le ré-ordonnancement est surtout utile si tous les accès mémoire atterrissent dans la même banque et dans la même ligne. Elle marche si plusieurs accès à une même ligne ne sont pas consécutifs, et qu'il y a des accès entre les deux. Après ré-ordonnancement, ces accès mémoire sont exécutés l'un à la suite de l'autre. Pour rendre le tout plus concret, voici un exemple. Imaginez que l'on sait les 3 accès mémoire suivants : * Une lecture ligne A ; * Une écriture ligne B ; * Une lecture ligne A. Sans réordonnancent, on doit émettre deux commandes PRECHARGE : une quand on passe de la ligne A à la ligne B, et une autre pour le passage inverse. pour éviter cela, le contrôleur mémoire peut retarder l'écriture, ou au contraire avancer la seconde lecture. le résultat est alors le suivant : * Lecture ligne A ; * Lecture ligne A ; * Une écriture ligne B. On a donc deux accès consécutifs à la même ligne, suivi par une écriture dans une autre ligne. La technique marche parce que l'on a un mix adéquat de lectures et d'écritures. Mais il existe des possibilités de réorganisation autres qui ne sont pas valides. Il faut par exemple prendre garde à éviter d'intervertir lectures et écritures à une même adresse, sous peine de problèmes. Encore une fois, il faut faire attention aux dépendances mémoires. Le controleur mémoire teste les dépendances mémoires avant d'envoyer des commandes à la mémoire DDR/SDRAM. ==L'entrelacement sur les mémoires SDRAM== L''''entrelacement''' fonctionne sur les mémoires SDRAM, mais il doit être fortement modifié. Concrètement, tout ce qui a étré dit plus est inapplicable pour les SDRAM, du fait de la présence du tampon de ligne. Des adresses consécutives atterrissent déjà dans la même ligne, ce qui fait qu'on n'a pas besoin d'entrelacement au niveau d'une ligne. Et ne parlons pas de ce la présence de rangées et de canaux mémoire ! Cependant, il peut y avoir un entrelacement lié à la présence des banques SDRAM. L'entrelacement est géré juste avant le séquenceur mémoire. Le '''circuit d'entrelacement''' intervertit certains bits de l'adresse lors des accès mémoires. On trouve aussi des mémoires FIFOs pour mettre en attente les requêtes mémoire provenant du processeur. Elles permettent de recevoir plusieurs accès mémoire consécutifs. Si vous vous demandez comment plusieurs accès mémoire consécutifs peuvent avoir lieu, sachez que c'est une bonne question. Pour le moment, nous allons dire que ces accès proviennent du processeur ou de la mémoire cache, sans expliquer comment. Le contrôleur mémoire reçoit une série d'accès mémoire assez rapprochés, qu'il doit exécuter au mieux. [[File:Controleur mémoire d'une SDRAM avec entrelacement.png|centre|vignette|upright=2.5|Contrôleur mémoire d'une SDRAM avec entrelacement.]] Pour gérer l'entrelacement, le contrôleur de SDRAM prend en entrée l'adresse envoyée par le processeur, et la découpe en plusieurs champs : un pour sélectionner la banque adéquate, un autre pour la ligne, un autre pour la colonne, un autre pour la rangée. Une solution simple désactive l'entrelacement. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de banque || Adresse de ligne || Adresse de colonne |} Si cette solution fonctionne bien si les accès mémoire sont assez aléatoires, en pratique, mieux vaut utiliser l'entrelacement. ===L'entrelacement de banques/lignes=== Une première méthode d'entrelacement est l''''entrelacement de banques'''. Elle répartit deux lignes consécutives dans deux banques différentes. Pour comprendre l'idée, prenons un exemple. Imaginons une mémoire avec deux banques et 4 lignes. Imaginons qu'on parcoure/balaye la mémoire RAM en partant des adresses basses. Sans entrelacement de ligne, les accès se feront comme suit, de gauche à droite : {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Banque numéro 1 !! colspan="4" | Banque numéro 2 |- | Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 || Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 |} Avec l'entrelacement de banques, les accès se feront comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 |- | Ligne 1 || Ligne 1 || Ligne 2 || Ligne 2 || Ligne 3 || Ligne 3 || Ligne 4 || Ligne 4 |} Avec N banques, la première ligne ira dans la première banque, la seconde ligne dans la seconde, et ainsi de suite jusqu'à la énième banque qui va dans la banque numéro N. Et la ligne N+1 repart dans la première banque. L'avantage est que passer d'une ligne à la suivante est assez rapide. Au lieu de devoir fermer la première ligne et ouvrir la suivante on ouvrira directement la ligne suivante dans une banque différente, ce qui sera plus rapide. La commande PRECHARGE pourra être envoyée à la seconde banque en avance. Pour cela, l'adresse est découpée comme suit. {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne (précise à l'octet) |} ===L'entrelacement de rangées=== Il est aussi possible de faire la même chose, mais avec les rangées. Pour simplifier fortement, une rangée est simplement un chip mémoire. En réalité, les rangées ne sont pas des chip mémoire, mais un ensemble de chips mémoire regroupés ensemble histoire d'atteindre les 64 bits du bus de données. Par exemple, une rangée peut combiner 8 chips mémoire avec un bus de données de 8 bits chacun pour obtenir les 64 bits du bus de données d'une SDRAM. Mais nous passons ce détail sous silence dans les explications qui vont suivre, par souci de simplification. Pour faire comprendre l'entrelacement de rangée, le mieux est d'utiliser un exemple, le même que précédemment. Sans entrelacement de rangée, on change de chip mémoire une fois qu'on a balayé toutes ses adresses. {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Rangée numéro 1 !! colspan="4" | Rangée numéro 2 |- | Banque 1 || Banque 2 || Banque 3 || Banque 4 || Banque 1 || Banque 2 || Banque 3 || Banque 4 |} Avec l'entrelacement de ligne, on change de rangée dès qu'on change de banque/ligne. {|class="wikitable" |+ Adresse mémoire |- ! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 |- | Banque 1 || Banque 1 || Banque 2 || Banque 2 || Banque 3 || Banque 3 || Banque 4 || Banque 4 |} En clair, quand on a épuisé toutes les banques dans une même rangée, on passe à la rangée suivante au lieu de rester dans la même rangée. Notons que dans l'exemple précédent, on a combiné l'entrelacement de rangée et de banque, mais on aurait pu utiliser l'entrelacement de rangée seul. Mais ce n'est pas le cas le plus courant en pratique. Toujours est-il qu'en combinant entrelacement de rangée et de ligne, le découpage de l'adresse est le suivant : {|class="wikitable" |+ Adresse mémoire |- | Adresse de ligne || Numéro de rangée || Adresse de banque || Adresse de colonne (précise à l'octet) |} D'autres méthodes d'entrelacement sont possibles, mais elles sont plus complexes. Chaque contrôleur mémoire fait un peu à sa sauce. Ils permettent en général de choisir entre plusieurs entrelacements. Au minimum, ils permettent de désactiver l'entrelacement, d'activer un entrelacement de ligne, et d'autres entrelacements plus complexes, dont certains sont propriétaires et tenus secrets par le fabricant. ===L'entrelacement avec le ''dual channel''=== [[File:Dual channel slots.jpg|vignette|Slots mémoires avec ''dual channel''.]] L'usage du ''dual channel'' complique encore l'entrelacement. Là encore, il y a deux grandes solutions : avec et sans entrelacement des canaux mémoire. Rappelons le principe : deux barrettes de RAM sont accédées en parallèle. Et pour cela, il faut utiliser l'entrelacement. Typiquement, chaque barrette mémoire fournit 64 bits, ce qui fait que l'on peut accéder à 128 bits d'un seul coup, par exemple avec un accès en rafale. Sans ''dual channel'', la première barrette correspond à la moitié haute de la RAM, la seconde barrette correspond à la moitié basse. Avec ''dual channel'', une forme spécifique d'entrelacement est activée. Concrétement, deux blocs de 64 bits sont placés dans des canaux mémoire séparés. Idem avec du triple ou quadruple canal, mais c'est alors trois ou quatre blocs de 64 bits qui sont dispersés dans des canaux consécutifs. Le découpage de l'adresse est alors le suivant : {|class="wikitable" |+ Adresse mémoire |- ! colspan="6" | Sans entrelacement |- | Numéro de canal/barrette || Reste de l'adresse || Position de l'octet dans un bloc de 64 bits |- | colspan="6" | |- ! colspan="6" | Avec entrelacement |- | Reste de l'adresse || Numéro de canal/barrette || Position de l'octet dans un bloc de 64 bits |} [[File:Décodage d'adresse avec dual channel.png|centre|vignette|upright=2|Décodage d'adresse avec dual channel.]] Les contrôleurs de SDRAM précédents sont assez basiques et ne représentent pas les contrôleurs les plus évolués. Ils étaient utilisés sur les anciens PC, à une époque où ils étaient encore sur la carte mère du processeur. Mais de nos jours, le contrôleur mémoire est intégré au processeur. Et il incorpore de nombreuses optimisations afin de gagner en performances. Malheureusement, ces optimisations sont elles-mêmes dépendantes des optimisations intégrées dans le processeur, ce qui fait qu'on ne peut pas en parler ici. Elles demandent que le contrôleur mémoire reçoive plusieurs accès mémoire simultanés, ce qui n'a aucun sens à ce stade du cours. Aussi, nous allons les passer sous silence pour le moment. Nous les verrons dans le chapitre sur le parallélisme mémoire. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=La désambiguïsation mémoire | prevText=La désambiguïsation mémoire | next=Les processeurs superscalaires | nextText=Les processeurs superscalaires }} </noinclude> 82104eidsmkg60uo56hnmch5fekq0cu 764719 764717 2026-04-23T21:24:59Z Mewtow 31375 /* Les optimisations liées aux commandes anticipées */ 764719 wikitext text/x-wiki Dans ce chapitre, nous allons voir les techniques qui permettent de gérer plusieurs accès mémoire simultanés directement au niveau du cache ou de la mémoire RAM. Par plusieurs accès mémoire simultanés, vous pensez sans doute à l'usage de cache multiports, voire de mémoires RAM multiport. C'est en effet une solution, mais ce n'est clairement pas la seule. Par exemple, il est possible de pipeliner l'accès au cache, voire à la mémoire RAM. Il existe de nombreuses techniques de '''parallélisme mémoire''', et nous allons les voir dans ce chapitre. Pour donner un exemple, il est possible de pipeliner les accès mémoire. Il est possible de pipeliner l'accès au cache et/ou l'accès à la mémoire, et nous verrons comment faire dans ce chapitre. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, qu'ils aient ou non un pipeline dynamique. Par contre, pipeliner la mémoire est une technique ancienne, aujourd'hui peu utilisée. Elle était utilisée sur des très vieux ordinateurs, qui avaient un pipeline mais pas de mémoire cache. Mais de nos jours, les mémoires SDRAM et DDR ne sont pas adaptées pour ça, il est très difficile de les pipeliner correctement. Le parallélisme mémoire est utile aussi bien sur des processeurs à émission dans l'ordre que dans le désordre. Par exemple, un processeur ''in-order'' avec des lectures non-bloquantes peut e, profiter. Il peut techniquement lancer une seconde lecture, après avoir rencontré une première lecture non-bloquante. En général, le nombre de lectures consécutives est cependant limité à moins d'une dizaine. À l'opposé, les processeurs sans lectures non-bloquantes profitent pas du parallélisme mémoire. À la rigueur, ils peuvent en profiter s'ils intègrent des techniques de préchargement. Pendant que le processeur exécute une lecture/écriture, il peut précharger une autre donnée en parallèle. Inutile de dire que sans pipeline processeur, le parallélisme mémoire ne sert pas à grand-chose. ==Le ''line fill buffer'' et les techniques associées== Lors d'un défaut de cache, le processeur doit attendre que toute la ligne de cache soit chargée avant d'être utilisable. Or, la taille d'une ligne de cache est supérieure à la largeur du bus mémoire, ce qui fait qu'une ligne de cache est chargée en plusieurs fois, morceaux par morceaux, mot mémoire par mot mémoire. Le chargement peut se faire directement dans le cache, mais ce n'est pas une solution très pratique. À la place, beaucoup de processeurs ajoutent une mémoire tampon entre la RAM et le cache, appelée le '''tampon de remplissage de ligne''' (''line-fill buffer'') dans les processeurs modernes. Lors d'un défaut de cache, le processeur charge la donnée de la RAM dans le tampon de remplissage de ligne, mot mémoire par mot mémoire. Une fois plein, le tampon de remplissage de ligne est recopié dans la ligne de cache. Le tampon de remplissage de ligne contient une ligne de cache, si ce n'est que certains bits de contrôle ne sont pas présents. Le tampon de remplissage de ligne contient un bit de validité pour chaque mot mémoire de la ligne de cache, qui indiquent si le mot mémoire a été chargé. Par exemple, prenons un processeur 64 bits, qui gère donc des mots mémoire de 8 octets, avec des lignes de cache 256 octets/32 mots mémoire. Le tampon de remplissage de ligne contiendra 32 bits de validité. Si le processeur a chargé les 6 premiers mots mémoire, les 6 premiers bits de validité seront mis à 1, les autres seront encore à zéro. Il faut noter que la disponibilité de la ligne complète se détermine assez facilement en faisant un ET logique entre tous les bits de validité. Le processeur sait ainsi quand la ligne de cache est disponible entièrement et donc quand la transférer dans le cache. La même chose existe avec une hiérarchie de cache, sauf que l'on trouve une mémoire tampon entre chaque niveau de cache. Il y a un tampon de remplissage de ligne entre le cache L1 et le cache L2, entre le cache L2 et le L3, etc. Si le cache ne gère qu'un seul défaut de cache à la fois, le ''fill line buffer'' est une mémoire très simple, qui ne mémorise qu'une seule ligne de cache. Et cela vaut aussi bien pour un cache bloquant que non-bloquant. Mais sur les caches capables de gérer plusieurs défauts simultanés, le tampon de remplissage de ligne est une mémoire de type FIFO ou LIFO, capable de mémoriser plusieurs lignes de cache. Notons que le tampon de remplissage de ligne est très utile pour implémenter certaines techniques, par exemple le contournement du cache. Nous avions vu dans le chapitre sur le cache que certains accès mémoire doivent contourner le cache, pour des raisons de cohérence des caches. C'est notamment nécessaire pour accéder aux périphériques, mais c'est aussi utile pour des raisons de performances dans des cas très spécifiques. Les accès qui contournent le cache se font directement dans le tampon de remplissage de ligne : le processeur écrit ou lit les données depuis ce tampon de remplissage de ligne, sans accéder au cache. ===L'''early restart'' et le ''critical word load''=== La présence du ''line buffer'' permet une optimisation assez intéressante, qui permet de réduire la latence des défauts de cache. L'optimisation consiste à lire un mot mémoire dans le tampon de remplissage de ligne, même si la ligne de cache complète n'a pas encore été chargée. Il existe deux manières de faire cela, qui portent les noms d'''early restart'' et de ''critical word load''. La première est la version la plus simple, la seconde est plus complexe mais plus performante. Avec la technique d''''''early restart''''', la ligne de cache est chargée normalement, en partant de son premier mot mémoire. Dès que le mot mémoire lu/écrit par le processeur est copié dans le ''line fill buffer'', il est envoyé au processeur immédiatement. Illustrons le tout par un exemple, où une ligne de cache fait 16 mots mémoire. Le processeur effectue une lecture, qui lit le 5ème mot mémoire. Sans ''early restart'', le processeur doit charger les 16 mots mémoire avant de faire la lecture dans le cache. Avec ''early restart'', le processeur reçoit la donnée dès que le 5ème mot mémoire est disponible. Le processeur doit attendre que les 4 premiers mots mémoire soient chargés, puis le 5ème mot mémoire arrive et est envoyé directement au processeur, il est lu directement depuis le ''line fill buffer''. Les 11 mots mémoire suivants sont ensuite chargés dans le cache pendant que le processeur fait des calculs dans son coin. Le ''critical word load'' est une optimisation de la technique précédente où le chargement de la ligne de cache commence directement à la donnée demandée par le processeur. Pour reprendre l'exemple précédent, où le processeur demande le 5ème mot mémoire sur 16, le ''critical word load'' charge le 5ème mot mémoire en premier et l'envoie au processeur, ce qui fait qu'il est chargé très rapidement. Pas besoin d'attendre que le processeur charge les 4 mots mémoire précédents comme avec l'''early restart''. Dans le détail, le processeur charge le 5ème mot mémoire en premier, puis charge les 11 suivants, et termine par les 4 mots mémoire du début. En clair, le ''critical word load'' commence par charger le mot mémoire lu, puis les blocs suivants, avant de revenir au début du bloc pour charger les blocs restants. Ainsi, la donnée demandée par le processeur sera la première disponible. Pour cela, l'organisation du tampon de remplissage de ligne est modifiée de manière à rendre cela possible. Il a une taille égale à une ligne de cache complète, qui contient elle-même plusieurs mots mémoire. Dans le ''line-fill buffer'', chaque mot mémoire est stocké avec un tag, qui indique l'adresse du mot mémoire stocké dans le ''line-fill buffer''. Le ''line-fill buffer'' est donc un cache un peu particulier, qui fonctionne comme un cache du point de vue du processeur, comme une mémoire FIFO pour les transferts avec le cache. Ainsi, un processeur qui veut lire dans le cache après un défaut peut accéder à la donnée directement depuis le tampon de remplissage de ligne, alors que la ligne de cache n'a pas encore été totalement recopiée en mémoire. ==Les caches pipelinés== Il est possible de pipeliner l'accès au cache, ce qui demande juste de rajouter des registres au bon endroits et quelques circuits de contrôle. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, même ceux avec un pipeline dynamique. Et l'implémentation peut se faire de plusieurs manières différentes. Il faut dire que découper une mémoire cache en plusieurs étapes peut se faire de plusieurs manière. Commençons par la plus simple. Beaucoup de processeurs des années 2000 avaient une fréquence peu élevée comparée aux standards d'aujourd'hui, ce qui fait que leur cache avait bien un temps d'accès d'un cycle d'horloge. Mais l'accès au cache se faisait en deux cycles d'horloge : un cycle pour calculer une adresse et un cycle pour l'accès mémoire proprement dit. Et ces deux étapes étaient pipelinées, à savoir que deux micro-opérations mémoire peuvent s'exécuter en même temps : une dans l'étage de calcul d'adresse, une autre dans le cache. L'unité mémoire était donc pipelinée, alors que l'accès au cache ne l'était pas. Les processeurs qui implémentaient cette technique regroupent les micro-architectures K5 et K6 d'AMD, les processeurs Intel de micro-architecture P6 (Pentium 2 et 3) et quelques autres. Il est aussi possible de pipeliner l'accès au cache lui-même. Avec un cache pipeliné, l'accès au cache ne se fait pas en un seul cycle, mais en plusieurs. Cependant, on peut lancer un nouvel accès au cache à chaque cycle d'horloge, comme un processeur pipeliné lance une nouvelle instruction à chaque cycle. L'implémentation est assez simple : il suffit d'ajouter des registres dans le cache. Pour cela, on profite que le cache est composé de plusieurs composants séparés, qui échangent des données dans un ordre bien précis, d'un composant à un autre. De plus, le trajet des informations dans un cache est linéaire, ce qui les rend parfois pour l'usage d'un pipeline. ===Le ''pipelining'' des caches ''direct-mapped''=== Voici ce qui se passe avec un cache directement adressé. Pour rappel ce genre de cache est conçu en combinant une mémoire RAM, généralement une SRAM, avec quelques circuits de comparaison et un MUX. L'accès au cache se fait globalement en deux étapes : la première lit la donnée et le ''tag'' dans la SRAM, et on utilise les deux dans une seconde étape. Nous avions vu il y a quelques chapitre comment pipeliner des mémoires, dans le chapitre sur les mémoires évoluées. Et bien ces méthodes peuvent s'utiliser pour la mémoire RAM interne au cache !L'idée est d'insérer un registre entre la sortie de la RAM et la suite du cache, pour en faire un cache pipéliné, à deux étages. Le premier étage lit dans la SRAM, le second fait le reste. L'implémentation sur les caches associatifs à plusieurs voies est globalement la même à quelques détails près. [[File:Cache directement adressé pipeliné - deux étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - deux étages]] Avec le circuit précédent, il est possible d'aller plus loin, cette fois en pipelinant l'accès à la mémoire RAM interne au cache. Pour comprendre comment, rappelons qu'une mémoire SRAM est composée d'un plan mémoire et d'un décodeur. L'accès à la mémoire demande d'abord que le décodeur fasse son travail pour sélectionner la case mémoire adéquate, puis ensuite la lecture ou écriture a lieu dans cette case mémoire. L'accès se fait donc en deux étapes successives séparées, on a juste à mettre un registre entre les deux. Il suffit donc de mettre un gros registre entre le décodeur et le plan mémoire. [[File:Cache directement adressé pipeliné - trois étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - trois étages]] Et on peut aller encore plus loin en découpant le décodeur en deux circuits séparés. En effet, rappelez-vous le chapitre sur les circuits de sélection : nous avions vu qu'il est possible de créer des décodeurs en assemblant des décodeurs plus petits, contrôlés par un circuit de prédécodage. Et bien on peut encore une ajouter un registre entre ce circuit de prédécodage et les petits décodeurs. : Théoriquement, toute l'adresse est fournie d'un seul coup au cache, la quasi-totalité des processeurs présentent l'adresse complète à un cache pipeliné. Mais le Pentium 4 fait autrement. Il faut noter que les premiers étages manipulent l'indice dans la SRAM, qui est dans les bits de poids faible, alors que les étages ultérieurs manipulent le ''tag'' qui est dans les bits de poids fort. Les concepteurs du Pentium 4 ont alors décidé de présenter les bits de poids faible lors du premier cycle d'accès au cache, puis ceux de poids fort au second cycle. Pour cela, l'ALU fonctionnait à une fréquence double de celle du processeur, tout comme le cache L1. Il n'y avait pas de pipeline proprement dit, mais cela réduisait grandement la latence d'accès au cache. ===Le ''pipelining'' des caches splittés/sériels=== Il est aussi possible de pipeliner un cache dits splittés, aussi appelés à accès sériel. Pour rappel, les caches à accès sériel vérifient si il y a succès ou défaut de cache, avant d'accéder aux lignes de cache en cas de succès. Ils font donc différemment des autres caches, qui accèdent à une ligne de cache, avant de déterminer s'il y a succès ou défaut en lisant le tag de la ligne de cache. Les caches sériels disposent de deux SRAM : une pour les tags des lignes de cache et une pour les données. Ils accèdent à la SRAM pour les tags, avant d’accéder à la SRAM des données en cas de succès de cache. Vu que l'accès se fait en deux étapes, une vérification des tags suivie de la lecture/écriture des données, il est facile à pipeliner. Pipeliner le cache permet de régler le problème des accès au cache L1, et elle est tout le temps utilisé sur les processeurs modernes. Mais que faire en cas de défaut de cache ? ==Les caches non bloquants== Un '''cache bloquant''' est un cache auquel le processeur ne peut pas accéder pendant un défaut de cache. Il faut attendre que la lecture ou écriture en RAM soit terminée avant de pouvoir utiliser de nouveau le cache. Un '''cache non bloquant''' n'a pas ce problème : on peut l'utiliser pendant un défaut de cache. Les caches non bloquants permettent de démarrer une nouvelle lecture ou écriture alors qu'une autre est en cours, ce qui permet d'exécuter plusieurs lectures ou écritures en même temps. ===Les ''Miss Handling Status Registers''=== Lors d'un défaut de cache, la mémoire RAM est consultée pendant le défaut de cache, mais le cache est inutilisé. Un défaut de cache n'utilise pas le cache, ce n'est qu'un accès en mémoire RAM, sur le bus mémoire (ou un accès aux niveaux de cache inférieurs, peu importe). Le cache en lui-même est laissé libre, rien n’empêche d'y accéder, il est en réalité intrinsèquement non-bloquant. Les caches, bloquants comme non-bloquants, sont en réalité composés d'une mémoire cache proprement dite, entourée de circuits qui servent d'interface entre le processeur et le cache lui-même. Et parmi les circuits tout autour, certains gèrent l'accès au cache lors d'un défaut de cache. Ils sont regroupés sous le terme de '''''Miss Handling Architecture''''' (MHA). La différence entre un cache bloquant et un cache non-bloquant est en réalité liée à l'implémentation de la MHA. Les caches bloquants coupent volontairement l'accès au cache lors d'un défaut, car les défauts sont plus simples à gérer ainsi. Le défaut de cache rapatrie une donnée depuis la RAM, qui sera écrite dans le cache. Et il ne faut pas qu'une tentative d'accès à cette donnée ait lieu avant qu'elle ne soit chargée. Mais ce blocage est général et touche tout le cache, alors que seule une ligne de cache est concernée par le défaut de cache. L'idée derrière un cache non-bloquant est que seule la ligne de cache est bloquée, mais les autres sont accessibles. L'idée est alors de mémoriser les lignes de cache concernées par le défaut de cache, afin d'en bloquer l'accès. À chaque accès, on vérifie si la ligne de cache est déjà réservée par un défaut de cache. La lecture/écriture est alors bloquée si c'est le cas, mais elle accepte les accès sinon. Pour cela, la ''Miss Handling Architecture'' contient des registres qui mémorisent des informations sur les défauts de cache en cours. Ils portent le nom de '''''miss status handling registers''''', que j’appellerais dorénavant MSHR, qui sont aussi appelés des '''''miss buffer'''''. Le contenu des MSHR varie beaucoup suivant le processeur, mais ces derniers stockent au minimum les informations suivantes : * Le numéro de la ligne de cache dans laquelle les données sont chargées. * Un bit de validité qui indique si le MSHR est vide ou pas, qui est mis à 0 quand le défaut de cache est résolu. * Un ou plusieurs champs de lecture/écriture, qui contiennent des informations sur la lecture/écriture. ** Pour une lecture, elle contient des informations sur la destination de la donnée, à savoir qui prévenir quand le défaut de cache est terminé. C'est parfois un nom/numéro de registre (celui dans lequel charger la donnée), mais c'est souvent le numéro de l'entrée dans le ''load/store queue''. ** Pour les écritures, elle contient la donnée à écrire, ou éventuellement un numéro de ''load/store queue'' où se trouve la donnée à écrire. Il faut noter que le bus mémoire ne peut gérer qu'un seul défaut de cache à la fois. Aussi, il est intéressant de regarder ce qui se passe lorsqu'un second défaut de cache survient, pendant qu'un premier est en cours. Dans ce cas, il y a deux réponses qui correspondent à deux types de caches non-bloquants, qui portent les noms barbares de caches de type succès après défaut et défaut après défaut. Sur le premier type, il ne peut pas y avoir plusieurs défauts de cache simultanés. Dès qu'un second défaut de cache survient, le cache stoppe son activité et on ne peut plus démarrer de nouvelle lecture/écriture, tant que le premier défaut de cache n'est pas résolu. Le second type est plus souple et autorise la survenue de plusieurs défauts de cache simultanés. Du moins, jusqu'à une certaine limite, car le cache ne peut supporter qu'un nombre limité d'accès mémoires simultanés (pipelinés). ===Les accès simultanés à une même ligne de cache=== Il arrive que le processeur fasse plusieurs accès mémoire simultanés à la même ligne de cache. Si la ligne de cache en question n'a pas encore été chargée dans le cache, alors on a plusieurs défauts de cache consécutifs pour la même ligne de cache, et le cache non-bloquant doit gérer la situation. Pour la suite, il va falloir faire une petite distinction entre les défauts primaires et secondaires. Imaginons qu'un défaut de cache ait lieu et demande à charger une donnée dans la ligne de cache numéro N. Il s'agit du premier défaut impliquant cette ligne de cache précise, ce qui lui vaut le nom de '''défaut de cache primaire'''. Mais par la suite, d'autres accès mémoire à la même ligne de cache ont lieu, alors que la ligne de cache n'est pas encore disponible. Dans ce cas, il s'agit de '''défauts de cache secondaires'''. Pour l'unité d'accès mémoire, les défauts de cache primaire et secondaire sont différents (ils prennent tous une entrée dans la ''load/store queue''). Mais pour le cache, ils ne correspondent qu'à un seul accès au cache : celui qui demande de charger la ligne de cache demandée. Les défauts de cache primaire et secondaire à la même ligne de cache se voient attribuer un MSHR unique. La gestion des défauts de cache secondaires dépend du cache non-bloquant. La solution la plus simple ne permet pas les défauts de cache secondaires. Le cache ne permet pas deux défauts de cache simultanés pour la même ligne de cache. Les autres solutions le permettent, en fusionnant des accès simultanés à la même ligne de cache en un seul au niveau des MSHR. Dans tous les cas, détecter les défauts de cache secondaires sont un problème qu'il faut détecter. La ''Miss Handling Architecture'' doit détecter les défauts de cache secondaire. Pour cela, elle procède comme suit. Lors de chaque défaut de cache, la MHA récupère le numéro de la ligne de cache associé. Il vérifie alors chaque MSHR pour vérifier s'il contient le numéro en question. S'il n'y a aucune correspondance dans les MSHR, alors c'est signe que le défaut de cache est un défaut primaire. Mais s'il y en a une, alors c'est un défaut secondaire. Évidemment, cela signifie que lors d'un défaut de cache, le numéro de ligne de cache est envoyé à tous les MSHR, pour comparaison. Les MSHR sont donc regroupés dans une mémoire associative, une sorte de mini-cache, faciliter l'implémentation. Lors d'un défaut de cache primaire, l'accès à un cache non-bloquant se fait comme suit : le processeur envoie une adresse au cache, accède à celui-ci, et détecte la survenue d'un défaut de cache. Il en profite alors pour attribuer une ligne de cache dans laquelle sera chargée ce défaut. L'attribution est très simple dans le cas des caches ''direct mapped'', ou associatifs par voie, pour lesquels l'attribution se fait assez simplement. Il mémorise alors cette information dans les MSHR, après avoir vérifié que le défaut de cache n'était pas un défaut secondaire. Lors des accès ultérieurs à une adresse proche, censée être dans la même ligne de cache, le processeur va encore une fois rencontrer un défaut de cache. Il va alors déterminer le numéro de la ligne de cache associée à l'adresse, et comparer ce numéro avec les MSHRs. Si un MSHR contient ce numéro, c'est signe que le défaut de cache est un défaut secondaire. La MHA réagit alors différemment selon le processeur considéré. Une première solution n'autorise pas les défauts de cache secondaires. Si l'un d'entre eux survient, le processeur est gelé par un ''pipeline stall'', une bulle de pipeline. Une autre solution fusionne les défauts de cache secondaires avec le défaut de cache primaire : tout cela ne correspond qu'à un seul défaut de cache pour lui. ===Les MSHR simples=== Un cache non-bloquant à '''MSHR simple''' contient juste plusieurs MSHR qui mémorisent juste un numéro de ligne de cache, un bit de validité, le champ de lecture/écriture, et l'adresse exacte de lecture/écriture. Avec cette organisation, il est possible d'avoir plusieurs défauts de cache séparés, mais à la condition que chaque défaut accède à une ligne de cache différente. Deux accès simultanés à une même ligne de cache ne sont pas possibles, les défauts de cache secondaires ne sont pas autorisés. Ainsi, chaque défaut de cache se voit attribuer son propre MSHR, chacun contient un numéro de ligne de cache différent. Pour comprendre pourquoi c'est impossible de gérer les défauts secondaires, il faut regarder le champ de lecture/écriture. Si on veut effectuer plusieurs écritures consécutives à la même adresse, le MSHR n'aura pas de quoi mémoriser les deux données à écrire. Il pourra mémoriser la première donnée à écrire, pas la seconde. Même chose lors d'une lecture : le champ lecture/écriture peut mémorisr la destination de la première lecture, pas de la seconde. L'avantage est que la MHA n'a besoin que des MSHR et de quelques circuits annexes. Les autres solutions rajoutent des circuits annexes pour gérer les défauts de cache secondaires, qui utilisent beaucoup de circuits. Le cout en circuit est donc élevé, mais le gain en performance est là. Passons maintenant aux caches non-bloquants qui autorisent les défauts de cache secondaire. La solution la plus simple consiste à utiliser ===Les MSHR adressés implicitement=== Avec les '''MSHR adressés implicitement''', il est possible de fusionner plusieurs accès mémoire à une même ligne de cache, mais sous une condition très importante : ces accès lisent/écrivent des mots mémoire différents. Par exemple, imaginons qu'une ligne de cache contienne 8 mots mémoire de 64 bits. Si un premier accès mémoire lit le mot mémoire numéro 7 (dernier mot mémoire de la ligne), et le second accès le mot mémoire numéro 3, alors la fusion est possible. Mais si deux accès mémoire veulent lire/écrire le mot mémoire numéro 7, alors la fusion n'est pas possible et le processeur se bloque, un ''pipeline stall'' survient. Un MSHR adressé implicitement est un MSHR simple, qui contient naturellement un numéro de ligne de cache (tag) et un bit de validité, sauf que le champ de lecture/écriture est dupliqué. Les différents champs lecture/écriture d'un MSHR sont regroupés dans une mémoire RAM/cache qui contient autant d'entrées qu'il y a de mot mémoire dans une ligne de cache. Chaque entrée de la table est associée à un mot mémoire de la ligne de cache et stocke des informations sur celui-ci. Une entrée mémorise, au minimum : * Un champ de lecture/écriture qui contient soit la destination de la lecture, soit la donnée à écrire. * Un bit R/W permettant d'interpréter correctement le champ de lecture/écriture. * Un bit de validité pour chaque entrée de la table, qui dit si un défaut de cache antérieur accède déjà à ce mot mémoire. La fusion de deux défauts de cache est ainsi assez simple. Un défaut de cache primaire/secondaire configure l'entrée associée au mot mémoire lu/écrit. Si un défaut secondaire ultérieur lit/écrit un mot mémoire différent (dont le bit de validité de l'entrée dans la table est à zéro), pas de conflit : il configure une autre entrée, vide. Mais s'il lit/écrit un mot mémoire pour lequel l'entrée de la table est occupée, il y a conflit, le processeur est bloqué par un ''pipeline stall'', une bulle de pipeline. Avec cette organisation, le nombre de MSHR indique combien de lignes de cache peuvent être lues en même temps depuis la mémoire. Quant au nombre d'entrées par MSHR, il détermine combien d'accès mémoires qui ne se recouvrent pas peuvent avoir lieu en même temps. ===Les MSHR adressés explicitement=== Les '''MSHR adressés explicitement''' n'ont pas les contraintes des MSHR adressés implicitement. Avec eux, il est possible d'avoir plusieurs défauts de cache pointant vers la même ligne de cache, mais aussi vers le même mot mémoire. De tels défauts de cache apparaissent sur les processeurs à exécution dans le désordre, mais sont très rares, voire inexistants, sur les processeurs ''in-order''. L'idée est encore que chaque MSHR est associée à une table mémoire qui mémorise des entrées. Cette table n'est autre qu'une mémoire FIFO. Sauf que cette fois-ci, une entrée n'est pas associée à un mot mémoire. Une entrée est associée à un défaut de cache. Une entrée mémorise là encore la destination de la lecture ou la donnée de l'écriture suivi du bit R/W, ainsi que d'un bit de validité par entrée, mais aussi : la position du mot mémoire lu dans la ligne de cache. C'est cette dernière information qui n'était pas présente dans les MSHR adressés implicitement. De plus, le nombre d'entrée par MSHR n'est pas égal au nombre de mots mémoires dans une ligne de cache, mais peut être arbitrairement grand. L'avantage d'un tel cache est qu'il est capable de traité les défauts secondaires concernant un même mot mémoire, et ce, car les accès à la ligne de cache concernée se produisent dans l'ordre de génération des défauts (la table mémoire est une FIFO). ===Les MSHRs inversés=== Généralement, plus on veut supporter de défauts de cache, plus le nombre de MSHR et d'entrées augmente. Mais au-delà d'un certain nombre d'entrées et de MSHR, les MSHR adressés implicitement et explicitement ont tendance à bouffer un peu trop de circuits. Utiliser une organisation un peu moins gourmande en circuits est donc une nécessité. Cette organisation plus économe se base sur des MSHR inversés. Les MSHR inversés ne contiennent qu'une seule entrée, en quelque sorte : au lieu d’utiliser n MSHR de m entrées chacun, on va utiliser n × m MSHR inversés. La différence, c'est que plusieurs MSHR peuvent contenir un tag identique, contrairement aux MSHR adressés implicitement et explicitement. Lorsqu'un défaut de cache a lieu, chaque MSHR est vérifié. Si jamais aucun MSHR ne contient de tag identique à celui utilisé par le défaut, un MSHR vide est choisi pour stocker ce défaut, et une requête de lecture en mémoire est lancée. Dans le cas contraire, un MSHR est réservé au défaut de cache, mais la requête n'est pas lancée. Quand la donnée est disponible, les MSHR correspondant à la ligne qui vient d'être chargée vont être utilisés un par un pour résoudre les défauts de cache en attente. ===Les MSHR intégrés au cache=== Certains chercheurs ont remarqué que pendant qu'une ligne de cache est en train de subir un défaut de cache, celle-ci reste inutilisée, et son contenu est destiné à être perdu une fois le défaut de cache résolu. Ils se sont dit que, plutôt que d'utiliser des MSHR séparés, il vaudrait mieux utiliser la ligne de cache pour stocker les informations sur les défauts de cache en attente dans cette ligne de cache. Pour éviter tout problème, il faut rajouter un bit dans les bits de contrôle de la ligne de cache, qui sert à indiquer que la ligne de cache est occupée : un défaut de cache a eu lieu dans cette ligne, et elle stocke donc des informations pour résoudre les défauts de cache. ==L'entrelacement mémoire== Nous venons de voir comment une mémoire cache peut gérer plusieurs accès mémoire simultanés. Il se trouve que ce genre d'optimisation dépasse largement le cadre du cache. Les mémoires RAM ne sont pas en reste, elles aussi ! Il est possible de pipeliner l'accès à une mémoire RAM, en utilisant des techniques dites d''''entrelacement'''. Précisons cependant que cette optimisation n'est pas utilisée sur les mémoires SDRAM ou DDR modernes, du moins pas sans modifications majeures. Mais divers ordinateurs assez anciens, dont des superordinateurs, ont utilisé cette technique. La technique demande d'utiliser plusieurs mémoires séparées. Sur les anciens ordinateurs, les mémoires en question sont des chips mémoire, à savoir des circuits intégrés de DRAM. Pour faire plus général, nous allons utiliser le terme de '''banques''', ou encore de bancs mémoire. La différence est qu'il existe des mémoires multi-banque, qui regroupent plusieurs banques indépendantes dans un seul boitier, dans un seul chip mémoire. En clair, de telles mémoires regroupent plusieurs sous-mémoires dans un seul circuit intégré. Il est aussi possible d'utiliser plusieurs chips mémoire séparés, chacun ayant une banque. Reste à combiner ces banques pour former un pseudo-pipeline. ===Utiliser plusieurs banques sans entrelacement=== Sans optimisation particulière, les adresses sont réparties dans les banques comme indiqué ci-dessous. Il s'agit de ce qui s'appelle un arrangement horizontal, et nous avions vu celui-ci dans le chapiutre sur les mémoires SDRAM. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Avec cet arrangement, les bits de poids fort de l'adresse sont utilisées pour sélectionner la banque adéquate, et le reste de l'adresse est envoyé sur le bus d'adresse. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Adresse de banque !! Adresse dans la banque |- | Quelques bits de poids fort || Reste de l'adresse |} L'entrelacement change cette répartition, afin d'accélérer les accès mémoire. L'entrelacement classique vise à améliorer les accès à des adresses mémoires consécutives, mais des formes plus évoluées d'entrelacement visent à optimiser des accès mémoire arbitraires. Voyons-les dans l'ordre, du plus simple au plus complexe. ===L'entrelacement classique=== Avec l''''entrelacement classique''', des adresses consécutives sont réparties dans des banques consécutives. Précisons que cette attribution des adresses n'implique absolument pas la mémoire virtuelle ou n'importe quel mécanisme dans le processeur. La répartition décide que telle adresse mémoire va dans telle banque, à telle adresse dans la banque. Elle est donc le fait du contrôleur mémoire, donc en dehors du processeur (les contrôleurs mémoires n’étaient pas encore intégrés dans le processeur à l'époque). [[File:Répartition des adresses dans une mémoire interleaved.png|centre|vignette|upright=1.5|Répartition des adresses dans une mémoire interleaved.]] L'entrelacement simple permet d'accélérer les accès mémoire qui se font à des adresses consécutives. Avec l'entrelacement, chaque accès mémoire tombe dans une banque mémoire différente, ce qui fait qu'on peut démarrer un nouvel accès mémoire à chaque cycle d'horloge. Pendant qu'une banque est occupée par un accès mémoire, on démarre le suivant dans une autre banque, et ainsi de suite. Le tout se termine soit quand on a épuisé toutes les banques libres, soit quand l'accès en rafales se termine. Pas besoin d'attendre que la mémoire ait fini sa lecture/écriture avant de démarrer la lecture/écriture suivante. [[File:Pipemining mémoire.png|centre|vignette|upright=2.5|Pipelining mémoire]] L'animation suivante illustre bien le processus, avec quatres banques. [[File:Interleaving.gif|centre|vignette|upright=2.5|Pipelining mémoire]] L'entrelacement simple utilise les bits de poids faible pour sélectionner la banque, et les bits de poids fort pour la case mémoire. [[File:Adresse mémoire d'une mémoire entrelacée.png|centre|vignette|upright=2|Adresse mémoire d'une mémoire entrelacée]] Précisons que le temps d'accès mémoire ne change pas beaucoup avec l'entrelacement. Par contre, on peut faire plus d'accès mémoire simultanés. On peut démarrer un accès mémoire par cycle d'horloge, mais l'accès en lui-même prend plusieurs cycles. Les mémoires à entrelacement ont donc un débit supérieur aux mémoires qui ne l'utilisent pas. L'entrelacement classique pourrait être utilisé pour accélérer les transferts entre mémoire RAM et cache, qui se font en blocs d'adresses consécutives. Mais de nos jours, il n'est pas utilisé dans cette optique. Il faut dire qu'il est redondant avec le mode rafale des mémoires DRAM. Les deux font globalement la même chose et on voit mal comment utiliser les deux en même temps. Le mode rafale étant plus simple à implémenter, et plus léger niveau utilisation du bus de commande mémoire, il est préféré à l'entrelacement. Par contre, l'entrelacement a eu son heure de gloire sur d'anciens processeurs qui n'avaient pas de mémoire cache, alors qu'ils disposaient d'un pipeline. L'exemple typique est celui des nombreux ''processeurs vectoriels'', que nous n'avons pas encore abordé à ce stade du cours. De tels processeurs avaient un pipeline, une unité de calcul fortement pipelinée, et parfois même de l'exécution dans le désordre et du renommage de registres ! ===L'entrelacement par décalage=== L'entrelacement simple est très utile pour les accès en rafle ou équivalents. Par contre, il existe de rares situations où il n'est pas optimal. Une de ces situations est celle des accès en enjambée (en ''stride''), où l'on accède en série à des adresses sont séparées par N mots mémoires. [[File:Accès par enjambées.png|centre|vignette|upright=2|Accès par enjambées.]] De tels accès surviennent quand un logiciel accède à des structures de données spécifiques, à savoir des tableaux de structures, des matrices, ou d'autres structures de données dans le genre. Un cas classique est celui du parcours d'une matrice colonne par colonne. Les matrices de nombres sont mémorisées ligne par ligne, ce qui fait que parcourir une colonne demande de passer d'une ligne à la suivante et de faire des grands sauts en mémoire RAM, mais tous espacés par la même distance. Avec l'entrelacement simple, les accès en enjambée sont moins performants que les accès à des adresses consécutives, mais cela ne fonctionne pas trop mal. Le pire cas est celui où l'on a N banques et où les données sont justement placées toutes les N adresses. Des accès consécutifs vont tous tomber dans la même banque, on ne peut plus accéder à des banques différentes en parallèle. Mais en dehors de ce cas, on voit une amélioration par rapport à la situation sans entrelacement. Pour obtenir des performances maximales pour les accès en enjambées, il faut répartir les mots mémoires dans la mémoire autrement. L'organisation idéale est la suivante. : Dans les explications qui vont suivre, la variable N représente le nombre de banques, qui sont numérotées de 0 à N-1. On commence par organiser les N premières adresses comme une mémoire entrelacée simple : l'adresse 0 correspond à la banque 0, l'adresse 1 à la banque 1, etc. Pour le bloc suivant, on décale tout d'une adresse, à savoir qu'on commence à remplir les banques à partir de la seconde, non de la première. Une fois la fin du bloc atteinte, on finit de remplir le bloc en repartant du début du bloc. Le troisième bloc d'adresse subit le même traitement, sauf qu'on commence à remplir à partir de la seconde banque. Et on poursuit l’assignation des adresses en décalant d'un cran en plus à chaque bloc. Ainsi, chaque bloc verra ses adresses décalées d'un cran en plus comparé au bloc précédent. Si jamais le décalage dépasse la fin d'un bloc, alors on reprend au début. [[File:Mémoire interleaved par décalage.png|centre|vignette|upright=1.5|Mémoire entrelacée par décalage.]] En faisant cela, on remarque que les banques situées à N adresses d'intervalle sont différentes. Dans l'exemple du dessus, nous avons ajouté un décalage de 1 à chaque nouveau bloc à remplir. Mais on aurait tout aussi bien pu prendre un décalage de 2, 3, etc. Dans tous les cas, on obtient un entrelacement par décalage. Ce décalage est appelé le pas d'entrelacement, noté P. Le calcul de l'adresse à envoyer à la banque, ainsi que la banque à sélectionner se fait en utilisant les formules suivantes : * adresse à envoyer à la banque = adresse totale / N ; * numéro de la banque = (adresse + décalage) modulo N ; * décalage = (adresse totale * P) mod N. Avec cet entrelacement par décalage, on peut prouver que la bande passante maximale est atteinte si le nombre de banques est un nombre premier. Seulement, utiliser un nombre de banques premier peut créer des trous dans la mémoire, des mots mémoires inadressables. Malgré ce défaut, la technique a été utilisée sur quelques ordinateurs, avec l'exemple notable du superordinateur ''Burroughs Scientific Processor''. Pour éviter cela, il y a plusieurs solutions. Par exemple, on peut faire en sorte que N et la taille d'une banque soient premiers entre eux : ils ne doivent pas avoir de diviseur commun. Mais en pratique, elles n'ont pas vraiment été implémentées dans une vraie machine, et sont restées à l'état de recherche, aussi je les passe sous silence. ===L'entrelacement pseudo-aléatoire=== Une dernière méthode de répartition consiste à répartir les adresses dans les banques de manière "pseudo-aléatoire". L'idée exacte est de faire passer l'adresse dans une '''fonction de hachage''', plus ou moins complexe. Pour rappel, une fonction de hachage prend une entrée de grande taille et fournit en sortie un résultat de petite taille. Idéalement, elles doivent donner des résultats aussi différents que possible pour des entrées similaires, histoire de simuler une sorte de pseudo-aléatoire. Ici, une adresse est transformée en un numéro de banque. Une fonction de hachage simple ne fait que permuter des bits de l'adresse pour obtenir son résultat. Elle ne fait qu'échanger des bits de place avant de couper l'adresse en deux morceaux : un pour la sélection de la banque, et un autre pour la sélection de l'adresse dans la banque. Cette permutation est fixe, et ne change pas suivant l'adresse. Des fonctions de hachage plus complexes font des XOR bit à bit entre certains bits de l'adresse. ==Les contrôleurs mémoires SDRAM/DDR optimisés== Après avoir vu le cas des mémoires RAM, nous allons nous concentrer sur le cas particulier des mémoires SDRAM. Les mémoires SDRAM et DDR modernes sont capables de gérer plusieurs accès simultanés à la mémoire RAM, et leurs contrôleurs mémoire en font tout autant. C'est une différence majeure avec les mémoires asynchrones FPM/EDO, qui n'acceptaient qu'un seul accès mémoire à la fois. Leur contrôleur mémoire n'acceptait qu'un seul accès mémoire à la fois, c'était un contrôleur mémoire bloquant. Les contrôleurs mémoires des SDRAM sont eux non-bloquants et peuvent encaisser une dizaine d'accès mémoire à la fois. Peu de choses sont connues sur les contrôleurs de SDRAM/DDR modernes, les fabricants ne donnant que peu de détails dessus. Les rares simulateurs qui tentent de décrire leur fonctionnement, comme DRAM SIM I et II, sont particulièrement simples et ne vont pas dans le détail. Néanmoins, le peu qu'on sait est tout de même instructif. ===Rappel sur les SDRAM : tampon de ligne et commandes=== Les mémoires SDRAM sont des mémoires à tampon de ligne. Elles font un accès mémoire en deux étapes. La première étape recopie une ligne de N * 64 bits dans un tampon interne à la SDRAM. La seconde étape sélectionne une colonne, à savoir une donnée de 64 bits, qui est soit envoyée sur le bus de données pour une lecture, soit modifiée par une écriture. [[File:Mémoire à tampon de ligne.png|centre|vignette|upright=2|Mémoire à tampon de ligne]] Les accès mémoire sont traduits par un séquenceur mémoire en une série de commandes mémoires, qui sont séparées par des délais mémoire de quelques cycles d'horloge. Les délais sont très précis, et sont à respecter à la lettre. Une lecture ou une écriture se fait en maximum trois commandes : une commande PRECHARGE qui ferme la ligne précédemment utilisée, une commande ACT qui précise l'adresse de la ligne, et une commande READ ou WRITE qui précise l'adresse de la colonne et éventuellement la donnée à écrire. : Pour être plus précis, la commande PRECHARGE précharge les lignes de bits du plan mémoire à une tension particulière, ce qui les vide de leur contenu. Mais c'est un détail sans importance pour ce qui va suivre. Les SDRAM permettent de se passer de la première étape si des accès consécutifs se font dans la même ligne. Une fois activée, la ligne reste ouverte et on peut accéder plusieurs fois de suite dedans. On a alors juste à préciser l'adresse de colonne dedans. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | bgcolor="#FFA0FF" | PRECHARGE || bgcolor="#FFA0FF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || bgcolor="#A0FFFF" | READ (2) || bgcolor="#A0FFFF" | READ (3) || || || bgcolor="#A0FFFF" | READ (4) || bgcolor="#A0FFFF" | READ (5) || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | Donnée READ 1 || ||bgcolor="#A0FFFF" | Donnée READ 2 | bgcolor="#A0FFFF" | Donnée READ 3 || || || bgcolor="#A0FFFF" | Donnée READ 4 || bgcolor="#A0FFFF" | Donnée READ 5 |} Les SDRAM sont parfois capables de démarrer une commande avant que la précédente soit terminée. Mais le respect des délais mémoire est très limitant, ce qui fait qu'on ne peut pas parler de réel pipeline, comme c'est le cas sur les processeurs. Parlons plutot de '''commandes anticipées'''. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | || bgcolor="#A0FFFF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || || || bgcolor="#FFA0FF" | READ (2) || || || || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 | bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 | |} ===La mise en attente des accès mémoire=== Un contrôleur de SDRAM peut accepter plusieurs accès mémoire en même temps. Mais cela ne signifie pas que le contrôleur sera prêt à les traiter immédiatement. Pour éviter tout problème de disponibilité, le contrôleur met en attente les accès mémoire que le processeur lui envoie, pour les exécuter dès que possible. Les accès mémoire sont mis en attente dans une mémoire FIFO, histoire de les exécuter dans leur ordre d'arrivée, notamment pour renvoyer les lectures dans l'ordre demandé. Il y a aussi une mémoire FIFO pour les données à écrire et une FIFO pour les données lues. Cette dernière sert au cas où le cache ne soit pas disponible quand on lui envoie la donnée lue. [[File:Module d'interface avec la mémoire.png|centre|vignette|upright=2.5|Contrôleur mémoire avec mise en attente des requêtes processeur et autres optimisations.]] Des optimisations sont possibles dès la mémoire FIFO. Par exemple, on peut regrouper plusieurs accès à des données consécutives en un seul accès en rafale. Le contrôleur mémoire analyse les requêtes mises en attente et détecte si certaines se font à des adresses consécutives. Si c'est le cas, il fusionne ces requêtes en une lecture/écriture en rafale. Une telle optimisation est appelée la '''combinaison de lecture''' pour les lectures, et la '''combinaison d'écriture''' pour les écritures. ===Les optimisations liées aux commandes anticipées=== Les commandes anticipées permettent de faire un minimum de parallélisme mémoire. Même si elle parait très limitée, cette possibilité est décuplée par la présence de plusieurs banques dans la mémoire SDRAM. Il est en effet possible de faire plusieurs accès mémoire en même temps, dans des banques différentes. Il est possible de lancer un accès mémoire dans deux banques en même temps, par exemple. Ou encore, on peut lancer un nouvel accès mémoire dans une banque si elle est inoccupée. La possibilité améliore grandement la performance de la SDRAM. Mais nous en reparlerons dans un chapitre ultérieur. {|class="wikitable" |+ Pipelining basique sur les SDRAM |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 |- ! Banque Numéro 1 | || || || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || || |- ! Banque Numéro 2 | || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || |- ! Banque Numéro 3 | || || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || |} L'implémentation n'a rien de vraiment compliqué. Il suffit d'utiliser une mémoire FIFO par banque. Ainsi, les accès dans une même banque se feront en série, dans l'ordre d'arrivée, mais des accès à des banques différentes se feront dans le désordre. Cela ne pose pas de problème car les accès se font à des adresses différentes, ce qui fait qu'il n'y a pas de conflits majeurs. [[File:Gestion parallèle des banques.png|centre|vignette|upright=2.5|Gestion parallèle des banques.]] ===Le ré-ordonnancement des commandes mémoires=== Un contrôleur mémoire moderne est capable de changer l'ordre des requêtes mémoires, pour gagner en performances. En clair : il peut faire l'équivalent mémoire de l'exécution dans le désordre des processeurs haute performance. Une différence majeure est que le contrôleur mémoire ne remet pas les accès mémoire dans l'ordre avant de les envoyer au processeur. C'est le processeur qui remet en ordre les accès mémoire, dans sa ''load queue''. Pour cela, les requêtes mémoire sont "numérotées", à savoir qu'on leur attribue un identifiant binaire. Le contrôleur mémoire exécute les lectures/écritures dans le désordre, mais garde la trace de leur identifiant. Il sait dans quel ordre il exécute les accès mémoire et connait leur latence. Ce qui fait qu'il sait que la donnée lue à tel instant est associée à la requête mémoire numéro X. Pour les lectures, il renvoie la donnée lue avec l'identifiant. Le processeur sait alors à quelle lecture la donnée lue correspond et il se débrouille en interne pour gérer la situation. En clair : le processeur voit les accès mémoire dans le désordre, c'est lui qui les remet en ordre. Maintenant que ces précisions sont faites, posons cette question : quel est l'intérêt de faire les accès mémoire dans le désordre ? Accéder à des banques en parallèle est une réponse possible, mais le ré-ordonnancement est surtout utile si tous les accès mémoire atterrissent dans la même banque et dans la même ligne. Elle marche si plusieurs accès à une même ligne ne sont pas consécutifs, et qu'il y a des accès entre les deux. Après ré-ordonnancement, ces accès mémoire sont exécutés l'un à la suite de l'autre. Pour rendre le tout plus concret, voici un exemple. Imaginez que l'on sait les 3 accès mémoire suivants : * Une lecture ligne A ; * Une écriture ligne B ; * Une lecture ligne A. Sans réordonnancent, on doit émettre deux commandes PRECHARGE : une quand on passe de la ligne A à la ligne B, et une autre pour le passage inverse. pour éviter cela, le contrôleur mémoire peut retarder l'écriture, ou au contraire avancer la seconde lecture. le résultat est alors le suivant : * Lecture ligne A ; * Lecture ligne A ; * Une écriture ligne B. On a donc deux accès consécutifs à la même ligne, suivi par une écriture dans une autre ligne. La technique marche parce que l'on a un mix adéquat de lectures et d'écritures. Mais il existe des possibilités de réorganisation autres qui ne sont pas valides. Il faut par exemple prendre garde à éviter d'intervertir lectures et écritures à une même adresse, sous peine de problèmes. Encore une fois, il faut faire attention aux dépendances mémoires. Le controleur mémoire teste les dépendances mémoires avant d'envoyer des commandes à la mémoire DDR/SDRAM. ==L'entrelacement sur les mémoires SDRAM== L''''entrelacement''' fonctionne sur les mémoires SDRAM, mais il doit être fortement modifié. Concrètement, tout ce qui a étré dit plus est inapplicable pour les SDRAM, du fait de la présence du tampon de ligne. Des adresses consécutives atterrissent déjà dans la même ligne, ce qui fait qu'on n'a pas besoin d'entrelacement au niveau d'une ligne. Et ne parlons pas de ce la présence de rangées et de canaux mémoire ! Cependant, il peut y avoir un entrelacement lié à la présence des banques SDRAM. L'entrelacement est géré juste avant le séquenceur mémoire. Le '''circuit d'entrelacement''' intervertit certains bits de l'adresse lors des accès mémoires. On trouve aussi des mémoires FIFOs pour mettre en attente les requêtes mémoire provenant du processeur. Elles permettent de recevoir plusieurs accès mémoire consécutifs. Si vous vous demandez comment plusieurs accès mémoire consécutifs peuvent avoir lieu, sachez que c'est une bonne question. Pour le moment, nous allons dire que ces accès proviennent du processeur ou de la mémoire cache, sans expliquer comment. Le contrôleur mémoire reçoit une série d'accès mémoire assez rapprochés, qu'il doit exécuter au mieux. [[File:Controleur mémoire d'une SDRAM avec entrelacement.png|centre|vignette|upright=2.5|Contrôleur mémoire d'une SDRAM avec entrelacement.]] Pour gérer l'entrelacement, le contrôleur de SDRAM prend en entrée l'adresse envoyée par le processeur, et la découpe en plusieurs champs : un pour sélectionner la banque adéquate, un autre pour la ligne, un autre pour la colonne, un autre pour la rangée. Une solution simple désactive l'entrelacement. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de banque || Adresse de ligne || Adresse de colonne |} Si cette solution fonctionne bien si les accès mémoire sont assez aléatoires, en pratique, mieux vaut utiliser l'entrelacement. ===L'entrelacement de banques/lignes=== Une première méthode d'entrelacement est l''''entrelacement de banques'''. Elle répartit deux lignes consécutives dans deux banques différentes. Pour comprendre l'idée, prenons un exemple. Imaginons une mémoire avec deux banques et 4 lignes. Imaginons qu'on parcoure/balaye la mémoire RAM en partant des adresses basses. Sans entrelacement de ligne, les accès se feront comme suit, de gauche à droite : {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Banque numéro 1 !! colspan="4" | Banque numéro 2 |- | Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 || Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 |} Avec l'entrelacement de banques, les accès se feront comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 |- | Ligne 1 || Ligne 1 || Ligne 2 || Ligne 2 || Ligne 3 || Ligne 3 || Ligne 4 || Ligne 4 |} Avec N banques, la première ligne ira dans la première banque, la seconde ligne dans la seconde, et ainsi de suite jusqu'à la énième banque qui va dans la banque numéro N. Et la ligne N+1 repart dans la première banque. L'avantage est que passer d'une ligne à la suivante est assez rapide. Au lieu de devoir fermer la première ligne et ouvrir la suivante on ouvrira directement la ligne suivante dans une banque différente, ce qui sera plus rapide. La commande PRECHARGE pourra être envoyée à la seconde banque en avance. Pour cela, l'adresse est découpée comme suit. {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne (précise à l'octet) |} ===L'entrelacement de rangées=== Il est aussi possible de faire la même chose, mais avec les rangées. Pour simplifier fortement, une rangée est simplement un chip mémoire. En réalité, les rangées ne sont pas des chip mémoire, mais un ensemble de chips mémoire regroupés ensemble histoire d'atteindre les 64 bits du bus de données. Par exemple, une rangée peut combiner 8 chips mémoire avec un bus de données de 8 bits chacun pour obtenir les 64 bits du bus de données d'une SDRAM. Mais nous passons ce détail sous silence dans les explications qui vont suivre, par souci de simplification. Pour faire comprendre l'entrelacement de rangée, le mieux est d'utiliser un exemple, le même que précédemment. Sans entrelacement de rangée, on change de chip mémoire une fois qu'on a balayé toutes ses adresses. {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Rangée numéro 1 !! colspan="4" | Rangée numéro 2 |- | Banque 1 || Banque 2 || Banque 3 || Banque 4 || Banque 1 || Banque 2 || Banque 3 || Banque 4 |} Avec l'entrelacement de ligne, on change de rangée dès qu'on change de banque/ligne. {|class="wikitable" |+ Adresse mémoire |- ! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 |- | Banque 1 || Banque 1 || Banque 2 || Banque 2 || Banque 3 || Banque 3 || Banque 4 || Banque 4 |} En clair, quand on a épuisé toutes les banques dans une même rangée, on passe à la rangée suivante au lieu de rester dans la même rangée. Notons que dans l'exemple précédent, on a combiné l'entrelacement de rangée et de banque, mais on aurait pu utiliser l'entrelacement de rangée seul. Mais ce n'est pas le cas le plus courant en pratique. Toujours est-il qu'en combinant entrelacement de rangée et de ligne, le découpage de l'adresse est le suivant : {|class="wikitable" |+ Adresse mémoire |- | Adresse de ligne || Numéro de rangée || Adresse de banque || Adresse de colonne (précise à l'octet) |} D'autres méthodes d'entrelacement sont possibles, mais elles sont plus complexes. Chaque contrôleur mémoire fait un peu à sa sauce. Ils permettent en général de choisir entre plusieurs entrelacements. Au minimum, ils permettent de désactiver l'entrelacement, d'activer un entrelacement de ligne, et d'autres entrelacements plus complexes, dont certains sont propriétaires et tenus secrets par le fabricant. ===L'entrelacement avec le ''dual channel''=== [[File:Dual channel slots.jpg|vignette|Slots mémoires avec ''dual channel''.]] L'usage du ''dual channel'' complique encore l'entrelacement. Là encore, il y a deux grandes solutions : avec et sans entrelacement des canaux mémoire. Rappelons le principe : deux barrettes de RAM sont accédées en parallèle. Et pour cela, il faut utiliser l'entrelacement. Typiquement, chaque barrette mémoire fournit 64 bits, ce qui fait que l'on peut accéder à 128 bits d'un seul coup, par exemple avec un accès en rafale. Sans ''dual channel'', la première barrette correspond à la moitié haute de la RAM, la seconde barrette correspond à la moitié basse. Avec ''dual channel'', une forme spécifique d'entrelacement est activée. Concrétement, deux blocs de 64 bits sont placés dans des canaux mémoire séparés. Idem avec du triple ou quadruple canal, mais c'est alors trois ou quatre blocs de 64 bits qui sont dispersés dans des canaux consécutifs. Le découpage de l'adresse est alors le suivant : {|class="wikitable" |+ Adresse mémoire |- ! colspan="6" | Sans entrelacement |- | Numéro de canal/barrette || Reste de l'adresse || Position de l'octet dans un bloc de 64 bits |- | colspan="6" | |- ! colspan="6" | Avec entrelacement |- | Reste de l'adresse || Numéro de canal/barrette || Position de l'octet dans un bloc de 64 bits |} [[File:Décodage d'adresse avec dual channel.png|centre|vignette|upright=2|Décodage d'adresse avec dual channel.]] Les contrôleurs de SDRAM précédents sont assez basiques et ne représentent pas les contrôleurs les plus évolués. Ils étaient utilisés sur les anciens PC, à une époque où ils étaient encore sur la carte mère du processeur. Mais de nos jours, le contrôleur mémoire est intégré au processeur. Et il incorpore de nombreuses optimisations afin de gagner en performances. Malheureusement, ces optimisations sont elles-mêmes dépendantes des optimisations intégrées dans le processeur, ce qui fait qu'on ne peut pas en parler ici. Elles demandent que le contrôleur mémoire reçoive plusieurs accès mémoire simultanés, ce qui n'a aucun sens à ce stade du cours. Aussi, nous allons les passer sous silence pour le moment. Nous les verrons dans le chapitre sur le parallélisme mémoire. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=La désambiguïsation mémoire | prevText=La désambiguïsation mémoire | next=Les processeurs superscalaires | nextText=Les processeurs superscalaires }} </noinclude> f0ofehpowgfsq0hs8mhmdgvq8dugw6y 764720 764719 2026-04-23T21:25:25Z Mewtow 31375 /* Rappel sur les SDRAM : tampon de ligne et commandes */ 764720 wikitext text/x-wiki Dans ce chapitre, nous allons voir les techniques qui permettent de gérer plusieurs accès mémoire simultanés directement au niveau du cache ou de la mémoire RAM. Par plusieurs accès mémoire simultanés, vous pensez sans doute à l'usage de cache multiports, voire de mémoires RAM multiport. C'est en effet une solution, mais ce n'est clairement pas la seule. Par exemple, il est possible de pipeliner l'accès au cache, voire à la mémoire RAM. Il existe de nombreuses techniques de '''parallélisme mémoire''', et nous allons les voir dans ce chapitre. Pour donner un exemple, il est possible de pipeliner les accès mémoire. Il est possible de pipeliner l'accès au cache et/ou l'accès à la mémoire, et nous verrons comment faire dans ce chapitre. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, qu'ils aient ou non un pipeline dynamique. Par contre, pipeliner la mémoire est une technique ancienne, aujourd'hui peu utilisée. Elle était utilisée sur des très vieux ordinateurs, qui avaient un pipeline mais pas de mémoire cache. Mais de nos jours, les mémoires SDRAM et DDR ne sont pas adaptées pour ça, il est très difficile de les pipeliner correctement. Le parallélisme mémoire est utile aussi bien sur des processeurs à émission dans l'ordre que dans le désordre. Par exemple, un processeur ''in-order'' avec des lectures non-bloquantes peut e, profiter. Il peut techniquement lancer une seconde lecture, après avoir rencontré une première lecture non-bloquante. En général, le nombre de lectures consécutives est cependant limité à moins d'une dizaine. À l'opposé, les processeurs sans lectures non-bloquantes profitent pas du parallélisme mémoire. À la rigueur, ils peuvent en profiter s'ils intègrent des techniques de préchargement. Pendant que le processeur exécute une lecture/écriture, il peut précharger une autre donnée en parallèle. Inutile de dire que sans pipeline processeur, le parallélisme mémoire ne sert pas à grand-chose. ==Le ''line fill buffer'' et les techniques associées== Lors d'un défaut de cache, le processeur doit attendre que toute la ligne de cache soit chargée avant d'être utilisable. Or, la taille d'une ligne de cache est supérieure à la largeur du bus mémoire, ce qui fait qu'une ligne de cache est chargée en plusieurs fois, morceaux par morceaux, mot mémoire par mot mémoire. Le chargement peut se faire directement dans le cache, mais ce n'est pas une solution très pratique. À la place, beaucoup de processeurs ajoutent une mémoire tampon entre la RAM et le cache, appelée le '''tampon de remplissage de ligne''' (''line-fill buffer'') dans les processeurs modernes. Lors d'un défaut de cache, le processeur charge la donnée de la RAM dans le tampon de remplissage de ligne, mot mémoire par mot mémoire. Une fois plein, le tampon de remplissage de ligne est recopié dans la ligne de cache. Le tampon de remplissage de ligne contient une ligne de cache, si ce n'est que certains bits de contrôle ne sont pas présents. Le tampon de remplissage de ligne contient un bit de validité pour chaque mot mémoire de la ligne de cache, qui indiquent si le mot mémoire a été chargé. Par exemple, prenons un processeur 64 bits, qui gère donc des mots mémoire de 8 octets, avec des lignes de cache 256 octets/32 mots mémoire. Le tampon de remplissage de ligne contiendra 32 bits de validité. Si le processeur a chargé les 6 premiers mots mémoire, les 6 premiers bits de validité seront mis à 1, les autres seront encore à zéro. Il faut noter que la disponibilité de la ligne complète se détermine assez facilement en faisant un ET logique entre tous les bits de validité. Le processeur sait ainsi quand la ligne de cache est disponible entièrement et donc quand la transférer dans le cache. La même chose existe avec une hiérarchie de cache, sauf que l'on trouve une mémoire tampon entre chaque niveau de cache. Il y a un tampon de remplissage de ligne entre le cache L1 et le cache L2, entre le cache L2 et le L3, etc. Si le cache ne gère qu'un seul défaut de cache à la fois, le ''fill line buffer'' est une mémoire très simple, qui ne mémorise qu'une seule ligne de cache. Et cela vaut aussi bien pour un cache bloquant que non-bloquant. Mais sur les caches capables de gérer plusieurs défauts simultanés, le tampon de remplissage de ligne est une mémoire de type FIFO ou LIFO, capable de mémoriser plusieurs lignes de cache. Notons que le tampon de remplissage de ligne est très utile pour implémenter certaines techniques, par exemple le contournement du cache. Nous avions vu dans le chapitre sur le cache que certains accès mémoire doivent contourner le cache, pour des raisons de cohérence des caches. C'est notamment nécessaire pour accéder aux périphériques, mais c'est aussi utile pour des raisons de performances dans des cas très spécifiques. Les accès qui contournent le cache se font directement dans le tampon de remplissage de ligne : le processeur écrit ou lit les données depuis ce tampon de remplissage de ligne, sans accéder au cache. ===L'''early restart'' et le ''critical word load''=== La présence du ''line buffer'' permet une optimisation assez intéressante, qui permet de réduire la latence des défauts de cache. L'optimisation consiste à lire un mot mémoire dans le tampon de remplissage de ligne, même si la ligne de cache complète n'a pas encore été chargée. Il existe deux manières de faire cela, qui portent les noms d'''early restart'' et de ''critical word load''. La première est la version la plus simple, la seconde est plus complexe mais plus performante. Avec la technique d''''''early restart''''', la ligne de cache est chargée normalement, en partant de son premier mot mémoire. Dès que le mot mémoire lu/écrit par le processeur est copié dans le ''line fill buffer'', il est envoyé au processeur immédiatement. Illustrons le tout par un exemple, où une ligne de cache fait 16 mots mémoire. Le processeur effectue une lecture, qui lit le 5ème mot mémoire. Sans ''early restart'', le processeur doit charger les 16 mots mémoire avant de faire la lecture dans le cache. Avec ''early restart'', le processeur reçoit la donnée dès que le 5ème mot mémoire est disponible. Le processeur doit attendre que les 4 premiers mots mémoire soient chargés, puis le 5ème mot mémoire arrive et est envoyé directement au processeur, il est lu directement depuis le ''line fill buffer''. Les 11 mots mémoire suivants sont ensuite chargés dans le cache pendant que le processeur fait des calculs dans son coin. Le ''critical word load'' est une optimisation de la technique précédente où le chargement de la ligne de cache commence directement à la donnée demandée par le processeur. Pour reprendre l'exemple précédent, où le processeur demande le 5ème mot mémoire sur 16, le ''critical word load'' charge le 5ème mot mémoire en premier et l'envoie au processeur, ce qui fait qu'il est chargé très rapidement. Pas besoin d'attendre que le processeur charge les 4 mots mémoire précédents comme avec l'''early restart''. Dans le détail, le processeur charge le 5ème mot mémoire en premier, puis charge les 11 suivants, et termine par les 4 mots mémoire du début. En clair, le ''critical word load'' commence par charger le mot mémoire lu, puis les blocs suivants, avant de revenir au début du bloc pour charger les blocs restants. Ainsi, la donnée demandée par le processeur sera la première disponible. Pour cela, l'organisation du tampon de remplissage de ligne est modifiée de manière à rendre cela possible. Il a une taille égale à une ligne de cache complète, qui contient elle-même plusieurs mots mémoire. Dans le ''line-fill buffer'', chaque mot mémoire est stocké avec un tag, qui indique l'adresse du mot mémoire stocké dans le ''line-fill buffer''. Le ''line-fill buffer'' est donc un cache un peu particulier, qui fonctionne comme un cache du point de vue du processeur, comme une mémoire FIFO pour les transferts avec le cache. Ainsi, un processeur qui veut lire dans le cache après un défaut peut accéder à la donnée directement depuis le tampon de remplissage de ligne, alors que la ligne de cache n'a pas encore été totalement recopiée en mémoire. ==Les caches pipelinés== Il est possible de pipeliner l'accès au cache, ce qui demande juste de rajouter des registres au bon endroits et quelques circuits de contrôle. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, même ceux avec un pipeline dynamique. Et l'implémentation peut se faire de plusieurs manières différentes. Il faut dire que découper une mémoire cache en plusieurs étapes peut se faire de plusieurs manière. Commençons par la plus simple. Beaucoup de processeurs des années 2000 avaient une fréquence peu élevée comparée aux standards d'aujourd'hui, ce qui fait que leur cache avait bien un temps d'accès d'un cycle d'horloge. Mais l'accès au cache se faisait en deux cycles d'horloge : un cycle pour calculer une adresse et un cycle pour l'accès mémoire proprement dit. Et ces deux étapes étaient pipelinées, à savoir que deux micro-opérations mémoire peuvent s'exécuter en même temps : une dans l'étage de calcul d'adresse, une autre dans le cache. L'unité mémoire était donc pipelinée, alors que l'accès au cache ne l'était pas. Les processeurs qui implémentaient cette technique regroupent les micro-architectures K5 et K6 d'AMD, les processeurs Intel de micro-architecture P6 (Pentium 2 et 3) et quelques autres. Il est aussi possible de pipeliner l'accès au cache lui-même. Avec un cache pipeliné, l'accès au cache ne se fait pas en un seul cycle, mais en plusieurs. Cependant, on peut lancer un nouvel accès au cache à chaque cycle d'horloge, comme un processeur pipeliné lance une nouvelle instruction à chaque cycle. L'implémentation est assez simple : il suffit d'ajouter des registres dans le cache. Pour cela, on profite que le cache est composé de plusieurs composants séparés, qui échangent des données dans un ordre bien précis, d'un composant à un autre. De plus, le trajet des informations dans un cache est linéaire, ce qui les rend parfois pour l'usage d'un pipeline. ===Le ''pipelining'' des caches ''direct-mapped''=== Voici ce qui se passe avec un cache directement adressé. Pour rappel ce genre de cache est conçu en combinant une mémoire RAM, généralement une SRAM, avec quelques circuits de comparaison et un MUX. L'accès au cache se fait globalement en deux étapes : la première lit la donnée et le ''tag'' dans la SRAM, et on utilise les deux dans une seconde étape. Nous avions vu il y a quelques chapitre comment pipeliner des mémoires, dans le chapitre sur les mémoires évoluées. Et bien ces méthodes peuvent s'utiliser pour la mémoire RAM interne au cache !L'idée est d'insérer un registre entre la sortie de la RAM et la suite du cache, pour en faire un cache pipéliné, à deux étages. Le premier étage lit dans la SRAM, le second fait le reste. L'implémentation sur les caches associatifs à plusieurs voies est globalement la même à quelques détails près. [[File:Cache directement adressé pipeliné - deux étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - deux étages]] Avec le circuit précédent, il est possible d'aller plus loin, cette fois en pipelinant l'accès à la mémoire RAM interne au cache. Pour comprendre comment, rappelons qu'une mémoire SRAM est composée d'un plan mémoire et d'un décodeur. L'accès à la mémoire demande d'abord que le décodeur fasse son travail pour sélectionner la case mémoire adéquate, puis ensuite la lecture ou écriture a lieu dans cette case mémoire. L'accès se fait donc en deux étapes successives séparées, on a juste à mettre un registre entre les deux. Il suffit donc de mettre un gros registre entre le décodeur et le plan mémoire. [[File:Cache directement adressé pipeliné - trois étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - trois étages]] Et on peut aller encore plus loin en découpant le décodeur en deux circuits séparés. En effet, rappelez-vous le chapitre sur les circuits de sélection : nous avions vu qu'il est possible de créer des décodeurs en assemblant des décodeurs plus petits, contrôlés par un circuit de prédécodage. Et bien on peut encore une ajouter un registre entre ce circuit de prédécodage et les petits décodeurs. : Théoriquement, toute l'adresse est fournie d'un seul coup au cache, la quasi-totalité des processeurs présentent l'adresse complète à un cache pipeliné. Mais le Pentium 4 fait autrement. Il faut noter que les premiers étages manipulent l'indice dans la SRAM, qui est dans les bits de poids faible, alors que les étages ultérieurs manipulent le ''tag'' qui est dans les bits de poids fort. Les concepteurs du Pentium 4 ont alors décidé de présenter les bits de poids faible lors du premier cycle d'accès au cache, puis ceux de poids fort au second cycle. Pour cela, l'ALU fonctionnait à une fréquence double de celle du processeur, tout comme le cache L1. Il n'y avait pas de pipeline proprement dit, mais cela réduisait grandement la latence d'accès au cache. ===Le ''pipelining'' des caches splittés/sériels=== Il est aussi possible de pipeliner un cache dits splittés, aussi appelés à accès sériel. Pour rappel, les caches à accès sériel vérifient si il y a succès ou défaut de cache, avant d'accéder aux lignes de cache en cas de succès. Ils font donc différemment des autres caches, qui accèdent à une ligne de cache, avant de déterminer s'il y a succès ou défaut en lisant le tag de la ligne de cache. Les caches sériels disposent de deux SRAM : une pour les tags des lignes de cache et une pour les données. Ils accèdent à la SRAM pour les tags, avant d’accéder à la SRAM des données en cas de succès de cache. Vu que l'accès se fait en deux étapes, une vérification des tags suivie de la lecture/écriture des données, il est facile à pipeliner. Pipeliner le cache permet de régler le problème des accès au cache L1, et elle est tout le temps utilisé sur les processeurs modernes. Mais que faire en cas de défaut de cache ? ==Les caches non bloquants== Un '''cache bloquant''' est un cache auquel le processeur ne peut pas accéder pendant un défaut de cache. Il faut attendre que la lecture ou écriture en RAM soit terminée avant de pouvoir utiliser de nouveau le cache. Un '''cache non bloquant''' n'a pas ce problème : on peut l'utiliser pendant un défaut de cache. Les caches non bloquants permettent de démarrer une nouvelle lecture ou écriture alors qu'une autre est en cours, ce qui permet d'exécuter plusieurs lectures ou écritures en même temps. ===Les ''Miss Handling Status Registers''=== Lors d'un défaut de cache, la mémoire RAM est consultée pendant le défaut de cache, mais le cache est inutilisé. Un défaut de cache n'utilise pas le cache, ce n'est qu'un accès en mémoire RAM, sur le bus mémoire (ou un accès aux niveaux de cache inférieurs, peu importe). Le cache en lui-même est laissé libre, rien n’empêche d'y accéder, il est en réalité intrinsèquement non-bloquant. Les caches, bloquants comme non-bloquants, sont en réalité composés d'une mémoire cache proprement dite, entourée de circuits qui servent d'interface entre le processeur et le cache lui-même. Et parmi les circuits tout autour, certains gèrent l'accès au cache lors d'un défaut de cache. Ils sont regroupés sous le terme de '''''Miss Handling Architecture''''' (MHA). La différence entre un cache bloquant et un cache non-bloquant est en réalité liée à l'implémentation de la MHA. Les caches bloquants coupent volontairement l'accès au cache lors d'un défaut, car les défauts sont plus simples à gérer ainsi. Le défaut de cache rapatrie une donnée depuis la RAM, qui sera écrite dans le cache. Et il ne faut pas qu'une tentative d'accès à cette donnée ait lieu avant qu'elle ne soit chargée. Mais ce blocage est général et touche tout le cache, alors que seule une ligne de cache est concernée par le défaut de cache. L'idée derrière un cache non-bloquant est que seule la ligne de cache est bloquée, mais les autres sont accessibles. L'idée est alors de mémoriser les lignes de cache concernées par le défaut de cache, afin d'en bloquer l'accès. À chaque accès, on vérifie si la ligne de cache est déjà réservée par un défaut de cache. La lecture/écriture est alors bloquée si c'est le cas, mais elle accepte les accès sinon. Pour cela, la ''Miss Handling Architecture'' contient des registres qui mémorisent des informations sur les défauts de cache en cours. Ils portent le nom de '''''miss status handling registers''''', que j’appellerais dorénavant MSHR, qui sont aussi appelés des '''''miss buffer'''''. Le contenu des MSHR varie beaucoup suivant le processeur, mais ces derniers stockent au minimum les informations suivantes : * Le numéro de la ligne de cache dans laquelle les données sont chargées. * Un bit de validité qui indique si le MSHR est vide ou pas, qui est mis à 0 quand le défaut de cache est résolu. * Un ou plusieurs champs de lecture/écriture, qui contiennent des informations sur la lecture/écriture. ** Pour une lecture, elle contient des informations sur la destination de la donnée, à savoir qui prévenir quand le défaut de cache est terminé. C'est parfois un nom/numéro de registre (celui dans lequel charger la donnée), mais c'est souvent le numéro de l'entrée dans le ''load/store queue''. ** Pour les écritures, elle contient la donnée à écrire, ou éventuellement un numéro de ''load/store queue'' où se trouve la donnée à écrire. Il faut noter que le bus mémoire ne peut gérer qu'un seul défaut de cache à la fois. Aussi, il est intéressant de regarder ce qui se passe lorsqu'un second défaut de cache survient, pendant qu'un premier est en cours. Dans ce cas, il y a deux réponses qui correspondent à deux types de caches non-bloquants, qui portent les noms barbares de caches de type succès après défaut et défaut après défaut. Sur le premier type, il ne peut pas y avoir plusieurs défauts de cache simultanés. Dès qu'un second défaut de cache survient, le cache stoppe son activité et on ne peut plus démarrer de nouvelle lecture/écriture, tant que le premier défaut de cache n'est pas résolu. Le second type est plus souple et autorise la survenue de plusieurs défauts de cache simultanés. Du moins, jusqu'à une certaine limite, car le cache ne peut supporter qu'un nombre limité d'accès mémoires simultanés (pipelinés). ===Les accès simultanés à une même ligne de cache=== Il arrive que le processeur fasse plusieurs accès mémoire simultanés à la même ligne de cache. Si la ligne de cache en question n'a pas encore été chargée dans le cache, alors on a plusieurs défauts de cache consécutifs pour la même ligne de cache, et le cache non-bloquant doit gérer la situation. Pour la suite, il va falloir faire une petite distinction entre les défauts primaires et secondaires. Imaginons qu'un défaut de cache ait lieu et demande à charger une donnée dans la ligne de cache numéro N. Il s'agit du premier défaut impliquant cette ligne de cache précise, ce qui lui vaut le nom de '''défaut de cache primaire'''. Mais par la suite, d'autres accès mémoire à la même ligne de cache ont lieu, alors que la ligne de cache n'est pas encore disponible. Dans ce cas, il s'agit de '''défauts de cache secondaires'''. Pour l'unité d'accès mémoire, les défauts de cache primaire et secondaire sont différents (ils prennent tous une entrée dans la ''load/store queue''). Mais pour le cache, ils ne correspondent qu'à un seul accès au cache : celui qui demande de charger la ligne de cache demandée. Les défauts de cache primaire et secondaire à la même ligne de cache se voient attribuer un MSHR unique. La gestion des défauts de cache secondaires dépend du cache non-bloquant. La solution la plus simple ne permet pas les défauts de cache secondaires. Le cache ne permet pas deux défauts de cache simultanés pour la même ligne de cache. Les autres solutions le permettent, en fusionnant des accès simultanés à la même ligne de cache en un seul au niveau des MSHR. Dans tous les cas, détecter les défauts de cache secondaires sont un problème qu'il faut détecter. La ''Miss Handling Architecture'' doit détecter les défauts de cache secondaire. Pour cela, elle procède comme suit. Lors de chaque défaut de cache, la MHA récupère le numéro de la ligne de cache associé. Il vérifie alors chaque MSHR pour vérifier s'il contient le numéro en question. S'il n'y a aucune correspondance dans les MSHR, alors c'est signe que le défaut de cache est un défaut primaire. Mais s'il y en a une, alors c'est un défaut secondaire. Évidemment, cela signifie que lors d'un défaut de cache, le numéro de ligne de cache est envoyé à tous les MSHR, pour comparaison. Les MSHR sont donc regroupés dans une mémoire associative, une sorte de mini-cache, faciliter l'implémentation. Lors d'un défaut de cache primaire, l'accès à un cache non-bloquant se fait comme suit : le processeur envoie une adresse au cache, accède à celui-ci, et détecte la survenue d'un défaut de cache. Il en profite alors pour attribuer une ligne de cache dans laquelle sera chargée ce défaut. L'attribution est très simple dans le cas des caches ''direct mapped'', ou associatifs par voie, pour lesquels l'attribution se fait assez simplement. Il mémorise alors cette information dans les MSHR, après avoir vérifié que le défaut de cache n'était pas un défaut secondaire. Lors des accès ultérieurs à une adresse proche, censée être dans la même ligne de cache, le processeur va encore une fois rencontrer un défaut de cache. Il va alors déterminer le numéro de la ligne de cache associée à l'adresse, et comparer ce numéro avec les MSHRs. Si un MSHR contient ce numéro, c'est signe que le défaut de cache est un défaut secondaire. La MHA réagit alors différemment selon le processeur considéré. Une première solution n'autorise pas les défauts de cache secondaires. Si l'un d'entre eux survient, le processeur est gelé par un ''pipeline stall'', une bulle de pipeline. Une autre solution fusionne les défauts de cache secondaires avec le défaut de cache primaire : tout cela ne correspond qu'à un seul défaut de cache pour lui. ===Les MSHR simples=== Un cache non-bloquant à '''MSHR simple''' contient juste plusieurs MSHR qui mémorisent juste un numéro de ligne de cache, un bit de validité, le champ de lecture/écriture, et l'adresse exacte de lecture/écriture. Avec cette organisation, il est possible d'avoir plusieurs défauts de cache séparés, mais à la condition que chaque défaut accède à une ligne de cache différente. Deux accès simultanés à une même ligne de cache ne sont pas possibles, les défauts de cache secondaires ne sont pas autorisés. Ainsi, chaque défaut de cache se voit attribuer son propre MSHR, chacun contient un numéro de ligne de cache différent. Pour comprendre pourquoi c'est impossible de gérer les défauts secondaires, il faut regarder le champ de lecture/écriture. Si on veut effectuer plusieurs écritures consécutives à la même adresse, le MSHR n'aura pas de quoi mémoriser les deux données à écrire. Il pourra mémoriser la première donnée à écrire, pas la seconde. Même chose lors d'une lecture : le champ lecture/écriture peut mémorisr la destination de la première lecture, pas de la seconde. L'avantage est que la MHA n'a besoin que des MSHR et de quelques circuits annexes. Les autres solutions rajoutent des circuits annexes pour gérer les défauts de cache secondaires, qui utilisent beaucoup de circuits. Le cout en circuit est donc élevé, mais le gain en performance est là. Passons maintenant aux caches non-bloquants qui autorisent les défauts de cache secondaire. La solution la plus simple consiste à utiliser ===Les MSHR adressés implicitement=== Avec les '''MSHR adressés implicitement''', il est possible de fusionner plusieurs accès mémoire à une même ligne de cache, mais sous une condition très importante : ces accès lisent/écrivent des mots mémoire différents. Par exemple, imaginons qu'une ligne de cache contienne 8 mots mémoire de 64 bits. Si un premier accès mémoire lit le mot mémoire numéro 7 (dernier mot mémoire de la ligne), et le second accès le mot mémoire numéro 3, alors la fusion est possible. Mais si deux accès mémoire veulent lire/écrire le mot mémoire numéro 7, alors la fusion n'est pas possible et le processeur se bloque, un ''pipeline stall'' survient. Un MSHR adressé implicitement est un MSHR simple, qui contient naturellement un numéro de ligne de cache (tag) et un bit de validité, sauf que le champ de lecture/écriture est dupliqué. Les différents champs lecture/écriture d'un MSHR sont regroupés dans une mémoire RAM/cache qui contient autant d'entrées qu'il y a de mot mémoire dans une ligne de cache. Chaque entrée de la table est associée à un mot mémoire de la ligne de cache et stocke des informations sur celui-ci. Une entrée mémorise, au minimum : * Un champ de lecture/écriture qui contient soit la destination de la lecture, soit la donnée à écrire. * Un bit R/W permettant d'interpréter correctement le champ de lecture/écriture. * Un bit de validité pour chaque entrée de la table, qui dit si un défaut de cache antérieur accède déjà à ce mot mémoire. La fusion de deux défauts de cache est ainsi assez simple. Un défaut de cache primaire/secondaire configure l'entrée associée au mot mémoire lu/écrit. Si un défaut secondaire ultérieur lit/écrit un mot mémoire différent (dont le bit de validité de l'entrée dans la table est à zéro), pas de conflit : il configure une autre entrée, vide. Mais s'il lit/écrit un mot mémoire pour lequel l'entrée de la table est occupée, il y a conflit, le processeur est bloqué par un ''pipeline stall'', une bulle de pipeline. Avec cette organisation, le nombre de MSHR indique combien de lignes de cache peuvent être lues en même temps depuis la mémoire. Quant au nombre d'entrées par MSHR, il détermine combien d'accès mémoires qui ne se recouvrent pas peuvent avoir lieu en même temps. ===Les MSHR adressés explicitement=== Les '''MSHR adressés explicitement''' n'ont pas les contraintes des MSHR adressés implicitement. Avec eux, il est possible d'avoir plusieurs défauts de cache pointant vers la même ligne de cache, mais aussi vers le même mot mémoire. De tels défauts de cache apparaissent sur les processeurs à exécution dans le désordre, mais sont très rares, voire inexistants, sur les processeurs ''in-order''. L'idée est encore que chaque MSHR est associée à une table mémoire qui mémorise des entrées. Cette table n'est autre qu'une mémoire FIFO. Sauf que cette fois-ci, une entrée n'est pas associée à un mot mémoire. Une entrée est associée à un défaut de cache. Une entrée mémorise là encore la destination de la lecture ou la donnée de l'écriture suivi du bit R/W, ainsi que d'un bit de validité par entrée, mais aussi : la position du mot mémoire lu dans la ligne de cache. C'est cette dernière information qui n'était pas présente dans les MSHR adressés implicitement. De plus, le nombre d'entrée par MSHR n'est pas égal au nombre de mots mémoires dans une ligne de cache, mais peut être arbitrairement grand. L'avantage d'un tel cache est qu'il est capable de traité les défauts secondaires concernant un même mot mémoire, et ce, car les accès à la ligne de cache concernée se produisent dans l'ordre de génération des défauts (la table mémoire est une FIFO). ===Les MSHRs inversés=== Généralement, plus on veut supporter de défauts de cache, plus le nombre de MSHR et d'entrées augmente. Mais au-delà d'un certain nombre d'entrées et de MSHR, les MSHR adressés implicitement et explicitement ont tendance à bouffer un peu trop de circuits. Utiliser une organisation un peu moins gourmande en circuits est donc une nécessité. Cette organisation plus économe se base sur des MSHR inversés. Les MSHR inversés ne contiennent qu'une seule entrée, en quelque sorte : au lieu d’utiliser n MSHR de m entrées chacun, on va utiliser n × m MSHR inversés. La différence, c'est que plusieurs MSHR peuvent contenir un tag identique, contrairement aux MSHR adressés implicitement et explicitement. Lorsqu'un défaut de cache a lieu, chaque MSHR est vérifié. Si jamais aucun MSHR ne contient de tag identique à celui utilisé par le défaut, un MSHR vide est choisi pour stocker ce défaut, et une requête de lecture en mémoire est lancée. Dans le cas contraire, un MSHR est réservé au défaut de cache, mais la requête n'est pas lancée. Quand la donnée est disponible, les MSHR correspondant à la ligne qui vient d'être chargée vont être utilisés un par un pour résoudre les défauts de cache en attente. ===Les MSHR intégrés au cache=== Certains chercheurs ont remarqué que pendant qu'une ligne de cache est en train de subir un défaut de cache, celle-ci reste inutilisée, et son contenu est destiné à être perdu une fois le défaut de cache résolu. Ils se sont dit que, plutôt que d'utiliser des MSHR séparés, il vaudrait mieux utiliser la ligne de cache pour stocker les informations sur les défauts de cache en attente dans cette ligne de cache. Pour éviter tout problème, il faut rajouter un bit dans les bits de contrôle de la ligne de cache, qui sert à indiquer que la ligne de cache est occupée : un défaut de cache a eu lieu dans cette ligne, et elle stocke donc des informations pour résoudre les défauts de cache. ==L'entrelacement mémoire== Nous venons de voir comment une mémoire cache peut gérer plusieurs accès mémoire simultanés. Il se trouve que ce genre d'optimisation dépasse largement le cadre du cache. Les mémoires RAM ne sont pas en reste, elles aussi ! Il est possible de pipeliner l'accès à une mémoire RAM, en utilisant des techniques dites d''''entrelacement'''. Précisons cependant que cette optimisation n'est pas utilisée sur les mémoires SDRAM ou DDR modernes, du moins pas sans modifications majeures. Mais divers ordinateurs assez anciens, dont des superordinateurs, ont utilisé cette technique. La technique demande d'utiliser plusieurs mémoires séparées. Sur les anciens ordinateurs, les mémoires en question sont des chips mémoire, à savoir des circuits intégrés de DRAM. Pour faire plus général, nous allons utiliser le terme de '''banques''', ou encore de bancs mémoire. La différence est qu'il existe des mémoires multi-banque, qui regroupent plusieurs banques indépendantes dans un seul boitier, dans un seul chip mémoire. En clair, de telles mémoires regroupent plusieurs sous-mémoires dans un seul circuit intégré. Il est aussi possible d'utiliser plusieurs chips mémoire séparés, chacun ayant une banque. Reste à combiner ces banques pour former un pseudo-pipeline. ===Utiliser plusieurs banques sans entrelacement=== Sans optimisation particulière, les adresses sont réparties dans les banques comme indiqué ci-dessous. Il s'agit de ce qui s'appelle un arrangement horizontal, et nous avions vu celui-ci dans le chapiutre sur les mémoires SDRAM. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Avec cet arrangement, les bits de poids fort de l'adresse sont utilisées pour sélectionner la banque adéquate, et le reste de l'adresse est envoyé sur le bus d'adresse. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Adresse de banque !! Adresse dans la banque |- | Quelques bits de poids fort || Reste de l'adresse |} L'entrelacement change cette répartition, afin d'accélérer les accès mémoire. L'entrelacement classique vise à améliorer les accès à des adresses mémoires consécutives, mais des formes plus évoluées d'entrelacement visent à optimiser des accès mémoire arbitraires. Voyons-les dans l'ordre, du plus simple au plus complexe. ===L'entrelacement classique=== Avec l''''entrelacement classique''', des adresses consécutives sont réparties dans des banques consécutives. Précisons que cette attribution des adresses n'implique absolument pas la mémoire virtuelle ou n'importe quel mécanisme dans le processeur. La répartition décide que telle adresse mémoire va dans telle banque, à telle adresse dans la banque. Elle est donc le fait du contrôleur mémoire, donc en dehors du processeur (les contrôleurs mémoires n’étaient pas encore intégrés dans le processeur à l'époque). [[File:Répartition des adresses dans une mémoire interleaved.png|centre|vignette|upright=1.5|Répartition des adresses dans une mémoire interleaved.]] L'entrelacement simple permet d'accélérer les accès mémoire qui se font à des adresses consécutives. Avec l'entrelacement, chaque accès mémoire tombe dans une banque mémoire différente, ce qui fait qu'on peut démarrer un nouvel accès mémoire à chaque cycle d'horloge. Pendant qu'une banque est occupée par un accès mémoire, on démarre le suivant dans une autre banque, et ainsi de suite. Le tout se termine soit quand on a épuisé toutes les banques libres, soit quand l'accès en rafales se termine. Pas besoin d'attendre que la mémoire ait fini sa lecture/écriture avant de démarrer la lecture/écriture suivante. [[File:Pipemining mémoire.png|centre|vignette|upright=2.5|Pipelining mémoire]] L'animation suivante illustre bien le processus, avec quatres banques. [[File:Interleaving.gif|centre|vignette|upright=2.5|Pipelining mémoire]] L'entrelacement simple utilise les bits de poids faible pour sélectionner la banque, et les bits de poids fort pour la case mémoire. [[File:Adresse mémoire d'une mémoire entrelacée.png|centre|vignette|upright=2|Adresse mémoire d'une mémoire entrelacée]] Précisons que le temps d'accès mémoire ne change pas beaucoup avec l'entrelacement. Par contre, on peut faire plus d'accès mémoire simultanés. On peut démarrer un accès mémoire par cycle d'horloge, mais l'accès en lui-même prend plusieurs cycles. Les mémoires à entrelacement ont donc un débit supérieur aux mémoires qui ne l'utilisent pas. L'entrelacement classique pourrait être utilisé pour accélérer les transferts entre mémoire RAM et cache, qui se font en blocs d'adresses consécutives. Mais de nos jours, il n'est pas utilisé dans cette optique. Il faut dire qu'il est redondant avec le mode rafale des mémoires DRAM. Les deux font globalement la même chose et on voit mal comment utiliser les deux en même temps. Le mode rafale étant plus simple à implémenter, et plus léger niveau utilisation du bus de commande mémoire, il est préféré à l'entrelacement. Par contre, l'entrelacement a eu son heure de gloire sur d'anciens processeurs qui n'avaient pas de mémoire cache, alors qu'ils disposaient d'un pipeline. L'exemple typique est celui des nombreux ''processeurs vectoriels'', que nous n'avons pas encore abordé à ce stade du cours. De tels processeurs avaient un pipeline, une unité de calcul fortement pipelinée, et parfois même de l'exécution dans le désordre et du renommage de registres ! ===L'entrelacement par décalage=== L'entrelacement simple est très utile pour les accès en rafle ou équivalents. Par contre, il existe de rares situations où il n'est pas optimal. Une de ces situations est celle des accès en enjambée (en ''stride''), où l'on accède en série à des adresses sont séparées par N mots mémoires. [[File:Accès par enjambées.png|centre|vignette|upright=2|Accès par enjambées.]] De tels accès surviennent quand un logiciel accède à des structures de données spécifiques, à savoir des tableaux de structures, des matrices, ou d'autres structures de données dans le genre. Un cas classique est celui du parcours d'une matrice colonne par colonne. Les matrices de nombres sont mémorisées ligne par ligne, ce qui fait que parcourir une colonne demande de passer d'une ligne à la suivante et de faire des grands sauts en mémoire RAM, mais tous espacés par la même distance. Avec l'entrelacement simple, les accès en enjambée sont moins performants que les accès à des adresses consécutives, mais cela ne fonctionne pas trop mal. Le pire cas est celui où l'on a N banques et où les données sont justement placées toutes les N adresses. Des accès consécutifs vont tous tomber dans la même banque, on ne peut plus accéder à des banques différentes en parallèle. Mais en dehors de ce cas, on voit une amélioration par rapport à la situation sans entrelacement. Pour obtenir des performances maximales pour les accès en enjambées, il faut répartir les mots mémoires dans la mémoire autrement. L'organisation idéale est la suivante. : Dans les explications qui vont suivre, la variable N représente le nombre de banques, qui sont numérotées de 0 à N-1. On commence par organiser les N premières adresses comme une mémoire entrelacée simple : l'adresse 0 correspond à la banque 0, l'adresse 1 à la banque 1, etc. Pour le bloc suivant, on décale tout d'une adresse, à savoir qu'on commence à remplir les banques à partir de la seconde, non de la première. Une fois la fin du bloc atteinte, on finit de remplir le bloc en repartant du début du bloc. Le troisième bloc d'adresse subit le même traitement, sauf qu'on commence à remplir à partir de la seconde banque. Et on poursuit l’assignation des adresses en décalant d'un cran en plus à chaque bloc. Ainsi, chaque bloc verra ses adresses décalées d'un cran en plus comparé au bloc précédent. Si jamais le décalage dépasse la fin d'un bloc, alors on reprend au début. [[File:Mémoire interleaved par décalage.png|centre|vignette|upright=1.5|Mémoire entrelacée par décalage.]] En faisant cela, on remarque que les banques situées à N adresses d'intervalle sont différentes. Dans l'exemple du dessus, nous avons ajouté un décalage de 1 à chaque nouveau bloc à remplir. Mais on aurait tout aussi bien pu prendre un décalage de 2, 3, etc. Dans tous les cas, on obtient un entrelacement par décalage. Ce décalage est appelé le pas d'entrelacement, noté P. Le calcul de l'adresse à envoyer à la banque, ainsi que la banque à sélectionner se fait en utilisant les formules suivantes : * adresse à envoyer à la banque = adresse totale / N ; * numéro de la banque = (adresse + décalage) modulo N ; * décalage = (adresse totale * P) mod N. Avec cet entrelacement par décalage, on peut prouver que la bande passante maximale est atteinte si le nombre de banques est un nombre premier. Seulement, utiliser un nombre de banques premier peut créer des trous dans la mémoire, des mots mémoires inadressables. Malgré ce défaut, la technique a été utilisée sur quelques ordinateurs, avec l'exemple notable du superordinateur ''Burroughs Scientific Processor''. Pour éviter cela, il y a plusieurs solutions. Par exemple, on peut faire en sorte que N et la taille d'une banque soient premiers entre eux : ils ne doivent pas avoir de diviseur commun. Mais en pratique, elles n'ont pas vraiment été implémentées dans une vraie machine, et sont restées à l'état de recherche, aussi je les passe sous silence. ===L'entrelacement pseudo-aléatoire=== Une dernière méthode de répartition consiste à répartir les adresses dans les banques de manière "pseudo-aléatoire". L'idée exacte est de faire passer l'adresse dans une '''fonction de hachage''', plus ou moins complexe. Pour rappel, une fonction de hachage prend une entrée de grande taille et fournit en sortie un résultat de petite taille. Idéalement, elles doivent donner des résultats aussi différents que possible pour des entrées similaires, histoire de simuler une sorte de pseudo-aléatoire. Ici, une adresse est transformée en un numéro de banque. Une fonction de hachage simple ne fait que permuter des bits de l'adresse pour obtenir son résultat. Elle ne fait qu'échanger des bits de place avant de couper l'adresse en deux morceaux : un pour la sélection de la banque, et un autre pour la sélection de l'adresse dans la banque. Cette permutation est fixe, et ne change pas suivant l'adresse. Des fonctions de hachage plus complexes font des XOR bit à bit entre certains bits de l'adresse. ==Les contrôleurs mémoires SDRAM/DDR optimisés== Après avoir vu le cas des mémoires RAM, nous allons nous concentrer sur le cas particulier des mémoires SDRAM. Les mémoires SDRAM et DDR modernes sont capables de gérer plusieurs accès simultanés à la mémoire RAM, et leurs contrôleurs mémoire en font tout autant. C'est une différence majeure avec les mémoires asynchrones FPM/EDO, qui n'acceptaient qu'un seul accès mémoire à la fois. Leur contrôleur mémoire n'acceptait qu'un seul accès mémoire à la fois, c'était un contrôleur mémoire bloquant. Les contrôleurs mémoires des SDRAM sont eux non-bloquants et peuvent encaisser une dizaine d'accès mémoire à la fois. Peu de choses sont connues sur les contrôleurs de SDRAM/DDR modernes, les fabricants ne donnant que peu de détails dessus. Les rares simulateurs qui tentent de décrire leur fonctionnement, comme DRAM SIM I et II, sont particulièrement simples et ne vont pas dans le détail. Néanmoins, le peu qu'on sait est tout de même instructif. ===Rappel sur les SDRAM : tampon de ligne et commandes=== Les mémoires SDRAM sont des mémoires à tampon de ligne. Elles font un accès mémoire en deux étapes. La première étape recopie une ligne de N * 64 bits dans un tampon interne à la SDRAM. La seconde étape sélectionne une colonne, à savoir une donnée de 64 bits, qui est soit envoyée sur le bus de données pour une lecture, soit modifiée par une écriture. [[File:Mémoire à tampon de ligne.png|centre|vignette|upright=2|Mémoire à tampon de ligne]] Les accès mémoire sont traduits par un séquenceur mémoire en une série de commandes mémoires, qui sont séparées par des délais mémoire de quelques cycles d'horloge. Les délais sont très précis, et sont à respecter à la lettre. Une lecture ou une écriture se fait en maximum trois commandes : une commande PRECHARGE qui ferme la ligne précédemment utilisée, une commande ACT qui précise l'adresse de la ligne, et une commande READ ou WRITE qui précise l'adresse de la colonne et éventuellement la donnée à écrire. : Pour être plus précis, la commande PRECHARGE précharge les lignes de bits du plan mémoire à une tension particulière, ce qui les vide de leur contenu. Mais c'est un détail sans importance pour ce qui va suivre. Les SDRAM permettent de se passer de la première étape si des accès consécutifs se font dans la même ligne. Une fois activée, la ligne reste ouverte et on peut accéder plusieurs fois de suite dedans. On a alors juste à préciser l'adresse de colonne dedans. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | bgcolor="#FFA0FF" | PRECHARGE || bgcolor="#FFA0FF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || bgcolor="#A0FFFF" | READ (2) || bgcolor="#A0FFFF" | READ (3) || || || bgcolor="#A0FFFF" | READ (4) || bgcolor="#A0FFFF" | READ (5) || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | Donnée READ 1 || ||bgcolor="#A0FFFF" | Donnée READ 2 | bgcolor="#A0FFFF" | Donnée READ 3 || || || bgcolor="#A0FFFF" | Donnée READ 4 || bgcolor="#A0FFFF" | Donnée READ 5 |} Les SDRAM sont parfois capables de démarrer une commande avant que la précédente soit terminée. Mais le respect des délais mémoire est très limitant, ce qui fait qu'on ne peut pas parler de réel pipeline, comme c'est le cas sur les processeurs. Parlons plutot de '''commandes anticipées'''. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | || bgcolor="#A0FFFF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || || || bgcolor="#FFA0FF" | READ (2) || || || || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 | bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 | |} Un dernier point est que les chips mémoires de SDRAM sont composés de plusieurs banques, chacune étant une sorte de mini-mémoire miniature. Chaque banque a son propre décodeur, son propre tampon de ligne, ses propres multiplexeurs de colonne, sa logique de rafraichissement mémoire, etc. C'est comme si une SDRAM regroupait plusieurs mémoires séparées dans un même circuit intégré. [[File:Arrangement vertical.jpg|centre|vignette|upright=2.5|Mémoire multi-banques.]] ===La mise en attente des accès mémoire=== Un contrôleur de SDRAM peut accepter plusieurs accès mémoire en même temps. Mais cela ne signifie pas que le contrôleur sera prêt à les traiter immédiatement. Pour éviter tout problème de disponibilité, le contrôleur met en attente les accès mémoire que le processeur lui envoie, pour les exécuter dès que possible. Les accès mémoire sont mis en attente dans une mémoire FIFO, histoire de les exécuter dans leur ordre d'arrivée, notamment pour renvoyer les lectures dans l'ordre demandé. Il y a aussi une mémoire FIFO pour les données à écrire et une FIFO pour les données lues. Cette dernière sert au cas où le cache ne soit pas disponible quand on lui envoie la donnée lue. [[File:Module d'interface avec la mémoire.png|centre|vignette|upright=2.5|Contrôleur mémoire avec mise en attente des requêtes processeur et autres optimisations.]] Des optimisations sont possibles dès la mémoire FIFO. Par exemple, on peut regrouper plusieurs accès à des données consécutives en un seul accès en rafale. Le contrôleur mémoire analyse les requêtes mises en attente et détecte si certaines se font à des adresses consécutives. Si c'est le cas, il fusionne ces requêtes en une lecture/écriture en rafale. Une telle optimisation est appelée la '''combinaison de lecture''' pour les lectures, et la '''combinaison d'écriture''' pour les écritures. ===Les optimisations liées aux commandes anticipées=== Les commandes anticipées permettent de faire un minimum de parallélisme mémoire. Même si elle parait très limitée, cette possibilité est décuplée par la présence de plusieurs banques dans la mémoire SDRAM. Il est en effet possible de faire plusieurs accès mémoire en même temps, dans des banques différentes. Il est possible de lancer un accès mémoire dans deux banques en même temps, par exemple. Ou encore, on peut lancer un nouvel accès mémoire dans une banque si elle est inoccupée. La possibilité améliore grandement la performance de la SDRAM. Mais nous en reparlerons dans un chapitre ultérieur. {|class="wikitable" |+ Pipelining basique sur les SDRAM |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 |- ! Banque Numéro 1 | || || || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || || |- ! Banque Numéro 2 | || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || |- ! Banque Numéro 3 | || || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || |} L'implémentation n'a rien de vraiment compliqué. Il suffit d'utiliser une mémoire FIFO par banque. Ainsi, les accès dans une même banque se feront en série, dans l'ordre d'arrivée, mais des accès à des banques différentes se feront dans le désordre. Cela ne pose pas de problème car les accès se font à des adresses différentes, ce qui fait qu'il n'y a pas de conflits majeurs. [[File:Gestion parallèle des banques.png|centre|vignette|upright=2.5|Gestion parallèle des banques.]] ===Le ré-ordonnancement des commandes mémoires=== Un contrôleur mémoire moderne est capable de changer l'ordre des requêtes mémoires, pour gagner en performances. En clair : il peut faire l'équivalent mémoire de l'exécution dans le désordre des processeurs haute performance. Une différence majeure est que le contrôleur mémoire ne remet pas les accès mémoire dans l'ordre avant de les envoyer au processeur. C'est le processeur qui remet en ordre les accès mémoire, dans sa ''load queue''. Pour cela, les requêtes mémoire sont "numérotées", à savoir qu'on leur attribue un identifiant binaire. Le contrôleur mémoire exécute les lectures/écritures dans le désordre, mais garde la trace de leur identifiant. Il sait dans quel ordre il exécute les accès mémoire et connait leur latence. Ce qui fait qu'il sait que la donnée lue à tel instant est associée à la requête mémoire numéro X. Pour les lectures, il renvoie la donnée lue avec l'identifiant. Le processeur sait alors à quelle lecture la donnée lue correspond et il se débrouille en interne pour gérer la situation. En clair : le processeur voit les accès mémoire dans le désordre, c'est lui qui les remet en ordre. Maintenant que ces précisions sont faites, posons cette question : quel est l'intérêt de faire les accès mémoire dans le désordre ? Accéder à des banques en parallèle est une réponse possible, mais le ré-ordonnancement est surtout utile si tous les accès mémoire atterrissent dans la même banque et dans la même ligne. Elle marche si plusieurs accès à une même ligne ne sont pas consécutifs, et qu'il y a des accès entre les deux. Après ré-ordonnancement, ces accès mémoire sont exécutés l'un à la suite de l'autre. Pour rendre le tout plus concret, voici un exemple. Imaginez que l'on sait les 3 accès mémoire suivants : * Une lecture ligne A ; * Une écriture ligne B ; * Une lecture ligne A. Sans réordonnancent, on doit émettre deux commandes PRECHARGE : une quand on passe de la ligne A à la ligne B, et une autre pour le passage inverse. pour éviter cela, le contrôleur mémoire peut retarder l'écriture, ou au contraire avancer la seconde lecture. le résultat est alors le suivant : * Lecture ligne A ; * Lecture ligne A ; * Une écriture ligne B. On a donc deux accès consécutifs à la même ligne, suivi par une écriture dans une autre ligne. La technique marche parce que l'on a un mix adéquat de lectures et d'écritures. Mais il existe des possibilités de réorganisation autres qui ne sont pas valides. Il faut par exemple prendre garde à éviter d'intervertir lectures et écritures à une même adresse, sous peine de problèmes. Encore une fois, il faut faire attention aux dépendances mémoires. Le controleur mémoire teste les dépendances mémoires avant d'envoyer des commandes à la mémoire DDR/SDRAM. ==L'entrelacement sur les mémoires SDRAM== L''''entrelacement''' fonctionne sur les mémoires SDRAM, mais il doit être fortement modifié. Concrètement, tout ce qui a étré dit plus est inapplicable pour les SDRAM, du fait de la présence du tampon de ligne. Des adresses consécutives atterrissent déjà dans la même ligne, ce qui fait qu'on n'a pas besoin d'entrelacement au niveau d'une ligne. Et ne parlons pas de ce la présence de rangées et de canaux mémoire ! Cependant, il peut y avoir un entrelacement lié à la présence des banques SDRAM. L'entrelacement est géré juste avant le séquenceur mémoire. Le '''circuit d'entrelacement''' intervertit certains bits de l'adresse lors des accès mémoires. On trouve aussi des mémoires FIFOs pour mettre en attente les requêtes mémoire provenant du processeur. Elles permettent de recevoir plusieurs accès mémoire consécutifs. Si vous vous demandez comment plusieurs accès mémoire consécutifs peuvent avoir lieu, sachez que c'est une bonne question. Pour le moment, nous allons dire que ces accès proviennent du processeur ou de la mémoire cache, sans expliquer comment. Le contrôleur mémoire reçoit une série d'accès mémoire assez rapprochés, qu'il doit exécuter au mieux. [[File:Controleur mémoire d'une SDRAM avec entrelacement.png|centre|vignette|upright=2.5|Contrôleur mémoire d'une SDRAM avec entrelacement.]] Pour gérer l'entrelacement, le contrôleur de SDRAM prend en entrée l'adresse envoyée par le processeur, et la découpe en plusieurs champs : un pour sélectionner la banque adéquate, un autre pour la ligne, un autre pour la colonne, un autre pour la rangée. Une solution simple désactive l'entrelacement. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de banque || Adresse de ligne || Adresse de colonne |} Si cette solution fonctionne bien si les accès mémoire sont assez aléatoires, en pratique, mieux vaut utiliser l'entrelacement. ===L'entrelacement de banques/lignes=== Une première méthode d'entrelacement est l''''entrelacement de banques'''. Elle répartit deux lignes consécutives dans deux banques différentes. Pour comprendre l'idée, prenons un exemple. Imaginons une mémoire avec deux banques et 4 lignes. Imaginons qu'on parcoure/balaye la mémoire RAM en partant des adresses basses. Sans entrelacement de ligne, les accès se feront comme suit, de gauche à droite : {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Banque numéro 1 !! colspan="4" | Banque numéro 2 |- | Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 || Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 |} Avec l'entrelacement de banques, les accès se feront comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 |- | Ligne 1 || Ligne 1 || Ligne 2 || Ligne 2 || Ligne 3 || Ligne 3 || Ligne 4 || Ligne 4 |} Avec N banques, la première ligne ira dans la première banque, la seconde ligne dans la seconde, et ainsi de suite jusqu'à la énième banque qui va dans la banque numéro N. Et la ligne N+1 repart dans la première banque. L'avantage est que passer d'une ligne à la suivante est assez rapide. Au lieu de devoir fermer la première ligne et ouvrir la suivante on ouvrira directement la ligne suivante dans une banque différente, ce qui sera plus rapide. La commande PRECHARGE pourra être envoyée à la seconde banque en avance. Pour cela, l'adresse est découpée comme suit. {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne (précise à l'octet) |} ===L'entrelacement de rangées=== Il est aussi possible de faire la même chose, mais avec les rangées. Pour simplifier fortement, une rangée est simplement un chip mémoire. En réalité, les rangées ne sont pas des chip mémoire, mais un ensemble de chips mémoire regroupés ensemble histoire d'atteindre les 64 bits du bus de données. Par exemple, une rangée peut combiner 8 chips mémoire avec un bus de données de 8 bits chacun pour obtenir les 64 bits du bus de données d'une SDRAM. Mais nous passons ce détail sous silence dans les explications qui vont suivre, par souci de simplification. Pour faire comprendre l'entrelacement de rangée, le mieux est d'utiliser un exemple, le même que précédemment. Sans entrelacement de rangée, on change de chip mémoire une fois qu'on a balayé toutes ses adresses. {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Rangée numéro 1 !! colspan="4" | Rangée numéro 2 |- | Banque 1 || Banque 2 || Banque 3 || Banque 4 || Banque 1 || Banque 2 || Banque 3 || Banque 4 |} Avec l'entrelacement de ligne, on change de rangée dès qu'on change de banque/ligne. {|class="wikitable" |+ Adresse mémoire |- ! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 |- | Banque 1 || Banque 1 || Banque 2 || Banque 2 || Banque 3 || Banque 3 || Banque 4 || Banque 4 |} En clair, quand on a épuisé toutes les banques dans une même rangée, on passe à la rangée suivante au lieu de rester dans la même rangée. Notons que dans l'exemple précédent, on a combiné l'entrelacement de rangée et de banque, mais on aurait pu utiliser l'entrelacement de rangée seul. Mais ce n'est pas le cas le plus courant en pratique. Toujours est-il qu'en combinant entrelacement de rangée et de ligne, le découpage de l'adresse est le suivant : {|class="wikitable" |+ Adresse mémoire |- | Adresse de ligne || Numéro de rangée || Adresse de banque || Adresse de colonne (précise à l'octet) |} D'autres méthodes d'entrelacement sont possibles, mais elles sont plus complexes. Chaque contrôleur mémoire fait un peu à sa sauce. Ils permettent en général de choisir entre plusieurs entrelacements. Au minimum, ils permettent de désactiver l'entrelacement, d'activer un entrelacement de ligne, et d'autres entrelacements plus complexes, dont certains sont propriétaires et tenus secrets par le fabricant. ===L'entrelacement avec le ''dual channel''=== [[File:Dual channel slots.jpg|vignette|Slots mémoires avec ''dual channel''.]] L'usage du ''dual channel'' complique encore l'entrelacement. Là encore, il y a deux grandes solutions : avec et sans entrelacement des canaux mémoire. Rappelons le principe : deux barrettes de RAM sont accédées en parallèle. Et pour cela, il faut utiliser l'entrelacement. Typiquement, chaque barrette mémoire fournit 64 bits, ce qui fait que l'on peut accéder à 128 bits d'un seul coup, par exemple avec un accès en rafale. Sans ''dual channel'', la première barrette correspond à la moitié haute de la RAM, la seconde barrette correspond à la moitié basse. Avec ''dual channel'', une forme spécifique d'entrelacement est activée. Concrétement, deux blocs de 64 bits sont placés dans des canaux mémoire séparés. Idem avec du triple ou quadruple canal, mais c'est alors trois ou quatre blocs de 64 bits qui sont dispersés dans des canaux consécutifs. Le découpage de l'adresse est alors le suivant : {|class="wikitable" |+ Adresse mémoire |- ! colspan="6" | Sans entrelacement |- | Numéro de canal/barrette || Reste de l'adresse || Position de l'octet dans un bloc de 64 bits |- | colspan="6" | |- ! colspan="6" | Avec entrelacement |- | Reste de l'adresse || Numéro de canal/barrette || Position de l'octet dans un bloc de 64 bits |} [[File:Décodage d'adresse avec dual channel.png|centre|vignette|upright=2|Décodage d'adresse avec dual channel.]] Les contrôleurs de SDRAM précédents sont assez basiques et ne représentent pas les contrôleurs les plus évolués. Ils étaient utilisés sur les anciens PC, à une époque où ils étaient encore sur la carte mère du processeur. Mais de nos jours, le contrôleur mémoire est intégré au processeur. Et il incorpore de nombreuses optimisations afin de gagner en performances. Malheureusement, ces optimisations sont elles-mêmes dépendantes des optimisations intégrées dans le processeur, ce qui fait qu'on ne peut pas en parler ici. Elles demandent que le contrôleur mémoire reçoive plusieurs accès mémoire simultanés, ce qui n'a aucun sens à ce stade du cours. Aussi, nous allons les passer sous silence pour le moment. Nous les verrons dans le chapitre sur le parallélisme mémoire. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=La désambiguïsation mémoire | prevText=La désambiguïsation mémoire | next=Les processeurs superscalaires | nextText=Les processeurs superscalaires }} </noinclude> iebrrw68wao4p9uhxr5tfb0pdy32jtq 764721 764720 2026-04-23T21:26:07Z Mewtow 31375 /* Les optimisations liées aux commandes anticipées */ 764721 wikitext text/x-wiki Dans ce chapitre, nous allons voir les techniques qui permettent de gérer plusieurs accès mémoire simultanés directement au niveau du cache ou de la mémoire RAM. Par plusieurs accès mémoire simultanés, vous pensez sans doute à l'usage de cache multiports, voire de mémoires RAM multiport. C'est en effet une solution, mais ce n'est clairement pas la seule. Par exemple, il est possible de pipeliner l'accès au cache, voire à la mémoire RAM. Il existe de nombreuses techniques de '''parallélisme mémoire''', et nous allons les voir dans ce chapitre. Pour donner un exemple, il est possible de pipeliner les accès mémoire. Il est possible de pipeliner l'accès au cache et/ou l'accès à la mémoire, et nous verrons comment faire dans ce chapitre. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, qu'ils aient ou non un pipeline dynamique. Par contre, pipeliner la mémoire est une technique ancienne, aujourd'hui peu utilisée. Elle était utilisée sur des très vieux ordinateurs, qui avaient un pipeline mais pas de mémoire cache. Mais de nos jours, les mémoires SDRAM et DDR ne sont pas adaptées pour ça, il est très difficile de les pipeliner correctement. Le parallélisme mémoire est utile aussi bien sur des processeurs à émission dans l'ordre que dans le désordre. Par exemple, un processeur ''in-order'' avec des lectures non-bloquantes peut e, profiter. Il peut techniquement lancer une seconde lecture, après avoir rencontré une première lecture non-bloquante. En général, le nombre de lectures consécutives est cependant limité à moins d'une dizaine. À l'opposé, les processeurs sans lectures non-bloquantes profitent pas du parallélisme mémoire. À la rigueur, ils peuvent en profiter s'ils intègrent des techniques de préchargement. Pendant que le processeur exécute une lecture/écriture, il peut précharger une autre donnée en parallèle. Inutile de dire que sans pipeline processeur, le parallélisme mémoire ne sert pas à grand-chose. ==Le ''line fill buffer'' et les techniques associées== Lors d'un défaut de cache, le processeur doit attendre que toute la ligne de cache soit chargée avant d'être utilisable. Or, la taille d'une ligne de cache est supérieure à la largeur du bus mémoire, ce qui fait qu'une ligne de cache est chargée en plusieurs fois, morceaux par morceaux, mot mémoire par mot mémoire. Le chargement peut se faire directement dans le cache, mais ce n'est pas une solution très pratique. À la place, beaucoup de processeurs ajoutent une mémoire tampon entre la RAM et le cache, appelée le '''tampon de remplissage de ligne''' (''line-fill buffer'') dans les processeurs modernes. Lors d'un défaut de cache, le processeur charge la donnée de la RAM dans le tampon de remplissage de ligne, mot mémoire par mot mémoire. Une fois plein, le tampon de remplissage de ligne est recopié dans la ligne de cache. Le tampon de remplissage de ligne contient une ligne de cache, si ce n'est que certains bits de contrôle ne sont pas présents. Le tampon de remplissage de ligne contient un bit de validité pour chaque mot mémoire de la ligne de cache, qui indiquent si le mot mémoire a été chargé. Par exemple, prenons un processeur 64 bits, qui gère donc des mots mémoire de 8 octets, avec des lignes de cache 256 octets/32 mots mémoire. Le tampon de remplissage de ligne contiendra 32 bits de validité. Si le processeur a chargé les 6 premiers mots mémoire, les 6 premiers bits de validité seront mis à 1, les autres seront encore à zéro. Il faut noter que la disponibilité de la ligne complète se détermine assez facilement en faisant un ET logique entre tous les bits de validité. Le processeur sait ainsi quand la ligne de cache est disponible entièrement et donc quand la transférer dans le cache. La même chose existe avec une hiérarchie de cache, sauf que l'on trouve une mémoire tampon entre chaque niveau de cache. Il y a un tampon de remplissage de ligne entre le cache L1 et le cache L2, entre le cache L2 et le L3, etc. Si le cache ne gère qu'un seul défaut de cache à la fois, le ''fill line buffer'' est une mémoire très simple, qui ne mémorise qu'une seule ligne de cache. Et cela vaut aussi bien pour un cache bloquant que non-bloquant. Mais sur les caches capables de gérer plusieurs défauts simultanés, le tampon de remplissage de ligne est une mémoire de type FIFO ou LIFO, capable de mémoriser plusieurs lignes de cache. Notons que le tampon de remplissage de ligne est très utile pour implémenter certaines techniques, par exemple le contournement du cache. Nous avions vu dans le chapitre sur le cache que certains accès mémoire doivent contourner le cache, pour des raisons de cohérence des caches. C'est notamment nécessaire pour accéder aux périphériques, mais c'est aussi utile pour des raisons de performances dans des cas très spécifiques. Les accès qui contournent le cache se font directement dans le tampon de remplissage de ligne : le processeur écrit ou lit les données depuis ce tampon de remplissage de ligne, sans accéder au cache. ===L'''early restart'' et le ''critical word load''=== La présence du ''line buffer'' permet une optimisation assez intéressante, qui permet de réduire la latence des défauts de cache. L'optimisation consiste à lire un mot mémoire dans le tampon de remplissage de ligne, même si la ligne de cache complète n'a pas encore été chargée. Il existe deux manières de faire cela, qui portent les noms d'''early restart'' et de ''critical word load''. La première est la version la plus simple, la seconde est plus complexe mais plus performante. Avec la technique d''''''early restart''''', la ligne de cache est chargée normalement, en partant de son premier mot mémoire. Dès que le mot mémoire lu/écrit par le processeur est copié dans le ''line fill buffer'', il est envoyé au processeur immédiatement. Illustrons le tout par un exemple, où une ligne de cache fait 16 mots mémoire. Le processeur effectue une lecture, qui lit le 5ème mot mémoire. Sans ''early restart'', le processeur doit charger les 16 mots mémoire avant de faire la lecture dans le cache. Avec ''early restart'', le processeur reçoit la donnée dès que le 5ème mot mémoire est disponible. Le processeur doit attendre que les 4 premiers mots mémoire soient chargés, puis le 5ème mot mémoire arrive et est envoyé directement au processeur, il est lu directement depuis le ''line fill buffer''. Les 11 mots mémoire suivants sont ensuite chargés dans le cache pendant que le processeur fait des calculs dans son coin. Le ''critical word load'' est une optimisation de la technique précédente où le chargement de la ligne de cache commence directement à la donnée demandée par le processeur. Pour reprendre l'exemple précédent, où le processeur demande le 5ème mot mémoire sur 16, le ''critical word load'' charge le 5ème mot mémoire en premier et l'envoie au processeur, ce qui fait qu'il est chargé très rapidement. Pas besoin d'attendre que le processeur charge les 4 mots mémoire précédents comme avec l'''early restart''. Dans le détail, le processeur charge le 5ème mot mémoire en premier, puis charge les 11 suivants, et termine par les 4 mots mémoire du début. En clair, le ''critical word load'' commence par charger le mot mémoire lu, puis les blocs suivants, avant de revenir au début du bloc pour charger les blocs restants. Ainsi, la donnée demandée par le processeur sera la première disponible. Pour cela, l'organisation du tampon de remplissage de ligne est modifiée de manière à rendre cela possible. Il a une taille égale à une ligne de cache complète, qui contient elle-même plusieurs mots mémoire. Dans le ''line-fill buffer'', chaque mot mémoire est stocké avec un tag, qui indique l'adresse du mot mémoire stocké dans le ''line-fill buffer''. Le ''line-fill buffer'' est donc un cache un peu particulier, qui fonctionne comme un cache du point de vue du processeur, comme une mémoire FIFO pour les transferts avec le cache. Ainsi, un processeur qui veut lire dans le cache après un défaut peut accéder à la donnée directement depuis le tampon de remplissage de ligne, alors que la ligne de cache n'a pas encore été totalement recopiée en mémoire. ==Les caches pipelinés== Il est possible de pipeliner l'accès au cache, ce qui demande juste de rajouter des registres au bon endroits et quelques circuits de contrôle. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, même ceux avec un pipeline dynamique. Et l'implémentation peut se faire de plusieurs manières différentes. Il faut dire que découper une mémoire cache en plusieurs étapes peut se faire de plusieurs manière. Commençons par la plus simple. Beaucoup de processeurs des années 2000 avaient une fréquence peu élevée comparée aux standards d'aujourd'hui, ce qui fait que leur cache avait bien un temps d'accès d'un cycle d'horloge. Mais l'accès au cache se faisait en deux cycles d'horloge : un cycle pour calculer une adresse et un cycle pour l'accès mémoire proprement dit. Et ces deux étapes étaient pipelinées, à savoir que deux micro-opérations mémoire peuvent s'exécuter en même temps : une dans l'étage de calcul d'adresse, une autre dans le cache. L'unité mémoire était donc pipelinée, alors que l'accès au cache ne l'était pas. Les processeurs qui implémentaient cette technique regroupent les micro-architectures K5 et K6 d'AMD, les processeurs Intel de micro-architecture P6 (Pentium 2 et 3) et quelques autres. Il est aussi possible de pipeliner l'accès au cache lui-même. Avec un cache pipeliné, l'accès au cache ne se fait pas en un seul cycle, mais en plusieurs. Cependant, on peut lancer un nouvel accès au cache à chaque cycle d'horloge, comme un processeur pipeliné lance une nouvelle instruction à chaque cycle. L'implémentation est assez simple : il suffit d'ajouter des registres dans le cache. Pour cela, on profite que le cache est composé de plusieurs composants séparés, qui échangent des données dans un ordre bien précis, d'un composant à un autre. De plus, le trajet des informations dans un cache est linéaire, ce qui les rend parfois pour l'usage d'un pipeline. ===Le ''pipelining'' des caches ''direct-mapped''=== Voici ce qui se passe avec un cache directement adressé. Pour rappel ce genre de cache est conçu en combinant une mémoire RAM, généralement une SRAM, avec quelques circuits de comparaison et un MUX. L'accès au cache se fait globalement en deux étapes : la première lit la donnée et le ''tag'' dans la SRAM, et on utilise les deux dans une seconde étape. Nous avions vu il y a quelques chapitre comment pipeliner des mémoires, dans le chapitre sur les mémoires évoluées. Et bien ces méthodes peuvent s'utiliser pour la mémoire RAM interne au cache !L'idée est d'insérer un registre entre la sortie de la RAM et la suite du cache, pour en faire un cache pipéliné, à deux étages. Le premier étage lit dans la SRAM, le second fait le reste. L'implémentation sur les caches associatifs à plusieurs voies est globalement la même à quelques détails près. [[File:Cache directement adressé pipeliné - deux étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - deux étages]] Avec le circuit précédent, il est possible d'aller plus loin, cette fois en pipelinant l'accès à la mémoire RAM interne au cache. Pour comprendre comment, rappelons qu'une mémoire SRAM est composée d'un plan mémoire et d'un décodeur. L'accès à la mémoire demande d'abord que le décodeur fasse son travail pour sélectionner la case mémoire adéquate, puis ensuite la lecture ou écriture a lieu dans cette case mémoire. L'accès se fait donc en deux étapes successives séparées, on a juste à mettre un registre entre les deux. Il suffit donc de mettre un gros registre entre le décodeur et le plan mémoire. [[File:Cache directement adressé pipeliné - trois étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - trois étages]] Et on peut aller encore plus loin en découpant le décodeur en deux circuits séparés. En effet, rappelez-vous le chapitre sur les circuits de sélection : nous avions vu qu'il est possible de créer des décodeurs en assemblant des décodeurs plus petits, contrôlés par un circuit de prédécodage. Et bien on peut encore une ajouter un registre entre ce circuit de prédécodage et les petits décodeurs. : Théoriquement, toute l'adresse est fournie d'un seul coup au cache, la quasi-totalité des processeurs présentent l'adresse complète à un cache pipeliné. Mais le Pentium 4 fait autrement. Il faut noter que les premiers étages manipulent l'indice dans la SRAM, qui est dans les bits de poids faible, alors que les étages ultérieurs manipulent le ''tag'' qui est dans les bits de poids fort. Les concepteurs du Pentium 4 ont alors décidé de présenter les bits de poids faible lors du premier cycle d'accès au cache, puis ceux de poids fort au second cycle. Pour cela, l'ALU fonctionnait à une fréquence double de celle du processeur, tout comme le cache L1. Il n'y avait pas de pipeline proprement dit, mais cela réduisait grandement la latence d'accès au cache. ===Le ''pipelining'' des caches splittés/sériels=== Il est aussi possible de pipeliner un cache dits splittés, aussi appelés à accès sériel. Pour rappel, les caches à accès sériel vérifient si il y a succès ou défaut de cache, avant d'accéder aux lignes de cache en cas de succès. Ils font donc différemment des autres caches, qui accèdent à une ligne de cache, avant de déterminer s'il y a succès ou défaut en lisant le tag de la ligne de cache. Les caches sériels disposent de deux SRAM : une pour les tags des lignes de cache et une pour les données. Ils accèdent à la SRAM pour les tags, avant d’accéder à la SRAM des données en cas de succès de cache. Vu que l'accès se fait en deux étapes, une vérification des tags suivie de la lecture/écriture des données, il est facile à pipeliner. Pipeliner le cache permet de régler le problème des accès au cache L1, et elle est tout le temps utilisé sur les processeurs modernes. Mais que faire en cas de défaut de cache ? ==Les caches non bloquants== Un '''cache bloquant''' est un cache auquel le processeur ne peut pas accéder pendant un défaut de cache. Il faut attendre que la lecture ou écriture en RAM soit terminée avant de pouvoir utiliser de nouveau le cache. Un '''cache non bloquant''' n'a pas ce problème : on peut l'utiliser pendant un défaut de cache. Les caches non bloquants permettent de démarrer une nouvelle lecture ou écriture alors qu'une autre est en cours, ce qui permet d'exécuter plusieurs lectures ou écritures en même temps. ===Les ''Miss Handling Status Registers''=== Lors d'un défaut de cache, la mémoire RAM est consultée pendant le défaut de cache, mais le cache est inutilisé. Un défaut de cache n'utilise pas le cache, ce n'est qu'un accès en mémoire RAM, sur le bus mémoire (ou un accès aux niveaux de cache inférieurs, peu importe). Le cache en lui-même est laissé libre, rien n’empêche d'y accéder, il est en réalité intrinsèquement non-bloquant. Les caches, bloquants comme non-bloquants, sont en réalité composés d'une mémoire cache proprement dite, entourée de circuits qui servent d'interface entre le processeur et le cache lui-même. Et parmi les circuits tout autour, certains gèrent l'accès au cache lors d'un défaut de cache. Ils sont regroupés sous le terme de '''''Miss Handling Architecture''''' (MHA). La différence entre un cache bloquant et un cache non-bloquant est en réalité liée à l'implémentation de la MHA. Les caches bloquants coupent volontairement l'accès au cache lors d'un défaut, car les défauts sont plus simples à gérer ainsi. Le défaut de cache rapatrie une donnée depuis la RAM, qui sera écrite dans le cache. Et il ne faut pas qu'une tentative d'accès à cette donnée ait lieu avant qu'elle ne soit chargée. Mais ce blocage est général et touche tout le cache, alors que seule une ligne de cache est concernée par le défaut de cache. L'idée derrière un cache non-bloquant est que seule la ligne de cache est bloquée, mais les autres sont accessibles. L'idée est alors de mémoriser les lignes de cache concernées par le défaut de cache, afin d'en bloquer l'accès. À chaque accès, on vérifie si la ligne de cache est déjà réservée par un défaut de cache. La lecture/écriture est alors bloquée si c'est le cas, mais elle accepte les accès sinon. Pour cela, la ''Miss Handling Architecture'' contient des registres qui mémorisent des informations sur les défauts de cache en cours. Ils portent le nom de '''''miss status handling registers''''', que j’appellerais dorénavant MSHR, qui sont aussi appelés des '''''miss buffer'''''. Le contenu des MSHR varie beaucoup suivant le processeur, mais ces derniers stockent au minimum les informations suivantes : * Le numéro de la ligne de cache dans laquelle les données sont chargées. * Un bit de validité qui indique si le MSHR est vide ou pas, qui est mis à 0 quand le défaut de cache est résolu. * Un ou plusieurs champs de lecture/écriture, qui contiennent des informations sur la lecture/écriture. ** Pour une lecture, elle contient des informations sur la destination de la donnée, à savoir qui prévenir quand le défaut de cache est terminé. C'est parfois un nom/numéro de registre (celui dans lequel charger la donnée), mais c'est souvent le numéro de l'entrée dans le ''load/store queue''. ** Pour les écritures, elle contient la donnée à écrire, ou éventuellement un numéro de ''load/store queue'' où se trouve la donnée à écrire. Il faut noter que le bus mémoire ne peut gérer qu'un seul défaut de cache à la fois. Aussi, il est intéressant de regarder ce qui se passe lorsqu'un second défaut de cache survient, pendant qu'un premier est en cours. Dans ce cas, il y a deux réponses qui correspondent à deux types de caches non-bloquants, qui portent les noms barbares de caches de type succès après défaut et défaut après défaut. Sur le premier type, il ne peut pas y avoir plusieurs défauts de cache simultanés. Dès qu'un second défaut de cache survient, le cache stoppe son activité et on ne peut plus démarrer de nouvelle lecture/écriture, tant que le premier défaut de cache n'est pas résolu. Le second type est plus souple et autorise la survenue de plusieurs défauts de cache simultanés. Du moins, jusqu'à une certaine limite, car le cache ne peut supporter qu'un nombre limité d'accès mémoires simultanés (pipelinés). ===Les accès simultanés à une même ligne de cache=== Il arrive que le processeur fasse plusieurs accès mémoire simultanés à la même ligne de cache. Si la ligne de cache en question n'a pas encore été chargée dans le cache, alors on a plusieurs défauts de cache consécutifs pour la même ligne de cache, et le cache non-bloquant doit gérer la situation. Pour la suite, il va falloir faire une petite distinction entre les défauts primaires et secondaires. Imaginons qu'un défaut de cache ait lieu et demande à charger une donnée dans la ligne de cache numéro N. Il s'agit du premier défaut impliquant cette ligne de cache précise, ce qui lui vaut le nom de '''défaut de cache primaire'''. Mais par la suite, d'autres accès mémoire à la même ligne de cache ont lieu, alors que la ligne de cache n'est pas encore disponible. Dans ce cas, il s'agit de '''défauts de cache secondaires'''. Pour l'unité d'accès mémoire, les défauts de cache primaire et secondaire sont différents (ils prennent tous une entrée dans la ''load/store queue''). Mais pour le cache, ils ne correspondent qu'à un seul accès au cache : celui qui demande de charger la ligne de cache demandée. Les défauts de cache primaire et secondaire à la même ligne de cache se voient attribuer un MSHR unique. La gestion des défauts de cache secondaires dépend du cache non-bloquant. La solution la plus simple ne permet pas les défauts de cache secondaires. Le cache ne permet pas deux défauts de cache simultanés pour la même ligne de cache. Les autres solutions le permettent, en fusionnant des accès simultanés à la même ligne de cache en un seul au niveau des MSHR. Dans tous les cas, détecter les défauts de cache secondaires sont un problème qu'il faut détecter. La ''Miss Handling Architecture'' doit détecter les défauts de cache secondaire. Pour cela, elle procède comme suit. Lors de chaque défaut de cache, la MHA récupère le numéro de la ligne de cache associé. Il vérifie alors chaque MSHR pour vérifier s'il contient le numéro en question. S'il n'y a aucune correspondance dans les MSHR, alors c'est signe que le défaut de cache est un défaut primaire. Mais s'il y en a une, alors c'est un défaut secondaire. Évidemment, cela signifie que lors d'un défaut de cache, le numéro de ligne de cache est envoyé à tous les MSHR, pour comparaison. Les MSHR sont donc regroupés dans une mémoire associative, une sorte de mini-cache, faciliter l'implémentation. Lors d'un défaut de cache primaire, l'accès à un cache non-bloquant se fait comme suit : le processeur envoie une adresse au cache, accède à celui-ci, et détecte la survenue d'un défaut de cache. Il en profite alors pour attribuer une ligne de cache dans laquelle sera chargée ce défaut. L'attribution est très simple dans le cas des caches ''direct mapped'', ou associatifs par voie, pour lesquels l'attribution se fait assez simplement. Il mémorise alors cette information dans les MSHR, après avoir vérifié que le défaut de cache n'était pas un défaut secondaire. Lors des accès ultérieurs à une adresse proche, censée être dans la même ligne de cache, le processeur va encore une fois rencontrer un défaut de cache. Il va alors déterminer le numéro de la ligne de cache associée à l'adresse, et comparer ce numéro avec les MSHRs. Si un MSHR contient ce numéro, c'est signe que le défaut de cache est un défaut secondaire. La MHA réagit alors différemment selon le processeur considéré. Une première solution n'autorise pas les défauts de cache secondaires. Si l'un d'entre eux survient, le processeur est gelé par un ''pipeline stall'', une bulle de pipeline. Une autre solution fusionne les défauts de cache secondaires avec le défaut de cache primaire : tout cela ne correspond qu'à un seul défaut de cache pour lui. ===Les MSHR simples=== Un cache non-bloquant à '''MSHR simple''' contient juste plusieurs MSHR qui mémorisent juste un numéro de ligne de cache, un bit de validité, le champ de lecture/écriture, et l'adresse exacte de lecture/écriture. Avec cette organisation, il est possible d'avoir plusieurs défauts de cache séparés, mais à la condition que chaque défaut accède à une ligne de cache différente. Deux accès simultanés à une même ligne de cache ne sont pas possibles, les défauts de cache secondaires ne sont pas autorisés. Ainsi, chaque défaut de cache se voit attribuer son propre MSHR, chacun contient un numéro de ligne de cache différent. Pour comprendre pourquoi c'est impossible de gérer les défauts secondaires, il faut regarder le champ de lecture/écriture. Si on veut effectuer plusieurs écritures consécutives à la même adresse, le MSHR n'aura pas de quoi mémoriser les deux données à écrire. Il pourra mémoriser la première donnée à écrire, pas la seconde. Même chose lors d'une lecture : le champ lecture/écriture peut mémorisr la destination de la première lecture, pas de la seconde. L'avantage est que la MHA n'a besoin que des MSHR et de quelques circuits annexes. Les autres solutions rajoutent des circuits annexes pour gérer les défauts de cache secondaires, qui utilisent beaucoup de circuits. Le cout en circuit est donc élevé, mais le gain en performance est là. Passons maintenant aux caches non-bloquants qui autorisent les défauts de cache secondaire. La solution la plus simple consiste à utiliser ===Les MSHR adressés implicitement=== Avec les '''MSHR adressés implicitement''', il est possible de fusionner plusieurs accès mémoire à une même ligne de cache, mais sous une condition très importante : ces accès lisent/écrivent des mots mémoire différents. Par exemple, imaginons qu'une ligne de cache contienne 8 mots mémoire de 64 bits. Si un premier accès mémoire lit le mot mémoire numéro 7 (dernier mot mémoire de la ligne), et le second accès le mot mémoire numéro 3, alors la fusion est possible. Mais si deux accès mémoire veulent lire/écrire le mot mémoire numéro 7, alors la fusion n'est pas possible et le processeur se bloque, un ''pipeline stall'' survient. Un MSHR adressé implicitement est un MSHR simple, qui contient naturellement un numéro de ligne de cache (tag) et un bit de validité, sauf que le champ de lecture/écriture est dupliqué. Les différents champs lecture/écriture d'un MSHR sont regroupés dans une mémoire RAM/cache qui contient autant d'entrées qu'il y a de mot mémoire dans une ligne de cache. Chaque entrée de la table est associée à un mot mémoire de la ligne de cache et stocke des informations sur celui-ci. Une entrée mémorise, au minimum : * Un champ de lecture/écriture qui contient soit la destination de la lecture, soit la donnée à écrire. * Un bit R/W permettant d'interpréter correctement le champ de lecture/écriture. * Un bit de validité pour chaque entrée de la table, qui dit si un défaut de cache antérieur accède déjà à ce mot mémoire. La fusion de deux défauts de cache est ainsi assez simple. Un défaut de cache primaire/secondaire configure l'entrée associée au mot mémoire lu/écrit. Si un défaut secondaire ultérieur lit/écrit un mot mémoire différent (dont le bit de validité de l'entrée dans la table est à zéro), pas de conflit : il configure une autre entrée, vide. Mais s'il lit/écrit un mot mémoire pour lequel l'entrée de la table est occupée, il y a conflit, le processeur est bloqué par un ''pipeline stall'', une bulle de pipeline. Avec cette organisation, le nombre de MSHR indique combien de lignes de cache peuvent être lues en même temps depuis la mémoire. Quant au nombre d'entrées par MSHR, il détermine combien d'accès mémoires qui ne se recouvrent pas peuvent avoir lieu en même temps. ===Les MSHR adressés explicitement=== Les '''MSHR adressés explicitement''' n'ont pas les contraintes des MSHR adressés implicitement. Avec eux, il est possible d'avoir plusieurs défauts de cache pointant vers la même ligne de cache, mais aussi vers le même mot mémoire. De tels défauts de cache apparaissent sur les processeurs à exécution dans le désordre, mais sont très rares, voire inexistants, sur les processeurs ''in-order''. L'idée est encore que chaque MSHR est associée à une table mémoire qui mémorise des entrées. Cette table n'est autre qu'une mémoire FIFO. Sauf que cette fois-ci, une entrée n'est pas associée à un mot mémoire. Une entrée est associée à un défaut de cache. Une entrée mémorise là encore la destination de la lecture ou la donnée de l'écriture suivi du bit R/W, ainsi que d'un bit de validité par entrée, mais aussi : la position du mot mémoire lu dans la ligne de cache. C'est cette dernière information qui n'était pas présente dans les MSHR adressés implicitement. De plus, le nombre d'entrée par MSHR n'est pas égal au nombre de mots mémoires dans une ligne de cache, mais peut être arbitrairement grand. L'avantage d'un tel cache est qu'il est capable de traité les défauts secondaires concernant un même mot mémoire, et ce, car les accès à la ligne de cache concernée se produisent dans l'ordre de génération des défauts (la table mémoire est une FIFO). ===Les MSHRs inversés=== Généralement, plus on veut supporter de défauts de cache, plus le nombre de MSHR et d'entrées augmente. Mais au-delà d'un certain nombre d'entrées et de MSHR, les MSHR adressés implicitement et explicitement ont tendance à bouffer un peu trop de circuits. Utiliser une organisation un peu moins gourmande en circuits est donc une nécessité. Cette organisation plus économe se base sur des MSHR inversés. Les MSHR inversés ne contiennent qu'une seule entrée, en quelque sorte : au lieu d’utiliser n MSHR de m entrées chacun, on va utiliser n × m MSHR inversés. La différence, c'est que plusieurs MSHR peuvent contenir un tag identique, contrairement aux MSHR adressés implicitement et explicitement. Lorsqu'un défaut de cache a lieu, chaque MSHR est vérifié. Si jamais aucun MSHR ne contient de tag identique à celui utilisé par le défaut, un MSHR vide est choisi pour stocker ce défaut, et une requête de lecture en mémoire est lancée. Dans le cas contraire, un MSHR est réservé au défaut de cache, mais la requête n'est pas lancée. Quand la donnée est disponible, les MSHR correspondant à la ligne qui vient d'être chargée vont être utilisés un par un pour résoudre les défauts de cache en attente. ===Les MSHR intégrés au cache=== Certains chercheurs ont remarqué que pendant qu'une ligne de cache est en train de subir un défaut de cache, celle-ci reste inutilisée, et son contenu est destiné à être perdu une fois le défaut de cache résolu. Ils se sont dit que, plutôt que d'utiliser des MSHR séparés, il vaudrait mieux utiliser la ligne de cache pour stocker les informations sur les défauts de cache en attente dans cette ligne de cache. Pour éviter tout problème, il faut rajouter un bit dans les bits de contrôle de la ligne de cache, qui sert à indiquer que la ligne de cache est occupée : un défaut de cache a eu lieu dans cette ligne, et elle stocke donc des informations pour résoudre les défauts de cache. ==L'entrelacement mémoire== Nous venons de voir comment une mémoire cache peut gérer plusieurs accès mémoire simultanés. Il se trouve que ce genre d'optimisation dépasse largement le cadre du cache. Les mémoires RAM ne sont pas en reste, elles aussi ! Il est possible de pipeliner l'accès à une mémoire RAM, en utilisant des techniques dites d''''entrelacement'''. Précisons cependant que cette optimisation n'est pas utilisée sur les mémoires SDRAM ou DDR modernes, du moins pas sans modifications majeures. Mais divers ordinateurs assez anciens, dont des superordinateurs, ont utilisé cette technique. La technique demande d'utiliser plusieurs mémoires séparées. Sur les anciens ordinateurs, les mémoires en question sont des chips mémoire, à savoir des circuits intégrés de DRAM. Pour faire plus général, nous allons utiliser le terme de '''banques''', ou encore de bancs mémoire. La différence est qu'il existe des mémoires multi-banque, qui regroupent plusieurs banques indépendantes dans un seul boitier, dans un seul chip mémoire. En clair, de telles mémoires regroupent plusieurs sous-mémoires dans un seul circuit intégré. Il est aussi possible d'utiliser plusieurs chips mémoire séparés, chacun ayant une banque. Reste à combiner ces banques pour former un pseudo-pipeline. ===Utiliser plusieurs banques sans entrelacement=== Sans optimisation particulière, les adresses sont réparties dans les banques comme indiqué ci-dessous. Il s'agit de ce qui s'appelle un arrangement horizontal, et nous avions vu celui-ci dans le chapiutre sur les mémoires SDRAM. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Avec cet arrangement, les bits de poids fort de l'adresse sont utilisées pour sélectionner la banque adéquate, et le reste de l'adresse est envoyé sur le bus d'adresse. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Adresse de banque !! Adresse dans la banque |- | Quelques bits de poids fort || Reste de l'adresse |} L'entrelacement change cette répartition, afin d'accélérer les accès mémoire. L'entrelacement classique vise à améliorer les accès à des adresses mémoires consécutives, mais des formes plus évoluées d'entrelacement visent à optimiser des accès mémoire arbitraires. Voyons-les dans l'ordre, du plus simple au plus complexe. ===L'entrelacement classique=== Avec l''''entrelacement classique''', des adresses consécutives sont réparties dans des banques consécutives. Précisons que cette attribution des adresses n'implique absolument pas la mémoire virtuelle ou n'importe quel mécanisme dans le processeur. La répartition décide que telle adresse mémoire va dans telle banque, à telle adresse dans la banque. Elle est donc le fait du contrôleur mémoire, donc en dehors du processeur (les contrôleurs mémoires n’étaient pas encore intégrés dans le processeur à l'époque). [[File:Répartition des adresses dans une mémoire interleaved.png|centre|vignette|upright=1.5|Répartition des adresses dans une mémoire interleaved.]] L'entrelacement simple permet d'accélérer les accès mémoire qui se font à des adresses consécutives. Avec l'entrelacement, chaque accès mémoire tombe dans une banque mémoire différente, ce qui fait qu'on peut démarrer un nouvel accès mémoire à chaque cycle d'horloge. Pendant qu'une banque est occupée par un accès mémoire, on démarre le suivant dans une autre banque, et ainsi de suite. Le tout se termine soit quand on a épuisé toutes les banques libres, soit quand l'accès en rafales se termine. Pas besoin d'attendre que la mémoire ait fini sa lecture/écriture avant de démarrer la lecture/écriture suivante. [[File:Pipemining mémoire.png|centre|vignette|upright=2.5|Pipelining mémoire]] L'animation suivante illustre bien le processus, avec quatres banques. [[File:Interleaving.gif|centre|vignette|upright=2.5|Pipelining mémoire]] L'entrelacement simple utilise les bits de poids faible pour sélectionner la banque, et les bits de poids fort pour la case mémoire. [[File:Adresse mémoire d'une mémoire entrelacée.png|centre|vignette|upright=2|Adresse mémoire d'une mémoire entrelacée]] Précisons que le temps d'accès mémoire ne change pas beaucoup avec l'entrelacement. Par contre, on peut faire plus d'accès mémoire simultanés. On peut démarrer un accès mémoire par cycle d'horloge, mais l'accès en lui-même prend plusieurs cycles. Les mémoires à entrelacement ont donc un débit supérieur aux mémoires qui ne l'utilisent pas. L'entrelacement classique pourrait être utilisé pour accélérer les transferts entre mémoire RAM et cache, qui se font en blocs d'adresses consécutives. Mais de nos jours, il n'est pas utilisé dans cette optique. Il faut dire qu'il est redondant avec le mode rafale des mémoires DRAM. Les deux font globalement la même chose et on voit mal comment utiliser les deux en même temps. Le mode rafale étant plus simple à implémenter, et plus léger niveau utilisation du bus de commande mémoire, il est préféré à l'entrelacement. Par contre, l'entrelacement a eu son heure de gloire sur d'anciens processeurs qui n'avaient pas de mémoire cache, alors qu'ils disposaient d'un pipeline. L'exemple typique est celui des nombreux ''processeurs vectoriels'', que nous n'avons pas encore abordé à ce stade du cours. De tels processeurs avaient un pipeline, une unité de calcul fortement pipelinée, et parfois même de l'exécution dans le désordre et du renommage de registres ! ===L'entrelacement par décalage=== L'entrelacement simple est très utile pour les accès en rafle ou équivalents. Par contre, il existe de rares situations où il n'est pas optimal. Une de ces situations est celle des accès en enjambée (en ''stride''), où l'on accède en série à des adresses sont séparées par N mots mémoires. [[File:Accès par enjambées.png|centre|vignette|upright=2|Accès par enjambées.]] De tels accès surviennent quand un logiciel accède à des structures de données spécifiques, à savoir des tableaux de structures, des matrices, ou d'autres structures de données dans le genre. Un cas classique est celui du parcours d'une matrice colonne par colonne. Les matrices de nombres sont mémorisées ligne par ligne, ce qui fait que parcourir une colonne demande de passer d'une ligne à la suivante et de faire des grands sauts en mémoire RAM, mais tous espacés par la même distance. Avec l'entrelacement simple, les accès en enjambée sont moins performants que les accès à des adresses consécutives, mais cela ne fonctionne pas trop mal. Le pire cas est celui où l'on a N banques et où les données sont justement placées toutes les N adresses. Des accès consécutifs vont tous tomber dans la même banque, on ne peut plus accéder à des banques différentes en parallèle. Mais en dehors de ce cas, on voit une amélioration par rapport à la situation sans entrelacement. Pour obtenir des performances maximales pour les accès en enjambées, il faut répartir les mots mémoires dans la mémoire autrement. L'organisation idéale est la suivante. : Dans les explications qui vont suivre, la variable N représente le nombre de banques, qui sont numérotées de 0 à N-1. On commence par organiser les N premières adresses comme une mémoire entrelacée simple : l'adresse 0 correspond à la banque 0, l'adresse 1 à la banque 1, etc. Pour le bloc suivant, on décale tout d'une adresse, à savoir qu'on commence à remplir les banques à partir de la seconde, non de la première. Une fois la fin du bloc atteinte, on finit de remplir le bloc en repartant du début du bloc. Le troisième bloc d'adresse subit le même traitement, sauf qu'on commence à remplir à partir de la seconde banque. Et on poursuit l’assignation des adresses en décalant d'un cran en plus à chaque bloc. Ainsi, chaque bloc verra ses adresses décalées d'un cran en plus comparé au bloc précédent. Si jamais le décalage dépasse la fin d'un bloc, alors on reprend au début. [[File:Mémoire interleaved par décalage.png|centre|vignette|upright=1.5|Mémoire entrelacée par décalage.]] En faisant cela, on remarque que les banques situées à N adresses d'intervalle sont différentes. Dans l'exemple du dessus, nous avons ajouté un décalage de 1 à chaque nouveau bloc à remplir. Mais on aurait tout aussi bien pu prendre un décalage de 2, 3, etc. Dans tous les cas, on obtient un entrelacement par décalage. Ce décalage est appelé le pas d'entrelacement, noté P. Le calcul de l'adresse à envoyer à la banque, ainsi que la banque à sélectionner se fait en utilisant les formules suivantes : * adresse à envoyer à la banque = adresse totale / N ; * numéro de la banque = (adresse + décalage) modulo N ; * décalage = (adresse totale * P) mod N. Avec cet entrelacement par décalage, on peut prouver que la bande passante maximale est atteinte si le nombre de banques est un nombre premier. Seulement, utiliser un nombre de banques premier peut créer des trous dans la mémoire, des mots mémoires inadressables. Malgré ce défaut, la technique a été utilisée sur quelques ordinateurs, avec l'exemple notable du superordinateur ''Burroughs Scientific Processor''. Pour éviter cela, il y a plusieurs solutions. Par exemple, on peut faire en sorte que N et la taille d'une banque soient premiers entre eux : ils ne doivent pas avoir de diviseur commun. Mais en pratique, elles n'ont pas vraiment été implémentées dans une vraie machine, et sont restées à l'état de recherche, aussi je les passe sous silence. ===L'entrelacement pseudo-aléatoire=== Une dernière méthode de répartition consiste à répartir les adresses dans les banques de manière "pseudo-aléatoire". L'idée exacte est de faire passer l'adresse dans une '''fonction de hachage''', plus ou moins complexe. Pour rappel, une fonction de hachage prend une entrée de grande taille et fournit en sortie un résultat de petite taille. Idéalement, elles doivent donner des résultats aussi différents que possible pour des entrées similaires, histoire de simuler une sorte de pseudo-aléatoire. Ici, une adresse est transformée en un numéro de banque. Une fonction de hachage simple ne fait que permuter des bits de l'adresse pour obtenir son résultat. Elle ne fait qu'échanger des bits de place avant de couper l'adresse en deux morceaux : un pour la sélection de la banque, et un autre pour la sélection de l'adresse dans la banque. Cette permutation est fixe, et ne change pas suivant l'adresse. Des fonctions de hachage plus complexes font des XOR bit à bit entre certains bits de l'adresse. ==Les contrôleurs mémoires SDRAM/DDR optimisés== Après avoir vu le cas des mémoires RAM, nous allons nous concentrer sur le cas particulier des mémoires SDRAM. Les mémoires SDRAM et DDR modernes sont capables de gérer plusieurs accès simultanés à la mémoire RAM, et leurs contrôleurs mémoire en font tout autant. C'est une différence majeure avec les mémoires asynchrones FPM/EDO, qui n'acceptaient qu'un seul accès mémoire à la fois. Leur contrôleur mémoire n'acceptait qu'un seul accès mémoire à la fois, c'était un contrôleur mémoire bloquant. Les contrôleurs mémoires des SDRAM sont eux non-bloquants et peuvent encaisser une dizaine d'accès mémoire à la fois. Peu de choses sont connues sur les contrôleurs de SDRAM/DDR modernes, les fabricants ne donnant que peu de détails dessus. Les rares simulateurs qui tentent de décrire leur fonctionnement, comme DRAM SIM I et II, sont particulièrement simples et ne vont pas dans le détail. Néanmoins, le peu qu'on sait est tout de même instructif. ===Rappel sur les SDRAM : tampon de ligne et commandes=== Les mémoires SDRAM sont des mémoires à tampon de ligne. Elles font un accès mémoire en deux étapes. La première étape recopie une ligne de N * 64 bits dans un tampon interne à la SDRAM. La seconde étape sélectionne une colonne, à savoir une donnée de 64 bits, qui est soit envoyée sur le bus de données pour une lecture, soit modifiée par une écriture. [[File:Mémoire à tampon de ligne.png|centre|vignette|upright=2|Mémoire à tampon de ligne]] Les accès mémoire sont traduits par un séquenceur mémoire en une série de commandes mémoires, qui sont séparées par des délais mémoire de quelques cycles d'horloge. Les délais sont très précis, et sont à respecter à la lettre. Une lecture ou une écriture se fait en maximum trois commandes : une commande PRECHARGE qui ferme la ligne précédemment utilisée, une commande ACT qui précise l'adresse de la ligne, et une commande READ ou WRITE qui précise l'adresse de la colonne et éventuellement la donnée à écrire. : Pour être plus précis, la commande PRECHARGE précharge les lignes de bits du plan mémoire à une tension particulière, ce qui les vide de leur contenu. Mais c'est un détail sans importance pour ce qui va suivre. Les SDRAM permettent de se passer de la première étape si des accès consécutifs se font dans la même ligne. Une fois activée, la ligne reste ouverte et on peut accéder plusieurs fois de suite dedans. On a alors juste à préciser l'adresse de colonne dedans. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | bgcolor="#FFA0FF" | PRECHARGE || bgcolor="#FFA0FF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || bgcolor="#A0FFFF" | READ (2) || bgcolor="#A0FFFF" | READ (3) || || || bgcolor="#A0FFFF" | READ (4) || bgcolor="#A0FFFF" | READ (5) || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | Donnée READ 1 || ||bgcolor="#A0FFFF" | Donnée READ 2 | bgcolor="#A0FFFF" | Donnée READ 3 || || || bgcolor="#A0FFFF" | Donnée READ 4 || bgcolor="#A0FFFF" | Donnée READ 5 |} Les SDRAM sont parfois capables de démarrer une commande avant que la précédente soit terminée. Mais le respect des délais mémoire est très limitant, ce qui fait qu'on ne peut pas parler de réel pipeline, comme c'est le cas sur les processeurs. Parlons plutot de '''commandes anticipées'''. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | || bgcolor="#A0FFFF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || || || bgcolor="#FFA0FF" | READ (2) || || || || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 | bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 | |} Un dernier point est que les chips mémoires de SDRAM sont composés de plusieurs banques, chacune étant une sorte de mini-mémoire miniature. Chaque banque a son propre décodeur, son propre tampon de ligne, ses propres multiplexeurs de colonne, sa logique de rafraichissement mémoire, etc. C'est comme si une SDRAM regroupait plusieurs mémoires séparées dans un même circuit intégré. [[File:Arrangement vertical.jpg|centre|vignette|upright=2.5|Mémoire multi-banques.]] ===La mise en attente des accès mémoire=== Un contrôleur de SDRAM peut accepter plusieurs accès mémoire en même temps. Mais cela ne signifie pas que le contrôleur sera prêt à les traiter immédiatement. Pour éviter tout problème de disponibilité, le contrôleur met en attente les accès mémoire que le processeur lui envoie, pour les exécuter dès que possible. Les accès mémoire sont mis en attente dans une mémoire FIFO, histoire de les exécuter dans leur ordre d'arrivée, notamment pour renvoyer les lectures dans l'ordre demandé. Il y a aussi une mémoire FIFO pour les données à écrire et une FIFO pour les données lues. Cette dernière sert au cas où le cache ne soit pas disponible quand on lui envoie la donnée lue. [[File:Module d'interface avec la mémoire.png|centre|vignette|upright=2.5|Contrôleur mémoire avec mise en attente des requêtes processeur et autres optimisations.]] Des optimisations sont possibles dès la mémoire FIFO. Par exemple, on peut regrouper plusieurs accès à des données consécutives en un seul accès en rafale. Le contrôleur mémoire analyse les requêtes mises en attente et détecte si certaines se font à des adresses consécutives. Si c'est le cas, il fusionne ces requêtes en une lecture/écriture en rafale. Une telle optimisation est appelée la '''combinaison de lecture''' pour les lectures, et la '''combinaison d'écriture''' pour les écritures. ===Les optimisations liées aux commandes anticipées=== Les commandes anticipées permettent de faire un minimum de parallélisme mémoire. Même si elle parait très limitée, cette possibilité est décuplée par la présence de plusieurs banques dans la mémoire SDRAM. Il est en effet possible de faire plusieurs accès mémoire en même temps, dans des banques différentes. Il est possible de lancer un accès mémoire dans deux banques en même temps, par exemple. La seule contrainte est que la SDRAM est limitée à 4 banques actives en même temps. {|class="wikitable" |+ Pipelining basique sur les SDRAM |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 |- ! Banque Numéro 1 | || || || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || || |- ! Banque Numéro 2 | || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || |- ! Banque Numéro 3 | || || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || |} L'implémentation n'a rien de vraiment compliqué. Il suffit d'utiliser une mémoire FIFO par banque. Ainsi, les accès dans une même banque se feront en série, dans l'ordre d'arrivée, mais des accès à des banques différentes se feront dans le désordre. Cela ne pose pas de problème car les accès se font à des adresses différentes, ce qui fait qu'il n'y a pas de conflits majeurs. [[File:Gestion parallèle des banques.png|centre|vignette|upright=2.5|Gestion parallèle des banques.]] ===Le ré-ordonnancement des commandes mémoires=== Un contrôleur mémoire moderne est capable de changer l'ordre des requêtes mémoires, pour gagner en performances. En clair : il peut faire l'équivalent mémoire de l'exécution dans le désordre des processeurs haute performance. Une différence majeure est que le contrôleur mémoire ne remet pas les accès mémoire dans l'ordre avant de les envoyer au processeur. C'est le processeur qui remet en ordre les accès mémoire, dans sa ''load queue''. Pour cela, les requêtes mémoire sont "numérotées", à savoir qu'on leur attribue un identifiant binaire. Le contrôleur mémoire exécute les lectures/écritures dans le désordre, mais garde la trace de leur identifiant. Il sait dans quel ordre il exécute les accès mémoire et connait leur latence. Ce qui fait qu'il sait que la donnée lue à tel instant est associée à la requête mémoire numéro X. Pour les lectures, il renvoie la donnée lue avec l'identifiant. Le processeur sait alors à quelle lecture la donnée lue correspond et il se débrouille en interne pour gérer la situation. En clair : le processeur voit les accès mémoire dans le désordre, c'est lui qui les remet en ordre. Maintenant que ces précisions sont faites, posons cette question : quel est l'intérêt de faire les accès mémoire dans le désordre ? Accéder à des banques en parallèle est une réponse possible, mais le ré-ordonnancement est surtout utile si tous les accès mémoire atterrissent dans la même banque et dans la même ligne. Elle marche si plusieurs accès à une même ligne ne sont pas consécutifs, et qu'il y a des accès entre les deux. Après ré-ordonnancement, ces accès mémoire sont exécutés l'un à la suite de l'autre. Pour rendre le tout plus concret, voici un exemple. Imaginez que l'on sait les 3 accès mémoire suivants : * Une lecture ligne A ; * Une écriture ligne B ; * Une lecture ligne A. Sans réordonnancent, on doit émettre deux commandes PRECHARGE : une quand on passe de la ligne A à la ligne B, et une autre pour le passage inverse. pour éviter cela, le contrôleur mémoire peut retarder l'écriture, ou au contraire avancer la seconde lecture. le résultat est alors le suivant : * Lecture ligne A ; * Lecture ligne A ; * Une écriture ligne B. On a donc deux accès consécutifs à la même ligne, suivi par une écriture dans une autre ligne. La technique marche parce que l'on a un mix adéquat de lectures et d'écritures. Mais il existe des possibilités de réorganisation autres qui ne sont pas valides. Il faut par exemple prendre garde à éviter d'intervertir lectures et écritures à une même adresse, sous peine de problèmes. Encore une fois, il faut faire attention aux dépendances mémoires. Le controleur mémoire teste les dépendances mémoires avant d'envoyer des commandes à la mémoire DDR/SDRAM. ==L'entrelacement sur les mémoires SDRAM== L''''entrelacement''' fonctionne sur les mémoires SDRAM, mais il doit être fortement modifié. Concrètement, tout ce qui a étré dit plus est inapplicable pour les SDRAM, du fait de la présence du tampon de ligne. Des adresses consécutives atterrissent déjà dans la même ligne, ce qui fait qu'on n'a pas besoin d'entrelacement au niveau d'une ligne. Et ne parlons pas de ce la présence de rangées et de canaux mémoire ! Cependant, il peut y avoir un entrelacement lié à la présence des banques SDRAM. L'entrelacement est géré juste avant le séquenceur mémoire. Le '''circuit d'entrelacement''' intervertit certains bits de l'adresse lors des accès mémoires. On trouve aussi des mémoires FIFOs pour mettre en attente les requêtes mémoire provenant du processeur. Elles permettent de recevoir plusieurs accès mémoire consécutifs. Si vous vous demandez comment plusieurs accès mémoire consécutifs peuvent avoir lieu, sachez que c'est une bonne question. Pour le moment, nous allons dire que ces accès proviennent du processeur ou de la mémoire cache, sans expliquer comment. Le contrôleur mémoire reçoit une série d'accès mémoire assez rapprochés, qu'il doit exécuter au mieux. [[File:Controleur mémoire d'une SDRAM avec entrelacement.png|centre|vignette|upright=2.5|Contrôleur mémoire d'une SDRAM avec entrelacement.]] Pour gérer l'entrelacement, le contrôleur de SDRAM prend en entrée l'adresse envoyée par le processeur, et la découpe en plusieurs champs : un pour sélectionner la banque adéquate, un autre pour la ligne, un autre pour la colonne, un autre pour la rangée. Une solution simple désactive l'entrelacement. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de banque || Adresse de ligne || Adresse de colonne |} Si cette solution fonctionne bien si les accès mémoire sont assez aléatoires, en pratique, mieux vaut utiliser l'entrelacement. ===L'entrelacement de banques/lignes=== Une première méthode d'entrelacement est l''''entrelacement de banques'''. Elle répartit deux lignes consécutives dans deux banques différentes. Pour comprendre l'idée, prenons un exemple. Imaginons une mémoire avec deux banques et 4 lignes. Imaginons qu'on parcoure/balaye la mémoire RAM en partant des adresses basses. Sans entrelacement de ligne, les accès se feront comme suit, de gauche à droite : {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Banque numéro 1 !! colspan="4" | Banque numéro 2 |- | Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 || Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 |} Avec l'entrelacement de banques, les accès se feront comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 |- | Ligne 1 || Ligne 1 || Ligne 2 || Ligne 2 || Ligne 3 || Ligne 3 || Ligne 4 || Ligne 4 |} Avec N banques, la première ligne ira dans la première banque, la seconde ligne dans la seconde, et ainsi de suite jusqu'à la énième banque qui va dans la banque numéro N. Et la ligne N+1 repart dans la première banque. L'avantage est que passer d'une ligne à la suivante est assez rapide. Au lieu de devoir fermer la première ligne et ouvrir la suivante on ouvrira directement la ligne suivante dans une banque différente, ce qui sera plus rapide. La commande PRECHARGE pourra être envoyée à la seconde banque en avance. Pour cela, l'adresse est découpée comme suit. {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne (précise à l'octet) |} ===L'entrelacement de rangées=== Il est aussi possible de faire la même chose, mais avec les rangées. Pour simplifier fortement, une rangée est simplement un chip mémoire. En réalité, les rangées ne sont pas des chip mémoire, mais un ensemble de chips mémoire regroupés ensemble histoire d'atteindre les 64 bits du bus de données. Par exemple, une rangée peut combiner 8 chips mémoire avec un bus de données de 8 bits chacun pour obtenir les 64 bits du bus de données d'une SDRAM. Mais nous passons ce détail sous silence dans les explications qui vont suivre, par souci de simplification. Pour faire comprendre l'entrelacement de rangée, le mieux est d'utiliser un exemple, le même que précédemment. Sans entrelacement de rangée, on change de chip mémoire une fois qu'on a balayé toutes ses adresses. {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Rangée numéro 1 !! colspan="4" | Rangée numéro 2 |- | Banque 1 || Banque 2 || Banque 3 || Banque 4 || Banque 1 || Banque 2 || Banque 3 || Banque 4 |} Avec l'entrelacement de ligne, on change de rangée dès qu'on change de banque/ligne. {|class="wikitable" |+ Adresse mémoire |- ! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 |- | Banque 1 || Banque 1 || Banque 2 || Banque 2 || Banque 3 || Banque 3 || Banque 4 || Banque 4 |} En clair, quand on a épuisé toutes les banques dans une même rangée, on passe à la rangée suivante au lieu de rester dans la même rangée. Notons que dans l'exemple précédent, on a combiné l'entrelacement de rangée et de banque, mais on aurait pu utiliser l'entrelacement de rangée seul. Mais ce n'est pas le cas le plus courant en pratique. Toujours est-il qu'en combinant entrelacement de rangée et de ligne, le découpage de l'adresse est le suivant : {|class="wikitable" |+ Adresse mémoire |- | Adresse de ligne || Numéro de rangée || Adresse de banque || Adresse de colonne (précise à l'octet) |} D'autres méthodes d'entrelacement sont possibles, mais elles sont plus complexes. Chaque contrôleur mémoire fait un peu à sa sauce. Ils permettent en général de choisir entre plusieurs entrelacements. Au minimum, ils permettent de désactiver l'entrelacement, d'activer un entrelacement de ligne, et d'autres entrelacements plus complexes, dont certains sont propriétaires et tenus secrets par le fabricant. ===L'entrelacement avec le ''dual channel''=== [[File:Dual channel slots.jpg|vignette|Slots mémoires avec ''dual channel''.]] L'usage du ''dual channel'' complique encore l'entrelacement. Là encore, il y a deux grandes solutions : avec et sans entrelacement des canaux mémoire. Rappelons le principe : deux barrettes de RAM sont accédées en parallèle. Et pour cela, il faut utiliser l'entrelacement. Typiquement, chaque barrette mémoire fournit 64 bits, ce qui fait que l'on peut accéder à 128 bits d'un seul coup, par exemple avec un accès en rafale. Sans ''dual channel'', la première barrette correspond à la moitié haute de la RAM, la seconde barrette correspond à la moitié basse. Avec ''dual channel'', une forme spécifique d'entrelacement est activée. Concrétement, deux blocs de 64 bits sont placés dans des canaux mémoire séparés. Idem avec du triple ou quadruple canal, mais c'est alors trois ou quatre blocs de 64 bits qui sont dispersés dans des canaux consécutifs. Le découpage de l'adresse est alors le suivant : {|class="wikitable" |+ Adresse mémoire |- ! colspan="6" | Sans entrelacement |- | Numéro de canal/barrette || Reste de l'adresse || Position de l'octet dans un bloc de 64 bits |- | colspan="6" | |- ! colspan="6" | Avec entrelacement |- | Reste de l'adresse || Numéro de canal/barrette || Position de l'octet dans un bloc de 64 bits |} [[File:Décodage d'adresse avec dual channel.png|centre|vignette|upright=2|Décodage d'adresse avec dual channel.]] Les contrôleurs de SDRAM précédents sont assez basiques et ne représentent pas les contrôleurs les plus évolués. Ils étaient utilisés sur les anciens PC, à une époque où ils étaient encore sur la carte mère du processeur. Mais de nos jours, le contrôleur mémoire est intégré au processeur. Et il incorpore de nombreuses optimisations afin de gagner en performances. Malheureusement, ces optimisations sont elles-mêmes dépendantes des optimisations intégrées dans le processeur, ce qui fait qu'on ne peut pas en parler ici. Elles demandent que le contrôleur mémoire reçoive plusieurs accès mémoire simultanés, ce qui n'a aucun sens à ce stade du cours. Aussi, nous allons les passer sous silence pour le moment. Nous les verrons dans le chapitre sur le parallélisme mémoire. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=La désambiguïsation mémoire | prevText=La désambiguïsation mémoire | next=Les processeurs superscalaires | nextText=Les processeurs superscalaires }} </noinclude> oaio4i3rwu4gfyfndjpq82nwcobrspm 764722 764721 2026-04-23T21:39:23Z Mewtow 31375 /* La mise en attente des accès mémoire */ 764722 wikitext text/x-wiki Dans ce chapitre, nous allons voir les techniques qui permettent de gérer plusieurs accès mémoire simultanés directement au niveau du cache ou de la mémoire RAM. Par plusieurs accès mémoire simultanés, vous pensez sans doute à l'usage de cache multiports, voire de mémoires RAM multiport. C'est en effet une solution, mais ce n'est clairement pas la seule. Par exemple, il est possible de pipeliner l'accès au cache, voire à la mémoire RAM. Il existe de nombreuses techniques de '''parallélisme mémoire''', et nous allons les voir dans ce chapitre. Pour donner un exemple, il est possible de pipeliner les accès mémoire. Il est possible de pipeliner l'accès au cache et/ou l'accès à la mémoire, et nous verrons comment faire dans ce chapitre. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, qu'ils aient ou non un pipeline dynamique. Par contre, pipeliner la mémoire est une technique ancienne, aujourd'hui peu utilisée. Elle était utilisée sur des très vieux ordinateurs, qui avaient un pipeline mais pas de mémoire cache. Mais de nos jours, les mémoires SDRAM et DDR ne sont pas adaptées pour ça, il est très difficile de les pipeliner correctement. Le parallélisme mémoire est utile aussi bien sur des processeurs à émission dans l'ordre que dans le désordre. Par exemple, un processeur ''in-order'' avec des lectures non-bloquantes peut e, profiter. Il peut techniquement lancer une seconde lecture, après avoir rencontré une première lecture non-bloquante. En général, le nombre de lectures consécutives est cependant limité à moins d'une dizaine. À l'opposé, les processeurs sans lectures non-bloquantes profitent pas du parallélisme mémoire. À la rigueur, ils peuvent en profiter s'ils intègrent des techniques de préchargement. Pendant que le processeur exécute une lecture/écriture, il peut précharger une autre donnée en parallèle. Inutile de dire que sans pipeline processeur, le parallélisme mémoire ne sert pas à grand-chose. ==Le ''line fill buffer'' et les techniques associées== Lors d'un défaut de cache, le processeur doit attendre que toute la ligne de cache soit chargée avant d'être utilisable. Or, la taille d'une ligne de cache est supérieure à la largeur du bus mémoire, ce qui fait qu'une ligne de cache est chargée en plusieurs fois, morceaux par morceaux, mot mémoire par mot mémoire. Le chargement peut se faire directement dans le cache, mais ce n'est pas une solution très pratique. À la place, beaucoup de processeurs ajoutent une mémoire tampon entre la RAM et le cache, appelée le '''tampon de remplissage de ligne''' (''line-fill buffer'') dans les processeurs modernes. Lors d'un défaut de cache, le processeur charge la donnée de la RAM dans le tampon de remplissage de ligne, mot mémoire par mot mémoire. Une fois plein, le tampon de remplissage de ligne est recopié dans la ligne de cache. Le tampon de remplissage de ligne contient une ligne de cache, si ce n'est que certains bits de contrôle ne sont pas présents. Le tampon de remplissage de ligne contient un bit de validité pour chaque mot mémoire de la ligne de cache, qui indiquent si le mot mémoire a été chargé. Par exemple, prenons un processeur 64 bits, qui gère donc des mots mémoire de 8 octets, avec des lignes de cache 256 octets/32 mots mémoire. Le tampon de remplissage de ligne contiendra 32 bits de validité. Si le processeur a chargé les 6 premiers mots mémoire, les 6 premiers bits de validité seront mis à 1, les autres seront encore à zéro. Il faut noter que la disponibilité de la ligne complète se détermine assez facilement en faisant un ET logique entre tous les bits de validité. Le processeur sait ainsi quand la ligne de cache est disponible entièrement et donc quand la transférer dans le cache. La même chose existe avec une hiérarchie de cache, sauf que l'on trouve une mémoire tampon entre chaque niveau de cache. Il y a un tampon de remplissage de ligne entre le cache L1 et le cache L2, entre le cache L2 et le L3, etc. Si le cache ne gère qu'un seul défaut de cache à la fois, le ''fill line buffer'' est une mémoire très simple, qui ne mémorise qu'une seule ligne de cache. Et cela vaut aussi bien pour un cache bloquant que non-bloquant. Mais sur les caches capables de gérer plusieurs défauts simultanés, le tampon de remplissage de ligne est une mémoire de type FIFO ou LIFO, capable de mémoriser plusieurs lignes de cache. Notons que le tampon de remplissage de ligne est très utile pour implémenter certaines techniques, par exemple le contournement du cache. Nous avions vu dans le chapitre sur le cache que certains accès mémoire doivent contourner le cache, pour des raisons de cohérence des caches. C'est notamment nécessaire pour accéder aux périphériques, mais c'est aussi utile pour des raisons de performances dans des cas très spécifiques. Les accès qui contournent le cache se font directement dans le tampon de remplissage de ligne : le processeur écrit ou lit les données depuis ce tampon de remplissage de ligne, sans accéder au cache. ===L'''early restart'' et le ''critical word load''=== La présence du ''line buffer'' permet une optimisation assez intéressante, qui permet de réduire la latence des défauts de cache. L'optimisation consiste à lire un mot mémoire dans le tampon de remplissage de ligne, même si la ligne de cache complète n'a pas encore été chargée. Il existe deux manières de faire cela, qui portent les noms d'''early restart'' et de ''critical word load''. La première est la version la plus simple, la seconde est plus complexe mais plus performante. Avec la technique d''''''early restart''''', la ligne de cache est chargée normalement, en partant de son premier mot mémoire. Dès que le mot mémoire lu/écrit par le processeur est copié dans le ''line fill buffer'', il est envoyé au processeur immédiatement. Illustrons le tout par un exemple, où une ligne de cache fait 16 mots mémoire. Le processeur effectue une lecture, qui lit le 5ème mot mémoire. Sans ''early restart'', le processeur doit charger les 16 mots mémoire avant de faire la lecture dans le cache. Avec ''early restart'', le processeur reçoit la donnée dès que le 5ème mot mémoire est disponible. Le processeur doit attendre que les 4 premiers mots mémoire soient chargés, puis le 5ème mot mémoire arrive et est envoyé directement au processeur, il est lu directement depuis le ''line fill buffer''. Les 11 mots mémoire suivants sont ensuite chargés dans le cache pendant que le processeur fait des calculs dans son coin. Le ''critical word load'' est une optimisation de la technique précédente où le chargement de la ligne de cache commence directement à la donnée demandée par le processeur. Pour reprendre l'exemple précédent, où le processeur demande le 5ème mot mémoire sur 16, le ''critical word load'' charge le 5ème mot mémoire en premier et l'envoie au processeur, ce qui fait qu'il est chargé très rapidement. Pas besoin d'attendre que le processeur charge les 4 mots mémoire précédents comme avec l'''early restart''. Dans le détail, le processeur charge le 5ème mot mémoire en premier, puis charge les 11 suivants, et termine par les 4 mots mémoire du début. En clair, le ''critical word load'' commence par charger le mot mémoire lu, puis les blocs suivants, avant de revenir au début du bloc pour charger les blocs restants. Ainsi, la donnée demandée par le processeur sera la première disponible. Pour cela, l'organisation du tampon de remplissage de ligne est modifiée de manière à rendre cela possible. Il a une taille égale à une ligne de cache complète, qui contient elle-même plusieurs mots mémoire. Dans le ''line-fill buffer'', chaque mot mémoire est stocké avec un tag, qui indique l'adresse du mot mémoire stocké dans le ''line-fill buffer''. Le ''line-fill buffer'' est donc un cache un peu particulier, qui fonctionne comme un cache du point de vue du processeur, comme une mémoire FIFO pour les transferts avec le cache. Ainsi, un processeur qui veut lire dans le cache après un défaut peut accéder à la donnée directement depuis le tampon de remplissage de ligne, alors que la ligne de cache n'a pas encore été totalement recopiée en mémoire. ==Les caches pipelinés== Il est possible de pipeliner l'accès au cache, ce qui demande juste de rajouter des registres au bon endroits et quelques circuits de contrôle. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, même ceux avec un pipeline dynamique. Et l'implémentation peut se faire de plusieurs manières différentes. Il faut dire que découper une mémoire cache en plusieurs étapes peut se faire de plusieurs manière. Commençons par la plus simple. Beaucoup de processeurs des années 2000 avaient une fréquence peu élevée comparée aux standards d'aujourd'hui, ce qui fait que leur cache avait bien un temps d'accès d'un cycle d'horloge. Mais l'accès au cache se faisait en deux cycles d'horloge : un cycle pour calculer une adresse et un cycle pour l'accès mémoire proprement dit. Et ces deux étapes étaient pipelinées, à savoir que deux micro-opérations mémoire peuvent s'exécuter en même temps : une dans l'étage de calcul d'adresse, une autre dans le cache. L'unité mémoire était donc pipelinée, alors que l'accès au cache ne l'était pas. Les processeurs qui implémentaient cette technique regroupent les micro-architectures K5 et K6 d'AMD, les processeurs Intel de micro-architecture P6 (Pentium 2 et 3) et quelques autres. Il est aussi possible de pipeliner l'accès au cache lui-même. Avec un cache pipeliné, l'accès au cache ne se fait pas en un seul cycle, mais en plusieurs. Cependant, on peut lancer un nouvel accès au cache à chaque cycle d'horloge, comme un processeur pipeliné lance une nouvelle instruction à chaque cycle. L'implémentation est assez simple : il suffit d'ajouter des registres dans le cache. Pour cela, on profite que le cache est composé de plusieurs composants séparés, qui échangent des données dans un ordre bien précis, d'un composant à un autre. De plus, le trajet des informations dans un cache est linéaire, ce qui les rend parfois pour l'usage d'un pipeline. ===Le ''pipelining'' des caches ''direct-mapped''=== Voici ce qui se passe avec un cache directement adressé. Pour rappel ce genre de cache est conçu en combinant une mémoire RAM, généralement une SRAM, avec quelques circuits de comparaison et un MUX. L'accès au cache se fait globalement en deux étapes : la première lit la donnée et le ''tag'' dans la SRAM, et on utilise les deux dans une seconde étape. Nous avions vu il y a quelques chapitre comment pipeliner des mémoires, dans le chapitre sur les mémoires évoluées. Et bien ces méthodes peuvent s'utiliser pour la mémoire RAM interne au cache !L'idée est d'insérer un registre entre la sortie de la RAM et la suite du cache, pour en faire un cache pipéliné, à deux étages. Le premier étage lit dans la SRAM, le second fait le reste. L'implémentation sur les caches associatifs à plusieurs voies est globalement la même à quelques détails près. [[File:Cache directement adressé pipeliné - deux étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - deux étages]] Avec le circuit précédent, il est possible d'aller plus loin, cette fois en pipelinant l'accès à la mémoire RAM interne au cache. Pour comprendre comment, rappelons qu'une mémoire SRAM est composée d'un plan mémoire et d'un décodeur. L'accès à la mémoire demande d'abord que le décodeur fasse son travail pour sélectionner la case mémoire adéquate, puis ensuite la lecture ou écriture a lieu dans cette case mémoire. L'accès se fait donc en deux étapes successives séparées, on a juste à mettre un registre entre les deux. Il suffit donc de mettre un gros registre entre le décodeur et le plan mémoire. [[File:Cache directement adressé pipeliné - trois étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - trois étages]] Et on peut aller encore plus loin en découpant le décodeur en deux circuits séparés. En effet, rappelez-vous le chapitre sur les circuits de sélection : nous avions vu qu'il est possible de créer des décodeurs en assemblant des décodeurs plus petits, contrôlés par un circuit de prédécodage. Et bien on peut encore une ajouter un registre entre ce circuit de prédécodage et les petits décodeurs. : Théoriquement, toute l'adresse est fournie d'un seul coup au cache, la quasi-totalité des processeurs présentent l'adresse complète à un cache pipeliné. Mais le Pentium 4 fait autrement. Il faut noter que les premiers étages manipulent l'indice dans la SRAM, qui est dans les bits de poids faible, alors que les étages ultérieurs manipulent le ''tag'' qui est dans les bits de poids fort. Les concepteurs du Pentium 4 ont alors décidé de présenter les bits de poids faible lors du premier cycle d'accès au cache, puis ceux de poids fort au second cycle. Pour cela, l'ALU fonctionnait à une fréquence double de celle du processeur, tout comme le cache L1. Il n'y avait pas de pipeline proprement dit, mais cela réduisait grandement la latence d'accès au cache. ===Le ''pipelining'' des caches splittés/sériels=== Il est aussi possible de pipeliner un cache dits splittés, aussi appelés à accès sériel. Pour rappel, les caches à accès sériel vérifient si il y a succès ou défaut de cache, avant d'accéder aux lignes de cache en cas de succès. Ils font donc différemment des autres caches, qui accèdent à une ligne de cache, avant de déterminer s'il y a succès ou défaut en lisant le tag de la ligne de cache. Les caches sériels disposent de deux SRAM : une pour les tags des lignes de cache et une pour les données. Ils accèdent à la SRAM pour les tags, avant d’accéder à la SRAM des données en cas de succès de cache. Vu que l'accès se fait en deux étapes, une vérification des tags suivie de la lecture/écriture des données, il est facile à pipeliner. Pipeliner le cache permet de régler le problème des accès au cache L1, et elle est tout le temps utilisé sur les processeurs modernes. Mais que faire en cas de défaut de cache ? ==Les caches non bloquants== Un '''cache bloquant''' est un cache auquel le processeur ne peut pas accéder pendant un défaut de cache. Il faut attendre que la lecture ou écriture en RAM soit terminée avant de pouvoir utiliser de nouveau le cache. Un '''cache non bloquant''' n'a pas ce problème : on peut l'utiliser pendant un défaut de cache. Les caches non bloquants permettent de démarrer une nouvelle lecture ou écriture alors qu'une autre est en cours, ce qui permet d'exécuter plusieurs lectures ou écritures en même temps. ===Les ''Miss Handling Status Registers''=== Lors d'un défaut de cache, la mémoire RAM est consultée pendant le défaut de cache, mais le cache est inutilisé. Un défaut de cache n'utilise pas le cache, ce n'est qu'un accès en mémoire RAM, sur le bus mémoire (ou un accès aux niveaux de cache inférieurs, peu importe). Le cache en lui-même est laissé libre, rien n’empêche d'y accéder, il est en réalité intrinsèquement non-bloquant. Les caches, bloquants comme non-bloquants, sont en réalité composés d'une mémoire cache proprement dite, entourée de circuits qui servent d'interface entre le processeur et le cache lui-même. Et parmi les circuits tout autour, certains gèrent l'accès au cache lors d'un défaut de cache. Ils sont regroupés sous le terme de '''''Miss Handling Architecture''''' (MHA). La différence entre un cache bloquant et un cache non-bloquant est en réalité liée à l'implémentation de la MHA. Les caches bloquants coupent volontairement l'accès au cache lors d'un défaut, car les défauts sont plus simples à gérer ainsi. Le défaut de cache rapatrie une donnée depuis la RAM, qui sera écrite dans le cache. Et il ne faut pas qu'une tentative d'accès à cette donnée ait lieu avant qu'elle ne soit chargée. Mais ce blocage est général et touche tout le cache, alors que seule une ligne de cache est concernée par le défaut de cache. L'idée derrière un cache non-bloquant est que seule la ligne de cache est bloquée, mais les autres sont accessibles. L'idée est alors de mémoriser les lignes de cache concernées par le défaut de cache, afin d'en bloquer l'accès. À chaque accès, on vérifie si la ligne de cache est déjà réservée par un défaut de cache. La lecture/écriture est alors bloquée si c'est le cas, mais elle accepte les accès sinon. Pour cela, la ''Miss Handling Architecture'' contient des registres qui mémorisent des informations sur les défauts de cache en cours. Ils portent le nom de '''''miss status handling registers''''', que j’appellerais dorénavant MSHR, qui sont aussi appelés des '''''miss buffer'''''. Le contenu des MSHR varie beaucoup suivant le processeur, mais ces derniers stockent au minimum les informations suivantes : * Le numéro de la ligne de cache dans laquelle les données sont chargées. * Un bit de validité qui indique si le MSHR est vide ou pas, qui est mis à 0 quand le défaut de cache est résolu. * Un ou plusieurs champs de lecture/écriture, qui contiennent des informations sur la lecture/écriture. ** Pour une lecture, elle contient des informations sur la destination de la donnée, à savoir qui prévenir quand le défaut de cache est terminé. C'est parfois un nom/numéro de registre (celui dans lequel charger la donnée), mais c'est souvent le numéro de l'entrée dans le ''load/store queue''. ** Pour les écritures, elle contient la donnée à écrire, ou éventuellement un numéro de ''load/store queue'' où se trouve la donnée à écrire. Il faut noter que le bus mémoire ne peut gérer qu'un seul défaut de cache à la fois. Aussi, il est intéressant de regarder ce qui se passe lorsqu'un second défaut de cache survient, pendant qu'un premier est en cours. Dans ce cas, il y a deux réponses qui correspondent à deux types de caches non-bloquants, qui portent les noms barbares de caches de type succès après défaut et défaut après défaut. Sur le premier type, il ne peut pas y avoir plusieurs défauts de cache simultanés. Dès qu'un second défaut de cache survient, le cache stoppe son activité et on ne peut plus démarrer de nouvelle lecture/écriture, tant que le premier défaut de cache n'est pas résolu. Le second type est plus souple et autorise la survenue de plusieurs défauts de cache simultanés. Du moins, jusqu'à une certaine limite, car le cache ne peut supporter qu'un nombre limité d'accès mémoires simultanés (pipelinés). ===Les accès simultanés à une même ligne de cache=== Il arrive que le processeur fasse plusieurs accès mémoire simultanés à la même ligne de cache. Si la ligne de cache en question n'a pas encore été chargée dans le cache, alors on a plusieurs défauts de cache consécutifs pour la même ligne de cache, et le cache non-bloquant doit gérer la situation. Pour la suite, il va falloir faire une petite distinction entre les défauts primaires et secondaires. Imaginons qu'un défaut de cache ait lieu et demande à charger une donnée dans la ligne de cache numéro N. Il s'agit du premier défaut impliquant cette ligne de cache précise, ce qui lui vaut le nom de '''défaut de cache primaire'''. Mais par la suite, d'autres accès mémoire à la même ligne de cache ont lieu, alors que la ligne de cache n'est pas encore disponible. Dans ce cas, il s'agit de '''défauts de cache secondaires'''. Pour l'unité d'accès mémoire, les défauts de cache primaire et secondaire sont différents (ils prennent tous une entrée dans la ''load/store queue''). Mais pour le cache, ils ne correspondent qu'à un seul accès au cache : celui qui demande de charger la ligne de cache demandée. Les défauts de cache primaire et secondaire à la même ligne de cache se voient attribuer un MSHR unique. La gestion des défauts de cache secondaires dépend du cache non-bloquant. La solution la plus simple ne permet pas les défauts de cache secondaires. Le cache ne permet pas deux défauts de cache simultanés pour la même ligne de cache. Les autres solutions le permettent, en fusionnant des accès simultanés à la même ligne de cache en un seul au niveau des MSHR. Dans tous les cas, détecter les défauts de cache secondaires sont un problème qu'il faut détecter. La ''Miss Handling Architecture'' doit détecter les défauts de cache secondaire. Pour cela, elle procède comme suit. Lors de chaque défaut de cache, la MHA récupère le numéro de la ligne de cache associé. Il vérifie alors chaque MSHR pour vérifier s'il contient le numéro en question. S'il n'y a aucune correspondance dans les MSHR, alors c'est signe que le défaut de cache est un défaut primaire. Mais s'il y en a une, alors c'est un défaut secondaire. Évidemment, cela signifie que lors d'un défaut de cache, le numéro de ligne de cache est envoyé à tous les MSHR, pour comparaison. Les MSHR sont donc regroupés dans une mémoire associative, une sorte de mini-cache, faciliter l'implémentation. Lors d'un défaut de cache primaire, l'accès à un cache non-bloquant se fait comme suit : le processeur envoie une adresse au cache, accède à celui-ci, et détecte la survenue d'un défaut de cache. Il en profite alors pour attribuer une ligne de cache dans laquelle sera chargée ce défaut. L'attribution est très simple dans le cas des caches ''direct mapped'', ou associatifs par voie, pour lesquels l'attribution se fait assez simplement. Il mémorise alors cette information dans les MSHR, après avoir vérifié que le défaut de cache n'était pas un défaut secondaire. Lors des accès ultérieurs à une adresse proche, censée être dans la même ligne de cache, le processeur va encore une fois rencontrer un défaut de cache. Il va alors déterminer le numéro de la ligne de cache associée à l'adresse, et comparer ce numéro avec les MSHRs. Si un MSHR contient ce numéro, c'est signe que le défaut de cache est un défaut secondaire. La MHA réagit alors différemment selon le processeur considéré. Une première solution n'autorise pas les défauts de cache secondaires. Si l'un d'entre eux survient, le processeur est gelé par un ''pipeline stall'', une bulle de pipeline. Une autre solution fusionne les défauts de cache secondaires avec le défaut de cache primaire : tout cela ne correspond qu'à un seul défaut de cache pour lui. ===Les MSHR simples=== Un cache non-bloquant à '''MSHR simple''' contient juste plusieurs MSHR qui mémorisent juste un numéro de ligne de cache, un bit de validité, le champ de lecture/écriture, et l'adresse exacte de lecture/écriture. Avec cette organisation, il est possible d'avoir plusieurs défauts de cache séparés, mais à la condition que chaque défaut accède à une ligne de cache différente. Deux accès simultanés à une même ligne de cache ne sont pas possibles, les défauts de cache secondaires ne sont pas autorisés. Ainsi, chaque défaut de cache se voit attribuer son propre MSHR, chacun contient un numéro de ligne de cache différent. Pour comprendre pourquoi c'est impossible de gérer les défauts secondaires, il faut regarder le champ de lecture/écriture. Si on veut effectuer plusieurs écritures consécutives à la même adresse, le MSHR n'aura pas de quoi mémoriser les deux données à écrire. Il pourra mémoriser la première donnée à écrire, pas la seconde. Même chose lors d'une lecture : le champ lecture/écriture peut mémorisr la destination de la première lecture, pas de la seconde. L'avantage est que la MHA n'a besoin que des MSHR et de quelques circuits annexes. Les autres solutions rajoutent des circuits annexes pour gérer les défauts de cache secondaires, qui utilisent beaucoup de circuits. Le cout en circuit est donc élevé, mais le gain en performance est là. Passons maintenant aux caches non-bloquants qui autorisent les défauts de cache secondaire. La solution la plus simple consiste à utiliser ===Les MSHR adressés implicitement=== Avec les '''MSHR adressés implicitement''', il est possible de fusionner plusieurs accès mémoire à une même ligne de cache, mais sous une condition très importante : ces accès lisent/écrivent des mots mémoire différents. Par exemple, imaginons qu'une ligne de cache contienne 8 mots mémoire de 64 bits. Si un premier accès mémoire lit le mot mémoire numéro 7 (dernier mot mémoire de la ligne), et le second accès le mot mémoire numéro 3, alors la fusion est possible. Mais si deux accès mémoire veulent lire/écrire le mot mémoire numéro 7, alors la fusion n'est pas possible et le processeur se bloque, un ''pipeline stall'' survient. Un MSHR adressé implicitement est un MSHR simple, qui contient naturellement un numéro de ligne de cache (tag) et un bit de validité, sauf que le champ de lecture/écriture est dupliqué. Les différents champs lecture/écriture d'un MSHR sont regroupés dans une mémoire RAM/cache qui contient autant d'entrées qu'il y a de mot mémoire dans une ligne de cache. Chaque entrée de la table est associée à un mot mémoire de la ligne de cache et stocke des informations sur celui-ci. Une entrée mémorise, au minimum : * Un champ de lecture/écriture qui contient soit la destination de la lecture, soit la donnée à écrire. * Un bit R/W permettant d'interpréter correctement le champ de lecture/écriture. * Un bit de validité pour chaque entrée de la table, qui dit si un défaut de cache antérieur accède déjà à ce mot mémoire. La fusion de deux défauts de cache est ainsi assez simple. Un défaut de cache primaire/secondaire configure l'entrée associée au mot mémoire lu/écrit. Si un défaut secondaire ultérieur lit/écrit un mot mémoire différent (dont le bit de validité de l'entrée dans la table est à zéro), pas de conflit : il configure une autre entrée, vide. Mais s'il lit/écrit un mot mémoire pour lequel l'entrée de la table est occupée, il y a conflit, le processeur est bloqué par un ''pipeline stall'', une bulle de pipeline. Avec cette organisation, le nombre de MSHR indique combien de lignes de cache peuvent être lues en même temps depuis la mémoire. Quant au nombre d'entrées par MSHR, il détermine combien d'accès mémoires qui ne se recouvrent pas peuvent avoir lieu en même temps. ===Les MSHR adressés explicitement=== Les '''MSHR adressés explicitement''' n'ont pas les contraintes des MSHR adressés implicitement. Avec eux, il est possible d'avoir plusieurs défauts de cache pointant vers la même ligne de cache, mais aussi vers le même mot mémoire. De tels défauts de cache apparaissent sur les processeurs à exécution dans le désordre, mais sont très rares, voire inexistants, sur les processeurs ''in-order''. L'idée est encore que chaque MSHR est associée à une table mémoire qui mémorise des entrées. Cette table n'est autre qu'une mémoire FIFO. Sauf que cette fois-ci, une entrée n'est pas associée à un mot mémoire. Une entrée est associée à un défaut de cache. Une entrée mémorise là encore la destination de la lecture ou la donnée de l'écriture suivi du bit R/W, ainsi que d'un bit de validité par entrée, mais aussi : la position du mot mémoire lu dans la ligne de cache. C'est cette dernière information qui n'était pas présente dans les MSHR adressés implicitement. De plus, le nombre d'entrée par MSHR n'est pas égal au nombre de mots mémoires dans une ligne de cache, mais peut être arbitrairement grand. L'avantage d'un tel cache est qu'il est capable de traité les défauts secondaires concernant un même mot mémoire, et ce, car les accès à la ligne de cache concernée se produisent dans l'ordre de génération des défauts (la table mémoire est une FIFO). ===Les MSHRs inversés=== Généralement, plus on veut supporter de défauts de cache, plus le nombre de MSHR et d'entrées augmente. Mais au-delà d'un certain nombre d'entrées et de MSHR, les MSHR adressés implicitement et explicitement ont tendance à bouffer un peu trop de circuits. Utiliser une organisation un peu moins gourmande en circuits est donc une nécessité. Cette organisation plus économe se base sur des MSHR inversés. Les MSHR inversés ne contiennent qu'une seule entrée, en quelque sorte : au lieu d’utiliser n MSHR de m entrées chacun, on va utiliser n × m MSHR inversés. La différence, c'est que plusieurs MSHR peuvent contenir un tag identique, contrairement aux MSHR adressés implicitement et explicitement. Lorsqu'un défaut de cache a lieu, chaque MSHR est vérifié. Si jamais aucun MSHR ne contient de tag identique à celui utilisé par le défaut, un MSHR vide est choisi pour stocker ce défaut, et une requête de lecture en mémoire est lancée. Dans le cas contraire, un MSHR est réservé au défaut de cache, mais la requête n'est pas lancée. Quand la donnée est disponible, les MSHR correspondant à la ligne qui vient d'être chargée vont être utilisés un par un pour résoudre les défauts de cache en attente. ===Les MSHR intégrés au cache=== Certains chercheurs ont remarqué que pendant qu'une ligne de cache est en train de subir un défaut de cache, celle-ci reste inutilisée, et son contenu est destiné à être perdu une fois le défaut de cache résolu. Ils se sont dit que, plutôt que d'utiliser des MSHR séparés, il vaudrait mieux utiliser la ligne de cache pour stocker les informations sur les défauts de cache en attente dans cette ligne de cache. Pour éviter tout problème, il faut rajouter un bit dans les bits de contrôle de la ligne de cache, qui sert à indiquer que la ligne de cache est occupée : un défaut de cache a eu lieu dans cette ligne, et elle stocke donc des informations pour résoudre les défauts de cache. ==L'entrelacement mémoire== Nous venons de voir comment une mémoire cache peut gérer plusieurs accès mémoire simultanés. Il se trouve que ce genre d'optimisation dépasse largement le cadre du cache. Les mémoires RAM ne sont pas en reste, elles aussi ! Il est possible de pipeliner l'accès à une mémoire RAM, en utilisant des techniques dites d''''entrelacement'''. Précisons cependant que cette optimisation n'est pas utilisée sur les mémoires SDRAM ou DDR modernes, du moins pas sans modifications majeures. Mais divers ordinateurs assez anciens, dont des superordinateurs, ont utilisé cette technique. La technique demande d'utiliser plusieurs mémoires séparées. Sur les anciens ordinateurs, les mémoires en question sont des chips mémoire, à savoir des circuits intégrés de DRAM. Pour faire plus général, nous allons utiliser le terme de '''banques''', ou encore de bancs mémoire. La différence est qu'il existe des mémoires multi-banque, qui regroupent plusieurs banques indépendantes dans un seul boitier, dans un seul chip mémoire. En clair, de telles mémoires regroupent plusieurs sous-mémoires dans un seul circuit intégré. Il est aussi possible d'utiliser plusieurs chips mémoire séparés, chacun ayant une banque. Reste à combiner ces banques pour former un pseudo-pipeline. ===Utiliser plusieurs banques sans entrelacement=== Sans optimisation particulière, les adresses sont réparties dans les banques comme indiqué ci-dessous. Il s'agit de ce qui s'appelle un arrangement horizontal, et nous avions vu celui-ci dans le chapiutre sur les mémoires SDRAM. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Avec cet arrangement, les bits de poids fort de l'adresse sont utilisées pour sélectionner la banque adéquate, et le reste de l'adresse est envoyé sur le bus d'adresse. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Adresse de banque !! Adresse dans la banque |- | Quelques bits de poids fort || Reste de l'adresse |} L'entrelacement change cette répartition, afin d'accélérer les accès mémoire. L'entrelacement classique vise à améliorer les accès à des adresses mémoires consécutives, mais des formes plus évoluées d'entrelacement visent à optimiser des accès mémoire arbitraires. Voyons-les dans l'ordre, du plus simple au plus complexe. ===L'entrelacement classique=== Avec l''''entrelacement classique''', des adresses consécutives sont réparties dans des banques consécutives. Précisons que cette attribution des adresses n'implique absolument pas la mémoire virtuelle ou n'importe quel mécanisme dans le processeur. La répartition décide que telle adresse mémoire va dans telle banque, à telle adresse dans la banque. Elle est donc le fait du contrôleur mémoire, donc en dehors du processeur (les contrôleurs mémoires n’étaient pas encore intégrés dans le processeur à l'époque). [[File:Répartition des adresses dans une mémoire interleaved.png|centre|vignette|upright=1.5|Répartition des adresses dans une mémoire interleaved.]] L'entrelacement simple permet d'accélérer les accès mémoire qui se font à des adresses consécutives. Avec l'entrelacement, chaque accès mémoire tombe dans une banque mémoire différente, ce qui fait qu'on peut démarrer un nouvel accès mémoire à chaque cycle d'horloge. Pendant qu'une banque est occupée par un accès mémoire, on démarre le suivant dans une autre banque, et ainsi de suite. Le tout se termine soit quand on a épuisé toutes les banques libres, soit quand l'accès en rafales se termine. Pas besoin d'attendre que la mémoire ait fini sa lecture/écriture avant de démarrer la lecture/écriture suivante. [[File:Pipemining mémoire.png|centre|vignette|upright=2.5|Pipelining mémoire]] L'animation suivante illustre bien le processus, avec quatres banques. [[File:Interleaving.gif|centre|vignette|upright=2.5|Pipelining mémoire]] L'entrelacement simple utilise les bits de poids faible pour sélectionner la banque, et les bits de poids fort pour la case mémoire. [[File:Adresse mémoire d'une mémoire entrelacée.png|centre|vignette|upright=2|Adresse mémoire d'une mémoire entrelacée]] Précisons que le temps d'accès mémoire ne change pas beaucoup avec l'entrelacement. Par contre, on peut faire plus d'accès mémoire simultanés. On peut démarrer un accès mémoire par cycle d'horloge, mais l'accès en lui-même prend plusieurs cycles. Les mémoires à entrelacement ont donc un débit supérieur aux mémoires qui ne l'utilisent pas. L'entrelacement classique pourrait être utilisé pour accélérer les transferts entre mémoire RAM et cache, qui se font en blocs d'adresses consécutives. Mais de nos jours, il n'est pas utilisé dans cette optique. Il faut dire qu'il est redondant avec le mode rafale des mémoires DRAM. Les deux font globalement la même chose et on voit mal comment utiliser les deux en même temps. Le mode rafale étant plus simple à implémenter, et plus léger niveau utilisation du bus de commande mémoire, il est préféré à l'entrelacement. Par contre, l'entrelacement a eu son heure de gloire sur d'anciens processeurs qui n'avaient pas de mémoire cache, alors qu'ils disposaient d'un pipeline. L'exemple typique est celui des nombreux ''processeurs vectoriels'', que nous n'avons pas encore abordé à ce stade du cours. De tels processeurs avaient un pipeline, une unité de calcul fortement pipelinée, et parfois même de l'exécution dans le désordre et du renommage de registres ! ===L'entrelacement par décalage=== L'entrelacement simple est très utile pour les accès en rafle ou équivalents. Par contre, il existe de rares situations où il n'est pas optimal. Une de ces situations est celle des accès en enjambée (en ''stride''), où l'on accède en série à des adresses sont séparées par N mots mémoires. [[File:Accès par enjambées.png|centre|vignette|upright=2|Accès par enjambées.]] De tels accès surviennent quand un logiciel accède à des structures de données spécifiques, à savoir des tableaux de structures, des matrices, ou d'autres structures de données dans le genre. Un cas classique est celui du parcours d'une matrice colonne par colonne. Les matrices de nombres sont mémorisées ligne par ligne, ce qui fait que parcourir une colonne demande de passer d'une ligne à la suivante et de faire des grands sauts en mémoire RAM, mais tous espacés par la même distance. Avec l'entrelacement simple, les accès en enjambée sont moins performants que les accès à des adresses consécutives, mais cela ne fonctionne pas trop mal. Le pire cas est celui où l'on a N banques et où les données sont justement placées toutes les N adresses. Des accès consécutifs vont tous tomber dans la même banque, on ne peut plus accéder à des banques différentes en parallèle. Mais en dehors de ce cas, on voit une amélioration par rapport à la situation sans entrelacement. Pour obtenir des performances maximales pour les accès en enjambées, il faut répartir les mots mémoires dans la mémoire autrement. L'organisation idéale est la suivante. : Dans les explications qui vont suivre, la variable N représente le nombre de banques, qui sont numérotées de 0 à N-1. On commence par organiser les N premières adresses comme une mémoire entrelacée simple : l'adresse 0 correspond à la banque 0, l'adresse 1 à la banque 1, etc. Pour le bloc suivant, on décale tout d'une adresse, à savoir qu'on commence à remplir les banques à partir de la seconde, non de la première. Une fois la fin du bloc atteinte, on finit de remplir le bloc en repartant du début du bloc. Le troisième bloc d'adresse subit le même traitement, sauf qu'on commence à remplir à partir de la seconde banque. Et on poursuit l’assignation des adresses en décalant d'un cran en plus à chaque bloc. Ainsi, chaque bloc verra ses adresses décalées d'un cran en plus comparé au bloc précédent. Si jamais le décalage dépasse la fin d'un bloc, alors on reprend au début. [[File:Mémoire interleaved par décalage.png|centre|vignette|upright=1.5|Mémoire entrelacée par décalage.]] En faisant cela, on remarque que les banques situées à N adresses d'intervalle sont différentes. Dans l'exemple du dessus, nous avons ajouté un décalage de 1 à chaque nouveau bloc à remplir. Mais on aurait tout aussi bien pu prendre un décalage de 2, 3, etc. Dans tous les cas, on obtient un entrelacement par décalage. Ce décalage est appelé le pas d'entrelacement, noté P. Le calcul de l'adresse à envoyer à la banque, ainsi que la banque à sélectionner se fait en utilisant les formules suivantes : * adresse à envoyer à la banque = adresse totale / N ; * numéro de la banque = (adresse + décalage) modulo N ; * décalage = (adresse totale * P) mod N. Avec cet entrelacement par décalage, on peut prouver que la bande passante maximale est atteinte si le nombre de banques est un nombre premier. Seulement, utiliser un nombre de banques premier peut créer des trous dans la mémoire, des mots mémoires inadressables. Malgré ce défaut, la technique a été utilisée sur quelques ordinateurs, avec l'exemple notable du superordinateur ''Burroughs Scientific Processor''. Pour éviter cela, il y a plusieurs solutions. Par exemple, on peut faire en sorte que N et la taille d'une banque soient premiers entre eux : ils ne doivent pas avoir de diviseur commun. Mais en pratique, elles n'ont pas vraiment été implémentées dans une vraie machine, et sont restées à l'état de recherche, aussi je les passe sous silence. ===L'entrelacement pseudo-aléatoire=== Une dernière méthode de répartition consiste à répartir les adresses dans les banques de manière "pseudo-aléatoire". L'idée exacte est de faire passer l'adresse dans une '''fonction de hachage''', plus ou moins complexe. Pour rappel, une fonction de hachage prend une entrée de grande taille et fournit en sortie un résultat de petite taille. Idéalement, elles doivent donner des résultats aussi différents que possible pour des entrées similaires, histoire de simuler une sorte de pseudo-aléatoire. Ici, une adresse est transformée en un numéro de banque. Une fonction de hachage simple ne fait que permuter des bits de l'adresse pour obtenir son résultat. Elle ne fait qu'échanger des bits de place avant de couper l'adresse en deux morceaux : un pour la sélection de la banque, et un autre pour la sélection de l'adresse dans la banque. Cette permutation est fixe, et ne change pas suivant l'adresse. Des fonctions de hachage plus complexes font des XOR bit à bit entre certains bits de l'adresse. ==Les contrôleurs mémoires SDRAM/DDR optimisés== Après avoir vu le cas des mémoires RAM, nous allons nous concentrer sur le cas particulier des mémoires SDRAM. Les mémoires SDRAM et DDR modernes sont capables de gérer plusieurs accès simultanés à la mémoire RAM, et leurs contrôleurs mémoire en font tout autant. C'est une différence majeure avec les mémoires asynchrones FPM/EDO, qui n'acceptaient qu'un seul accès mémoire à la fois. Leur contrôleur mémoire n'acceptait qu'un seul accès mémoire à la fois, c'était un contrôleur mémoire bloquant. Les contrôleurs mémoires des SDRAM sont eux non-bloquants et peuvent encaisser une dizaine d'accès mémoire à la fois. Peu de choses sont connues sur les contrôleurs de SDRAM/DDR modernes, les fabricants ne donnant que peu de détails dessus. Les rares simulateurs qui tentent de décrire leur fonctionnement, comme DRAM SIM I et II, sont particulièrement simples et ne vont pas dans le détail. Néanmoins, le peu qu'on sait est tout de même instructif. ===Rappel sur les SDRAM : tampon de ligne et commandes=== Les mémoires SDRAM sont des mémoires à tampon de ligne. Elles font un accès mémoire en deux étapes. La première étape recopie une ligne de N * 64 bits dans un tampon interne à la SDRAM. La seconde étape sélectionne une colonne, à savoir une donnée de 64 bits, qui est soit envoyée sur le bus de données pour une lecture, soit modifiée par une écriture. [[File:Mémoire à tampon de ligne.png|centre|vignette|upright=2|Mémoire à tampon de ligne]] Les accès mémoire sont traduits par un séquenceur mémoire en une série de commandes mémoires, qui sont séparées par des délais mémoire de quelques cycles d'horloge. Les délais sont très précis, et sont à respecter à la lettre. Une lecture ou une écriture se fait en maximum trois commandes : une commande PRECHARGE qui ferme la ligne précédemment utilisée, une commande ACT qui précise l'adresse de la ligne, et une commande READ ou WRITE qui précise l'adresse de la colonne et éventuellement la donnée à écrire. : Pour être plus précis, la commande PRECHARGE précharge les lignes de bits du plan mémoire à une tension particulière, ce qui les vide de leur contenu. Mais c'est un détail sans importance pour ce qui va suivre. Les SDRAM permettent de se passer de la première étape si des accès consécutifs se font dans la même ligne. Une fois activée, la ligne reste ouverte et on peut accéder plusieurs fois de suite dedans. On a alors juste à préciser l'adresse de colonne dedans. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | bgcolor="#FFA0FF" | PRECHARGE || bgcolor="#FFA0FF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || bgcolor="#A0FFFF" | READ (2) || bgcolor="#A0FFFF" | READ (3) || || || bgcolor="#A0FFFF" | READ (4) || bgcolor="#A0FFFF" | READ (5) || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | Donnée READ 1 || ||bgcolor="#A0FFFF" | Donnée READ 2 | bgcolor="#A0FFFF" | Donnée READ 3 || || || bgcolor="#A0FFFF" | Donnée READ 4 || bgcolor="#A0FFFF" | Donnée READ 5 |} Les SDRAM sont parfois capables de démarrer une commande avant que la précédente soit terminée. Mais le respect des délais mémoire est très limitant, ce qui fait qu'on ne peut pas parler de réel pipeline, comme c'est le cas sur les processeurs. Parlons plutot de '''commandes anticipées'''. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | || bgcolor="#A0FFFF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || || || bgcolor="#FFA0FF" | READ (2) || || || || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 | bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 | |} Un dernier point est que les chips mémoires de SDRAM sont composés de plusieurs banques, chacune étant une sorte de mini-mémoire miniature. Chaque banque a son propre décodeur, son propre tampon de ligne, ses propres multiplexeurs de colonne, sa logique de rafraichissement mémoire, etc. C'est comme si une SDRAM regroupait plusieurs mémoires séparées dans un même circuit intégré. [[File:Arrangement vertical.jpg|centre|vignette|upright=2.5|Mémoire multi-banques.]] ===La mise en attente des accès mémoire=== Un contrôleur de SDRAM peut accepter plusieurs accès mémoire en même temps. Mais cela ne signifie pas que le contrôleur sera prêt à les traiter immédiatement. Pour éviter tout problème de disponibilité, le contrôleur met en attente les accès mémoire que le processeur lui envoie, pour les exécuter dès que possible. Les accès mémoire sont mis en attente dans une mémoire FIFO, histoire de les exécuter dans leur ordre d'arrivée, notamment pour renvoyer les lectures dans l'ordre demandé. Il y a aussi une mémoire FIFO pour les données à écrire et une FIFO pour les données lues. Cette dernière sert au cas où le cache ne soit pas disponible quand on lui envoie la donnée lue. [[File:Module d'interface avec la mémoire.png|centre|vignette|upright=2.5|Contrôleur mémoire avec mise en attente des requêtes processeur et autres optimisations.]] Des optimisations sont possibles dès la mémoire FIFO. Par exemple, on peut regrouper plusieurs accès à des données consécutives en un seul accès en rafale. Le contrôleur mémoire analyse les requêtes mises en attente et détecte si certaines se font à des adresses consécutives. Si c'est le cas, il fusionne ces requêtes en une lecture/écriture en rafale. Une telle optimisation est appelée la '''combinaison de lecture''' pour les lectures, et la '''combinaison d'écriture''' pour les écritures. Une autre optimisation possible est le réacheminement lecture sur écriture (''Store to load forwarding''). Il s'agit d'un équivalent de l’optimisation utilisée dans le cadre de la désambiguïsation mémoire. Quand une lecture demande à accéder à une donnée écrite récemment, il se peut que l'écriture ait été mise en attente dans le contrôleur mémoire. Dans ce cas, le contrôleur mémoire peut renvoyer la donnée directement depuis ses mémoires FIFOs, sans faire d'accès mémoire en lecture. Les deux optimisations précédentes ne paraissent pas payer de mine, mais elles deviennent plus compréhensible quand on sait que certains contrôleurs mémoire peuvent mettre en attente beaucoup de données. Par exemple, l'''Intel E8870 - ''Scalable Memory Controller'' peut mettre en attente 64 lignes de cache dans la mémoire FIFO, soit 8 kibioctets de RAM ! Deux pages mémoire si la mémoire virtuelle utilise des pages de 4 kibioctets ! ===Les optimisations liées aux commandes anticipées=== Les commandes anticipées permettent de faire un minimum de parallélisme mémoire. Même si elle parait très limitée, cette possibilité est décuplée par la présence de plusieurs banques dans la mémoire SDRAM. Il est en effet possible de faire plusieurs accès mémoire en même temps, dans des banques différentes. Il est possible de lancer un accès mémoire dans deux banques en même temps, par exemple. La seule contrainte est que la SDRAM est limitée à 4 banques actives en même temps. {|class="wikitable" |+ Pipelining basique sur les SDRAM |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 |- ! Banque Numéro 1 | || || || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || || |- ! Banque Numéro 2 | || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || |- ! Banque Numéro 3 | || || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || |} L'implémentation n'a rien de vraiment compliqué. Il suffit d'utiliser une mémoire FIFO par banque. Ainsi, les accès dans une même banque se feront en série, dans l'ordre d'arrivée, mais des accès à des banques différentes se feront dans le désordre. Cela ne pose pas de problème car les accès se font à des adresses différentes, ce qui fait qu'il n'y a pas de conflits majeurs. [[File:Gestion parallèle des banques.png|centre|vignette|upright=2.5|Gestion parallèle des banques.]] ===Le ré-ordonnancement des commandes mémoires=== Un contrôleur mémoire moderne est capable de changer l'ordre des requêtes mémoires, pour gagner en performances. En clair : il peut faire l'équivalent mémoire de l'exécution dans le désordre des processeurs haute performance. Une différence majeure est que le contrôleur mémoire ne remet pas les accès mémoire dans l'ordre avant de les envoyer au processeur. C'est le processeur qui remet en ordre les accès mémoire, dans sa ''load queue''. Pour cela, les requêtes mémoire sont "numérotées", à savoir qu'on leur attribue un identifiant binaire. Le contrôleur mémoire exécute les lectures/écritures dans le désordre, mais garde la trace de leur identifiant. Il sait dans quel ordre il exécute les accès mémoire et connait leur latence. Ce qui fait qu'il sait que la donnée lue à tel instant est associée à la requête mémoire numéro X. Pour les lectures, il renvoie la donnée lue avec l'identifiant. Le processeur sait alors à quelle lecture la donnée lue correspond et il se débrouille en interne pour gérer la situation. En clair : le processeur voit les accès mémoire dans le désordre, c'est lui qui les remet en ordre. Maintenant que ces précisions sont faites, posons cette question : quel est l'intérêt de faire les accès mémoire dans le désordre ? Accéder à des banques en parallèle est une réponse possible, mais le ré-ordonnancement est surtout utile si tous les accès mémoire atterrissent dans la même banque et dans la même ligne. Elle marche si plusieurs accès à une même ligne ne sont pas consécutifs, et qu'il y a des accès entre les deux. Après ré-ordonnancement, ces accès mémoire sont exécutés l'un à la suite de l'autre. Pour rendre le tout plus concret, voici un exemple. Imaginez que l'on sait les 3 accès mémoire suivants : * Une lecture ligne A ; * Une écriture ligne B ; * Une lecture ligne A. Sans réordonnancent, on doit émettre deux commandes PRECHARGE : une quand on passe de la ligne A à la ligne B, et une autre pour le passage inverse. pour éviter cela, le contrôleur mémoire peut retarder l'écriture, ou au contraire avancer la seconde lecture. le résultat est alors le suivant : * Lecture ligne A ; * Lecture ligne A ; * Une écriture ligne B. On a donc deux accès consécutifs à la même ligne, suivi par une écriture dans une autre ligne. La technique marche parce que l'on a un mix adéquat de lectures et d'écritures. Mais il existe des possibilités de réorganisation autres qui ne sont pas valides. Il faut par exemple prendre garde à éviter d'intervertir lectures et écritures à une même adresse, sous peine de problèmes. Encore une fois, il faut faire attention aux dépendances mémoires. Le controleur mémoire teste les dépendances mémoires avant d'envoyer des commandes à la mémoire DDR/SDRAM. ==L'entrelacement sur les mémoires SDRAM== L''''entrelacement''' fonctionne sur les mémoires SDRAM, mais il doit être fortement modifié. Concrètement, tout ce qui a étré dit plus est inapplicable pour les SDRAM, du fait de la présence du tampon de ligne. Des adresses consécutives atterrissent déjà dans la même ligne, ce qui fait qu'on n'a pas besoin d'entrelacement au niveau d'une ligne. Et ne parlons pas de ce la présence de rangées et de canaux mémoire ! Cependant, il peut y avoir un entrelacement lié à la présence des banques SDRAM. L'entrelacement est géré juste avant le séquenceur mémoire. Le '''circuit d'entrelacement''' intervertit certains bits de l'adresse lors des accès mémoires. On trouve aussi des mémoires FIFOs pour mettre en attente les requêtes mémoire provenant du processeur. Elles permettent de recevoir plusieurs accès mémoire consécutifs. Si vous vous demandez comment plusieurs accès mémoire consécutifs peuvent avoir lieu, sachez que c'est une bonne question. Pour le moment, nous allons dire que ces accès proviennent du processeur ou de la mémoire cache, sans expliquer comment. Le contrôleur mémoire reçoit une série d'accès mémoire assez rapprochés, qu'il doit exécuter au mieux. [[File:Controleur mémoire d'une SDRAM avec entrelacement.png|centre|vignette|upright=2.5|Contrôleur mémoire d'une SDRAM avec entrelacement.]] Pour gérer l'entrelacement, le contrôleur de SDRAM prend en entrée l'adresse envoyée par le processeur, et la découpe en plusieurs champs : un pour sélectionner la banque adéquate, un autre pour la ligne, un autre pour la colonne, un autre pour la rangée. Une solution simple désactive l'entrelacement. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de banque || Adresse de ligne || Adresse de colonne |} Si cette solution fonctionne bien si les accès mémoire sont assez aléatoires, en pratique, mieux vaut utiliser l'entrelacement. ===L'entrelacement de banques/lignes=== Une première méthode d'entrelacement est l''''entrelacement de banques'''. Elle répartit deux lignes consécutives dans deux banques différentes. Pour comprendre l'idée, prenons un exemple. Imaginons une mémoire avec deux banques et 4 lignes. Imaginons qu'on parcoure/balaye la mémoire RAM en partant des adresses basses. Sans entrelacement de ligne, les accès se feront comme suit, de gauche à droite : {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Banque numéro 1 !! colspan="4" | Banque numéro 2 |- | Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 || Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 |} Avec l'entrelacement de banques, les accès se feront comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 |- | Ligne 1 || Ligne 1 || Ligne 2 || Ligne 2 || Ligne 3 || Ligne 3 || Ligne 4 || Ligne 4 |} Avec N banques, la première ligne ira dans la première banque, la seconde ligne dans la seconde, et ainsi de suite jusqu'à la énième banque qui va dans la banque numéro N. Et la ligne N+1 repart dans la première banque. L'avantage est que passer d'une ligne à la suivante est assez rapide. Au lieu de devoir fermer la première ligne et ouvrir la suivante on ouvrira directement la ligne suivante dans une banque différente, ce qui sera plus rapide. La commande PRECHARGE pourra être envoyée à la seconde banque en avance. Pour cela, l'adresse est découpée comme suit. {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne (précise à l'octet) |} ===L'entrelacement de rangées=== Il est aussi possible de faire la même chose, mais avec les rangées. Pour simplifier fortement, une rangée est simplement un chip mémoire. En réalité, les rangées ne sont pas des chip mémoire, mais un ensemble de chips mémoire regroupés ensemble histoire d'atteindre les 64 bits du bus de données. Par exemple, une rangée peut combiner 8 chips mémoire avec un bus de données de 8 bits chacun pour obtenir les 64 bits du bus de données d'une SDRAM. Mais nous passons ce détail sous silence dans les explications qui vont suivre, par souci de simplification. Pour faire comprendre l'entrelacement de rangée, le mieux est d'utiliser un exemple, le même que précédemment. Sans entrelacement de rangée, on change de chip mémoire une fois qu'on a balayé toutes ses adresses. {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Rangée numéro 1 !! colspan="4" | Rangée numéro 2 |- | Banque 1 || Banque 2 || Banque 3 || Banque 4 || Banque 1 || Banque 2 || Banque 3 || Banque 4 |} Avec l'entrelacement de ligne, on change de rangée dès qu'on change de banque/ligne. {|class="wikitable" |+ Adresse mémoire |- ! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 |- | Banque 1 || Banque 1 || Banque 2 || Banque 2 || Banque 3 || Banque 3 || Banque 4 || Banque 4 |} En clair, quand on a épuisé toutes les banques dans une même rangée, on passe à la rangée suivante au lieu de rester dans la même rangée. Notons que dans l'exemple précédent, on a combiné l'entrelacement de rangée et de banque, mais on aurait pu utiliser l'entrelacement de rangée seul. Mais ce n'est pas le cas le plus courant en pratique. Toujours est-il qu'en combinant entrelacement de rangée et de ligne, le découpage de l'adresse est le suivant : {|class="wikitable" |+ Adresse mémoire |- | Adresse de ligne || Numéro de rangée || Adresse de banque || Adresse de colonne (précise à l'octet) |} D'autres méthodes d'entrelacement sont possibles, mais elles sont plus complexes. Chaque contrôleur mémoire fait un peu à sa sauce. Ils permettent en général de choisir entre plusieurs entrelacements. Au minimum, ils permettent de désactiver l'entrelacement, d'activer un entrelacement de ligne, et d'autres entrelacements plus complexes, dont certains sont propriétaires et tenus secrets par le fabricant. ===L'entrelacement avec le ''dual channel''=== [[File:Dual channel slots.jpg|vignette|Slots mémoires avec ''dual channel''.]] L'usage du ''dual channel'' complique encore l'entrelacement. Là encore, il y a deux grandes solutions : avec et sans entrelacement des canaux mémoire. Rappelons le principe : deux barrettes de RAM sont accédées en parallèle. Et pour cela, il faut utiliser l'entrelacement. Typiquement, chaque barrette mémoire fournit 64 bits, ce qui fait que l'on peut accéder à 128 bits d'un seul coup, par exemple avec un accès en rafale. Sans ''dual channel'', la première barrette correspond à la moitié haute de la RAM, la seconde barrette correspond à la moitié basse. Avec ''dual channel'', une forme spécifique d'entrelacement est activée. Concrétement, deux blocs de 64 bits sont placés dans des canaux mémoire séparés. Idem avec du triple ou quadruple canal, mais c'est alors trois ou quatre blocs de 64 bits qui sont dispersés dans des canaux consécutifs. Le découpage de l'adresse est alors le suivant : {|class="wikitable" |+ Adresse mémoire |- ! colspan="6" | Sans entrelacement |- | Numéro de canal/barrette || Reste de l'adresse || Position de l'octet dans un bloc de 64 bits |- | colspan="6" | |- ! colspan="6" | Avec entrelacement |- | Reste de l'adresse || Numéro de canal/barrette || Position de l'octet dans un bloc de 64 bits |} [[File:Décodage d'adresse avec dual channel.png|centre|vignette|upright=2|Décodage d'adresse avec dual channel.]] Les contrôleurs de SDRAM précédents sont assez basiques et ne représentent pas les contrôleurs les plus évolués. Ils étaient utilisés sur les anciens PC, à une époque où ils étaient encore sur la carte mère du processeur. Mais de nos jours, le contrôleur mémoire est intégré au processeur. Et il incorpore de nombreuses optimisations afin de gagner en performances. Malheureusement, ces optimisations sont elles-mêmes dépendantes des optimisations intégrées dans le processeur, ce qui fait qu'on ne peut pas en parler ici. Elles demandent que le contrôleur mémoire reçoive plusieurs accès mémoire simultanés, ce qui n'a aucun sens à ce stade du cours. Aussi, nous allons les passer sous silence pour le moment. Nous les verrons dans le chapitre sur le parallélisme mémoire. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=La désambiguïsation mémoire | prevText=La désambiguïsation mémoire | next=Les processeurs superscalaires | nextText=Les processeurs superscalaires }} </noinclude> 7t4a9tkt16zgl9yf9glog66mvtulmb5 764723 764722 2026-04-23T22:07:17Z Mewtow 31375 /* Le ré-ordonnancement des commandes mémoires */ 764723 wikitext text/x-wiki Dans ce chapitre, nous allons voir les techniques qui permettent de gérer plusieurs accès mémoire simultanés directement au niveau du cache ou de la mémoire RAM. Par plusieurs accès mémoire simultanés, vous pensez sans doute à l'usage de cache multiports, voire de mémoires RAM multiport. C'est en effet une solution, mais ce n'est clairement pas la seule. Par exemple, il est possible de pipeliner l'accès au cache, voire à la mémoire RAM. Il existe de nombreuses techniques de '''parallélisme mémoire''', et nous allons les voir dans ce chapitre. Pour donner un exemple, il est possible de pipeliner les accès mémoire. Il est possible de pipeliner l'accès au cache et/ou l'accès à la mémoire, et nous verrons comment faire dans ce chapitre. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, qu'ils aient ou non un pipeline dynamique. Par contre, pipeliner la mémoire est une technique ancienne, aujourd'hui peu utilisée. Elle était utilisée sur des très vieux ordinateurs, qui avaient un pipeline mais pas de mémoire cache. Mais de nos jours, les mémoires SDRAM et DDR ne sont pas adaptées pour ça, il est très difficile de les pipeliner correctement. Le parallélisme mémoire est utile aussi bien sur des processeurs à émission dans l'ordre que dans le désordre. Par exemple, un processeur ''in-order'' avec des lectures non-bloquantes peut e, profiter. Il peut techniquement lancer une seconde lecture, après avoir rencontré une première lecture non-bloquante. En général, le nombre de lectures consécutives est cependant limité à moins d'une dizaine. À l'opposé, les processeurs sans lectures non-bloquantes profitent pas du parallélisme mémoire. À la rigueur, ils peuvent en profiter s'ils intègrent des techniques de préchargement. Pendant que le processeur exécute une lecture/écriture, il peut précharger une autre donnée en parallèle. Inutile de dire que sans pipeline processeur, le parallélisme mémoire ne sert pas à grand-chose. ==Le ''line fill buffer'' et les techniques associées== Lors d'un défaut de cache, le processeur doit attendre que toute la ligne de cache soit chargée avant d'être utilisable. Or, la taille d'une ligne de cache est supérieure à la largeur du bus mémoire, ce qui fait qu'une ligne de cache est chargée en plusieurs fois, morceaux par morceaux, mot mémoire par mot mémoire. Le chargement peut se faire directement dans le cache, mais ce n'est pas une solution très pratique. À la place, beaucoup de processeurs ajoutent une mémoire tampon entre la RAM et le cache, appelée le '''tampon de remplissage de ligne''' (''line-fill buffer'') dans les processeurs modernes. Lors d'un défaut de cache, le processeur charge la donnée de la RAM dans le tampon de remplissage de ligne, mot mémoire par mot mémoire. Une fois plein, le tampon de remplissage de ligne est recopié dans la ligne de cache. Le tampon de remplissage de ligne contient une ligne de cache, si ce n'est que certains bits de contrôle ne sont pas présents. Le tampon de remplissage de ligne contient un bit de validité pour chaque mot mémoire de la ligne de cache, qui indiquent si le mot mémoire a été chargé. Par exemple, prenons un processeur 64 bits, qui gère donc des mots mémoire de 8 octets, avec des lignes de cache 256 octets/32 mots mémoire. Le tampon de remplissage de ligne contiendra 32 bits de validité. Si le processeur a chargé les 6 premiers mots mémoire, les 6 premiers bits de validité seront mis à 1, les autres seront encore à zéro. Il faut noter que la disponibilité de la ligne complète se détermine assez facilement en faisant un ET logique entre tous les bits de validité. Le processeur sait ainsi quand la ligne de cache est disponible entièrement et donc quand la transférer dans le cache. La même chose existe avec une hiérarchie de cache, sauf que l'on trouve une mémoire tampon entre chaque niveau de cache. Il y a un tampon de remplissage de ligne entre le cache L1 et le cache L2, entre le cache L2 et le L3, etc. Si le cache ne gère qu'un seul défaut de cache à la fois, le ''fill line buffer'' est une mémoire très simple, qui ne mémorise qu'une seule ligne de cache. Et cela vaut aussi bien pour un cache bloquant que non-bloquant. Mais sur les caches capables de gérer plusieurs défauts simultanés, le tampon de remplissage de ligne est une mémoire de type FIFO ou LIFO, capable de mémoriser plusieurs lignes de cache. Notons que le tampon de remplissage de ligne est très utile pour implémenter certaines techniques, par exemple le contournement du cache. Nous avions vu dans le chapitre sur le cache que certains accès mémoire doivent contourner le cache, pour des raisons de cohérence des caches. C'est notamment nécessaire pour accéder aux périphériques, mais c'est aussi utile pour des raisons de performances dans des cas très spécifiques. Les accès qui contournent le cache se font directement dans le tampon de remplissage de ligne : le processeur écrit ou lit les données depuis ce tampon de remplissage de ligne, sans accéder au cache. ===L'''early restart'' et le ''critical word load''=== La présence du ''line buffer'' permet une optimisation assez intéressante, qui permet de réduire la latence des défauts de cache. L'optimisation consiste à lire un mot mémoire dans le tampon de remplissage de ligne, même si la ligne de cache complète n'a pas encore été chargée. Il existe deux manières de faire cela, qui portent les noms d'''early restart'' et de ''critical word load''. La première est la version la plus simple, la seconde est plus complexe mais plus performante. Avec la technique d''''''early restart''''', la ligne de cache est chargée normalement, en partant de son premier mot mémoire. Dès que le mot mémoire lu/écrit par le processeur est copié dans le ''line fill buffer'', il est envoyé au processeur immédiatement. Illustrons le tout par un exemple, où une ligne de cache fait 16 mots mémoire. Le processeur effectue une lecture, qui lit le 5ème mot mémoire. Sans ''early restart'', le processeur doit charger les 16 mots mémoire avant de faire la lecture dans le cache. Avec ''early restart'', le processeur reçoit la donnée dès que le 5ème mot mémoire est disponible. Le processeur doit attendre que les 4 premiers mots mémoire soient chargés, puis le 5ème mot mémoire arrive et est envoyé directement au processeur, il est lu directement depuis le ''line fill buffer''. Les 11 mots mémoire suivants sont ensuite chargés dans le cache pendant que le processeur fait des calculs dans son coin. Le ''critical word load'' est une optimisation de la technique précédente où le chargement de la ligne de cache commence directement à la donnée demandée par le processeur. Pour reprendre l'exemple précédent, où le processeur demande le 5ème mot mémoire sur 16, le ''critical word load'' charge le 5ème mot mémoire en premier et l'envoie au processeur, ce qui fait qu'il est chargé très rapidement. Pas besoin d'attendre que le processeur charge les 4 mots mémoire précédents comme avec l'''early restart''. Dans le détail, le processeur charge le 5ème mot mémoire en premier, puis charge les 11 suivants, et termine par les 4 mots mémoire du début. En clair, le ''critical word load'' commence par charger le mot mémoire lu, puis les blocs suivants, avant de revenir au début du bloc pour charger les blocs restants. Ainsi, la donnée demandée par le processeur sera la première disponible. Pour cela, l'organisation du tampon de remplissage de ligne est modifiée de manière à rendre cela possible. Il a une taille égale à une ligne de cache complète, qui contient elle-même plusieurs mots mémoire. Dans le ''line-fill buffer'', chaque mot mémoire est stocké avec un tag, qui indique l'adresse du mot mémoire stocké dans le ''line-fill buffer''. Le ''line-fill buffer'' est donc un cache un peu particulier, qui fonctionne comme un cache du point de vue du processeur, comme une mémoire FIFO pour les transferts avec le cache. Ainsi, un processeur qui veut lire dans le cache après un défaut peut accéder à la donnée directement depuis le tampon de remplissage de ligne, alors que la ligne de cache n'a pas encore été totalement recopiée en mémoire. ==Les caches pipelinés== Il est possible de pipeliner l'accès au cache, ce qui demande juste de rajouter des registres au bon endroits et quelques circuits de contrôle. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, même ceux avec un pipeline dynamique. Et l'implémentation peut se faire de plusieurs manières différentes. Il faut dire que découper une mémoire cache en plusieurs étapes peut se faire de plusieurs manière. Commençons par la plus simple. Beaucoup de processeurs des années 2000 avaient une fréquence peu élevée comparée aux standards d'aujourd'hui, ce qui fait que leur cache avait bien un temps d'accès d'un cycle d'horloge. Mais l'accès au cache se faisait en deux cycles d'horloge : un cycle pour calculer une adresse et un cycle pour l'accès mémoire proprement dit. Et ces deux étapes étaient pipelinées, à savoir que deux micro-opérations mémoire peuvent s'exécuter en même temps : une dans l'étage de calcul d'adresse, une autre dans le cache. L'unité mémoire était donc pipelinée, alors que l'accès au cache ne l'était pas. Les processeurs qui implémentaient cette technique regroupent les micro-architectures K5 et K6 d'AMD, les processeurs Intel de micro-architecture P6 (Pentium 2 et 3) et quelques autres. Il est aussi possible de pipeliner l'accès au cache lui-même. Avec un cache pipeliné, l'accès au cache ne se fait pas en un seul cycle, mais en plusieurs. Cependant, on peut lancer un nouvel accès au cache à chaque cycle d'horloge, comme un processeur pipeliné lance une nouvelle instruction à chaque cycle. L'implémentation est assez simple : il suffit d'ajouter des registres dans le cache. Pour cela, on profite que le cache est composé de plusieurs composants séparés, qui échangent des données dans un ordre bien précis, d'un composant à un autre. De plus, le trajet des informations dans un cache est linéaire, ce qui les rend parfois pour l'usage d'un pipeline. ===Le ''pipelining'' des caches ''direct-mapped''=== Voici ce qui se passe avec un cache directement adressé. Pour rappel ce genre de cache est conçu en combinant une mémoire RAM, généralement une SRAM, avec quelques circuits de comparaison et un MUX. L'accès au cache se fait globalement en deux étapes : la première lit la donnée et le ''tag'' dans la SRAM, et on utilise les deux dans une seconde étape. Nous avions vu il y a quelques chapitre comment pipeliner des mémoires, dans le chapitre sur les mémoires évoluées. Et bien ces méthodes peuvent s'utiliser pour la mémoire RAM interne au cache !L'idée est d'insérer un registre entre la sortie de la RAM et la suite du cache, pour en faire un cache pipéliné, à deux étages. Le premier étage lit dans la SRAM, le second fait le reste. L'implémentation sur les caches associatifs à plusieurs voies est globalement la même à quelques détails près. [[File:Cache directement adressé pipeliné - deux étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - deux étages]] Avec le circuit précédent, il est possible d'aller plus loin, cette fois en pipelinant l'accès à la mémoire RAM interne au cache. Pour comprendre comment, rappelons qu'une mémoire SRAM est composée d'un plan mémoire et d'un décodeur. L'accès à la mémoire demande d'abord que le décodeur fasse son travail pour sélectionner la case mémoire adéquate, puis ensuite la lecture ou écriture a lieu dans cette case mémoire. L'accès se fait donc en deux étapes successives séparées, on a juste à mettre un registre entre les deux. Il suffit donc de mettre un gros registre entre le décodeur et le plan mémoire. [[File:Cache directement adressé pipeliné - trois étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - trois étages]] Et on peut aller encore plus loin en découpant le décodeur en deux circuits séparés. En effet, rappelez-vous le chapitre sur les circuits de sélection : nous avions vu qu'il est possible de créer des décodeurs en assemblant des décodeurs plus petits, contrôlés par un circuit de prédécodage. Et bien on peut encore une ajouter un registre entre ce circuit de prédécodage et les petits décodeurs. : Théoriquement, toute l'adresse est fournie d'un seul coup au cache, la quasi-totalité des processeurs présentent l'adresse complète à un cache pipeliné. Mais le Pentium 4 fait autrement. Il faut noter que les premiers étages manipulent l'indice dans la SRAM, qui est dans les bits de poids faible, alors que les étages ultérieurs manipulent le ''tag'' qui est dans les bits de poids fort. Les concepteurs du Pentium 4 ont alors décidé de présenter les bits de poids faible lors du premier cycle d'accès au cache, puis ceux de poids fort au second cycle. Pour cela, l'ALU fonctionnait à une fréquence double de celle du processeur, tout comme le cache L1. Il n'y avait pas de pipeline proprement dit, mais cela réduisait grandement la latence d'accès au cache. ===Le ''pipelining'' des caches splittés/sériels=== Il est aussi possible de pipeliner un cache dits splittés, aussi appelés à accès sériel. Pour rappel, les caches à accès sériel vérifient si il y a succès ou défaut de cache, avant d'accéder aux lignes de cache en cas de succès. Ils font donc différemment des autres caches, qui accèdent à une ligne de cache, avant de déterminer s'il y a succès ou défaut en lisant le tag de la ligne de cache. Les caches sériels disposent de deux SRAM : une pour les tags des lignes de cache et une pour les données. Ils accèdent à la SRAM pour les tags, avant d’accéder à la SRAM des données en cas de succès de cache. Vu que l'accès se fait en deux étapes, une vérification des tags suivie de la lecture/écriture des données, il est facile à pipeliner. Pipeliner le cache permet de régler le problème des accès au cache L1, et elle est tout le temps utilisé sur les processeurs modernes. Mais que faire en cas de défaut de cache ? ==Les caches non bloquants== Un '''cache bloquant''' est un cache auquel le processeur ne peut pas accéder pendant un défaut de cache. Il faut attendre que la lecture ou écriture en RAM soit terminée avant de pouvoir utiliser de nouveau le cache. Un '''cache non bloquant''' n'a pas ce problème : on peut l'utiliser pendant un défaut de cache. Les caches non bloquants permettent de démarrer une nouvelle lecture ou écriture alors qu'une autre est en cours, ce qui permet d'exécuter plusieurs lectures ou écritures en même temps. ===Les ''Miss Handling Status Registers''=== Lors d'un défaut de cache, la mémoire RAM est consultée pendant le défaut de cache, mais le cache est inutilisé. Un défaut de cache n'utilise pas le cache, ce n'est qu'un accès en mémoire RAM, sur le bus mémoire (ou un accès aux niveaux de cache inférieurs, peu importe). Le cache en lui-même est laissé libre, rien n’empêche d'y accéder, il est en réalité intrinsèquement non-bloquant. Les caches, bloquants comme non-bloquants, sont en réalité composés d'une mémoire cache proprement dite, entourée de circuits qui servent d'interface entre le processeur et le cache lui-même. Et parmi les circuits tout autour, certains gèrent l'accès au cache lors d'un défaut de cache. Ils sont regroupés sous le terme de '''''Miss Handling Architecture''''' (MHA). La différence entre un cache bloquant et un cache non-bloquant est en réalité liée à l'implémentation de la MHA. Les caches bloquants coupent volontairement l'accès au cache lors d'un défaut, car les défauts sont plus simples à gérer ainsi. Le défaut de cache rapatrie une donnée depuis la RAM, qui sera écrite dans le cache. Et il ne faut pas qu'une tentative d'accès à cette donnée ait lieu avant qu'elle ne soit chargée. Mais ce blocage est général et touche tout le cache, alors que seule une ligne de cache est concernée par le défaut de cache. L'idée derrière un cache non-bloquant est que seule la ligne de cache est bloquée, mais les autres sont accessibles. L'idée est alors de mémoriser les lignes de cache concernées par le défaut de cache, afin d'en bloquer l'accès. À chaque accès, on vérifie si la ligne de cache est déjà réservée par un défaut de cache. La lecture/écriture est alors bloquée si c'est le cas, mais elle accepte les accès sinon. Pour cela, la ''Miss Handling Architecture'' contient des registres qui mémorisent des informations sur les défauts de cache en cours. Ils portent le nom de '''''miss status handling registers''''', que j’appellerais dorénavant MSHR, qui sont aussi appelés des '''''miss buffer'''''. Le contenu des MSHR varie beaucoup suivant le processeur, mais ces derniers stockent au minimum les informations suivantes : * Le numéro de la ligne de cache dans laquelle les données sont chargées. * Un bit de validité qui indique si le MSHR est vide ou pas, qui est mis à 0 quand le défaut de cache est résolu. * Un ou plusieurs champs de lecture/écriture, qui contiennent des informations sur la lecture/écriture. ** Pour une lecture, elle contient des informations sur la destination de la donnée, à savoir qui prévenir quand le défaut de cache est terminé. C'est parfois un nom/numéro de registre (celui dans lequel charger la donnée), mais c'est souvent le numéro de l'entrée dans le ''load/store queue''. ** Pour les écritures, elle contient la donnée à écrire, ou éventuellement un numéro de ''load/store queue'' où se trouve la donnée à écrire. Il faut noter que le bus mémoire ne peut gérer qu'un seul défaut de cache à la fois. Aussi, il est intéressant de regarder ce qui se passe lorsqu'un second défaut de cache survient, pendant qu'un premier est en cours. Dans ce cas, il y a deux réponses qui correspondent à deux types de caches non-bloquants, qui portent les noms barbares de caches de type succès après défaut et défaut après défaut. Sur le premier type, il ne peut pas y avoir plusieurs défauts de cache simultanés. Dès qu'un second défaut de cache survient, le cache stoppe son activité et on ne peut plus démarrer de nouvelle lecture/écriture, tant que le premier défaut de cache n'est pas résolu. Le second type est plus souple et autorise la survenue de plusieurs défauts de cache simultanés. Du moins, jusqu'à une certaine limite, car le cache ne peut supporter qu'un nombre limité d'accès mémoires simultanés (pipelinés). ===Les accès simultanés à une même ligne de cache=== Il arrive que le processeur fasse plusieurs accès mémoire simultanés à la même ligne de cache. Si la ligne de cache en question n'a pas encore été chargée dans le cache, alors on a plusieurs défauts de cache consécutifs pour la même ligne de cache, et le cache non-bloquant doit gérer la situation. Pour la suite, il va falloir faire une petite distinction entre les défauts primaires et secondaires. Imaginons qu'un défaut de cache ait lieu et demande à charger une donnée dans la ligne de cache numéro N. Il s'agit du premier défaut impliquant cette ligne de cache précise, ce qui lui vaut le nom de '''défaut de cache primaire'''. Mais par la suite, d'autres accès mémoire à la même ligne de cache ont lieu, alors que la ligne de cache n'est pas encore disponible. Dans ce cas, il s'agit de '''défauts de cache secondaires'''. Pour l'unité d'accès mémoire, les défauts de cache primaire et secondaire sont différents (ils prennent tous une entrée dans la ''load/store queue''). Mais pour le cache, ils ne correspondent qu'à un seul accès au cache : celui qui demande de charger la ligne de cache demandée. Les défauts de cache primaire et secondaire à la même ligne de cache se voient attribuer un MSHR unique. La gestion des défauts de cache secondaires dépend du cache non-bloquant. La solution la plus simple ne permet pas les défauts de cache secondaires. Le cache ne permet pas deux défauts de cache simultanés pour la même ligne de cache. Les autres solutions le permettent, en fusionnant des accès simultanés à la même ligne de cache en un seul au niveau des MSHR. Dans tous les cas, détecter les défauts de cache secondaires sont un problème qu'il faut détecter. La ''Miss Handling Architecture'' doit détecter les défauts de cache secondaire. Pour cela, elle procède comme suit. Lors de chaque défaut de cache, la MHA récupère le numéro de la ligne de cache associé. Il vérifie alors chaque MSHR pour vérifier s'il contient le numéro en question. S'il n'y a aucune correspondance dans les MSHR, alors c'est signe que le défaut de cache est un défaut primaire. Mais s'il y en a une, alors c'est un défaut secondaire. Évidemment, cela signifie que lors d'un défaut de cache, le numéro de ligne de cache est envoyé à tous les MSHR, pour comparaison. Les MSHR sont donc regroupés dans une mémoire associative, une sorte de mini-cache, faciliter l'implémentation. Lors d'un défaut de cache primaire, l'accès à un cache non-bloquant se fait comme suit : le processeur envoie une adresse au cache, accède à celui-ci, et détecte la survenue d'un défaut de cache. Il en profite alors pour attribuer une ligne de cache dans laquelle sera chargée ce défaut. L'attribution est très simple dans le cas des caches ''direct mapped'', ou associatifs par voie, pour lesquels l'attribution se fait assez simplement. Il mémorise alors cette information dans les MSHR, après avoir vérifié que le défaut de cache n'était pas un défaut secondaire. Lors des accès ultérieurs à une adresse proche, censée être dans la même ligne de cache, le processeur va encore une fois rencontrer un défaut de cache. Il va alors déterminer le numéro de la ligne de cache associée à l'adresse, et comparer ce numéro avec les MSHRs. Si un MSHR contient ce numéro, c'est signe que le défaut de cache est un défaut secondaire. La MHA réagit alors différemment selon le processeur considéré. Une première solution n'autorise pas les défauts de cache secondaires. Si l'un d'entre eux survient, le processeur est gelé par un ''pipeline stall'', une bulle de pipeline. Une autre solution fusionne les défauts de cache secondaires avec le défaut de cache primaire : tout cela ne correspond qu'à un seul défaut de cache pour lui. ===Les MSHR simples=== Un cache non-bloquant à '''MSHR simple''' contient juste plusieurs MSHR qui mémorisent juste un numéro de ligne de cache, un bit de validité, le champ de lecture/écriture, et l'adresse exacte de lecture/écriture. Avec cette organisation, il est possible d'avoir plusieurs défauts de cache séparés, mais à la condition que chaque défaut accède à une ligne de cache différente. Deux accès simultanés à une même ligne de cache ne sont pas possibles, les défauts de cache secondaires ne sont pas autorisés. Ainsi, chaque défaut de cache se voit attribuer son propre MSHR, chacun contient un numéro de ligne de cache différent. Pour comprendre pourquoi c'est impossible de gérer les défauts secondaires, il faut regarder le champ de lecture/écriture. Si on veut effectuer plusieurs écritures consécutives à la même adresse, le MSHR n'aura pas de quoi mémoriser les deux données à écrire. Il pourra mémoriser la première donnée à écrire, pas la seconde. Même chose lors d'une lecture : le champ lecture/écriture peut mémorisr la destination de la première lecture, pas de la seconde. L'avantage est que la MHA n'a besoin que des MSHR et de quelques circuits annexes. Les autres solutions rajoutent des circuits annexes pour gérer les défauts de cache secondaires, qui utilisent beaucoup de circuits. Le cout en circuit est donc élevé, mais le gain en performance est là. Passons maintenant aux caches non-bloquants qui autorisent les défauts de cache secondaire. La solution la plus simple consiste à utiliser ===Les MSHR adressés implicitement=== Avec les '''MSHR adressés implicitement''', il est possible de fusionner plusieurs accès mémoire à une même ligne de cache, mais sous une condition très importante : ces accès lisent/écrivent des mots mémoire différents. Par exemple, imaginons qu'une ligne de cache contienne 8 mots mémoire de 64 bits. Si un premier accès mémoire lit le mot mémoire numéro 7 (dernier mot mémoire de la ligne), et le second accès le mot mémoire numéro 3, alors la fusion est possible. Mais si deux accès mémoire veulent lire/écrire le mot mémoire numéro 7, alors la fusion n'est pas possible et le processeur se bloque, un ''pipeline stall'' survient. Un MSHR adressé implicitement est un MSHR simple, qui contient naturellement un numéro de ligne de cache (tag) et un bit de validité, sauf que le champ de lecture/écriture est dupliqué. Les différents champs lecture/écriture d'un MSHR sont regroupés dans une mémoire RAM/cache qui contient autant d'entrées qu'il y a de mot mémoire dans une ligne de cache. Chaque entrée de la table est associée à un mot mémoire de la ligne de cache et stocke des informations sur celui-ci. Une entrée mémorise, au minimum : * Un champ de lecture/écriture qui contient soit la destination de la lecture, soit la donnée à écrire. * Un bit R/W permettant d'interpréter correctement le champ de lecture/écriture. * Un bit de validité pour chaque entrée de la table, qui dit si un défaut de cache antérieur accède déjà à ce mot mémoire. La fusion de deux défauts de cache est ainsi assez simple. Un défaut de cache primaire/secondaire configure l'entrée associée au mot mémoire lu/écrit. Si un défaut secondaire ultérieur lit/écrit un mot mémoire différent (dont le bit de validité de l'entrée dans la table est à zéro), pas de conflit : il configure une autre entrée, vide. Mais s'il lit/écrit un mot mémoire pour lequel l'entrée de la table est occupée, il y a conflit, le processeur est bloqué par un ''pipeline stall'', une bulle de pipeline. Avec cette organisation, le nombre de MSHR indique combien de lignes de cache peuvent être lues en même temps depuis la mémoire. Quant au nombre d'entrées par MSHR, il détermine combien d'accès mémoires qui ne se recouvrent pas peuvent avoir lieu en même temps. ===Les MSHR adressés explicitement=== Les '''MSHR adressés explicitement''' n'ont pas les contraintes des MSHR adressés implicitement. Avec eux, il est possible d'avoir plusieurs défauts de cache pointant vers la même ligne de cache, mais aussi vers le même mot mémoire. De tels défauts de cache apparaissent sur les processeurs à exécution dans le désordre, mais sont très rares, voire inexistants, sur les processeurs ''in-order''. L'idée est encore que chaque MSHR est associée à une table mémoire qui mémorise des entrées. Cette table n'est autre qu'une mémoire FIFO. Sauf que cette fois-ci, une entrée n'est pas associée à un mot mémoire. Une entrée est associée à un défaut de cache. Une entrée mémorise là encore la destination de la lecture ou la donnée de l'écriture suivi du bit R/W, ainsi que d'un bit de validité par entrée, mais aussi : la position du mot mémoire lu dans la ligne de cache. C'est cette dernière information qui n'était pas présente dans les MSHR adressés implicitement. De plus, le nombre d'entrée par MSHR n'est pas égal au nombre de mots mémoires dans une ligne de cache, mais peut être arbitrairement grand. L'avantage d'un tel cache est qu'il est capable de traité les défauts secondaires concernant un même mot mémoire, et ce, car les accès à la ligne de cache concernée se produisent dans l'ordre de génération des défauts (la table mémoire est une FIFO). ===Les MSHRs inversés=== Généralement, plus on veut supporter de défauts de cache, plus le nombre de MSHR et d'entrées augmente. Mais au-delà d'un certain nombre d'entrées et de MSHR, les MSHR adressés implicitement et explicitement ont tendance à bouffer un peu trop de circuits. Utiliser une organisation un peu moins gourmande en circuits est donc une nécessité. Cette organisation plus économe se base sur des MSHR inversés. Les MSHR inversés ne contiennent qu'une seule entrée, en quelque sorte : au lieu d’utiliser n MSHR de m entrées chacun, on va utiliser n × m MSHR inversés. La différence, c'est que plusieurs MSHR peuvent contenir un tag identique, contrairement aux MSHR adressés implicitement et explicitement. Lorsqu'un défaut de cache a lieu, chaque MSHR est vérifié. Si jamais aucun MSHR ne contient de tag identique à celui utilisé par le défaut, un MSHR vide est choisi pour stocker ce défaut, et une requête de lecture en mémoire est lancée. Dans le cas contraire, un MSHR est réservé au défaut de cache, mais la requête n'est pas lancée. Quand la donnée est disponible, les MSHR correspondant à la ligne qui vient d'être chargée vont être utilisés un par un pour résoudre les défauts de cache en attente. ===Les MSHR intégrés au cache=== Certains chercheurs ont remarqué que pendant qu'une ligne de cache est en train de subir un défaut de cache, celle-ci reste inutilisée, et son contenu est destiné à être perdu une fois le défaut de cache résolu. Ils se sont dit que, plutôt que d'utiliser des MSHR séparés, il vaudrait mieux utiliser la ligne de cache pour stocker les informations sur les défauts de cache en attente dans cette ligne de cache. Pour éviter tout problème, il faut rajouter un bit dans les bits de contrôle de la ligne de cache, qui sert à indiquer que la ligne de cache est occupée : un défaut de cache a eu lieu dans cette ligne, et elle stocke donc des informations pour résoudre les défauts de cache. ==L'entrelacement mémoire== Nous venons de voir comment une mémoire cache peut gérer plusieurs accès mémoire simultanés. Il se trouve que ce genre d'optimisation dépasse largement le cadre du cache. Les mémoires RAM ne sont pas en reste, elles aussi ! Il est possible de pipeliner l'accès à une mémoire RAM, en utilisant des techniques dites d''''entrelacement'''. Précisons cependant que cette optimisation n'est pas utilisée sur les mémoires SDRAM ou DDR modernes, du moins pas sans modifications majeures. Mais divers ordinateurs assez anciens, dont des superordinateurs, ont utilisé cette technique. La technique demande d'utiliser plusieurs mémoires séparées. Sur les anciens ordinateurs, les mémoires en question sont des chips mémoire, à savoir des circuits intégrés de DRAM. Pour faire plus général, nous allons utiliser le terme de '''banques''', ou encore de bancs mémoire. La différence est qu'il existe des mémoires multi-banque, qui regroupent plusieurs banques indépendantes dans un seul boitier, dans un seul chip mémoire. En clair, de telles mémoires regroupent plusieurs sous-mémoires dans un seul circuit intégré. Il est aussi possible d'utiliser plusieurs chips mémoire séparés, chacun ayant une banque. Reste à combiner ces banques pour former un pseudo-pipeline. ===Utiliser plusieurs banques sans entrelacement=== Sans optimisation particulière, les adresses sont réparties dans les banques comme indiqué ci-dessous. Il s'agit de ce qui s'appelle un arrangement horizontal, et nous avions vu celui-ci dans le chapiutre sur les mémoires SDRAM. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Avec cet arrangement, les bits de poids fort de l'adresse sont utilisées pour sélectionner la banque adéquate, et le reste de l'adresse est envoyé sur le bus d'adresse. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Adresse de banque !! Adresse dans la banque |- | Quelques bits de poids fort || Reste de l'adresse |} L'entrelacement change cette répartition, afin d'accélérer les accès mémoire. L'entrelacement classique vise à améliorer les accès à des adresses mémoires consécutives, mais des formes plus évoluées d'entrelacement visent à optimiser des accès mémoire arbitraires. Voyons-les dans l'ordre, du plus simple au plus complexe. ===L'entrelacement classique=== Avec l''''entrelacement classique''', des adresses consécutives sont réparties dans des banques consécutives. Précisons que cette attribution des adresses n'implique absolument pas la mémoire virtuelle ou n'importe quel mécanisme dans le processeur. La répartition décide que telle adresse mémoire va dans telle banque, à telle adresse dans la banque. Elle est donc le fait du contrôleur mémoire, donc en dehors du processeur (les contrôleurs mémoires n’étaient pas encore intégrés dans le processeur à l'époque). [[File:Répartition des adresses dans une mémoire interleaved.png|centre|vignette|upright=1.5|Répartition des adresses dans une mémoire interleaved.]] L'entrelacement simple permet d'accélérer les accès mémoire qui se font à des adresses consécutives. Avec l'entrelacement, chaque accès mémoire tombe dans une banque mémoire différente, ce qui fait qu'on peut démarrer un nouvel accès mémoire à chaque cycle d'horloge. Pendant qu'une banque est occupée par un accès mémoire, on démarre le suivant dans une autre banque, et ainsi de suite. Le tout se termine soit quand on a épuisé toutes les banques libres, soit quand l'accès en rafales se termine. Pas besoin d'attendre que la mémoire ait fini sa lecture/écriture avant de démarrer la lecture/écriture suivante. [[File:Pipemining mémoire.png|centre|vignette|upright=2.5|Pipelining mémoire]] L'animation suivante illustre bien le processus, avec quatres banques. [[File:Interleaving.gif|centre|vignette|upright=2.5|Pipelining mémoire]] L'entrelacement simple utilise les bits de poids faible pour sélectionner la banque, et les bits de poids fort pour la case mémoire. [[File:Adresse mémoire d'une mémoire entrelacée.png|centre|vignette|upright=2|Adresse mémoire d'une mémoire entrelacée]] Précisons que le temps d'accès mémoire ne change pas beaucoup avec l'entrelacement. Par contre, on peut faire plus d'accès mémoire simultanés. On peut démarrer un accès mémoire par cycle d'horloge, mais l'accès en lui-même prend plusieurs cycles. Les mémoires à entrelacement ont donc un débit supérieur aux mémoires qui ne l'utilisent pas. L'entrelacement classique pourrait être utilisé pour accélérer les transferts entre mémoire RAM et cache, qui se font en blocs d'adresses consécutives. Mais de nos jours, il n'est pas utilisé dans cette optique. Il faut dire qu'il est redondant avec le mode rafale des mémoires DRAM. Les deux font globalement la même chose et on voit mal comment utiliser les deux en même temps. Le mode rafale étant plus simple à implémenter, et plus léger niveau utilisation du bus de commande mémoire, il est préféré à l'entrelacement. Par contre, l'entrelacement a eu son heure de gloire sur d'anciens processeurs qui n'avaient pas de mémoire cache, alors qu'ils disposaient d'un pipeline. L'exemple typique est celui des nombreux ''processeurs vectoriels'', que nous n'avons pas encore abordé à ce stade du cours. De tels processeurs avaient un pipeline, une unité de calcul fortement pipelinée, et parfois même de l'exécution dans le désordre et du renommage de registres ! ===L'entrelacement par décalage=== L'entrelacement simple est très utile pour les accès en rafle ou équivalents. Par contre, il existe de rares situations où il n'est pas optimal. Une de ces situations est celle des accès en enjambée (en ''stride''), où l'on accède en série à des adresses sont séparées par N mots mémoires. [[File:Accès par enjambées.png|centre|vignette|upright=2|Accès par enjambées.]] De tels accès surviennent quand un logiciel accède à des structures de données spécifiques, à savoir des tableaux de structures, des matrices, ou d'autres structures de données dans le genre. Un cas classique est celui du parcours d'une matrice colonne par colonne. Les matrices de nombres sont mémorisées ligne par ligne, ce qui fait que parcourir une colonne demande de passer d'une ligne à la suivante et de faire des grands sauts en mémoire RAM, mais tous espacés par la même distance. Avec l'entrelacement simple, les accès en enjambée sont moins performants que les accès à des adresses consécutives, mais cela ne fonctionne pas trop mal. Le pire cas est celui où l'on a N banques et où les données sont justement placées toutes les N adresses. Des accès consécutifs vont tous tomber dans la même banque, on ne peut plus accéder à des banques différentes en parallèle. Mais en dehors de ce cas, on voit une amélioration par rapport à la situation sans entrelacement. Pour obtenir des performances maximales pour les accès en enjambées, il faut répartir les mots mémoires dans la mémoire autrement. L'organisation idéale est la suivante. : Dans les explications qui vont suivre, la variable N représente le nombre de banques, qui sont numérotées de 0 à N-1. On commence par organiser les N premières adresses comme une mémoire entrelacée simple : l'adresse 0 correspond à la banque 0, l'adresse 1 à la banque 1, etc. Pour le bloc suivant, on décale tout d'une adresse, à savoir qu'on commence à remplir les banques à partir de la seconde, non de la première. Une fois la fin du bloc atteinte, on finit de remplir le bloc en repartant du début du bloc. Le troisième bloc d'adresse subit le même traitement, sauf qu'on commence à remplir à partir de la seconde banque. Et on poursuit l’assignation des adresses en décalant d'un cran en plus à chaque bloc. Ainsi, chaque bloc verra ses adresses décalées d'un cran en plus comparé au bloc précédent. Si jamais le décalage dépasse la fin d'un bloc, alors on reprend au début. [[File:Mémoire interleaved par décalage.png|centre|vignette|upright=1.5|Mémoire entrelacée par décalage.]] En faisant cela, on remarque que les banques situées à N adresses d'intervalle sont différentes. Dans l'exemple du dessus, nous avons ajouté un décalage de 1 à chaque nouveau bloc à remplir. Mais on aurait tout aussi bien pu prendre un décalage de 2, 3, etc. Dans tous les cas, on obtient un entrelacement par décalage. Ce décalage est appelé le pas d'entrelacement, noté P. Le calcul de l'adresse à envoyer à la banque, ainsi que la banque à sélectionner se fait en utilisant les formules suivantes : * adresse à envoyer à la banque = adresse totale / N ; * numéro de la banque = (adresse + décalage) modulo N ; * décalage = (adresse totale * P) mod N. Avec cet entrelacement par décalage, on peut prouver que la bande passante maximale est atteinte si le nombre de banques est un nombre premier. Seulement, utiliser un nombre de banques premier peut créer des trous dans la mémoire, des mots mémoires inadressables. Malgré ce défaut, la technique a été utilisée sur quelques ordinateurs, avec l'exemple notable du superordinateur ''Burroughs Scientific Processor''. Pour éviter cela, il y a plusieurs solutions. Par exemple, on peut faire en sorte que N et la taille d'une banque soient premiers entre eux : ils ne doivent pas avoir de diviseur commun. Mais en pratique, elles n'ont pas vraiment été implémentées dans une vraie machine, et sont restées à l'état de recherche, aussi je les passe sous silence. ===L'entrelacement pseudo-aléatoire=== Une dernière méthode de répartition consiste à répartir les adresses dans les banques de manière "pseudo-aléatoire". L'idée exacte est de faire passer l'adresse dans une '''fonction de hachage''', plus ou moins complexe. Pour rappel, une fonction de hachage prend une entrée de grande taille et fournit en sortie un résultat de petite taille. Idéalement, elles doivent donner des résultats aussi différents que possible pour des entrées similaires, histoire de simuler une sorte de pseudo-aléatoire. Ici, une adresse est transformée en un numéro de banque. Une fonction de hachage simple ne fait que permuter des bits de l'adresse pour obtenir son résultat. Elle ne fait qu'échanger des bits de place avant de couper l'adresse en deux morceaux : un pour la sélection de la banque, et un autre pour la sélection de l'adresse dans la banque. Cette permutation est fixe, et ne change pas suivant l'adresse. Des fonctions de hachage plus complexes font des XOR bit à bit entre certains bits de l'adresse. ==Les contrôleurs mémoires SDRAM/DDR optimisés== Après avoir vu le cas des mémoires RAM, nous allons nous concentrer sur le cas particulier des mémoires SDRAM. Les mémoires SDRAM et DDR modernes sont capables de gérer plusieurs accès simultanés à la mémoire RAM, et leurs contrôleurs mémoire en font tout autant. C'est une différence majeure avec les mémoires asynchrones FPM/EDO, qui n'acceptaient qu'un seul accès mémoire à la fois. Leur contrôleur mémoire n'acceptait qu'un seul accès mémoire à la fois, c'était un contrôleur mémoire bloquant. Les contrôleurs mémoires des SDRAM sont eux non-bloquants et peuvent encaisser une dizaine d'accès mémoire à la fois. Peu de choses sont connues sur les contrôleurs de SDRAM/DDR modernes, les fabricants ne donnant que peu de détails dessus. Les rares simulateurs qui tentent de décrire leur fonctionnement, comme DRAM SIM I et II, sont particulièrement simples et ne vont pas dans le détail. Néanmoins, le peu qu'on sait est tout de même instructif. ===Rappel sur les SDRAM : tampon de ligne et commandes=== Les mémoires SDRAM sont des mémoires à tampon de ligne. Elles font un accès mémoire en deux étapes. La première étape recopie une ligne de N * 64 bits dans un tampon interne à la SDRAM. La seconde étape sélectionne une colonne, à savoir une donnée de 64 bits, qui est soit envoyée sur le bus de données pour une lecture, soit modifiée par une écriture. [[File:Mémoire à tampon de ligne.png|centre|vignette|upright=2|Mémoire à tampon de ligne]] Les accès mémoire sont traduits par un séquenceur mémoire en une série de commandes mémoires, qui sont séparées par des délais mémoire de quelques cycles d'horloge. Les délais sont très précis, et sont à respecter à la lettre. Une lecture ou une écriture se fait en maximum trois commandes : une commande PRECHARGE qui ferme la ligne précédemment utilisée, une commande ACT qui précise l'adresse de la ligne, et une commande READ ou WRITE qui précise l'adresse de la colonne et éventuellement la donnée à écrire. : Pour être plus précis, la commande PRECHARGE précharge les lignes de bits du plan mémoire à une tension particulière, ce qui les vide de leur contenu. Mais c'est un détail sans importance pour ce qui va suivre. Les SDRAM permettent de se passer de la première étape si des accès consécutifs se font dans la même ligne. Une fois activée, la ligne reste ouverte et on peut accéder plusieurs fois de suite dedans. On a alors juste à préciser l'adresse de colonne dedans. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | bgcolor="#FFA0FF" | PRECHARGE || bgcolor="#FFA0FF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || bgcolor="#A0FFFF" | READ (2) || bgcolor="#A0FFFF" | READ (3) || || || bgcolor="#A0FFFF" | READ (4) || bgcolor="#A0FFFF" | READ (5) || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | Donnée READ 1 || ||bgcolor="#A0FFFF" | Donnée READ 2 | bgcolor="#A0FFFF" | Donnée READ 3 || || || bgcolor="#A0FFFF" | Donnée READ 4 || bgcolor="#A0FFFF" | Donnée READ 5 |} Les SDRAM sont parfois capables de démarrer une commande avant que la précédente soit terminée. Mais le respect des délais mémoire est très limitant, ce qui fait qu'on ne peut pas parler de réel pipeline, comme c'est le cas sur les processeurs. Parlons plutot de '''commandes anticipées'''. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | || bgcolor="#A0FFFF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || || || bgcolor="#FFA0FF" | READ (2) || || || || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 | bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 | |} Un dernier point est que les chips mémoires de SDRAM sont composés de plusieurs banques, chacune étant une sorte de mini-mémoire miniature. Chaque banque a son propre décodeur, son propre tampon de ligne, ses propres multiplexeurs de colonne, sa logique de rafraichissement mémoire, etc. C'est comme si une SDRAM regroupait plusieurs mémoires séparées dans un même circuit intégré. [[File:Arrangement vertical.jpg|centre|vignette|upright=2.5|Mémoire multi-banques.]] ===La mise en attente des accès mémoire=== Un contrôleur de SDRAM peut accepter plusieurs accès mémoire en même temps. Mais cela ne signifie pas que le contrôleur sera prêt à les traiter immédiatement. Pour éviter tout problème de disponibilité, le contrôleur met en attente les accès mémoire que le processeur lui envoie, pour les exécuter dès que possible. Les accès mémoire sont mis en attente dans une mémoire FIFO, histoire de les exécuter dans leur ordre d'arrivée, notamment pour renvoyer les lectures dans l'ordre demandé. Il y a aussi une mémoire FIFO pour les données à écrire et une FIFO pour les données lues. Cette dernière sert au cas où le cache ne soit pas disponible quand on lui envoie la donnée lue. [[File:Module d'interface avec la mémoire.png|centre|vignette|upright=2.5|Contrôleur mémoire avec mise en attente des requêtes processeur et autres optimisations.]] Des optimisations sont possibles dès la mémoire FIFO. Par exemple, on peut regrouper plusieurs accès à des données consécutives en un seul accès en rafale. Le contrôleur mémoire analyse les requêtes mises en attente et détecte si certaines se font à des adresses consécutives. Si c'est le cas, il fusionne ces requêtes en une lecture/écriture en rafale. Une telle optimisation est appelée la '''combinaison de lecture''' pour les lectures, et la '''combinaison d'écriture''' pour les écritures. Une autre optimisation possible est le réacheminement lecture sur écriture (''Store to load forwarding''). Il s'agit d'un équivalent de l’optimisation utilisée dans le cadre de la désambiguïsation mémoire. Quand une lecture demande à accéder à une donnée écrite récemment, il se peut que l'écriture ait été mise en attente dans le contrôleur mémoire. Dans ce cas, le contrôleur mémoire peut renvoyer la donnée directement depuis ses mémoires FIFOs, sans faire d'accès mémoire en lecture. Les deux optimisations précédentes ne paraissent pas payer de mine, mais elles deviennent plus compréhensible quand on sait que certains contrôleurs mémoire peuvent mettre en attente beaucoup de données. Par exemple, l'''Intel E8870 - ''Scalable Memory Controller'' peut mettre en attente 64 lignes de cache dans la mémoire FIFO, soit 8 kibioctets de RAM ! Deux pages mémoire si la mémoire virtuelle utilise des pages de 4 kibioctets ! ===Les optimisations liées aux commandes anticipées=== Les commandes anticipées permettent de faire un minimum de parallélisme mémoire. Même si elle parait très limitée, cette possibilité est décuplée par la présence de plusieurs banques dans la mémoire SDRAM. Il est en effet possible de faire plusieurs accès mémoire en même temps, dans des banques différentes. Il est possible de lancer un accès mémoire dans deux banques en même temps, par exemple. La seule contrainte est que la SDRAM est limitée à 4 banques actives en même temps. {|class="wikitable" |+ Pipelining basique sur les SDRAM |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 |- ! Banque Numéro 1 | || || || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || || |- ! Banque Numéro 2 | || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || |- ! Banque Numéro 3 | || || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || |} L'implémentation n'a rien de vraiment compliqué. Il suffit d'utiliser une mémoire FIFO par banque. Ainsi, les accès dans une même banque se feront en série, dans l'ordre d'arrivée, mais des accès à des banques différentes se feront dans le désordre. Cela ne pose pas de problème car les accès se font à des adresses différentes, ce qui fait qu'il n'y a pas de conflits majeurs. [[File:Gestion parallèle des banques.png|centre|vignette|upright=2.5|Gestion parallèle des banques.]] ===Le ré-ordonnancement des commandes mémoires=== Un contrôleur mémoire moderne est capable de changer l'ordre des requêtes mémoires, pour gagner en performances. En clair : il peut faire l'équivalent mémoire de l'exécution dans le désordre des processeurs haute performance. Une différence majeure est que le contrôleur mémoire ne remet pas les accès mémoire dans l'ordre avant de les envoyer au processeur. C'est le processeur qui remet en ordre les accès mémoire, dans sa ''load queue''. Pour cela, les requêtes mémoire sont "numérotées", à savoir qu'on leur attribue un identifiant binaire. Le contrôleur mémoire exécute les lectures/écritures dans le désordre, mais garde la trace de leur identifiant. Il sait dans quel ordre il exécute les accès mémoire et connait leur latence. Ce qui fait qu'il sait que la donnée lue à tel instant est associée à la requête mémoire numéro X. Pour les lectures, il renvoie la donnée lue avec l'identifiant. Le processeur sait alors à quelle lecture la donnée lue correspond et il se débrouille en interne pour gérer la situation. En clair : le processeur voit les accès mémoire dans le désordre, c'est lui qui les remet en ordre. Maintenant que ces précisions sont faites, posons cette question : quel est l'intérêt de faire les accès mémoire dans le désordre ? Accéder à des banques en parallèle est une réponse possible, mais le ré-ordonnancement est surtout utile si tous les accès mémoire atterrissent dans la même banque et dans la même ligne. Elle marche si plusieurs accès à une même ligne ne sont pas consécutifs, et qu'il y a des accès entre les deux. Après ré-ordonnancement, ces accès mémoire sont exécutés l'un à la suite de l'autre. Pour rendre le tout plus concret, voici un exemple. Imaginez que l'on sait les 3 accès mémoire suivants : * Une lecture ligne A ; * Une écriture ligne B ; * Une lecture ligne A. Sans réordonnancent, on doit émettre deux commandes PRECHARGE : une quand on passe de la ligne A à la ligne B, et une autre pour le passage inverse. pour éviter cela, le contrôleur mémoire peut retarder l'écriture, ou au contraire avancer la seconde lecture. le résultat est alors le suivant : * Lecture ligne A ; * Lecture ligne A ; * Une écriture ligne B. On a donc deux accès consécutifs à la même ligne, suivi par une écriture dans une autre ligne. La technique marche parce que l'on a un mix adéquat de lectures et d'écritures. Mais il existe des possibilités de réorganisation autres qui ne sont pas valides. Il faut par exemple prendre garde à éviter d'intervertir lectures et écritures à une même adresse, sous peine de problèmes. Encore une fois, il faut faire attention aux dépendances de données, encore une fois ! Ici des dépendances mémoires, mais les règles sont les mêmes. Le contrôleur mémoire teste les dépendances mémoires avant d'envoyer des commandes à la mémoire DDR/SDRAM. Il a existé des contrôleurs mémoires du temps des vieilles DDR qui implémentaient un ordonnancement très simples. Par exemple, sur les coprocesseurs IO 81341 et 81342, qui étaient en réalité des ''chipset'' intégrant un controleur SDRAM, les possibilités de réorganisation étaient assez limitées. Le ''chipset'' avait 5 ports d'entrée : un pour les processeurs, un pour le pont sud ('"'southbridge'') et trois pour des canaux DMA. Pour le premier, il n'y avait pas de réordonnancement. Pour les autres, les lectures se font dans l'ordre, les écritures se font dans l'ordre, mais les écritures peuvent passer avant une lecture non-dépendante. Sans doute que le processeur avait des files séparées pour les lectures et écritures dans une même banque. [[File:Double file d'attente pour les lectures et écritures.png|centre|vignette|upright=2|Double file d'attente pour les lectures et écritures]] ==L'entrelacement sur les mémoires SDRAM== L''''entrelacement''' fonctionne sur les mémoires SDRAM, mais il doit être fortement modifié. Concrètement, tout ce qui a étré dit plus est inapplicable pour les SDRAM, du fait de la présence du tampon de ligne. Des adresses consécutives atterrissent déjà dans la même ligne, ce qui fait qu'on n'a pas besoin d'entrelacement au niveau d'une ligne. Et ne parlons pas de ce la présence de rangées et de canaux mémoire ! Cependant, il peut y avoir un entrelacement lié à la présence des banques SDRAM. L'entrelacement est géré juste avant le séquenceur mémoire. Le '''circuit d'entrelacement''' intervertit certains bits de l'adresse lors des accès mémoires. On trouve aussi des mémoires FIFOs pour mettre en attente les requêtes mémoire provenant du processeur. Elles permettent de recevoir plusieurs accès mémoire consécutifs. Si vous vous demandez comment plusieurs accès mémoire consécutifs peuvent avoir lieu, sachez que c'est une bonne question. Pour le moment, nous allons dire que ces accès proviennent du processeur ou de la mémoire cache, sans expliquer comment. Le contrôleur mémoire reçoit une série d'accès mémoire assez rapprochés, qu'il doit exécuter au mieux. [[File:Controleur mémoire d'une SDRAM avec entrelacement.png|centre|vignette|upright=2.5|Contrôleur mémoire d'une SDRAM avec entrelacement.]] Pour gérer l'entrelacement, le contrôleur de SDRAM prend en entrée l'adresse envoyée par le processeur, et la découpe en plusieurs champs : un pour sélectionner la banque adéquate, un autre pour la ligne, un autre pour la colonne, un autre pour la rangée. Une solution simple désactive l'entrelacement. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de banque || Adresse de ligne || Adresse de colonne |} Si cette solution fonctionne bien si les accès mémoire sont assez aléatoires, en pratique, mieux vaut utiliser l'entrelacement. ===L'entrelacement de banques/lignes=== Une première méthode d'entrelacement est l''''entrelacement de banques'''. Elle répartit deux lignes consécutives dans deux banques différentes. Pour comprendre l'idée, prenons un exemple. Imaginons une mémoire avec deux banques et 4 lignes. Imaginons qu'on parcoure/balaye la mémoire RAM en partant des adresses basses. Sans entrelacement de ligne, les accès se feront comme suit, de gauche à droite : {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Banque numéro 1 !! colspan="4" | Banque numéro 2 |- | Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 || Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 |} Avec l'entrelacement de banques, les accès se feront comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 |- | Ligne 1 || Ligne 1 || Ligne 2 || Ligne 2 || Ligne 3 || Ligne 3 || Ligne 4 || Ligne 4 |} Avec N banques, la première ligne ira dans la première banque, la seconde ligne dans la seconde, et ainsi de suite jusqu'à la énième banque qui va dans la banque numéro N. Et la ligne N+1 repart dans la première banque. L'avantage est que passer d'une ligne à la suivante est assez rapide. Au lieu de devoir fermer la première ligne et ouvrir la suivante on ouvrira directement la ligne suivante dans une banque différente, ce qui sera plus rapide. La commande PRECHARGE pourra être envoyée à la seconde banque en avance. Pour cela, l'adresse est découpée comme suit. {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne (précise à l'octet) |} ===L'entrelacement de rangées=== Il est aussi possible de faire la même chose, mais avec les rangées. Pour simplifier fortement, une rangée est simplement un chip mémoire. En réalité, les rangées ne sont pas des chip mémoire, mais un ensemble de chips mémoire regroupés ensemble histoire d'atteindre les 64 bits du bus de données. Par exemple, une rangée peut combiner 8 chips mémoire avec un bus de données de 8 bits chacun pour obtenir les 64 bits du bus de données d'une SDRAM. Mais nous passons ce détail sous silence dans les explications qui vont suivre, par souci de simplification. Pour faire comprendre l'entrelacement de rangée, le mieux est d'utiliser un exemple, le même que précédemment. Sans entrelacement de rangée, on change de chip mémoire une fois qu'on a balayé toutes ses adresses. {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Rangée numéro 1 !! colspan="4" | Rangée numéro 2 |- | Banque 1 || Banque 2 || Banque 3 || Banque 4 || Banque 1 || Banque 2 || Banque 3 || Banque 4 |} Avec l'entrelacement de ligne, on change de rangée dès qu'on change de banque/ligne. {|class="wikitable" |+ Adresse mémoire |- ! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 |- | Banque 1 || Banque 1 || Banque 2 || Banque 2 || Banque 3 || Banque 3 || Banque 4 || Banque 4 |} En clair, quand on a épuisé toutes les banques dans une même rangée, on passe à la rangée suivante au lieu de rester dans la même rangée. Notons que dans l'exemple précédent, on a combiné l'entrelacement de rangée et de banque, mais on aurait pu utiliser l'entrelacement de rangée seul. Mais ce n'est pas le cas le plus courant en pratique. Toujours est-il qu'en combinant entrelacement de rangée et de ligne, le découpage de l'adresse est le suivant : {|class="wikitable" |+ Adresse mémoire |- | Adresse de ligne || Numéro de rangée || Adresse de banque || Adresse de colonne (précise à l'octet) |} D'autres méthodes d'entrelacement sont possibles, mais elles sont plus complexes. Chaque contrôleur mémoire fait un peu à sa sauce. Ils permettent en général de choisir entre plusieurs entrelacements. Au minimum, ils permettent de désactiver l'entrelacement, d'activer un entrelacement de ligne, et d'autres entrelacements plus complexes, dont certains sont propriétaires et tenus secrets par le fabricant. ===L'entrelacement avec le ''dual channel''=== [[File:Dual channel slots.jpg|vignette|Slots mémoires avec ''dual channel''.]] L'usage du ''dual channel'' complique encore l'entrelacement. Là encore, il y a deux grandes solutions : avec et sans entrelacement des canaux mémoire. Rappelons le principe : deux barrettes de RAM sont accédées en parallèle. Et pour cela, il faut utiliser l'entrelacement. Typiquement, chaque barrette mémoire fournit 64 bits, ce qui fait que l'on peut accéder à 128 bits d'un seul coup, par exemple avec un accès en rafale. Sans ''dual channel'', la première barrette correspond à la moitié haute de la RAM, la seconde barrette correspond à la moitié basse. Avec ''dual channel'', une forme spécifique d'entrelacement est activée. Concrétement, deux blocs de 64 bits sont placés dans des canaux mémoire séparés. Idem avec du triple ou quadruple canal, mais c'est alors trois ou quatre blocs de 64 bits qui sont dispersés dans des canaux consécutifs. Le découpage de l'adresse est alors le suivant : {|class="wikitable" |+ Adresse mémoire |- ! colspan="6" | Sans entrelacement |- | Numéro de canal/barrette || Reste de l'adresse || Position de l'octet dans un bloc de 64 bits |- | colspan="6" | |- ! colspan="6" | Avec entrelacement |- | Reste de l'adresse || Numéro de canal/barrette || Position de l'octet dans un bloc de 64 bits |} [[File:Décodage d'adresse avec dual channel.png|centre|vignette|upright=2|Décodage d'adresse avec dual channel.]] Les contrôleurs de SDRAM précédents sont assez basiques et ne représentent pas les contrôleurs les plus évolués. Ils étaient utilisés sur les anciens PC, à une époque où ils étaient encore sur la carte mère du processeur. Mais de nos jours, le contrôleur mémoire est intégré au processeur. Et il incorpore de nombreuses optimisations afin de gagner en performances. Malheureusement, ces optimisations sont elles-mêmes dépendantes des optimisations intégrées dans le processeur, ce qui fait qu'on ne peut pas en parler ici. Elles demandent que le contrôleur mémoire reçoive plusieurs accès mémoire simultanés, ce qui n'a aucun sens à ce stade du cours. Aussi, nous allons les passer sous silence pour le moment. Nous les verrons dans le chapitre sur le parallélisme mémoire. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=La désambiguïsation mémoire | prevText=La désambiguïsation mémoire | next=Les processeurs superscalaires | nextText=Les processeurs superscalaires }} </noinclude> qkqxrocx4lomdzwm92jiyoe4ds75gpt 764724 764723 2026-04-23T22:13:09Z Mewtow 31375 /* Le ré-ordonnancement des commandes mémoires */ 764724 wikitext text/x-wiki Dans ce chapitre, nous allons voir les techniques qui permettent de gérer plusieurs accès mémoire simultanés directement au niveau du cache ou de la mémoire RAM. Par plusieurs accès mémoire simultanés, vous pensez sans doute à l'usage de cache multiports, voire de mémoires RAM multiport. C'est en effet une solution, mais ce n'est clairement pas la seule. Par exemple, il est possible de pipeliner l'accès au cache, voire à la mémoire RAM. Il existe de nombreuses techniques de '''parallélisme mémoire''', et nous allons les voir dans ce chapitre. Pour donner un exemple, il est possible de pipeliner les accès mémoire. Il est possible de pipeliner l'accès au cache et/ou l'accès à la mémoire, et nous verrons comment faire dans ce chapitre. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, qu'ils aient ou non un pipeline dynamique. Par contre, pipeliner la mémoire est une technique ancienne, aujourd'hui peu utilisée. Elle était utilisée sur des très vieux ordinateurs, qui avaient un pipeline mais pas de mémoire cache. Mais de nos jours, les mémoires SDRAM et DDR ne sont pas adaptées pour ça, il est très difficile de les pipeliner correctement. Le parallélisme mémoire est utile aussi bien sur des processeurs à émission dans l'ordre que dans le désordre. Par exemple, un processeur ''in-order'' avec des lectures non-bloquantes peut e, profiter. Il peut techniquement lancer une seconde lecture, après avoir rencontré une première lecture non-bloquante. En général, le nombre de lectures consécutives est cependant limité à moins d'une dizaine. À l'opposé, les processeurs sans lectures non-bloquantes profitent pas du parallélisme mémoire. À la rigueur, ils peuvent en profiter s'ils intègrent des techniques de préchargement. Pendant que le processeur exécute une lecture/écriture, il peut précharger une autre donnée en parallèle. Inutile de dire que sans pipeline processeur, le parallélisme mémoire ne sert pas à grand-chose. ==Le ''line fill buffer'' et les techniques associées== Lors d'un défaut de cache, le processeur doit attendre que toute la ligne de cache soit chargée avant d'être utilisable. Or, la taille d'une ligne de cache est supérieure à la largeur du bus mémoire, ce qui fait qu'une ligne de cache est chargée en plusieurs fois, morceaux par morceaux, mot mémoire par mot mémoire. Le chargement peut se faire directement dans le cache, mais ce n'est pas une solution très pratique. À la place, beaucoup de processeurs ajoutent une mémoire tampon entre la RAM et le cache, appelée le '''tampon de remplissage de ligne''' (''line-fill buffer'') dans les processeurs modernes. Lors d'un défaut de cache, le processeur charge la donnée de la RAM dans le tampon de remplissage de ligne, mot mémoire par mot mémoire. Une fois plein, le tampon de remplissage de ligne est recopié dans la ligne de cache. Le tampon de remplissage de ligne contient une ligne de cache, si ce n'est que certains bits de contrôle ne sont pas présents. Le tampon de remplissage de ligne contient un bit de validité pour chaque mot mémoire de la ligne de cache, qui indiquent si le mot mémoire a été chargé. Par exemple, prenons un processeur 64 bits, qui gère donc des mots mémoire de 8 octets, avec des lignes de cache 256 octets/32 mots mémoire. Le tampon de remplissage de ligne contiendra 32 bits de validité. Si le processeur a chargé les 6 premiers mots mémoire, les 6 premiers bits de validité seront mis à 1, les autres seront encore à zéro. Il faut noter que la disponibilité de la ligne complète se détermine assez facilement en faisant un ET logique entre tous les bits de validité. Le processeur sait ainsi quand la ligne de cache est disponible entièrement et donc quand la transférer dans le cache. La même chose existe avec une hiérarchie de cache, sauf que l'on trouve une mémoire tampon entre chaque niveau de cache. Il y a un tampon de remplissage de ligne entre le cache L1 et le cache L2, entre le cache L2 et le L3, etc. Si le cache ne gère qu'un seul défaut de cache à la fois, le ''fill line buffer'' est une mémoire très simple, qui ne mémorise qu'une seule ligne de cache. Et cela vaut aussi bien pour un cache bloquant que non-bloquant. Mais sur les caches capables de gérer plusieurs défauts simultanés, le tampon de remplissage de ligne est une mémoire de type FIFO ou LIFO, capable de mémoriser plusieurs lignes de cache. Notons que le tampon de remplissage de ligne est très utile pour implémenter certaines techniques, par exemple le contournement du cache. Nous avions vu dans le chapitre sur le cache que certains accès mémoire doivent contourner le cache, pour des raisons de cohérence des caches. C'est notamment nécessaire pour accéder aux périphériques, mais c'est aussi utile pour des raisons de performances dans des cas très spécifiques. Les accès qui contournent le cache se font directement dans le tampon de remplissage de ligne : le processeur écrit ou lit les données depuis ce tampon de remplissage de ligne, sans accéder au cache. ===L'''early restart'' et le ''critical word load''=== La présence du ''line buffer'' permet une optimisation assez intéressante, qui permet de réduire la latence des défauts de cache. L'optimisation consiste à lire un mot mémoire dans le tampon de remplissage de ligne, même si la ligne de cache complète n'a pas encore été chargée. Il existe deux manières de faire cela, qui portent les noms d'''early restart'' et de ''critical word load''. La première est la version la plus simple, la seconde est plus complexe mais plus performante. Avec la technique d''''''early restart''''', la ligne de cache est chargée normalement, en partant de son premier mot mémoire. Dès que le mot mémoire lu/écrit par le processeur est copié dans le ''line fill buffer'', il est envoyé au processeur immédiatement. Illustrons le tout par un exemple, où une ligne de cache fait 16 mots mémoire. Le processeur effectue une lecture, qui lit le 5ème mot mémoire. Sans ''early restart'', le processeur doit charger les 16 mots mémoire avant de faire la lecture dans le cache. Avec ''early restart'', le processeur reçoit la donnée dès que le 5ème mot mémoire est disponible. Le processeur doit attendre que les 4 premiers mots mémoire soient chargés, puis le 5ème mot mémoire arrive et est envoyé directement au processeur, il est lu directement depuis le ''line fill buffer''. Les 11 mots mémoire suivants sont ensuite chargés dans le cache pendant que le processeur fait des calculs dans son coin. Le ''critical word load'' est une optimisation de la technique précédente où le chargement de la ligne de cache commence directement à la donnée demandée par le processeur. Pour reprendre l'exemple précédent, où le processeur demande le 5ème mot mémoire sur 16, le ''critical word load'' charge le 5ème mot mémoire en premier et l'envoie au processeur, ce qui fait qu'il est chargé très rapidement. Pas besoin d'attendre que le processeur charge les 4 mots mémoire précédents comme avec l'''early restart''. Dans le détail, le processeur charge le 5ème mot mémoire en premier, puis charge les 11 suivants, et termine par les 4 mots mémoire du début. En clair, le ''critical word load'' commence par charger le mot mémoire lu, puis les blocs suivants, avant de revenir au début du bloc pour charger les blocs restants. Ainsi, la donnée demandée par le processeur sera la première disponible. Pour cela, l'organisation du tampon de remplissage de ligne est modifiée de manière à rendre cela possible. Il a une taille égale à une ligne de cache complète, qui contient elle-même plusieurs mots mémoire. Dans le ''line-fill buffer'', chaque mot mémoire est stocké avec un tag, qui indique l'adresse du mot mémoire stocké dans le ''line-fill buffer''. Le ''line-fill buffer'' est donc un cache un peu particulier, qui fonctionne comme un cache du point de vue du processeur, comme une mémoire FIFO pour les transferts avec le cache. Ainsi, un processeur qui veut lire dans le cache après un défaut peut accéder à la donnée directement depuis le tampon de remplissage de ligne, alors que la ligne de cache n'a pas encore été totalement recopiée en mémoire. ==Les caches pipelinés== Il est possible de pipeliner l'accès au cache, ce qui demande juste de rajouter des registres au bon endroits et quelques circuits de contrôle. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, même ceux avec un pipeline dynamique. Et l'implémentation peut se faire de plusieurs manières différentes. Il faut dire que découper une mémoire cache en plusieurs étapes peut se faire de plusieurs manière. Commençons par la plus simple. Beaucoup de processeurs des années 2000 avaient une fréquence peu élevée comparée aux standards d'aujourd'hui, ce qui fait que leur cache avait bien un temps d'accès d'un cycle d'horloge. Mais l'accès au cache se faisait en deux cycles d'horloge : un cycle pour calculer une adresse et un cycle pour l'accès mémoire proprement dit. Et ces deux étapes étaient pipelinées, à savoir que deux micro-opérations mémoire peuvent s'exécuter en même temps : une dans l'étage de calcul d'adresse, une autre dans le cache. L'unité mémoire était donc pipelinée, alors que l'accès au cache ne l'était pas. Les processeurs qui implémentaient cette technique regroupent les micro-architectures K5 et K6 d'AMD, les processeurs Intel de micro-architecture P6 (Pentium 2 et 3) et quelques autres. Il est aussi possible de pipeliner l'accès au cache lui-même. Avec un cache pipeliné, l'accès au cache ne se fait pas en un seul cycle, mais en plusieurs. Cependant, on peut lancer un nouvel accès au cache à chaque cycle d'horloge, comme un processeur pipeliné lance une nouvelle instruction à chaque cycle. L'implémentation est assez simple : il suffit d'ajouter des registres dans le cache. Pour cela, on profite que le cache est composé de plusieurs composants séparés, qui échangent des données dans un ordre bien précis, d'un composant à un autre. De plus, le trajet des informations dans un cache est linéaire, ce qui les rend parfois pour l'usage d'un pipeline. ===Le ''pipelining'' des caches ''direct-mapped''=== Voici ce qui se passe avec un cache directement adressé. Pour rappel ce genre de cache est conçu en combinant une mémoire RAM, généralement une SRAM, avec quelques circuits de comparaison et un MUX. L'accès au cache se fait globalement en deux étapes : la première lit la donnée et le ''tag'' dans la SRAM, et on utilise les deux dans une seconde étape. Nous avions vu il y a quelques chapitre comment pipeliner des mémoires, dans le chapitre sur les mémoires évoluées. Et bien ces méthodes peuvent s'utiliser pour la mémoire RAM interne au cache !L'idée est d'insérer un registre entre la sortie de la RAM et la suite du cache, pour en faire un cache pipéliné, à deux étages. Le premier étage lit dans la SRAM, le second fait le reste. L'implémentation sur les caches associatifs à plusieurs voies est globalement la même à quelques détails près. [[File:Cache directement adressé pipeliné - deux étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - deux étages]] Avec le circuit précédent, il est possible d'aller plus loin, cette fois en pipelinant l'accès à la mémoire RAM interne au cache. Pour comprendre comment, rappelons qu'une mémoire SRAM est composée d'un plan mémoire et d'un décodeur. L'accès à la mémoire demande d'abord que le décodeur fasse son travail pour sélectionner la case mémoire adéquate, puis ensuite la lecture ou écriture a lieu dans cette case mémoire. L'accès se fait donc en deux étapes successives séparées, on a juste à mettre un registre entre les deux. Il suffit donc de mettre un gros registre entre le décodeur et le plan mémoire. [[File:Cache directement adressé pipeliné - trois étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - trois étages]] Et on peut aller encore plus loin en découpant le décodeur en deux circuits séparés. En effet, rappelez-vous le chapitre sur les circuits de sélection : nous avions vu qu'il est possible de créer des décodeurs en assemblant des décodeurs plus petits, contrôlés par un circuit de prédécodage. Et bien on peut encore une ajouter un registre entre ce circuit de prédécodage et les petits décodeurs. : Théoriquement, toute l'adresse est fournie d'un seul coup au cache, la quasi-totalité des processeurs présentent l'adresse complète à un cache pipeliné. Mais le Pentium 4 fait autrement. Il faut noter que les premiers étages manipulent l'indice dans la SRAM, qui est dans les bits de poids faible, alors que les étages ultérieurs manipulent le ''tag'' qui est dans les bits de poids fort. Les concepteurs du Pentium 4 ont alors décidé de présenter les bits de poids faible lors du premier cycle d'accès au cache, puis ceux de poids fort au second cycle. Pour cela, l'ALU fonctionnait à une fréquence double de celle du processeur, tout comme le cache L1. Il n'y avait pas de pipeline proprement dit, mais cela réduisait grandement la latence d'accès au cache. ===Le ''pipelining'' des caches splittés/sériels=== Il est aussi possible de pipeliner un cache dits splittés, aussi appelés à accès sériel. Pour rappel, les caches à accès sériel vérifient si il y a succès ou défaut de cache, avant d'accéder aux lignes de cache en cas de succès. Ils font donc différemment des autres caches, qui accèdent à une ligne de cache, avant de déterminer s'il y a succès ou défaut en lisant le tag de la ligne de cache. Les caches sériels disposent de deux SRAM : une pour les tags des lignes de cache et une pour les données. Ils accèdent à la SRAM pour les tags, avant d’accéder à la SRAM des données en cas de succès de cache. Vu que l'accès se fait en deux étapes, une vérification des tags suivie de la lecture/écriture des données, il est facile à pipeliner. Pipeliner le cache permet de régler le problème des accès au cache L1, et elle est tout le temps utilisé sur les processeurs modernes. Mais que faire en cas de défaut de cache ? ==Les caches non bloquants== Un '''cache bloquant''' est un cache auquel le processeur ne peut pas accéder pendant un défaut de cache. Il faut attendre que la lecture ou écriture en RAM soit terminée avant de pouvoir utiliser de nouveau le cache. Un '''cache non bloquant''' n'a pas ce problème : on peut l'utiliser pendant un défaut de cache. Les caches non bloquants permettent de démarrer une nouvelle lecture ou écriture alors qu'une autre est en cours, ce qui permet d'exécuter plusieurs lectures ou écritures en même temps. ===Les ''Miss Handling Status Registers''=== Lors d'un défaut de cache, la mémoire RAM est consultée pendant le défaut de cache, mais le cache est inutilisé. Un défaut de cache n'utilise pas le cache, ce n'est qu'un accès en mémoire RAM, sur le bus mémoire (ou un accès aux niveaux de cache inférieurs, peu importe). Le cache en lui-même est laissé libre, rien n’empêche d'y accéder, il est en réalité intrinsèquement non-bloquant. Les caches, bloquants comme non-bloquants, sont en réalité composés d'une mémoire cache proprement dite, entourée de circuits qui servent d'interface entre le processeur et le cache lui-même. Et parmi les circuits tout autour, certains gèrent l'accès au cache lors d'un défaut de cache. Ils sont regroupés sous le terme de '''''Miss Handling Architecture''''' (MHA). La différence entre un cache bloquant et un cache non-bloquant est en réalité liée à l'implémentation de la MHA. Les caches bloquants coupent volontairement l'accès au cache lors d'un défaut, car les défauts sont plus simples à gérer ainsi. Le défaut de cache rapatrie une donnée depuis la RAM, qui sera écrite dans le cache. Et il ne faut pas qu'une tentative d'accès à cette donnée ait lieu avant qu'elle ne soit chargée. Mais ce blocage est général et touche tout le cache, alors que seule une ligne de cache est concernée par le défaut de cache. L'idée derrière un cache non-bloquant est que seule la ligne de cache est bloquée, mais les autres sont accessibles. L'idée est alors de mémoriser les lignes de cache concernées par le défaut de cache, afin d'en bloquer l'accès. À chaque accès, on vérifie si la ligne de cache est déjà réservée par un défaut de cache. La lecture/écriture est alors bloquée si c'est le cas, mais elle accepte les accès sinon. Pour cela, la ''Miss Handling Architecture'' contient des registres qui mémorisent des informations sur les défauts de cache en cours. Ils portent le nom de '''''miss status handling registers''''', que j’appellerais dorénavant MSHR, qui sont aussi appelés des '''''miss buffer'''''. Le contenu des MSHR varie beaucoup suivant le processeur, mais ces derniers stockent au minimum les informations suivantes : * Le numéro de la ligne de cache dans laquelle les données sont chargées. * Un bit de validité qui indique si le MSHR est vide ou pas, qui est mis à 0 quand le défaut de cache est résolu. * Un ou plusieurs champs de lecture/écriture, qui contiennent des informations sur la lecture/écriture. ** Pour une lecture, elle contient des informations sur la destination de la donnée, à savoir qui prévenir quand le défaut de cache est terminé. C'est parfois un nom/numéro de registre (celui dans lequel charger la donnée), mais c'est souvent le numéro de l'entrée dans le ''load/store queue''. ** Pour les écritures, elle contient la donnée à écrire, ou éventuellement un numéro de ''load/store queue'' où se trouve la donnée à écrire. Il faut noter que le bus mémoire ne peut gérer qu'un seul défaut de cache à la fois. Aussi, il est intéressant de regarder ce qui se passe lorsqu'un second défaut de cache survient, pendant qu'un premier est en cours. Dans ce cas, il y a deux réponses qui correspondent à deux types de caches non-bloquants, qui portent les noms barbares de caches de type succès après défaut et défaut après défaut. Sur le premier type, il ne peut pas y avoir plusieurs défauts de cache simultanés. Dès qu'un second défaut de cache survient, le cache stoppe son activité et on ne peut plus démarrer de nouvelle lecture/écriture, tant que le premier défaut de cache n'est pas résolu. Le second type est plus souple et autorise la survenue de plusieurs défauts de cache simultanés. Du moins, jusqu'à une certaine limite, car le cache ne peut supporter qu'un nombre limité d'accès mémoires simultanés (pipelinés). ===Les accès simultanés à une même ligne de cache=== Il arrive que le processeur fasse plusieurs accès mémoire simultanés à la même ligne de cache. Si la ligne de cache en question n'a pas encore été chargée dans le cache, alors on a plusieurs défauts de cache consécutifs pour la même ligne de cache, et le cache non-bloquant doit gérer la situation. Pour la suite, il va falloir faire une petite distinction entre les défauts primaires et secondaires. Imaginons qu'un défaut de cache ait lieu et demande à charger une donnée dans la ligne de cache numéro N. Il s'agit du premier défaut impliquant cette ligne de cache précise, ce qui lui vaut le nom de '''défaut de cache primaire'''. Mais par la suite, d'autres accès mémoire à la même ligne de cache ont lieu, alors que la ligne de cache n'est pas encore disponible. Dans ce cas, il s'agit de '''défauts de cache secondaires'''. Pour l'unité d'accès mémoire, les défauts de cache primaire et secondaire sont différents (ils prennent tous une entrée dans la ''load/store queue''). Mais pour le cache, ils ne correspondent qu'à un seul accès au cache : celui qui demande de charger la ligne de cache demandée. Les défauts de cache primaire et secondaire à la même ligne de cache se voient attribuer un MSHR unique. La gestion des défauts de cache secondaires dépend du cache non-bloquant. La solution la plus simple ne permet pas les défauts de cache secondaires. Le cache ne permet pas deux défauts de cache simultanés pour la même ligne de cache. Les autres solutions le permettent, en fusionnant des accès simultanés à la même ligne de cache en un seul au niveau des MSHR. Dans tous les cas, détecter les défauts de cache secondaires sont un problème qu'il faut détecter. La ''Miss Handling Architecture'' doit détecter les défauts de cache secondaire. Pour cela, elle procède comme suit. Lors de chaque défaut de cache, la MHA récupère le numéro de la ligne de cache associé. Il vérifie alors chaque MSHR pour vérifier s'il contient le numéro en question. S'il n'y a aucune correspondance dans les MSHR, alors c'est signe que le défaut de cache est un défaut primaire. Mais s'il y en a une, alors c'est un défaut secondaire. Évidemment, cela signifie que lors d'un défaut de cache, le numéro de ligne de cache est envoyé à tous les MSHR, pour comparaison. Les MSHR sont donc regroupés dans une mémoire associative, une sorte de mini-cache, faciliter l'implémentation. Lors d'un défaut de cache primaire, l'accès à un cache non-bloquant se fait comme suit : le processeur envoie une adresse au cache, accède à celui-ci, et détecte la survenue d'un défaut de cache. Il en profite alors pour attribuer une ligne de cache dans laquelle sera chargée ce défaut. L'attribution est très simple dans le cas des caches ''direct mapped'', ou associatifs par voie, pour lesquels l'attribution se fait assez simplement. Il mémorise alors cette information dans les MSHR, après avoir vérifié que le défaut de cache n'était pas un défaut secondaire. Lors des accès ultérieurs à une adresse proche, censée être dans la même ligne de cache, le processeur va encore une fois rencontrer un défaut de cache. Il va alors déterminer le numéro de la ligne de cache associée à l'adresse, et comparer ce numéro avec les MSHRs. Si un MSHR contient ce numéro, c'est signe que le défaut de cache est un défaut secondaire. La MHA réagit alors différemment selon le processeur considéré. Une première solution n'autorise pas les défauts de cache secondaires. Si l'un d'entre eux survient, le processeur est gelé par un ''pipeline stall'', une bulle de pipeline. Une autre solution fusionne les défauts de cache secondaires avec le défaut de cache primaire : tout cela ne correspond qu'à un seul défaut de cache pour lui. ===Les MSHR simples=== Un cache non-bloquant à '''MSHR simple''' contient juste plusieurs MSHR qui mémorisent juste un numéro de ligne de cache, un bit de validité, le champ de lecture/écriture, et l'adresse exacte de lecture/écriture. Avec cette organisation, il est possible d'avoir plusieurs défauts de cache séparés, mais à la condition que chaque défaut accède à une ligne de cache différente. Deux accès simultanés à une même ligne de cache ne sont pas possibles, les défauts de cache secondaires ne sont pas autorisés. Ainsi, chaque défaut de cache se voit attribuer son propre MSHR, chacun contient un numéro de ligne de cache différent. Pour comprendre pourquoi c'est impossible de gérer les défauts secondaires, il faut regarder le champ de lecture/écriture. Si on veut effectuer plusieurs écritures consécutives à la même adresse, le MSHR n'aura pas de quoi mémoriser les deux données à écrire. Il pourra mémoriser la première donnée à écrire, pas la seconde. Même chose lors d'une lecture : le champ lecture/écriture peut mémorisr la destination de la première lecture, pas de la seconde. L'avantage est que la MHA n'a besoin que des MSHR et de quelques circuits annexes. Les autres solutions rajoutent des circuits annexes pour gérer les défauts de cache secondaires, qui utilisent beaucoup de circuits. Le cout en circuit est donc élevé, mais le gain en performance est là. Passons maintenant aux caches non-bloquants qui autorisent les défauts de cache secondaire. La solution la plus simple consiste à utiliser ===Les MSHR adressés implicitement=== Avec les '''MSHR adressés implicitement''', il est possible de fusionner plusieurs accès mémoire à une même ligne de cache, mais sous une condition très importante : ces accès lisent/écrivent des mots mémoire différents. Par exemple, imaginons qu'une ligne de cache contienne 8 mots mémoire de 64 bits. Si un premier accès mémoire lit le mot mémoire numéro 7 (dernier mot mémoire de la ligne), et le second accès le mot mémoire numéro 3, alors la fusion est possible. Mais si deux accès mémoire veulent lire/écrire le mot mémoire numéro 7, alors la fusion n'est pas possible et le processeur se bloque, un ''pipeline stall'' survient. Un MSHR adressé implicitement est un MSHR simple, qui contient naturellement un numéro de ligne de cache (tag) et un bit de validité, sauf que le champ de lecture/écriture est dupliqué. Les différents champs lecture/écriture d'un MSHR sont regroupés dans une mémoire RAM/cache qui contient autant d'entrées qu'il y a de mot mémoire dans une ligne de cache. Chaque entrée de la table est associée à un mot mémoire de la ligne de cache et stocke des informations sur celui-ci. Une entrée mémorise, au minimum : * Un champ de lecture/écriture qui contient soit la destination de la lecture, soit la donnée à écrire. * Un bit R/W permettant d'interpréter correctement le champ de lecture/écriture. * Un bit de validité pour chaque entrée de la table, qui dit si un défaut de cache antérieur accède déjà à ce mot mémoire. La fusion de deux défauts de cache est ainsi assez simple. Un défaut de cache primaire/secondaire configure l'entrée associée au mot mémoire lu/écrit. Si un défaut secondaire ultérieur lit/écrit un mot mémoire différent (dont le bit de validité de l'entrée dans la table est à zéro), pas de conflit : il configure une autre entrée, vide. Mais s'il lit/écrit un mot mémoire pour lequel l'entrée de la table est occupée, il y a conflit, le processeur est bloqué par un ''pipeline stall'', une bulle de pipeline. Avec cette organisation, le nombre de MSHR indique combien de lignes de cache peuvent être lues en même temps depuis la mémoire. Quant au nombre d'entrées par MSHR, il détermine combien d'accès mémoires qui ne se recouvrent pas peuvent avoir lieu en même temps. ===Les MSHR adressés explicitement=== Les '''MSHR adressés explicitement''' n'ont pas les contraintes des MSHR adressés implicitement. Avec eux, il est possible d'avoir plusieurs défauts de cache pointant vers la même ligne de cache, mais aussi vers le même mot mémoire. De tels défauts de cache apparaissent sur les processeurs à exécution dans le désordre, mais sont très rares, voire inexistants, sur les processeurs ''in-order''. L'idée est encore que chaque MSHR est associée à une table mémoire qui mémorise des entrées. Cette table n'est autre qu'une mémoire FIFO. Sauf que cette fois-ci, une entrée n'est pas associée à un mot mémoire. Une entrée est associée à un défaut de cache. Une entrée mémorise là encore la destination de la lecture ou la donnée de l'écriture suivi du bit R/W, ainsi que d'un bit de validité par entrée, mais aussi : la position du mot mémoire lu dans la ligne de cache. C'est cette dernière information qui n'était pas présente dans les MSHR adressés implicitement. De plus, le nombre d'entrée par MSHR n'est pas égal au nombre de mots mémoires dans une ligne de cache, mais peut être arbitrairement grand. L'avantage d'un tel cache est qu'il est capable de traité les défauts secondaires concernant un même mot mémoire, et ce, car les accès à la ligne de cache concernée se produisent dans l'ordre de génération des défauts (la table mémoire est une FIFO). ===Les MSHRs inversés=== Généralement, plus on veut supporter de défauts de cache, plus le nombre de MSHR et d'entrées augmente. Mais au-delà d'un certain nombre d'entrées et de MSHR, les MSHR adressés implicitement et explicitement ont tendance à bouffer un peu trop de circuits. Utiliser une organisation un peu moins gourmande en circuits est donc une nécessité. Cette organisation plus économe se base sur des MSHR inversés. Les MSHR inversés ne contiennent qu'une seule entrée, en quelque sorte : au lieu d’utiliser n MSHR de m entrées chacun, on va utiliser n × m MSHR inversés. La différence, c'est que plusieurs MSHR peuvent contenir un tag identique, contrairement aux MSHR adressés implicitement et explicitement. Lorsqu'un défaut de cache a lieu, chaque MSHR est vérifié. Si jamais aucun MSHR ne contient de tag identique à celui utilisé par le défaut, un MSHR vide est choisi pour stocker ce défaut, et une requête de lecture en mémoire est lancée. Dans le cas contraire, un MSHR est réservé au défaut de cache, mais la requête n'est pas lancée. Quand la donnée est disponible, les MSHR correspondant à la ligne qui vient d'être chargée vont être utilisés un par un pour résoudre les défauts de cache en attente. ===Les MSHR intégrés au cache=== Certains chercheurs ont remarqué que pendant qu'une ligne de cache est en train de subir un défaut de cache, celle-ci reste inutilisée, et son contenu est destiné à être perdu une fois le défaut de cache résolu. Ils se sont dit que, plutôt que d'utiliser des MSHR séparés, il vaudrait mieux utiliser la ligne de cache pour stocker les informations sur les défauts de cache en attente dans cette ligne de cache. Pour éviter tout problème, il faut rajouter un bit dans les bits de contrôle de la ligne de cache, qui sert à indiquer que la ligne de cache est occupée : un défaut de cache a eu lieu dans cette ligne, et elle stocke donc des informations pour résoudre les défauts de cache. ==L'entrelacement mémoire== Nous venons de voir comment une mémoire cache peut gérer plusieurs accès mémoire simultanés. Il se trouve que ce genre d'optimisation dépasse largement le cadre du cache. Les mémoires RAM ne sont pas en reste, elles aussi ! Il est possible de pipeliner l'accès à une mémoire RAM, en utilisant des techniques dites d''''entrelacement'''. Précisons cependant que cette optimisation n'est pas utilisée sur les mémoires SDRAM ou DDR modernes, du moins pas sans modifications majeures. Mais divers ordinateurs assez anciens, dont des superordinateurs, ont utilisé cette technique. La technique demande d'utiliser plusieurs mémoires séparées. Sur les anciens ordinateurs, les mémoires en question sont des chips mémoire, à savoir des circuits intégrés de DRAM. Pour faire plus général, nous allons utiliser le terme de '''banques''', ou encore de bancs mémoire. La différence est qu'il existe des mémoires multi-banque, qui regroupent plusieurs banques indépendantes dans un seul boitier, dans un seul chip mémoire. En clair, de telles mémoires regroupent plusieurs sous-mémoires dans un seul circuit intégré. Il est aussi possible d'utiliser plusieurs chips mémoire séparés, chacun ayant une banque. Reste à combiner ces banques pour former un pseudo-pipeline. ===Utiliser plusieurs banques sans entrelacement=== Sans optimisation particulière, les adresses sont réparties dans les banques comme indiqué ci-dessous. Il s'agit de ce qui s'appelle un arrangement horizontal, et nous avions vu celui-ci dans le chapiutre sur les mémoires SDRAM. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Avec cet arrangement, les bits de poids fort de l'adresse sont utilisées pour sélectionner la banque adéquate, et le reste de l'adresse est envoyé sur le bus d'adresse. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Adresse de banque !! Adresse dans la banque |- | Quelques bits de poids fort || Reste de l'adresse |} L'entrelacement change cette répartition, afin d'accélérer les accès mémoire. L'entrelacement classique vise à améliorer les accès à des adresses mémoires consécutives, mais des formes plus évoluées d'entrelacement visent à optimiser des accès mémoire arbitraires. Voyons-les dans l'ordre, du plus simple au plus complexe. ===L'entrelacement classique=== Avec l''''entrelacement classique''', des adresses consécutives sont réparties dans des banques consécutives. Précisons que cette attribution des adresses n'implique absolument pas la mémoire virtuelle ou n'importe quel mécanisme dans le processeur. La répartition décide que telle adresse mémoire va dans telle banque, à telle adresse dans la banque. Elle est donc le fait du contrôleur mémoire, donc en dehors du processeur (les contrôleurs mémoires n’étaient pas encore intégrés dans le processeur à l'époque). [[File:Répartition des adresses dans une mémoire interleaved.png|centre|vignette|upright=1.5|Répartition des adresses dans une mémoire interleaved.]] L'entrelacement simple permet d'accélérer les accès mémoire qui se font à des adresses consécutives. Avec l'entrelacement, chaque accès mémoire tombe dans une banque mémoire différente, ce qui fait qu'on peut démarrer un nouvel accès mémoire à chaque cycle d'horloge. Pendant qu'une banque est occupée par un accès mémoire, on démarre le suivant dans une autre banque, et ainsi de suite. Le tout se termine soit quand on a épuisé toutes les banques libres, soit quand l'accès en rafales se termine. Pas besoin d'attendre que la mémoire ait fini sa lecture/écriture avant de démarrer la lecture/écriture suivante. [[File:Pipemining mémoire.png|centre|vignette|upright=2.5|Pipelining mémoire]] L'animation suivante illustre bien le processus, avec quatres banques. [[File:Interleaving.gif|centre|vignette|upright=2.5|Pipelining mémoire]] L'entrelacement simple utilise les bits de poids faible pour sélectionner la banque, et les bits de poids fort pour la case mémoire. [[File:Adresse mémoire d'une mémoire entrelacée.png|centre|vignette|upright=2|Adresse mémoire d'une mémoire entrelacée]] Précisons que le temps d'accès mémoire ne change pas beaucoup avec l'entrelacement. Par contre, on peut faire plus d'accès mémoire simultanés. On peut démarrer un accès mémoire par cycle d'horloge, mais l'accès en lui-même prend plusieurs cycles. Les mémoires à entrelacement ont donc un débit supérieur aux mémoires qui ne l'utilisent pas. L'entrelacement classique pourrait être utilisé pour accélérer les transferts entre mémoire RAM et cache, qui se font en blocs d'adresses consécutives. Mais de nos jours, il n'est pas utilisé dans cette optique. Il faut dire qu'il est redondant avec le mode rafale des mémoires DRAM. Les deux font globalement la même chose et on voit mal comment utiliser les deux en même temps. Le mode rafale étant plus simple à implémenter, et plus léger niveau utilisation du bus de commande mémoire, il est préféré à l'entrelacement. Par contre, l'entrelacement a eu son heure de gloire sur d'anciens processeurs qui n'avaient pas de mémoire cache, alors qu'ils disposaient d'un pipeline. L'exemple typique est celui des nombreux ''processeurs vectoriels'', que nous n'avons pas encore abordé à ce stade du cours. De tels processeurs avaient un pipeline, une unité de calcul fortement pipelinée, et parfois même de l'exécution dans le désordre et du renommage de registres ! ===L'entrelacement par décalage=== L'entrelacement simple est très utile pour les accès en rafle ou équivalents. Par contre, il existe de rares situations où il n'est pas optimal. Une de ces situations est celle des accès en enjambée (en ''stride''), où l'on accède en série à des adresses sont séparées par N mots mémoires. [[File:Accès par enjambées.png|centre|vignette|upright=2|Accès par enjambées.]] De tels accès surviennent quand un logiciel accède à des structures de données spécifiques, à savoir des tableaux de structures, des matrices, ou d'autres structures de données dans le genre. Un cas classique est celui du parcours d'une matrice colonne par colonne. Les matrices de nombres sont mémorisées ligne par ligne, ce qui fait que parcourir une colonne demande de passer d'une ligne à la suivante et de faire des grands sauts en mémoire RAM, mais tous espacés par la même distance. Avec l'entrelacement simple, les accès en enjambée sont moins performants que les accès à des adresses consécutives, mais cela ne fonctionne pas trop mal. Le pire cas est celui où l'on a N banques et où les données sont justement placées toutes les N adresses. Des accès consécutifs vont tous tomber dans la même banque, on ne peut plus accéder à des banques différentes en parallèle. Mais en dehors de ce cas, on voit une amélioration par rapport à la situation sans entrelacement. Pour obtenir des performances maximales pour les accès en enjambées, il faut répartir les mots mémoires dans la mémoire autrement. L'organisation idéale est la suivante. : Dans les explications qui vont suivre, la variable N représente le nombre de banques, qui sont numérotées de 0 à N-1. On commence par organiser les N premières adresses comme une mémoire entrelacée simple : l'adresse 0 correspond à la banque 0, l'adresse 1 à la banque 1, etc. Pour le bloc suivant, on décale tout d'une adresse, à savoir qu'on commence à remplir les banques à partir de la seconde, non de la première. Une fois la fin du bloc atteinte, on finit de remplir le bloc en repartant du début du bloc. Le troisième bloc d'adresse subit le même traitement, sauf qu'on commence à remplir à partir de la seconde banque. Et on poursuit l’assignation des adresses en décalant d'un cran en plus à chaque bloc. Ainsi, chaque bloc verra ses adresses décalées d'un cran en plus comparé au bloc précédent. Si jamais le décalage dépasse la fin d'un bloc, alors on reprend au début. [[File:Mémoire interleaved par décalage.png|centre|vignette|upright=1.5|Mémoire entrelacée par décalage.]] En faisant cela, on remarque que les banques situées à N adresses d'intervalle sont différentes. Dans l'exemple du dessus, nous avons ajouté un décalage de 1 à chaque nouveau bloc à remplir. Mais on aurait tout aussi bien pu prendre un décalage de 2, 3, etc. Dans tous les cas, on obtient un entrelacement par décalage. Ce décalage est appelé le pas d'entrelacement, noté P. Le calcul de l'adresse à envoyer à la banque, ainsi que la banque à sélectionner se fait en utilisant les formules suivantes : * adresse à envoyer à la banque = adresse totale / N ; * numéro de la banque = (adresse + décalage) modulo N ; * décalage = (adresse totale * P) mod N. Avec cet entrelacement par décalage, on peut prouver que la bande passante maximale est atteinte si le nombre de banques est un nombre premier. Seulement, utiliser un nombre de banques premier peut créer des trous dans la mémoire, des mots mémoires inadressables. Malgré ce défaut, la technique a été utilisée sur quelques ordinateurs, avec l'exemple notable du superordinateur ''Burroughs Scientific Processor''. Pour éviter cela, il y a plusieurs solutions. Par exemple, on peut faire en sorte que N et la taille d'une banque soient premiers entre eux : ils ne doivent pas avoir de diviseur commun. Mais en pratique, elles n'ont pas vraiment été implémentées dans une vraie machine, et sont restées à l'état de recherche, aussi je les passe sous silence. ===L'entrelacement pseudo-aléatoire=== Une dernière méthode de répartition consiste à répartir les adresses dans les banques de manière "pseudo-aléatoire". L'idée exacte est de faire passer l'adresse dans une '''fonction de hachage''', plus ou moins complexe. Pour rappel, une fonction de hachage prend une entrée de grande taille et fournit en sortie un résultat de petite taille. Idéalement, elles doivent donner des résultats aussi différents que possible pour des entrées similaires, histoire de simuler une sorte de pseudo-aléatoire. Ici, une adresse est transformée en un numéro de banque. Une fonction de hachage simple ne fait que permuter des bits de l'adresse pour obtenir son résultat. Elle ne fait qu'échanger des bits de place avant de couper l'adresse en deux morceaux : un pour la sélection de la banque, et un autre pour la sélection de l'adresse dans la banque. Cette permutation est fixe, et ne change pas suivant l'adresse. Des fonctions de hachage plus complexes font des XOR bit à bit entre certains bits de l'adresse. ==Les contrôleurs mémoires SDRAM/DDR optimisés== Après avoir vu le cas des mémoires RAM, nous allons nous concentrer sur le cas particulier des mémoires SDRAM. Les mémoires SDRAM et DDR modernes sont capables de gérer plusieurs accès simultanés à la mémoire RAM, et leurs contrôleurs mémoire en font tout autant. C'est une différence majeure avec les mémoires asynchrones FPM/EDO, qui n'acceptaient qu'un seul accès mémoire à la fois. Leur contrôleur mémoire n'acceptait qu'un seul accès mémoire à la fois, c'était un contrôleur mémoire bloquant. Les contrôleurs mémoires des SDRAM sont eux non-bloquants et peuvent encaisser une dizaine d'accès mémoire à la fois. Peu de choses sont connues sur les contrôleurs de SDRAM/DDR modernes, les fabricants ne donnant que peu de détails dessus. Les rares simulateurs qui tentent de décrire leur fonctionnement, comme DRAM SIM I et II, sont particulièrement simples et ne vont pas dans le détail. Néanmoins, le peu qu'on sait est tout de même instructif. ===Rappel sur les SDRAM : tampon de ligne et commandes=== Les mémoires SDRAM sont des mémoires à tampon de ligne. Elles font un accès mémoire en deux étapes. La première étape recopie une ligne de N * 64 bits dans un tampon interne à la SDRAM. La seconde étape sélectionne une colonne, à savoir une donnée de 64 bits, qui est soit envoyée sur le bus de données pour une lecture, soit modifiée par une écriture. [[File:Mémoire à tampon de ligne.png|centre|vignette|upright=2|Mémoire à tampon de ligne]] Les accès mémoire sont traduits par un séquenceur mémoire en une série de commandes mémoires, qui sont séparées par des délais mémoire de quelques cycles d'horloge. Les délais sont très précis, et sont à respecter à la lettre. Une lecture ou une écriture se fait en maximum trois commandes : une commande PRECHARGE qui ferme la ligne précédemment utilisée, une commande ACT qui précise l'adresse de la ligne, et une commande READ ou WRITE qui précise l'adresse de la colonne et éventuellement la donnée à écrire. : Pour être plus précis, la commande PRECHARGE précharge les lignes de bits du plan mémoire à une tension particulière, ce qui les vide de leur contenu. Mais c'est un détail sans importance pour ce qui va suivre. Les SDRAM permettent de se passer de la première étape si des accès consécutifs se font dans la même ligne. Une fois activée, la ligne reste ouverte et on peut accéder plusieurs fois de suite dedans. On a alors juste à préciser l'adresse de colonne dedans. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | bgcolor="#FFA0FF" | PRECHARGE || bgcolor="#FFA0FF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || bgcolor="#A0FFFF" | READ (2) || bgcolor="#A0FFFF" | READ (3) || || || bgcolor="#A0FFFF" | READ (4) || bgcolor="#A0FFFF" | READ (5) || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | Donnée READ 1 || ||bgcolor="#A0FFFF" | Donnée READ 2 | bgcolor="#A0FFFF" | Donnée READ 3 || || || bgcolor="#A0FFFF" | Donnée READ 4 || bgcolor="#A0FFFF" | Donnée READ 5 |} Les SDRAM sont parfois capables de démarrer une commande avant que la précédente soit terminée. Mais le respect des délais mémoire est très limitant, ce qui fait qu'on ne peut pas parler de réel pipeline, comme c'est le cas sur les processeurs. Parlons plutot de '''commandes anticipées'''. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | || bgcolor="#A0FFFF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || || || bgcolor="#FFA0FF" | READ (2) || || || || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 | bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 | |} Un dernier point est que les chips mémoires de SDRAM sont composés de plusieurs banques, chacune étant une sorte de mini-mémoire miniature. Chaque banque a son propre décodeur, son propre tampon de ligne, ses propres multiplexeurs de colonne, sa logique de rafraichissement mémoire, etc. C'est comme si une SDRAM regroupait plusieurs mémoires séparées dans un même circuit intégré. [[File:Arrangement vertical.jpg|centre|vignette|upright=2.5|Mémoire multi-banques.]] ===La mise en attente des accès mémoire=== Un contrôleur de SDRAM peut accepter plusieurs accès mémoire en même temps. Mais cela ne signifie pas que le contrôleur sera prêt à les traiter immédiatement. Pour éviter tout problème de disponibilité, le contrôleur met en attente les accès mémoire que le processeur lui envoie, pour les exécuter dès que possible. Les accès mémoire sont mis en attente dans une mémoire FIFO, histoire de les exécuter dans leur ordre d'arrivée, notamment pour renvoyer les lectures dans l'ordre demandé. Il y a aussi une mémoire FIFO pour les données à écrire et une FIFO pour les données lues. Cette dernière sert au cas où le cache ne soit pas disponible quand on lui envoie la donnée lue. [[File:Module d'interface avec la mémoire.png|centre|vignette|upright=2.5|Contrôleur mémoire avec mise en attente des requêtes processeur et autres optimisations.]] Des optimisations sont possibles dès la mémoire FIFO. Par exemple, on peut regrouper plusieurs accès à des données consécutives en un seul accès en rafale. Le contrôleur mémoire analyse les requêtes mises en attente et détecte si certaines se font à des adresses consécutives. Si c'est le cas, il fusionne ces requêtes en une lecture/écriture en rafale. Une telle optimisation est appelée la '''combinaison de lecture''' pour les lectures, et la '''combinaison d'écriture''' pour les écritures. Une autre optimisation possible est le réacheminement lecture sur écriture (''Store to load forwarding''). Il s'agit d'un équivalent de l’optimisation utilisée dans le cadre de la désambiguïsation mémoire. Quand une lecture demande à accéder à une donnée écrite récemment, il se peut que l'écriture ait été mise en attente dans le contrôleur mémoire. Dans ce cas, le contrôleur mémoire peut renvoyer la donnée directement depuis ses mémoires FIFOs, sans faire d'accès mémoire en lecture. Les deux optimisations précédentes ne paraissent pas payer de mine, mais elles deviennent plus compréhensible quand on sait que certains contrôleurs mémoire peuvent mettre en attente beaucoup de données. Par exemple, l'''Intel E8870 - ''Scalable Memory Controller'' peut mettre en attente 64 lignes de cache dans la mémoire FIFO, soit 8 kibioctets de RAM ! Deux pages mémoire si la mémoire virtuelle utilise des pages de 4 kibioctets ! ===Les optimisations liées aux commandes anticipées=== Les commandes anticipées permettent de faire un minimum de parallélisme mémoire. Même si elle parait très limitée, cette possibilité est décuplée par la présence de plusieurs banques dans la mémoire SDRAM. Il est en effet possible de faire plusieurs accès mémoire en même temps, dans des banques différentes. Il est possible de lancer un accès mémoire dans deux banques en même temps, par exemple. La seule contrainte est que la SDRAM est limitée à 4 banques actives en même temps. {|class="wikitable" |+ Pipelining basique sur les SDRAM |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 |- ! Banque Numéro 1 | || || || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || || |- ! Banque Numéro 2 | || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || |- ! Banque Numéro 3 | || || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || |} L'implémentation n'a rien de vraiment compliqué. Il suffit d'utiliser une mémoire FIFO par banque. Ainsi, les accès dans une même banque se feront en série, dans l'ordre d'arrivée, mais des accès à des banques différentes se feront dans le désordre. Cela ne pose pas de problème car les accès se font à des adresses différentes, ce qui fait qu'il n'y a pas de conflits majeurs. [[File:Gestion parallèle des banques.png|centre|vignette|upright=2.5|Gestion parallèle des banques.]] ===Le ré-ordonnancement des commandes mémoires=== Un contrôleur mémoire moderne est capable de changer l'ordre des requêtes mémoires, pour gagner en performances. En clair : il peut faire l'équivalent mémoire de l'exécution dans le désordre des processeurs haute performance. Une différence majeure est que le contrôleur mémoire ne remet pas les accès mémoire dans l'ordre avant de les envoyer au processeur. C'est le processeur qui remet en ordre les accès mémoire, dans sa ''load queue''. Pour cela, les requêtes mémoire sont "numérotées", à savoir qu'on leur attribue un identifiant binaire. Le contrôleur mémoire exécute les lectures/écritures dans le désordre, mais garde la trace de leur identifiant. Il sait dans quel ordre il exécute les accès mémoire et connait leur latence. Ce qui fait qu'il sait que la donnée lue à tel instant est associée à la requête mémoire numéro X. Pour les lectures, il renvoie la donnée lue avec l'identifiant. Le processeur sait alors à quelle lecture la donnée lue correspond et il se débrouille en interne pour gérer la situation. En clair : le processeur voit les accès mémoire dans le désordre, c'est lui qui les remet en ordre. Maintenant que ces précisions sont faites, posons cette question : quel est l'intérêt de faire les accès mémoire dans le désordre ? Accéder à des banques en parallèle est une réponse possible, mais le ré-ordonnancement est surtout utile si tous les accès mémoire atterrissent dans la même banque et dans la même ligne. Elle marche si plusieurs accès à une même ligne ne sont pas consécutifs, et qu'il y a des accès entre les deux. Après ré-ordonnancement, ces accès mémoire sont exécutés l'un à la suite de l'autre. Pour rendre le tout plus concret, voici un exemple. Imaginez que l'on sait les 3 accès mémoire suivants : * Une lecture ligne A ; * Une écriture ligne B ; * Une lecture ligne A. Sans réordonnancent, on doit émettre deux commandes PRECHARGE : une quand on passe de la ligne A à la ligne B, et une autre pour le passage inverse. pour éviter cela, le contrôleur mémoire peut retarder l'écriture, ou au contraire avancer la seconde lecture. le résultat est alors le suivant : * Lecture ligne A ; * Lecture ligne A ; * Une écriture ligne B. On a donc deux accès consécutifs à la même ligne, suivi par une écriture dans une autre ligne. La technique marche parce que l'on a un mix adéquat de lectures et d'écritures. Mais il existe des possibilités de réorganisation autres qui ne sont pas valides. Il faut par exemple prendre garde à éviter d'intervertir lectures et écritures à une même adresse, sous peine de problèmes. Encore une fois, il faut faire attention aux dépendances de données, encore une fois ! Ici des dépendances mémoires, mais les règles sont les mêmes. Le contrôleur mémoire teste les dépendances mémoires avant d'envoyer des commandes à la mémoire DDR/SDRAM. Il a existé des contrôleurs mémoires du temps des vieilles DDR qui implémentaient un ordonnancement très simples. Prenons l'exemple des coprocesseurs IO 81341 et 81342, qui étaient en réalité des ''chipsets'' intégrant un contrôleur SDRAM. Le ''chipset'' avait 5 ports : un pour les processeurs, un pour le pont sud (''southbridge'') et trois pour des canaux DMA. Pour le premier, il n'y avait pas de réordonnancement. Pour les autres, les lectures se font dans l'ordre, les écritures se font dans l'ordre, mais les écritures peuvent passer avant une lecture non-dépendante. Sans doute que le processeur avait des files séparées pour les lectures et écritures dans une même banque. Le séquenceur mémoire vérifiait les dépendances mémoire de type RAW et autres. [[File:Double file d'attente pour les lectures et écritures.png|centre|vignette|upright=2|Double file d'attente pour les lectures et écritures]] De nos jours, les contrôleurs mémoires sont beaucoup plus évolués et sont capables d'exécution dans le désordre bien plus poussée. ==L'entrelacement sur les mémoires SDRAM== L''''entrelacement''' fonctionne sur les mémoires SDRAM, mais il doit être fortement modifié. Concrètement, tout ce qui a étré dit plus est inapplicable pour les SDRAM, du fait de la présence du tampon de ligne. Des adresses consécutives atterrissent déjà dans la même ligne, ce qui fait qu'on n'a pas besoin d'entrelacement au niveau d'une ligne. Et ne parlons pas de ce la présence de rangées et de canaux mémoire ! Cependant, il peut y avoir un entrelacement lié à la présence des banques SDRAM. L'entrelacement est géré juste avant le séquenceur mémoire. Le '''circuit d'entrelacement''' intervertit certains bits de l'adresse lors des accès mémoires. On trouve aussi des mémoires FIFOs pour mettre en attente les requêtes mémoire provenant du processeur. Elles permettent de recevoir plusieurs accès mémoire consécutifs. Si vous vous demandez comment plusieurs accès mémoire consécutifs peuvent avoir lieu, sachez que c'est une bonne question. Pour le moment, nous allons dire que ces accès proviennent du processeur ou de la mémoire cache, sans expliquer comment. Le contrôleur mémoire reçoit une série d'accès mémoire assez rapprochés, qu'il doit exécuter au mieux. [[File:Controleur mémoire d'une SDRAM avec entrelacement.png|centre|vignette|upright=2.5|Contrôleur mémoire d'une SDRAM avec entrelacement.]] Pour gérer l'entrelacement, le contrôleur de SDRAM prend en entrée l'adresse envoyée par le processeur, et la découpe en plusieurs champs : un pour sélectionner la banque adéquate, un autre pour la ligne, un autre pour la colonne, un autre pour la rangée. Une solution simple désactive l'entrelacement. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de banque || Adresse de ligne || Adresse de colonne |} Si cette solution fonctionne bien si les accès mémoire sont assez aléatoires, en pratique, mieux vaut utiliser l'entrelacement. ===L'entrelacement de banques/lignes=== Une première méthode d'entrelacement est l''''entrelacement de banques'''. Elle répartit deux lignes consécutives dans deux banques différentes. Pour comprendre l'idée, prenons un exemple. Imaginons une mémoire avec deux banques et 4 lignes. Imaginons qu'on parcoure/balaye la mémoire RAM en partant des adresses basses. Sans entrelacement de ligne, les accès se feront comme suit, de gauche à droite : {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Banque numéro 1 !! colspan="4" | Banque numéro 2 |- | Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 || Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 |} Avec l'entrelacement de banques, les accès se feront comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 |- | Ligne 1 || Ligne 1 || Ligne 2 || Ligne 2 || Ligne 3 || Ligne 3 || Ligne 4 || Ligne 4 |} Avec N banques, la première ligne ira dans la première banque, la seconde ligne dans la seconde, et ainsi de suite jusqu'à la énième banque qui va dans la banque numéro N. Et la ligne N+1 repart dans la première banque. L'avantage est que passer d'une ligne à la suivante est assez rapide. Au lieu de devoir fermer la première ligne et ouvrir la suivante on ouvrira directement la ligne suivante dans une banque différente, ce qui sera plus rapide. La commande PRECHARGE pourra être envoyée à la seconde banque en avance. Pour cela, l'adresse est découpée comme suit. {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne (précise à l'octet) |} ===L'entrelacement de rangées=== Il est aussi possible de faire la même chose, mais avec les rangées. Pour simplifier fortement, une rangée est simplement un chip mémoire. En réalité, les rangées ne sont pas des chip mémoire, mais un ensemble de chips mémoire regroupés ensemble histoire d'atteindre les 64 bits du bus de données. Par exemple, une rangée peut combiner 8 chips mémoire avec un bus de données de 8 bits chacun pour obtenir les 64 bits du bus de données d'une SDRAM. Mais nous passons ce détail sous silence dans les explications qui vont suivre, par souci de simplification. Pour faire comprendre l'entrelacement de rangée, le mieux est d'utiliser un exemple, le même que précédemment. Sans entrelacement de rangée, on change de chip mémoire une fois qu'on a balayé toutes ses adresses. {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Rangée numéro 1 !! colspan="4" | Rangée numéro 2 |- | Banque 1 || Banque 2 || Banque 3 || Banque 4 || Banque 1 || Banque 2 || Banque 3 || Banque 4 |} Avec l'entrelacement de ligne, on change de rangée dès qu'on change de banque/ligne. {|class="wikitable" |+ Adresse mémoire |- ! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 |- | Banque 1 || Banque 1 || Banque 2 || Banque 2 || Banque 3 || Banque 3 || Banque 4 || Banque 4 |} En clair, quand on a épuisé toutes les banques dans une même rangée, on passe à la rangée suivante au lieu de rester dans la même rangée. Notons que dans l'exemple précédent, on a combiné l'entrelacement de rangée et de banque, mais on aurait pu utiliser l'entrelacement de rangée seul. Mais ce n'est pas le cas le plus courant en pratique. Toujours est-il qu'en combinant entrelacement de rangée et de ligne, le découpage de l'adresse est le suivant : {|class="wikitable" |+ Adresse mémoire |- | Adresse de ligne || Numéro de rangée || Adresse de banque || Adresse de colonne (précise à l'octet) |} D'autres méthodes d'entrelacement sont possibles, mais elles sont plus complexes. Chaque contrôleur mémoire fait un peu à sa sauce. Ils permettent en général de choisir entre plusieurs entrelacements. Au minimum, ils permettent de désactiver l'entrelacement, d'activer un entrelacement de ligne, et d'autres entrelacements plus complexes, dont certains sont propriétaires et tenus secrets par le fabricant. ===L'entrelacement avec le ''dual channel''=== [[File:Dual channel slots.jpg|vignette|Slots mémoires avec ''dual channel''.]] L'usage du ''dual channel'' complique encore l'entrelacement. Là encore, il y a deux grandes solutions : avec et sans entrelacement des canaux mémoire. Rappelons le principe : deux barrettes de RAM sont accédées en parallèle. Et pour cela, il faut utiliser l'entrelacement. Typiquement, chaque barrette mémoire fournit 64 bits, ce qui fait que l'on peut accéder à 128 bits d'un seul coup, par exemple avec un accès en rafale. Sans ''dual channel'', la première barrette correspond à la moitié haute de la RAM, la seconde barrette correspond à la moitié basse. Avec ''dual channel'', une forme spécifique d'entrelacement est activée. Concrétement, deux blocs de 64 bits sont placés dans des canaux mémoire séparés. Idem avec du triple ou quadruple canal, mais c'est alors trois ou quatre blocs de 64 bits qui sont dispersés dans des canaux consécutifs. Le découpage de l'adresse est alors le suivant : {|class="wikitable" |+ Adresse mémoire |- ! colspan="6" | Sans entrelacement |- | Numéro de canal/barrette || Reste de l'adresse || Position de l'octet dans un bloc de 64 bits |- | colspan="6" | |- ! colspan="6" | Avec entrelacement |- | Reste de l'adresse || Numéro de canal/barrette || Position de l'octet dans un bloc de 64 bits |} [[File:Décodage d'adresse avec dual channel.png|centre|vignette|upright=2|Décodage d'adresse avec dual channel.]] Les contrôleurs de SDRAM précédents sont assez basiques et ne représentent pas les contrôleurs les plus évolués. Ils étaient utilisés sur les anciens PC, à une époque où ils étaient encore sur la carte mère du processeur. Mais de nos jours, le contrôleur mémoire est intégré au processeur. Et il incorpore de nombreuses optimisations afin de gagner en performances. Malheureusement, ces optimisations sont elles-mêmes dépendantes des optimisations intégrées dans le processeur, ce qui fait qu'on ne peut pas en parler ici. Elles demandent que le contrôleur mémoire reçoive plusieurs accès mémoire simultanés, ce qui n'a aucun sens à ce stade du cours. Aussi, nous allons les passer sous silence pour le moment. Nous les verrons dans le chapitre sur le parallélisme mémoire. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=La désambiguïsation mémoire | prevText=La désambiguïsation mémoire | next=Les processeurs superscalaires | nextText=Les processeurs superscalaires }} </noinclude> 1rmqugsmmtysnnqrlp3ac13phhkzsgw 764725 764724 2026-04-23T22:15:06Z Mewtow 31375 /* Le ré-ordonnancement des commandes mémoires */ 764725 wikitext text/x-wiki Dans ce chapitre, nous allons voir les techniques qui permettent de gérer plusieurs accès mémoire simultanés directement au niveau du cache ou de la mémoire RAM. Par plusieurs accès mémoire simultanés, vous pensez sans doute à l'usage de cache multiports, voire de mémoires RAM multiport. C'est en effet une solution, mais ce n'est clairement pas la seule. Par exemple, il est possible de pipeliner l'accès au cache, voire à la mémoire RAM. Il existe de nombreuses techniques de '''parallélisme mémoire''', et nous allons les voir dans ce chapitre. Pour donner un exemple, il est possible de pipeliner les accès mémoire. Il est possible de pipeliner l'accès au cache et/ou l'accès à la mémoire, et nous verrons comment faire dans ce chapitre. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, qu'ils aient ou non un pipeline dynamique. Par contre, pipeliner la mémoire est une technique ancienne, aujourd'hui peu utilisée. Elle était utilisée sur des très vieux ordinateurs, qui avaient un pipeline mais pas de mémoire cache. Mais de nos jours, les mémoires SDRAM et DDR ne sont pas adaptées pour ça, il est très difficile de les pipeliner correctement. Le parallélisme mémoire est utile aussi bien sur des processeurs à émission dans l'ordre que dans le désordre. Par exemple, un processeur ''in-order'' avec des lectures non-bloquantes peut e, profiter. Il peut techniquement lancer une seconde lecture, après avoir rencontré une première lecture non-bloquante. En général, le nombre de lectures consécutives est cependant limité à moins d'une dizaine. À l'opposé, les processeurs sans lectures non-bloquantes profitent pas du parallélisme mémoire. À la rigueur, ils peuvent en profiter s'ils intègrent des techniques de préchargement. Pendant que le processeur exécute une lecture/écriture, il peut précharger une autre donnée en parallèle. Inutile de dire que sans pipeline processeur, le parallélisme mémoire ne sert pas à grand-chose. ==Le ''line fill buffer'' et les techniques associées== Lors d'un défaut de cache, le processeur doit attendre que toute la ligne de cache soit chargée avant d'être utilisable. Or, la taille d'une ligne de cache est supérieure à la largeur du bus mémoire, ce qui fait qu'une ligne de cache est chargée en plusieurs fois, morceaux par morceaux, mot mémoire par mot mémoire. Le chargement peut se faire directement dans le cache, mais ce n'est pas une solution très pratique. À la place, beaucoup de processeurs ajoutent une mémoire tampon entre la RAM et le cache, appelée le '''tampon de remplissage de ligne''' (''line-fill buffer'') dans les processeurs modernes. Lors d'un défaut de cache, le processeur charge la donnée de la RAM dans le tampon de remplissage de ligne, mot mémoire par mot mémoire. Une fois plein, le tampon de remplissage de ligne est recopié dans la ligne de cache. Le tampon de remplissage de ligne contient une ligne de cache, si ce n'est que certains bits de contrôle ne sont pas présents. Le tampon de remplissage de ligne contient un bit de validité pour chaque mot mémoire de la ligne de cache, qui indiquent si le mot mémoire a été chargé. Par exemple, prenons un processeur 64 bits, qui gère donc des mots mémoire de 8 octets, avec des lignes de cache 256 octets/32 mots mémoire. Le tampon de remplissage de ligne contiendra 32 bits de validité. Si le processeur a chargé les 6 premiers mots mémoire, les 6 premiers bits de validité seront mis à 1, les autres seront encore à zéro. Il faut noter que la disponibilité de la ligne complète se détermine assez facilement en faisant un ET logique entre tous les bits de validité. Le processeur sait ainsi quand la ligne de cache est disponible entièrement et donc quand la transférer dans le cache. La même chose existe avec une hiérarchie de cache, sauf que l'on trouve une mémoire tampon entre chaque niveau de cache. Il y a un tampon de remplissage de ligne entre le cache L1 et le cache L2, entre le cache L2 et le L3, etc. Si le cache ne gère qu'un seul défaut de cache à la fois, le ''fill line buffer'' est une mémoire très simple, qui ne mémorise qu'une seule ligne de cache. Et cela vaut aussi bien pour un cache bloquant que non-bloquant. Mais sur les caches capables de gérer plusieurs défauts simultanés, le tampon de remplissage de ligne est une mémoire de type FIFO ou LIFO, capable de mémoriser plusieurs lignes de cache. Notons que le tampon de remplissage de ligne est très utile pour implémenter certaines techniques, par exemple le contournement du cache. Nous avions vu dans le chapitre sur le cache que certains accès mémoire doivent contourner le cache, pour des raisons de cohérence des caches. C'est notamment nécessaire pour accéder aux périphériques, mais c'est aussi utile pour des raisons de performances dans des cas très spécifiques. Les accès qui contournent le cache se font directement dans le tampon de remplissage de ligne : le processeur écrit ou lit les données depuis ce tampon de remplissage de ligne, sans accéder au cache. ===L'''early restart'' et le ''critical word load''=== La présence du ''line buffer'' permet une optimisation assez intéressante, qui permet de réduire la latence des défauts de cache. L'optimisation consiste à lire un mot mémoire dans le tampon de remplissage de ligne, même si la ligne de cache complète n'a pas encore été chargée. Il existe deux manières de faire cela, qui portent les noms d'''early restart'' et de ''critical word load''. La première est la version la plus simple, la seconde est plus complexe mais plus performante. Avec la technique d''''''early restart''''', la ligne de cache est chargée normalement, en partant de son premier mot mémoire. Dès que le mot mémoire lu/écrit par le processeur est copié dans le ''line fill buffer'', il est envoyé au processeur immédiatement. Illustrons le tout par un exemple, où une ligne de cache fait 16 mots mémoire. Le processeur effectue une lecture, qui lit le 5ème mot mémoire. Sans ''early restart'', le processeur doit charger les 16 mots mémoire avant de faire la lecture dans le cache. Avec ''early restart'', le processeur reçoit la donnée dès que le 5ème mot mémoire est disponible. Le processeur doit attendre que les 4 premiers mots mémoire soient chargés, puis le 5ème mot mémoire arrive et est envoyé directement au processeur, il est lu directement depuis le ''line fill buffer''. Les 11 mots mémoire suivants sont ensuite chargés dans le cache pendant que le processeur fait des calculs dans son coin. Le ''critical word load'' est une optimisation de la technique précédente où le chargement de la ligne de cache commence directement à la donnée demandée par le processeur. Pour reprendre l'exemple précédent, où le processeur demande le 5ème mot mémoire sur 16, le ''critical word load'' charge le 5ème mot mémoire en premier et l'envoie au processeur, ce qui fait qu'il est chargé très rapidement. Pas besoin d'attendre que le processeur charge les 4 mots mémoire précédents comme avec l'''early restart''. Dans le détail, le processeur charge le 5ème mot mémoire en premier, puis charge les 11 suivants, et termine par les 4 mots mémoire du début. En clair, le ''critical word load'' commence par charger le mot mémoire lu, puis les blocs suivants, avant de revenir au début du bloc pour charger les blocs restants. Ainsi, la donnée demandée par le processeur sera la première disponible. Pour cela, l'organisation du tampon de remplissage de ligne est modifiée de manière à rendre cela possible. Il a une taille égale à une ligne de cache complète, qui contient elle-même plusieurs mots mémoire. Dans le ''line-fill buffer'', chaque mot mémoire est stocké avec un tag, qui indique l'adresse du mot mémoire stocké dans le ''line-fill buffer''. Le ''line-fill buffer'' est donc un cache un peu particulier, qui fonctionne comme un cache du point de vue du processeur, comme une mémoire FIFO pour les transferts avec le cache. Ainsi, un processeur qui veut lire dans le cache après un défaut peut accéder à la donnée directement depuis le tampon de remplissage de ligne, alors que la ligne de cache n'a pas encore été totalement recopiée en mémoire. ==Les caches pipelinés== Il est possible de pipeliner l'accès au cache, ce qui demande juste de rajouter des registres au bon endroits et quelques circuits de contrôle. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, même ceux avec un pipeline dynamique. Et l'implémentation peut se faire de plusieurs manières différentes. Il faut dire que découper une mémoire cache en plusieurs étapes peut se faire de plusieurs manière. Commençons par la plus simple. Beaucoup de processeurs des années 2000 avaient une fréquence peu élevée comparée aux standards d'aujourd'hui, ce qui fait que leur cache avait bien un temps d'accès d'un cycle d'horloge. Mais l'accès au cache se faisait en deux cycles d'horloge : un cycle pour calculer une adresse et un cycle pour l'accès mémoire proprement dit. Et ces deux étapes étaient pipelinées, à savoir que deux micro-opérations mémoire peuvent s'exécuter en même temps : une dans l'étage de calcul d'adresse, une autre dans le cache. L'unité mémoire était donc pipelinée, alors que l'accès au cache ne l'était pas. Les processeurs qui implémentaient cette technique regroupent les micro-architectures K5 et K6 d'AMD, les processeurs Intel de micro-architecture P6 (Pentium 2 et 3) et quelques autres. Il est aussi possible de pipeliner l'accès au cache lui-même. Avec un cache pipeliné, l'accès au cache ne se fait pas en un seul cycle, mais en plusieurs. Cependant, on peut lancer un nouvel accès au cache à chaque cycle d'horloge, comme un processeur pipeliné lance une nouvelle instruction à chaque cycle. L'implémentation est assez simple : il suffit d'ajouter des registres dans le cache. Pour cela, on profite que le cache est composé de plusieurs composants séparés, qui échangent des données dans un ordre bien précis, d'un composant à un autre. De plus, le trajet des informations dans un cache est linéaire, ce qui les rend parfois pour l'usage d'un pipeline. ===Le ''pipelining'' des caches ''direct-mapped''=== Voici ce qui se passe avec un cache directement adressé. Pour rappel ce genre de cache est conçu en combinant une mémoire RAM, généralement une SRAM, avec quelques circuits de comparaison et un MUX. L'accès au cache se fait globalement en deux étapes : la première lit la donnée et le ''tag'' dans la SRAM, et on utilise les deux dans une seconde étape. Nous avions vu il y a quelques chapitre comment pipeliner des mémoires, dans le chapitre sur les mémoires évoluées. Et bien ces méthodes peuvent s'utiliser pour la mémoire RAM interne au cache !L'idée est d'insérer un registre entre la sortie de la RAM et la suite du cache, pour en faire un cache pipéliné, à deux étages. Le premier étage lit dans la SRAM, le second fait le reste. L'implémentation sur les caches associatifs à plusieurs voies est globalement la même à quelques détails près. [[File:Cache directement adressé pipeliné - deux étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - deux étages]] Avec le circuit précédent, il est possible d'aller plus loin, cette fois en pipelinant l'accès à la mémoire RAM interne au cache. Pour comprendre comment, rappelons qu'une mémoire SRAM est composée d'un plan mémoire et d'un décodeur. L'accès à la mémoire demande d'abord que le décodeur fasse son travail pour sélectionner la case mémoire adéquate, puis ensuite la lecture ou écriture a lieu dans cette case mémoire. L'accès se fait donc en deux étapes successives séparées, on a juste à mettre un registre entre les deux. Il suffit donc de mettre un gros registre entre le décodeur et le plan mémoire. [[File:Cache directement adressé pipeliné - trois étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - trois étages]] Et on peut aller encore plus loin en découpant le décodeur en deux circuits séparés. En effet, rappelez-vous le chapitre sur les circuits de sélection : nous avions vu qu'il est possible de créer des décodeurs en assemblant des décodeurs plus petits, contrôlés par un circuit de prédécodage. Et bien on peut encore une ajouter un registre entre ce circuit de prédécodage et les petits décodeurs. : Théoriquement, toute l'adresse est fournie d'un seul coup au cache, la quasi-totalité des processeurs présentent l'adresse complète à un cache pipeliné. Mais le Pentium 4 fait autrement. Il faut noter que les premiers étages manipulent l'indice dans la SRAM, qui est dans les bits de poids faible, alors que les étages ultérieurs manipulent le ''tag'' qui est dans les bits de poids fort. Les concepteurs du Pentium 4 ont alors décidé de présenter les bits de poids faible lors du premier cycle d'accès au cache, puis ceux de poids fort au second cycle. Pour cela, l'ALU fonctionnait à une fréquence double de celle du processeur, tout comme le cache L1. Il n'y avait pas de pipeline proprement dit, mais cela réduisait grandement la latence d'accès au cache. ===Le ''pipelining'' des caches splittés/sériels=== Il est aussi possible de pipeliner un cache dits splittés, aussi appelés à accès sériel. Pour rappel, les caches à accès sériel vérifient si il y a succès ou défaut de cache, avant d'accéder aux lignes de cache en cas de succès. Ils font donc différemment des autres caches, qui accèdent à une ligne de cache, avant de déterminer s'il y a succès ou défaut en lisant le tag de la ligne de cache. Les caches sériels disposent de deux SRAM : une pour les tags des lignes de cache et une pour les données. Ils accèdent à la SRAM pour les tags, avant d’accéder à la SRAM des données en cas de succès de cache. Vu que l'accès se fait en deux étapes, une vérification des tags suivie de la lecture/écriture des données, il est facile à pipeliner. Pipeliner le cache permet de régler le problème des accès au cache L1, et elle est tout le temps utilisé sur les processeurs modernes. Mais que faire en cas de défaut de cache ? ==Les caches non bloquants== Un '''cache bloquant''' est un cache auquel le processeur ne peut pas accéder pendant un défaut de cache. Il faut attendre que la lecture ou écriture en RAM soit terminée avant de pouvoir utiliser de nouveau le cache. Un '''cache non bloquant''' n'a pas ce problème : on peut l'utiliser pendant un défaut de cache. Les caches non bloquants permettent de démarrer une nouvelle lecture ou écriture alors qu'une autre est en cours, ce qui permet d'exécuter plusieurs lectures ou écritures en même temps. ===Les ''Miss Handling Status Registers''=== Lors d'un défaut de cache, la mémoire RAM est consultée pendant le défaut de cache, mais le cache est inutilisé. Un défaut de cache n'utilise pas le cache, ce n'est qu'un accès en mémoire RAM, sur le bus mémoire (ou un accès aux niveaux de cache inférieurs, peu importe). Le cache en lui-même est laissé libre, rien n’empêche d'y accéder, il est en réalité intrinsèquement non-bloquant. Les caches, bloquants comme non-bloquants, sont en réalité composés d'une mémoire cache proprement dite, entourée de circuits qui servent d'interface entre le processeur et le cache lui-même. Et parmi les circuits tout autour, certains gèrent l'accès au cache lors d'un défaut de cache. Ils sont regroupés sous le terme de '''''Miss Handling Architecture''''' (MHA). La différence entre un cache bloquant et un cache non-bloquant est en réalité liée à l'implémentation de la MHA. Les caches bloquants coupent volontairement l'accès au cache lors d'un défaut, car les défauts sont plus simples à gérer ainsi. Le défaut de cache rapatrie une donnée depuis la RAM, qui sera écrite dans le cache. Et il ne faut pas qu'une tentative d'accès à cette donnée ait lieu avant qu'elle ne soit chargée. Mais ce blocage est général et touche tout le cache, alors que seule une ligne de cache est concernée par le défaut de cache. L'idée derrière un cache non-bloquant est que seule la ligne de cache est bloquée, mais les autres sont accessibles. L'idée est alors de mémoriser les lignes de cache concernées par le défaut de cache, afin d'en bloquer l'accès. À chaque accès, on vérifie si la ligne de cache est déjà réservée par un défaut de cache. La lecture/écriture est alors bloquée si c'est le cas, mais elle accepte les accès sinon. Pour cela, la ''Miss Handling Architecture'' contient des registres qui mémorisent des informations sur les défauts de cache en cours. Ils portent le nom de '''''miss status handling registers''''', que j’appellerais dorénavant MSHR, qui sont aussi appelés des '''''miss buffer'''''. Le contenu des MSHR varie beaucoup suivant le processeur, mais ces derniers stockent au minimum les informations suivantes : * Le numéro de la ligne de cache dans laquelle les données sont chargées. * Un bit de validité qui indique si le MSHR est vide ou pas, qui est mis à 0 quand le défaut de cache est résolu. * Un ou plusieurs champs de lecture/écriture, qui contiennent des informations sur la lecture/écriture. ** Pour une lecture, elle contient des informations sur la destination de la donnée, à savoir qui prévenir quand le défaut de cache est terminé. C'est parfois un nom/numéro de registre (celui dans lequel charger la donnée), mais c'est souvent le numéro de l'entrée dans le ''load/store queue''. ** Pour les écritures, elle contient la donnée à écrire, ou éventuellement un numéro de ''load/store queue'' où se trouve la donnée à écrire. Il faut noter que le bus mémoire ne peut gérer qu'un seul défaut de cache à la fois. Aussi, il est intéressant de regarder ce qui se passe lorsqu'un second défaut de cache survient, pendant qu'un premier est en cours. Dans ce cas, il y a deux réponses qui correspondent à deux types de caches non-bloquants, qui portent les noms barbares de caches de type succès après défaut et défaut après défaut. Sur le premier type, il ne peut pas y avoir plusieurs défauts de cache simultanés. Dès qu'un second défaut de cache survient, le cache stoppe son activité et on ne peut plus démarrer de nouvelle lecture/écriture, tant que le premier défaut de cache n'est pas résolu. Le second type est plus souple et autorise la survenue de plusieurs défauts de cache simultanés. Du moins, jusqu'à une certaine limite, car le cache ne peut supporter qu'un nombre limité d'accès mémoires simultanés (pipelinés). ===Les accès simultanés à une même ligne de cache=== Il arrive que le processeur fasse plusieurs accès mémoire simultanés à la même ligne de cache. Si la ligne de cache en question n'a pas encore été chargée dans le cache, alors on a plusieurs défauts de cache consécutifs pour la même ligne de cache, et le cache non-bloquant doit gérer la situation. Pour la suite, il va falloir faire une petite distinction entre les défauts primaires et secondaires. Imaginons qu'un défaut de cache ait lieu et demande à charger une donnée dans la ligne de cache numéro N. Il s'agit du premier défaut impliquant cette ligne de cache précise, ce qui lui vaut le nom de '''défaut de cache primaire'''. Mais par la suite, d'autres accès mémoire à la même ligne de cache ont lieu, alors que la ligne de cache n'est pas encore disponible. Dans ce cas, il s'agit de '''défauts de cache secondaires'''. Pour l'unité d'accès mémoire, les défauts de cache primaire et secondaire sont différents (ils prennent tous une entrée dans la ''load/store queue''). Mais pour le cache, ils ne correspondent qu'à un seul accès au cache : celui qui demande de charger la ligne de cache demandée. Les défauts de cache primaire et secondaire à la même ligne de cache se voient attribuer un MSHR unique. La gestion des défauts de cache secondaires dépend du cache non-bloquant. La solution la plus simple ne permet pas les défauts de cache secondaires. Le cache ne permet pas deux défauts de cache simultanés pour la même ligne de cache. Les autres solutions le permettent, en fusionnant des accès simultanés à la même ligne de cache en un seul au niveau des MSHR. Dans tous les cas, détecter les défauts de cache secondaires sont un problème qu'il faut détecter. La ''Miss Handling Architecture'' doit détecter les défauts de cache secondaire. Pour cela, elle procède comme suit. Lors de chaque défaut de cache, la MHA récupère le numéro de la ligne de cache associé. Il vérifie alors chaque MSHR pour vérifier s'il contient le numéro en question. S'il n'y a aucune correspondance dans les MSHR, alors c'est signe que le défaut de cache est un défaut primaire. Mais s'il y en a une, alors c'est un défaut secondaire. Évidemment, cela signifie que lors d'un défaut de cache, le numéro de ligne de cache est envoyé à tous les MSHR, pour comparaison. Les MSHR sont donc regroupés dans une mémoire associative, une sorte de mini-cache, faciliter l'implémentation. Lors d'un défaut de cache primaire, l'accès à un cache non-bloquant se fait comme suit : le processeur envoie une adresse au cache, accède à celui-ci, et détecte la survenue d'un défaut de cache. Il en profite alors pour attribuer une ligne de cache dans laquelle sera chargée ce défaut. L'attribution est très simple dans le cas des caches ''direct mapped'', ou associatifs par voie, pour lesquels l'attribution se fait assez simplement. Il mémorise alors cette information dans les MSHR, après avoir vérifié que le défaut de cache n'était pas un défaut secondaire. Lors des accès ultérieurs à une adresse proche, censée être dans la même ligne de cache, le processeur va encore une fois rencontrer un défaut de cache. Il va alors déterminer le numéro de la ligne de cache associée à l'adresse, et comparer ce numéro avec les MSHRs. Si un MSHR contient ce numéro, c'est signe que le défaut de cache est un défaut secondaire. La MHA réagit alors différemment selon le processeur considéré. Une première solution n'autorise pas les défauts de cache secondaires. Si l'un d'entre eux survient, le processeur est gelé par un ''pipeline stall'', une bulle de pipeline. Une autre solution fusionne les défauts de cache secondaires avec le défaut de cache primaire : tout cela ne correspond qu'à un seul défaut de cache pour lui. ===Les MSHR simples=== Un cache non-bloquant à '''MSHR simple''' contient juste plusieurs MSHR qui mémorisent juste un numéro de ligne de cache, un bit de validité, le champ de lecture/écriture, et l'adresse exacte de lecture/écriture. Avec cette organisation, il est possible d'avoir plusieurs défauts de cache séparés, mais à la condition que chaque défaut accède à une ligne de cache différente. Deux accès simultanés à une même ligne de cache ne sont pas possibles, les défauts de cache secondaires ne sont pas autorisés. Ainsi, chaque défaut de cache se voit attribuer son propre MSHR, chacun contient un numéro de ligne de cache différent. Pour comprendre pourquoi c'est impossible de gérer les défauts secondaires, il faut regarder le champ de lecture/écriture. Si on veut effectuer plusieurs écritures consécutives à la même adresse, le MSHR n'aura pas de quoi mémoriser les deux données à écrire. Il pourra mémoriser la première donnée à écrire, pas la seconde. Même chose lors d'une lecture : le champ lecture/écriture peut mémorisr la destination de la première lecture, pas de la seconde. L'avantage est que la MHA n'a besoin que des MSHR et de quelques circuits annexes. Les autres solutions rajoutent des circuits annexes pour gérer les défauts de cache secondaires, qui utilisent beaucoup de circuits. Le cout en circuit est donc élevé, mais le gain en performance est là. Passons maintenant aux caches non-bloquants qui autorisent les défauts de cache secondaire. La solution la plus simple consiste à utiliser ===Les MSHR adressés implicitement=== Avec les '''MSHR adressés implicitement''', il est possible de fusionner plusieurs accès mémoire à une même ligne de cache, mais sous une condition très importante : ces accès lisent/écrivent des mots mémoire différents. Par exemple, imaginons qu'une ligne de cache contienne 8 mots mémoire de 64 bits. Si un premier accès mémoire lit le mot mémoire numéro 7 (dernier mot mémoire de la ligne), et le second accès le mot mémoire numéro 3, alors la fusion est possible. Mais si deux accès mémoire veulent lire/écrire le mot mémoire numéro 7, alors la fusion n'est pas possible et le processeur se bloque, un ''pipeline stall'' survient. Un MSHR adressé implicitement est un MSHR simple, qui contient naturellement un numéro de ligne de cache (tag) et un bit de validité, sauf que le champ de lecture/écriture est dupliqué. Les différents champs lecture/écriture d'un MSHR sont regroupés dans une mémoire RAM/cache qui contient autant d'entrées qu'il y a de mot mémoire dans une ligne de cache. Chaque entrée de la table est associée à un mot mémoire de la ligne de cache et stocke des informations sur celui-ci. Une entrée mémorise, au minimum : * Un champ de lecture/écriture qui contient soit la destination de la lecture, soit la donnée à écrire. * Un bit R/W permettant d'interpréter correctement le champ de lecture/écriture. * Un bit de validité pour chaque entrée de la table, qui dit si un défaut de cache antérieur accède déjà à ce mot mémoire. La fusion de deux défauts de cache est ainsi assez simple. Un défaut de cache primaire/secondaire configure l'entrée associée au mot mémoire lu/écrit. Si un défaut secondaire ultérieur lit/écrit un mot mémoire différent (dont le bit de validité de l'entrée dans la table est à zéro), pas de conflit : il configure une autre entrée, vide. Mais s'il lit/écrit un mot mémoire pour lequel l'entrée de la table est occupée, il y a conflit, le processeur est bloqué par un ''pipeline stall'', une bulle de pipeline. Avec cette organisation, le nombre de MSHR indique combien de lignes de cache peuvent être lues en même temps depuis la mémoire. Quant au nombre d'entrées par MSHR, il détermine combien d'accès mémoires qui ne se recouvrent pas peuvent avoir lieu en même temps. ===Les MSHR adressés explicitement=== Les '''MSHR adressés explicitement''' n'ont pas les contraintes des MSHR adressés implicitement. Avec eux, il est possible d'avoir plusieurs défauts de cache pointant vers la même ligne de cache, mais aussi vers le même mot mémoire. De tels défauts de cache apparaissent sur les processeurs à exécution dans le désordre, mais sont très rares, voire inexistants, sur les processeurs ''in-order''. L'idée est encore que chaque MSHR est associée à une table mémoire qui mémorise des entrées. Cette table n'est autre qu'une mémoire FIFO. Sauf que cette fois-ci, une entrée n'est pas associée à un mot mémoire. Une entrée est associée à un défaut de cache. Une entrée mémorise là encore la destination de la lecture ou la donnée de l'écriture suivi du bit R/W, ainsi que d'un bit de validité par entrée, mais aussi : la position du mot mémoire lu dans la ligne de cache. C'est cette dernière information qui n'était pas présente dans les MSHR adressés implicitement. De plus, le nombre d'entrée par MSHR n'est pas égal au nombre de mots mémoires dans une ligne de cache, mais peut être arbitrairement grand. L'avantage d'un tel cache est qu'il est capable de traité les défauts secondaires concernant un même mot mémoire, et ce, car les accès à la ligne de cache concernée se produisent dans l'ordre de génération des défauts (la table mémoire est une FIFO). ===Les MSHRs inversés=== Généralement, plus on veut supporter de défauts de cache, plus le nombre de MSHR et d'entrées augmente. Mais au-delà d'un certain nombre d'entrées et de MSHR, les MSHR adressés implicitement et explicitement ont tendance à bouffer un peu trop de circuits. Utiliser une organisation un peu moins gourmande en circuits est donc une nécessité. Cette organisation plus économe se base sur des MSHR inversés. Les MSHR inversés ne contiennent qu'une seule entrée, en quelque sorte : au lieu d’utiliser n MSHR de m entrées chacun, on va utiliser n × m MSHR inversés. La différence, c'est que plusieurs MSHR peuvent contenir un tag identique, contrairement aux MSHR adressés implicitement et explicitement. Lorsqu'un défaut de cache a lieu, chaque MSHR est vérifié. Si jamais aucun MSHR ne contient de tag identique à celui utilisé par le défaut, un MSHR vide est choisi pour stocker ce défaut, et une requête de lecture en mémoire est lancée. Dans le cas contraire, un MSHR est réservé au défaut de cache, mais la requête n'est pas lancée. Quand la donnée est disponible, les MSHR correspondant à la ligne qui vient d'être chargée vont être utilisés un par un pour résoudre les défauts de cache en attente. ===Les MSHR intégrés au cache=== Certains chercheurs ont remarqué que pendant qu'une ligne de cache est en train de subir un défaut de cache, celle-ci reste inutilisée, et son contenu est destiné à être perdu une fois le défaut de cache résolu. Ils se sont dit que, plutôt que d'utiliser des MSHR séparés, il vaudrait mieux utiliser la ligne de cache pour stocker les informations sur les défauts de cache en attente dans cette ligne de cache. Pour éviter tout problème, il faut rajouter un bit dans les bits de contrôle de la ligne de cache, qui sert à indiquer que la ligne de cache est occupée : un défaut de cache a eu lieu dans cette ligne, et elle stocke donc des informations pour résoudre les défauts de cache. ==L'entrelacement mémoire== Nous venons de voir comment une mémoire cache peut gérer plusieurs accès mémoire simultanés. Il se trouve que ce genre d'optimisation dépasse largement le cadre du cache. Les mémoires RAM ne sont pas en reste, elles aussi ! Il est possible de pipeliner l'accès à une mémoire RAM, en utilisant des techniques dites d''''entrelacement'''. Précisons cependant que cette optimisation n'est pas utilisée sur les mémoires SDRAM ou DDR modernes, du moins pas sans modifications majeures. Mais divers ordinateurs assez anciens, dont des superordinateurs, ont utilisé cette technique. La technique demande d'utiliser plusieurs mémoires séparées. Sur les anciens ordinateurs, les mémoires en question sont des chips mémoire, à savoir des circuits intégrés de DRAM. Pour faire plus général, nous allons utiliser le terme de '''banques''', ou encore de bancs mémoire. La différence est qu'il existe des mémoires multi-banque, qui regroupent plusieurs banques indépendantes dans un seul boitier, dans un seul chip mémoire. En clair, de telles mémoires regroupent plusieurs sous-mémoires dans un seul circuit intégré. Il est aussi possible d'utiliser plusieurs chips mémoire séparés, chacun ayant une banque. Reste à combiner ces banques pour former un pseudo-pipeline. ===Utiliser plusieurs banques sans entrelacement=== Sans optimisation particulière, les adresses sont réparties dans les banques comme indiqué ci-dessous. Il s'agit de ce qui s'appelle un arrangement horizontal, et nous avions vu celui-ci dans le chapiutre sur les mémoires SDRAM. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Avec cet arrangement, les bits de poids fort de l'adresse sont utilisées pour sélectionner la banque adéquate, et le reste de l'adresse est envoyé sur le bus d'adresse. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Adresse de banque !! Adresse dans la banque |- | Quelques bits de poids fort || Reste de l'adresse |} L'entrelacement change cette répartition, afin d'accélérer les accès mémoire. L'entrelacement classique vise à améliorer les accès à des adresses mémoires consécutives, mais des formes plus évoluées d'entrelacement visent à optimiser des accès mémoire arbitraires. Voyons-les dans l'ordre, du plus simple au plus complexe. ===L'entrelacement classique=== Avec l''''entrelacement classique''', des adresses consécutives sont réparties dans des banques consécutives. Précisons que cette attribution des adresses n'implique absolument pas la mémoire virtuelle ou n'importe quel mécanisme dans le processeur. La répartition décide que telle adresse mémoire va dans telle banque, à telle adresse dans la banque. Elle est donc le fait du contrôleur mémoire, donc en dehors du processeur (les contrôleurs mémoires n’étaient pas encore intégrés dans le processeur à l'époque). [[File:Répartition des adresses dans une mémoire interleaved.png|centre|vignette|upright=1.5|Répartition des adresses dans une mémoire interleaved.]] L'entrelacement simple permet d'accélérer les accès mémoire qui se font à des adresses consécutives. Avec l'entrelacement, chaque accès mémoire tombe dans une banque mémoire différente, ce qui fait qu'on peut démarrer un nouvel accès mémoire à chaque cycle d'horloge. Pendant qu'une banque est occupée par un accès mémoire, on démarre le suivant dans une autre banque, et ainsi de suite. Le tout se termine soit quand on a épuisé toutes les banques libres, soit quand l'accès en rafales se termine. Pas besoin d'attendre que la mémoire ait fini sa lecture/écriture avant de démarrer la lecture/écriture suivante. [[File:Pipemining mémoire.png|centre|vignette|upright=2.5|Pipelining mémoire]] L'animation suivante illustre bien le processus, avec quatres banques. [[File:Interleaving.gif|centre|vignette|upright=2.5|Pipelining mémoire]] L'entrelacement simple utilise les bits de poids faible pour sélectionner la banque, et les bits de poids fort pour la case mémoire. [[File:Adresse mémoire d'une mémoire entrelacée.png|centre|vignette|upright=2|Adresse mémoire d'une mémoire entrelacée]] Précisons que le temps d'accès mémoire ne change pas beaucoup avec l'entrelacement. Par contre, on peut faire plus d'accès mémoire simultanés. On peut démarrer un accès mémoire par cycle d'horloge, mais l'accès en lui-même prend plusieurs cycles. Les mémoires à entrelacement ont donc un débit supérieur aux mémoires qui ne l'utilisent pas. L'entrelacement classique pourrait être utilisé pour accélérer les transferts entre mémoire RAM et cache, qui se font en blocs d'adresses consécutives. Mais de nos jours, il n'est pas utilisé dans cette optique. Il faut dire qu'il est redondant avec le mode rafale des mémoires DRAM. Les deux font globalement la même chose et on voit mal comment utiliser les deux en même temps. Le mode rafale étant plus simple à implémenter, et plus léger niveau utilisation du bus de commande mémoire, il est préféré à l'entrelacement. Par contre, l'entrelacement a eu son heure de gloire sur d'anciens processeurs qui n'avaient pas de mémoire cache, alors qu'ils disposaient d'un pipeline. L'exemple typique est celui des nombreux ''processeurs vectoriels'', que nous n'avons pas encore abordé à ce stade du cours. De tels processeurs avaient un pipeline, une unité de calcul fortement pipelinée, et parfois même de l'exécution dans le désordre et du renommage de registres ! ===L'entrelacement par décalage=== L'entrelacement simple est très utile pour les accès en rafle ou équivalents. Par contre, il existe de rares situations où il n'est pas optimal. Une de ces situations est celle des accès en enjambée (en ''stride''), où l'on accède en série à des adresses sont séparées par N mots mémoires. [[File:Accès par enjambées.png|centre|vignette|upright=2|Accès par enjambées.]] De tels accès surviennent quand un logiciel accède à des structures de données spécifiques, à savoir des tableaux de structures, des matrices, ou d'autres structures de données dans le genre. Un cas classique est celui du parcours d'une matrice colonne par colonne. Les matrices de nombres sont mémorisées ligne par ligne, ce qui fait que parcourir une colonne demande de passer d'une ligne à la suivante et de faire des grands sauts en mémoire RAM, mais tous espacés par la même distance. Avec l'entrelacement simple, les accès en enjambée sont moins performants que les accès à des adresses consécutives, mais cela ne fonctionne pas trop mal. Le pire cas est celui où l'on a N banques et où les données sont justement placées toutes les N adresses. Des accès consécutifs vont tous tomber dans la même banque, on ne peut plus accéder à des banques différentes en parallèle. Mais en dehors de ce cas, on voit une amélioration par rapport à la situation sans entrelacement. Pour obtenir des performances maximales pour les accès en enjambées, il faut répartir les mots mémoires dans la mémoire autrement. L'organisation idéale est la suivante. : Dans les explications qui vont suivre, la variable N représente le nombre de banques, qui sont numérotées de 0 à N-1. On commence par organiser les N premières adresses comme une mémoire entrelacée simple : l'adresse 0 correspond à la banque 0, l'adresse 1 à la banque 1, etc. Pour le bloc suivant, on décale tout d'une adresse, à savoir qu'on commence à remplir les banques à partir de la seconde, non de la première. Une fois la fin du bloc atteinte, on finit de remplir le bloc en repartant du début du bloc. Le troisième bloc d'adresse subit le même traitement, sauf qu'on commence à remplir à partir de la seconde banque. Et on poursuit l’assignation des adresses en décalant d'un cran en plus à chaque bloc. Ainsi, chaque bloc verra ses adresses décalées d'un cran en plus comparé au bloc précédent. Si jamais le décalage dépasse la fin d'un bloc, alors on reprend au début. [[File:Mémoire interleaved par décalage.png|centre|vignette|upright=1.5|Mémoire entrelacée par décalage.]] En faisant cela, on remarque que les banques situées à N adresses d'intervalle sont différentes. Dans l'exemple du dessus, nous avons ajouté un décalage de 1 à chaque nouveau bloc à remplir. Mais on aurait tout aussi bien pu prendre un décalage de 2, 3, etc. Dans tous les cas, on obtient un entrelacement par décalage. Ce décalage est appelé le pas d'entrelacement, noté P. Le calcul de l'adresse à envoyer à la banque, ainsi que la banque à sélectionner se fait en utilisant les formules suivantes : * adresse à envoyer à la banque = adresse totale / N ; * numéro de la banque = (adresse + décalage) modulo N ; * décalage = (adresse totale * P) mod N. Avec cet entrelacement par décalage, on peut prouver que la bande passante maximale est atteinte si le nombre de banques est un nombre premier. Seulement, utiliser un nombre de banques premier peut créer des trous dans la mémoire, des mots mémoires inadressables. Malgré ce défaut, la technique a été utilisée sur quelques ordinateurs, avec l'exemple notable du superordinateur ''Burroughs Scientific Processor''. Pour éviter cela, il y a plusieurs solutions. Par exemple, on peut faire en sorte que N et la taille d'une banque soient premiers entre eux : ils ne doivent pas avoir de diviseur commun. Mais en pratique, elles n'ont pas vraiment été implémentées dans une vraie machine, et sont restées à l'état de recherche, aussi je les passe sous silence. ===L'entrelacement pseudo-aléatoire=== Une dernière méthode de répartition consiste à répartir les adresses dans les banques de manière "pseudo-aléatoire". L'idée exacte est de faire passer l'adresse dans une '''fonction de hachage''', plus ou moins complexe. Pour rappel, une fonction de hachage prend une entrée de grande taille et fournit en sortie un résultat de petite taille. Idéalement, elles doivent donner des résultats aussi différents que possible pour des entrées similaires, histoire de simuler une sorte de pseudo-aléatoire. Ici, une adresse est transformée en un numéro de banque. Une fonction de hachage simple ne fait que permuter des bits de l'adresse pour obtenir son résultat. Elle ne fait qu'échanger des bits de place avant de couper l'adresse en deux morceaux : un pour la sélection de la banque, et un autre pour la sélection de l'adresse dans la banque. Cette permutation est fixe, et ne change pas suivant l'adresse. Des fonctions de hachage plus complexes font des XOR bit à bit entre certains bits de l'adresse. ==Les contrôleurs mémoires SDRAM/DDR optimisés== Après avoir vu le cas des mémoires RAM, nous allons nous concentrer sur le cas particulier des mémoires SDRAM. Les mémoires SDRAM et DDR modernes sont capables de gérer plusieurs accès simultanés à la mémoire RAM, et leurs contrôleurs mémoire en font tout autant. C'est une différence majeure avec les mémoires asynchrones FPM/EDO, qui n'acceptaient qu'un seul accès mémoire à la fois. Leur contrôleur mémoire n'acceptait qu'un seul accès mémoire à la fois, c'était un contrôleur mémoire bloquant. Les contrôleurs mémoires des SDRAM sont eux non-bloquants et peuvent encaisser une dizaine d'accès mémoire à la fois. Peu de choses sont connues sur les contrôleurs de SDRAM/DDR modernes, les fabricants ne donnant que peu de détails dessus. Les rares simulateurs qui tentent de décrire leur fonctionnement, comme DRAM SIM I et II, sont particulièrement simples et ne vont pas dans le détail. Néanmoins, le peu qu'on sait est tout de même instructif. ===Rappel sur les SDRAM : tampon de ligne et commandes=== Les mémoires SDRAM sont des mémoires à tampon de ligne. Elles font un accès mémoire en deux étapes. La première étape recopie une ligne de N * 64 bits dans un tampon interne à la SDRAM. La seconde étape sélectionne une colonne, à savoir une donnée de 64 bits, qui est soit envoyée sur le bus de données pour une lecture, soit modifiée par une écriture. [[File:Mémoire à tampon de ligne.png|centre|vignette|upright=2|Mémoire à tampon de ligne]] Les accès mémoire sont traduits par un séquenceur mémoire en une série de commandes mémoires, qui sont séparées par des délais mémoire de quelques cycles d'horloge. Les délais sont très précis, et sont à respecter à la lettre. Une lecture ou une écriture se fait en maximum trois commandes : une commande PRECHARGE qui ferme la ligne précédemment utilisée, une commande ACT qui précise l'adresse de la ligne, et une commande READ ou WRITE qui précise l'adresse de la colonne et éventuellement la donnée à écrire. : Pour être plus précis, la commande PRECHARGE précharge les lignes de bits du plan mémoire à une tension particulière, ce qui les vide de leur contenu. Mais c'est un détail sans importance pour ce qui va suivre. Les SDRAM permettent de se passer de la première étape si des accès consécutifs se font dans la même ligne. Une fois activée, la ligne reste ouverte et on peut accéder plusieurs fois de suite dedans. On a alors juste à préciser l'adresse de colonne dedans. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | bgcolor="#FFA0FF" | PRECHARGE || bgcolor="#FFA0FF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || bgcolor="#A0FFFF" | READ (2) || bgcolor="#A0FFFF" | READ (3) || || || bgcolor="#A0FFFF" | READ (4) || bgcolor="#A0FFFF" | READ (5) || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | Donnée READ 1 || ||bgcolor="#A0FFFF" | Donnée READ 2 | bgcolor="#A0FFFF" | Donnée READ 3 || || || bgcolor="#A0FFFF" | Donnée READ 4 || bgcolor="#A0FFFF" | Donnée READ 5 |} Les SDRAM sont parfois capables de démarrer une commande avant que la précédente soit terminée. Mais le respect des délais mémoire est très limitant, ce qui fait qu'on ne peut pas parler de réel pipeline, comme c'est le cas sur les processeurs. Parlons plutot de '''commandes anticipées'''. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | || bgcolor="#A0FFFF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || || || bgcolor="#FFA0FF" | READ (2) || || || || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 | bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 | |} Un dernier point est que les chips mémoires de SDRAM sont composés de plusieurs banques, chacune étant une sorte de mini-mémoire miniature. Chaque banque a son propre décodeur, son propre tampon de ligne, ses propres multiplexeurs de colonne, sa logique de rafraichissement mémoire, etc. C'est comme si une SDRAM regroupait plusieurs mémoires séparées dans un même circuit intégré. [[File:Arrangement vertical.jpg|centre|vignette|upright=2.5|Mémoire multi-banques.]] ===La mise en attente des accès mémoire=== Un contrôleur de SDRAM peut accepter plusieurs accès mémoire en même temps. Mais cela ne signifie pas que le contrôleur sera prêt à les traiter immédiatement. Pour éviter tout problème de disponibilité, le contrôleur met en attente les accès mémoire que le processeur lui envoie, pour les exécuter dès que possible. Les accès mémoire sont mis en attente dans une mémoire FIFO, histoire de les exécuter dans leur ordre d'arrivée, notamment pour renvoyer les lectures dans l'ordre demandé. Il y a aussi une mémoire FIFO pour les données à écrire et une FIFO pour les données lues. Cette dernière sert au cas où le cache ne soit pas disponible quand on lui envoie la donnée lue. [[File:Module d'interface avec la mémoire.png|centre|vignette|upright=2.5|Contrôleur mémoire avec mise en attente des requêtes processeur et autres optimisations.]] Des optimisations sont possibles dès la mémoire FIFO. Par exemple, on peut regrouper plusieurs accès à des données consécutives en un seul accès en rafale. Le contrôleur mémoire analyse les requêtes mises en attente et détecte si certaines se font à des adresses consécutives. Si c'est le cas, il fusionne ces requêtes en une lecture/écriture en rafale. Une telle optimisation est appelée la '''combinaison de lecture''' pour les lectures, et la '''combinaison d'écriture''' pour les écritures. Une autre optimisation possible est le réacheminement lecture sur écriture (''Store to load forwarding''). Il s'agit d'un équivalent de l’optimisation utilisée dans le cadre de la désambiguïsation mémoire. Quand une lecture demande à accéder à une donnée écrite récemment, il se peut que l'écriture ait été mise en attente dans le contrôleur mémoire. Dans ce cas, le contrôleur mémoire peut renvoyer la donnée directement depuis ses mémoires FIFOs, sans faire d'accès mémoire en lecture. Les deux optimisations précédentes ne paraissent pas payer de mine, mais elles deviennent plus compréhensible quand on sait que certains contrôleurs mémoire peuvent mettre en attente beaucoup de données. Par exemple, l'''Intel E8870 - ''Scalable Memory Controller'' peut mettre en attente 64 lignes de cache dans la mémoire FIFO, soit 8 kibioctets de RAM ! Deux pages mémoire si la mémoire virtuelle utilise des pages de 4 kibioctets ! ===Les optimisations liées aux commandes anticipées=== Les commandes anticipées permettent de faire un minimum de parallélisme mémoire. Même si elle parait très limitée, cette possibilité est décuplée par la présence de plusieurs banques dans la mémoire SDRAM. Il est en effet possible de faire plusieurs accès mémoire en même temps, dans des banques différentes. Il est possible de lancer un accès mémoire dans deux banques en même temps, par exemple. La seule contrainte est que la SDRAM est limitée à 4 banques actives en même temps. {|class="wikitable" |+ Pipelining basique sur les SDRAM |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 |- ! Banque Numéro 1 | || || || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || || |- ! Banque Numéro 2 | || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || |- ! Banque Numéro 3 | || || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || |} L'implémentation n'a rien de vraiment compliqué. Il suffit d'utiliser une mémoire FIFO par banque. Ainsi, les accès dans une même banque se feront en série, dans l'ordre d'arrivée, mais des accès à des banques différentes se feront dans le désordre. Cela ne pose pas de problème car les accès se font à des adresses différentes, ce qui fait qu'il n'y a pas de conflits majeurs. [[File:Gestion parallèle des banques.png|centre|vignette|upright=2.5|Gestion parallèle des banques.]] ===Le ré-ordonnancement des commandes mémoires=== Un contrôleur mémoire moderne est capable de changer l'ordre des requêtes mémoires, pour gagner en performances. En clair : il peut faire l'équivalent mémoire de l'exécution dans le désordre des processeurs haute performance. Une différence majeure est que le contrôleur mémoire ne remet pas les accès mémoire dans l'ordre avant de les envoyer au processeur. C'est le processeur qui remet en ordre les accès mémoire, dans sa ''load queue''. Pour cela, les requêtes mémoire sont "numérotées", à savoir qu'on leur attribue un identifiant binaire. Le contrôleur mémoire exécute les lectures/écritures dans le désordre, mais garde la trace de leur identifiant. Il sait dans quel ordre il exécute les accès mémoire et connait leur latence. Ce qui fait qu'il sait que la donnée lue à tel instant est associée à la requête mémoire numéro X. Pour les lectures, il renvoie la donnée lue avec l'identifiant. Le processeur sait alors à quelle lecture la donnée lue correspond et il se débrouille en interne pour gérer la situation. En clair : le processeur voit les accès mémoire dans le désordre, c'est lui qui les remet en ordre. Maintenant que ces précisions sont faites, posons cette question : quel est l'intérêt de faire les accès mémoire dans le désordre ? Accéder à des banques en parallèle est une réponse possible, mais le ré-ordonnancement est surtout utile si tous les accès mémoire atterrissent dans la même banque et dans la même ligne. Elle marche si plusieurs accès à une même ligne ne sont pas consécutifs, et qu'il y a des accès entre les deux. Après ré-ordonnancement, ces accès mémoire sont exécutés l'un à la suite de l'autre. Pour rendre le tout plus concret, voici un exemple. Imaginez que l'on sait les 3 accès mémoire suivants : * Une lecture ligne A ; * Une écriture ligne B ; * Une lecture ligne A. Sans réordonnancent, on doit émettre deux commandes PRECHARGE : une quand on passe de la ligne A à la ligne B, et une autre pour le passage inverse. pour éviter cela, le contrôleur mémoire peut retarder l'écriture, ou au contraire avancer la seconde lecture. le résultat est alors le suivant : * Lecture ligne A ; * Lecture ligne A ; * Une écriture ligne B. On a donc deux accès consécutifs à la même ligne, suivi par une écriture dans une autre ligne. La technique marche parce que l'on a un mix adéquat de lectures et d'écritures. Mais il existe des possibilités de réorganisation autres qui ne sont pas valides. Il faut par exemple prendre garde à éviter d'intervertir lectures et écritures à une même adresse, sous peine de problèmes. Encore une fois, il faut faire attention aux dépendances de données, encore une fois ! Ici des dépendances mémoires, mais les règles sont les mêmes. Le contrôleur mémoire teste les dépendances mémoires avant d'envoyer des commandes à la mémoire DDR/SDRAM. Il a existé des contrôleurs mémoires du temps des vieilles DDR qui implémentaient un ordonnancement très simples. Prenons l'exemple des coprocesseurs IO 81341 et 81342, qui étaient en réalité des ''chipsets'' intégrant un contrôleur SDRAM. Le ''chipset'' avait 5 ports : un pour les processeurs, un pour le pont sud (''southbridge'') et trois pour des canaux DMA. Pour le premier, il n'y avait pas de réordonnancement. Pour les autres, les lectures se font dans l'ordre, les écritures se font dans l'ordre, mais les écritures peuvent passer avant une lecture non-dépendante. L'implémentation était assez simple : le séquenceur était précédé par plusieurs mémoires FIFO. Le port processeur avait une seule mémoire FIFO, ce qui conservait l'ordre d'exécution des commandes mémoire. Pour les autres ports, le processeur avait vraisemblablement des files séparées pour les lectures et écritures. Le séquenceur mémoire vérifiait les dépendances mémoire de type RAW et autres. De nos jours, les contrôleurs mémoires sont beaucoup plus évolués et sont capables d'exécution dans le désordre bien plus poussée. ==L'entrelacement sur les mémoires SDRAM== L''''entrelacement''' fonctionne sur les mémoires SDRAM, mais il doit être fortement modifié. Concrètement, tout ce qui a étré dit plus est inapplicable pour les SDRAM, du fait de la présence du tampon de ligne. Des adresses consécutives atterrissent déjà dans la même ligne, ce qui fait qu'on n'a pas besoin d'entrelacement au niveau d'une ligne. Et ne parlons pas de ce la présence de rangées et de canaux mémoire ! Cependant, il peut y avoir un entrelacement lié à la présence des banques SDRAM. L'entrelacement est géré juste avant le séquenceur mémoire. Le '''circuit d'entrelacement''' intervertit certains bits de l'adresse lors des accès mémoires. On trouve aussi des mémoires FIFOs pour mettre en attente les requêtes mémoire provenant du processeur. Elles permettent de recevoir plusieurs accès mémoire consécutifs. Si vous vous demandez comment plusieurs accès mémoire consécutifs peuvent avoir lieu, sachez que c'est une bonne question. Pour le moment, nous allons dire que ces accès proviennent du processeur ou de la mémoire cache, sans expliquer comment. Le contrôleur mémoire reçoit une série d'accès mémoire assez rapprochés, qu'il doit exécuter au mieux. [[File:Controleur mémoire d'une SDRAM avec entrelacement.png|centre|vignette|upright=2.5|Contrôleur mémoire d'une SDRAM avec entrelacement.]] Pour gérer l'entrelacement, le contrôleur de SDRAM prend en entrée l'adresse envoyée par le processeur, et la découpe en plusieurs champs : un pour sélectionner la banque adéquate, un autre pour la ligne, un autre pour la colonne, un autre pour la rangée. Une solution simple désactive l'entrelacement. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de banque || Adresse de ligne || Adresse de colonne |} Si cette solution fonctionne bien si les accès mémoire sont assez aléatoires, en pratique, mieux vaut utiliser l'entrelacement. ===L'entrelacement de banques/lignes=== Une première méthode d'entrelacement est l''''entrelacement de banques'''. Elle répartit deux lignes consécutives dans deux banques différentes. Pour comprendre l'idée, prenons un exemple. Imaginons une mémoire avec deux banques et 4 lignes. Imaginons qu'on parcoure/balaye la mémoire RAM en partant des adresses basses. Sans entrelacement de ligne, les accès se feront comme suit, de gauche à droite : {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Banque numéro 1 !! colspan="4" | Banque numéro 2 |- | Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 || Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 |} Avec l'entrelacement de banques, les accès se feront comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 |- | Ligne 1 || Ligne 1 || Ligne 2 || Ligne 2 || Ligne 3 || Ligne 3 || Ligne 4 || Ligne 4 |} Avec N banques, la première ligne ira dans la première banque, la seconde ligne dans la seconde, et ainsi de suite jusqu'à la énième banque qui va dans la banque numéro N. Et la ligne N+1 repart dans la première banque. L'avantage est que passer d'une ligne à la suivante est assez rapide. Au lieu de devoir fermer la première ligne et ouvrir la suivante on ouvrira directement la ligne suivante dans une banque différente, ce qui sera plus rapide. La commande PRECHARGE pourra être envoyée à la seconde banque en avance. Pour cela, l'adresse est découpée comme suit. {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne (précise à l'octet) |} ===L'entrelacement de rangées=== Il est aussi possible de faire la même chose, mais avec les rangées. Pour simplifier fortement, une rangée est simplement un chip mémoire. En réalité, les rangées ne sont pas des chip mémoire, mais un ensemble de chips mémoire regroupés ensemble histoire d'atteindre les 64 bits du bus de données. Par exemple, une rangée peut combiner 8 chips mémoire avec un bus de données de 8 bits chacun pour obtenir les 64 bits du bus de données d'une SDRAM. Mais nous passons ce détail sous silence dans les explications qui vont suivre, par souci de simplification. Pour faire comprendre l'entrelacement de rangée, le mieux est d'utiliser un exemple, le même que précédemment. Sans entrelacement de rangée, on change de chip mémoire une fois qu'on a balayé toutes ses adresses. {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Rangée numéro 1 !! colspan="4" | Rangée numéro 2 |- | Banque 1 || Banque 2 || Banque 3 || Banque 4 || Banque 1 || Banque 2 || Banque 3 || Banque 4 |} Avec l'entrelacement de ligne, on change de rangée dès qu'on change de banque/ligne. {|class="wikitable" |+ Adresse mémoire |- ! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 |- | Banque 1 || Banque 1 || Banque 2 || Banque 2 || Banque 3 || Banque 3 || Banque 4 || Banque 4 |} En clair, quand on a épuisé toutes les banques dans une même rangée, on passe à la rangée suivante au lieu de rester dans la même rangée. Notons que dans l'exemple précédent, on a combiné l'entrelacement de rangée et de banque, mais on aurait pu utiliser l'entrelacement de rangée seul. Mais ce n'est pas le cas le plus courant en pratique. Toujours est-il qu'en combinant entrelacement de rangée et de ligne, le découpage de l'adresse est le suivant : {|class="wikitable" |+ Adresse mémoire |- | Adresse de ligne || Numéro de rangée || Adresse de banque || Adresse de colonne (précise à l'octet) |} D'autres méthodes d'entrelacement sont possibles, mais elles sont plus complexes. Chaque contrôleur mémoire fait un peu à sa sauce. Ils permettent en général de choisir entre plusieurs entrelacements. Au minimum, ils permettent de désactiver l'entrelacement, d'activer un entrelacement de ligne, et d'autres entrelacements plus complexes, dont certains sont propriétaires et tenus secrets par le fabricant. ===L'entrelacement avec le ''dual channel''=== [[File:Dual channel slots.jpg|vignette|Slots mémoires avec ''dual channel''.]] L'usage du ''dual channel'' complique encore l'entrelacement. Là encore, il y a deux grandes solutions : avec et sans entrelacement des canaux mémoire. Rappelons le principe : deux barrettes de RAM sont accédées en parallèle. Et pour cela, il faut utiliser l'entrelacement. Typiquement, chaque barrette mémoire fournit 64 bits, ce qui fait que l'on peut accéder à 128 bits d'un seul coup, par exemple avec un accès en rafale. Sans ''dual channel'', la première barrette correspond à la moitié haute de la RAM, la seconde barrette correspond à la moitié basse. Avec ''dual channel'', une forme spécifique d'entrelacement est activée. Concrétement, deux blocs de 64 bits sont placés dans des canaux mémoire séparés. Idem avec du triple ou quadruple canal, mais c'est alors trois ou quatre blocs de 64 bits qui sont dispersés dans des canaux consécutifs. Le découpage de l'adresse est alors le suivant : {|class="wikitable" |+ Adresse mémoire |- ! colspan="6" | Sans entrelacement |- | Numéro de canal/barrette || Reste de l'adresse || Position de l'octet dans un bloc de 64 bits |- | colspan="6" | |- ! colspan="6" | Avec entrelacement |- | Reste de l'adresse || Numéro de canal/barrette || Position de l'octet dans un bloc de 64 bits |} [[File:Décodage d'adresse avec dual channel.png|centre|vignette|upright=2|Décodage d'adresse avec dual channel.]] Les contrôleurs de SDRAM précédents sont assez basiques et ne représentent pas les contrôleurs les plus évolués. Ils étaient utilisés sur les anciens PC, à une époque où ils étaient encore sur la carte mère du processeur. Mais de nos jours, le contrôleur mémoire est intégré au processeur. Et il incorpore de nombreuses optimisations afin de gagner en performances. Malheureusement, ces optimisations sont elles-mêmes dépendantes des optimisations intégrées dans le processeur, ce qui fait qu'on ne peut pas en parler ici. Elles demandent que le contrôleur mémoire reçoive plusieurs accès mémoire simultanés, ce qui n'a aucun sens à ce stade du cours. Aussi, nous allons les passer sous silence pour le moment. Nous les verrons dans le chapitre sur le parallélisme mémoire. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=La désambiguïsation mémoire | prevText=La désambiguïsation mémoire | next=Les processeurs superscalaires | nextText=Les processeurs superscalaires }} </noinclude> dmtbd7usc15xrai9z7jzchwot0qwbot 764726 764725 2026-04-23T22:21:47Z Mewtow 31375 /* Le ré-ordonnancement des commandes mémoires */ 764726 wikitext text/x-wiki Dans ce chapitre, nous allons voir les techniques qui permettent de gérer plusieurs accès mémoire simultanés directement au niveau du cache ou de la mémoire RAM. Par plusieurs accès mémoire simultanés, vous pensez sans doute à l'usage de cache multiports, voire de mémoires RAM multiport. C'est en effet une solution, mais ce n'est clairement pas la seule. Par exemple, il est possible de pipeliner l'accès au cache, voire à la mémoire RAM. Il existe de nombreuses techniques de '''parallélisme mémoire''', et nous allons les voir dans ce chapitre. Pour donner un exemple, il est possible de pipeliner les accès mémoire. Il est possible de pipeliner l'accès au cache et/ou l'accès à la mémoire, et nous verrons comment faire dans ce chapitre. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, qu'ils aient ou non un pipeline dynamique. Par contre, pipeliner la mémoire est une technique ancienne, aujourd'hui peu utilisée. Elle était utilisée sur des très vieux ordinateurs, qui avaient un pipeline mais pas de mémoire cache. Mais de nos jours, les mémoires SDRAM et DDR ne sont pas adaptées pour ça, il est très difficile de les pipeliner correctement. Le parallélisme mémoire est utile aussi bien sur des processeurs à émission dans l'ordre que dans le désordre. Par exemple, un processeur ''in-order'' avec des lectures non-bloquantes peut e, profiter. Il peut techniquement lancer une seconde lecture, après avoir rencontré une première lecture non-bloquante. En général, le nombre de lectures consécutives est cependant limité à moins d'une dizaine. À l'opposé, les processeurs sans lectures non-bloquantes profitent pas du parallélisme mémoire. À la rigueur, ils peuvent en profiter s'ils intègrent des techniques de préchargement. Pendant que le processeur exécute une lecture/écriture, il peut précharger une autre donnée en parallèle. Inutile de dire que sans pipeline processeur, le parallélisme mémoire ne sert pas à grand-chose. ==Le ''line fill buffer'' et les techniques associées== Lors d'un défaut de cache, le processeur doit attendre que toute la ligne de cache soit chargée avant d'être utilisable. Or, la taille d'une ligne de cache est supérieure à la largeur du bus mémoire, ce qui fait qu'une ligne de cache est chargée en plusieurs fois, morceaux par morceaux, mot mémoire par mot mémoire. Le chargement peut se faire directement dans le cache, mais ce n'est pas une solution très pratique. À la place, beaucoup de processeurs ajoutent une mémoire tampon entre la RAM et le cache, appelée le '''tampon de remplissage de ligne''' (''line-fill buffer'') dans les processeurs modernes. Lors d'un défaut de cache, le processeur charge la donnée de la RAM dans le tampon de remplissage de ligne, mot mémoire par mot mémoire. Une fois plein, le tampon de remplissage de ligne est recopié dans la ligne de cache. Le tampon de remplissage de ligne contient une ligne de cache, si ce n'est que certains bits de contrôle ne sont pas présents. Le tampon de remplissage de ligne contient un bit de validité pour chaque mot mémoire de la ligne de cache, qui indiquent si le mot mémoire a été chargé. Par exemple, prenons un processeur 64 bits, qui gère donc des mots mémoire de 8 octets, avec des lignes de cache 256 octets/32 mots mémoire. Le tampon de remplissage de ligne contiendra 32 bits de validité. Si le processeur a chargé les 6 premiers mots mémoire, les 6 premiers bits de validité seront mis à 1, les autres seront encore à zéro. Il faut noter que la disponibilité de la ligne complète se détermine assez facilement en faisant un ET logique entre tous les bits de validité. Le processeur sait ainsi quand la ligne de cache est disponible entièrement et donc quand la transférer dans le cache. La même chose existe avec une hiérarchie de cache, sauf que l'on trouve une mémoire tampon entre chaque niveau de cache. Il y a un tampon de remplissage de ligne entre le cache L1 et le cache L2, entre le cache L2 et le L3, etc. Si le cache ne gère qu'un seul défaut de cache à la fois, le ''fill line buffer'' est une mémoire très simple, qui ne mémorise qu'une seule ligne de cache. Et cela vaut aussi bien pour un cache bloquant que non-bloquant. Mais sur les caches capables de gérer plusieurs défauts simultanés, le tampon de remplissage de ligne est une mémoire de type FIFO ou LIFO, capable de mémoriser plusieurs lignes de cache. Notons que le tampon de remplissage de ligne est très utile pour implémenter certaines techniques, par exemple le contournement du cache. Nous avions vu dans le chapitre sur le cache que certains accès mémoire doivent contourner le cache, pour des raisons de cohérence des caches. C'est notamment nécessaire pour accéder aux périphériques, mais c'est aussi utile pour des raisons de performances dans des cas très spécifiques. Les accès qui contournent le cache se font directement dans le tampon de remplissage de ligne : le processeur écrit ou lit les données depuis ce tampon de remplissage de ligne, sans accéder au cache. ===L'''early restart'' et le ''critical word load''=== La présence du ''line buffer'' permet une optimisation assez intéressante, qui permet de réduire la latence des défauts de cache. L'optimisation consiste à lire un mot mémoire dans le tampon de remplissage de ligne, même si la ligne de cache complète n'a pas encore été chargée. Il existe deux manières de faire cela, qui portent les noms d'''early restart'' et de ''critical word load''. La première est la version la plus simple, la seconde est plus complexe mais plus performante. Avec la technique d''''''early restart''''', la ligne de cache est chargée normalement, en partant de son premier mot mémoire. Dès que le mot mémoire lu/écrit par le processeur est copié dans le ''line fill buffer'', il est envoyé au processeur immédiatement. Illustrons le tout par un exemple, où une ligne de cache fait 16 mots mémoire. Le processeur effectue une lecture, qui lit le 5ème mot mémoire. Sans ''early restart'', le processeur doit charger les 16 mots mémoire avant de faire la lecture dans le cache. Avec ''early restart'', le processeur reçoit la donnée dès que le 5ème mot mémoire est disponible. Le processeur doit attendre que les 4 premiers mots mémoire soient chargés, puis le 5ème mot mémoire arrive et est envoyé directement au processeur, il est lu directement depuis le ''line fill buffer''. Les 11 mots mémoire suivants sont ensuite chargés dans le cache pendant que le processeur fait des calculs dans son coin. Le ''critical word load'' est une optimisation de la technique précédente où le chargement de la ligne de cache commence directement à la donnée demandée par le processeur. Pour reprendre l'exemple précédent, où le processeur demande le 5ème mot mémoire sur 16, le ''critical word load'' charge le 5ème mot mémoire en premier et l'envoie au processeur, ce qui fait qu'il est chargé très rapidement. Pas besoin d'attendre que le processeur charge les 4 mots mémoire précédents comme avec l'''early restart''. Dans le détail, le processeur charge le 5ème mot mémoire en premier, puis charge les 11 suivants, et termine par les 4 mots mémoire du début. En clair, le ''critical word load'' commence par charger le mot mémoire lu, puis les blocs suivants, avant de revenir au début du bloc pour charger les blocs restants. Ainsi, la donnée demandée par le processeur sera la première disponible. Pour cela, l'organisation du tampon de remplissage de ligne est modifiée de manière à rendre cela possible. Il a une taille égale à une ligne de cache complète, qui contient elle-même plusieurs mots mémoire. Dans le ''line-fill buffer'', chaque mot mémoire est stocké avec un tag, qui indique l'adresse du mot mémoire stocké dans le ''line-fill buffer''. Le ''line-fill buffer'' est donc un cache un peu particulier, qui fonctionne comme un cache du point de vue du processeur, comme une mémoire FIFO pour les transferts avec le cache. Ainsi, un processeur qui veut lire dans le cache après un défaut peut accéder à la donnée directement depuis le tampon de remplissage de ligne, alors que la ligne de cache n'a pas encore été totalement recopiée en mémoire. ==Les caches pipelinés== Il est possible de pipeliner l'accès au cache, ce qui demande juste de rajouter des registres au bon endroits et quelques circuits de contrôle. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, même ceux avec un pipeline dynamique. Et l'implémentation peut se faire de plusieurs manières différentes. Il faut dire que découper une mémoire cache en plusieurs étapes peut se faire de plusieurs manière. Commençons par la plus simple. Beaucoup de processeurs des années 2000 avaient une fréquence peu élevée comparée aux standards d'aujourd'hui, ce qui fait que leur cache avait bien un temps d'accès d'un cycle d'horloge. Mais l'accès au cache se faisait en deux cycles d'horloge : un cycle pour calculer une adresse et un cycle pour l'accès mémoire proprement dit. Et ces deux étapes étaient pipelinées, à savoir que deux micro-opérations mémoire peuvent s'exécuter en même temps : une dans l'étage de calcul d'adresse, une autre dans le cache. L'unité mémoire était donc pipelinée, alors que l'accès au cache ne l'était pas. Les processeurs qui implémentaient cette technique regroupent les micro-architectures K5 et K6 d'AMD, les processeurs Intel de micro-architecture P6 (Pentium 2 et 3) et quelques autres. Il est aussi possible de pipeliner l'accès au cache lui-même. Avec un cache pipeliné, l'accès au cache ne se fait pas en un seul cycle, mais en plusieurs. Cependant, on peut lancer un nouvel accès au cache à chaque cycle d'horloge, comme un processeur pipeliné lance une nouvelle instruction à chaque cycle. L'implémentation est assez simple : il suffit d'ajouter des registres dans le cache. Pour cela, on profite que le cache est composé de plusieurs composants séparés, qui échangent des données dans un ordre bien précis, d'un composant à un autre. De plus, le trajet des informations dans un cache est linéaire, ce qui les rend parfois pour l'usage d'un pipeline. ===Le ''pipelining'' des caches ''direct-mapped''=== Voici ce qui se passe avec un cache directement adressé. Pour rappel ce genre de cache est conçu en combinant une mémoire RAM, généralement une SRAM, avec quelques circuits de comparaison et un MUX. L'accès au cache se fait globalement en deux étapes : la première lit la donnée et le ''tag'' dans la SRAM, et on utilise les deux dans une seconde étape. Nous avions vu il y a quelques chapitre comment pipeliner des mémoires, dans le chapitre sur les mémoires évoluées. Et bien ces méthodes peuvent s'utiliser pour la mémoire RAM interne au cache !L'idée est d'insérer un registre entre la sortie de la RAM et la suite du cache, pour en faire un cache pipéliné, à deux étages. Le premier étage lit dans la SRAM, le second fait le reste. L'implémentation sur les caches associatifs à plusieurs voies est globalement la même à quelques détails près. [[File:Cache directement adressé pipeliné - deux étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - deux étages]] Avec le circuit précédent, il est possible d'aller plus loin, cette fois en pipelinant l'accès à la mémoire RAM interne au cache. Pour comprendre comment, rappelons qu'une mémoire SRAM est composée d'un plan mémoire et d'un décodeur. L'accès à la mémoire demande d'abord que le décodeur fasse son travail pour sélectionner la case mémoire adéquate, puis ensuite la lecture ou écriture a lieu dans cette case mémoire. L'accès se fait donc en deux étapes successives séparées, on a juste à mettre un registre entre les deux. Il suffit donc de mettre un gros registre entre le décodeur et le plan mémoire. [[File:Cache directement adressé pipeliné - trois étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - trois étages]] Et on peut aller encore plus loin en découpant le décodeur en deux circuits séparés. En effet, rappelez-vous le chapitre sur les circuits de sélection : nous avions vu qu'il est possible de créer des décodeurs en assemblant des décodeurs plus petits, contrôlés par un circuit de prédécodage. Et bien on peut encore une ajouter un registre entre ce circuit de prédécodage et les petits décodeurs. : Théoriquement, toute l'adresse est fournie d'un seul coup au cache, la quasi-totalité des processeurs présentent l'adresse complète à un cache pipeliné. Mais le Pentium 4 fait autrement. Il faut noter que les premiers étages manipulent l'indice dans la SRAM, qui est dans les bits de poids faible, alors que les étages ultérieurs manipulent le ''tag'' qui est dans les bits de poids fort. Les concepteurs du Pentium 4 ont alors décidé de présenter les bits de poids faible lors du premier cycle d'accès au cache, puis ceux de poids fort au second cycle. Pour cela, l'ALU fonctionnait à une fréquence double de celle du processeur, tout comme le cache L1. Il n'y avait pas de pipeline proprement dit, mais cela réduisait grandement la latence d'accès au cache. ===Le ''pipelining'' des caches splittés/sériels=== Il est aussi possible de pipeliner un cache dits splittés, aussi appelés à accès sériel. Pour rappel, les caches à accès sériel vérifient si il y a succès ou défaut de cache, avant d'accéder aux lignes de cache en cas de succès. Ils font donc différemment des autres caches, qui accèdent à une ligne de cache, avant de déterminer s'il y a succès ou défaut en lisant le tag de la ligne de cache. Les caches sériels disposent de deux SRAM : une pour les tags des lignes de cache et une pour les données. Ils accèdent à la SRAM pour les tags, avant d’accéder à la SRAM des données en cas de succès de cache. Vu que l'accès se fait en deux étapes, une vérification des tags suivie de la lecture/écriture des données, il est facile à pipeliner. Pipeliner le cache permet de régler le problème des accès au cache L1, et elle est tout le temps utilisé sur les processeurs modernes. Mais que faire en cas de défaut de cache ? ==Les caches non bloquants== Un '''cache bloquant''' est un cache auquel le processeur ne peut pas accéder pendant un défaut de cache. Il faut attendre que la lecture ou écriture en RAM soit terminée avant de pouvoir utiliser de nouveau le cache. Un '''cache non bloquant''' n'a pas ce problème : on peut l'utiliser pendant un défaut de cache. Les caches non bloquants permettent de démarrer une nouvelle lecture ou écriture alors qu'une autre est en cours, ce qui permet d'exécuter plusieurs lectures ou écritures en même temps. ===Les ''Miss Handling Status Registers''=== Lors d'un défaut de cache, la mémoire RAM est consultée pendant le défaut de cache, mais le cache est inutilisé. Un défaut de cache n'utilise pas le cache, ce n'est qu'un accès en mémoire RAM, sur le bus mémoire (ou un accès aux niveaux de cache inférieurs, peu importe). Le cache en lui-même est laissé libre, rien n’empêche d'y accéder, il est en réalité intrinsèquement non-bloquant. Les caches, bloquants comme non-bloquants, sont en réalité composés d'une mémoire cache proprement dite, entourée de circuits qui servent d'interface entre le processeur et le cache lui-même. Et parmi les circuits tout autour, certains gèrent l'accès au cache lors d'un défaut de cache. Ils sont regroupés sous le terme de '''''Miss Handling Architecture''''' (MHA). La différence entre un cache bloquant et un cache non-bloquant est en réalité liée à l'implémentation de la MHA. Les caches bloquants coupent volontairement l'accès au cache lors d'un défaut, car les défauts sont plus simples à gérer ainsi. Le défaut de cache rapatrie une donnée depuis la RAM, qui sera écrite dans le cache. Et il ne faut pas qu'une tentative d'accès à cette donnée ait lieu avant qu'elle ne soit chargée. Mais ce blocage est général et touche tout le cache, alors que seule une ligne de cache est concernée par le défaut de cache. L'idée derrière un cache non-bloquant est que seule la ligne de cache est bloquée, mais les autres sont accessibles. L'idée est alors de mémoriser les lignes de cache concernées par le défaut de cache, afin d'en bloquer l'accès. À chaque accès, on vérifie si la ligne de cache est déjà réservée par un défaut de cache. La lecture/écriture est alors bloquée si c'est le cas, mais elle accepte les accès sinon. Pour cela, la ''Miss Handling Architecture'' contient des registres qui mémorisent des informations sur les défauts de cache en cours. Ils portent le nom de '''''miss status handling registers''''', que j’appellerais dorénavant MSHR, qui sont aussi appelés des '''''miss buffer'''''. Le contenu des MSHR varie beaucoup suivant le processeur, mais ces derniers stockent au minimum les informations suivantes : * Le numéro de la ligne de cache dans laquelle les données sont chargées. * Un bit de validité qui indique si le MSHR est vide ou pas, qui est mis à 0 quand le défaut de cache est résolu. * Un ou plusieurs champs de lecture/écriture, qui contiennent des informations sur la lecture/écriture. ** Pour une lecture, elle contient des informations sur la destination de la donnée, à savoir qui prévenir quand le défaut de cache est terminé. C'est parfois un nom/numéro de registre (celui dans lequel charger la donnée), mais c'est souvent le numéro de l'entrée dans le ''load/store queue''. ** Pour les écritures, elle contient la donnée à écrire, ou éventuellement un numéro de ''load/store queue'' où se trouve la donnée à écrire. Il faut noter que le bus mémoire ne peut gérer qu'un seul défaut de cache à la fois. Aussi, il est intéressant de regarder ce qui se passe lorsqu'un second défaut de cache survient, pendant qu'un premier est en cours. Dans ce cas, il y a deux réponses qui correspondent à deux types de caches non-bloquants, qui portent les noms barbares de caches de type succès après défaut et défaut après défaut. Sur le premier type, il ne peut pas y avoir plusieurs défauts de cache simultanés. Dès qu'un second défaut de cache survient, le cache stoppe son activité et on ne peut plus démarrer de nouvelle lecture/écriture, tant que le premier défaut de cache n'est pas résolu. Le second type est plus souple et autorise la survenue de plusieurs défauts de cache simultanés. Du moins, jusqu'à une certaine limite, car le cache ne peut supporter qu'un nombre limité d'accès mémoires simultanés (pipelinés). ===Les accès simultanés à une même ligne de cache=== Il arrive que le processeur fasse plusieurs accès mémoire simultanés à la même ligne de cache. Si la ligne de cache en question n'a pas encore été chargée dans le cache, alors on a plusieurs défauts de cache consécutifs pour la même ligne de cache, et le cache non-bloquant doit gérer la situation. Pour la suite, il va falloir faire une petite distinction entre les défauts primaires et secondaires. Imaginons qu'un défaut de cache ait lieu et demande à charger une donnée dans la ligne de cache numéro N. Il s'agit du premier défaut impliquant cette ligne de cache précise, ce qui lui vaut le nom de '''défaut de cache primaire'''. Mais par la suite, d'autres accès mémoire à la même ligne de cache ont lieu, alors que la ligne de cache n'est pas encore disponible. Dans ce cas, il s'agit de '''défauts de cache secondaires'''. Pour l'unité d'accès mémoire, les défauts de cache primaire et secondaire sont différents (ils prennent tous une entrée dans la ''load/store queue''). Mais pour le cache, ils ne correspondent qu'à un seul accès au cache : celui qui demande de charger la ligne de cache demandée. Les défauts de cache primaire et secondaire à la même ligne de cache se voient attribuer un MSHR unique. La gestion des défauts de cache secondaires dépend du cache non-bloquant. La solution la plus simple ne permet pas les défauts de cache secondaires. Le cache ne permet pas deux défauts de cache simultanés pour la même ligne de cache. Les autres solutions le permettent, en fusionnant des accès simultanés à la même ligne de cache en un seul au niveau des MSHR. Dans tous les cas, détecter les défauts de cache secondaires sont un problème qu'il faut détecter. La ''Miss Handling Architecture'' doit détecter les défauts de cache secondaire. Pour cela, elle procède comme suit. Lors de chaque défaut de cache, la MHA récupère le numéro de la ligne de cache associé. Il vérifie alors chaque MSHR pour vérifier s'il contient le numéro en question. S'il n'y a aucune correspondance dans les MSHR, alors c'est signe que le défaut de cache est un défaut primaire. Mais s'il y en a une, alors c'est un défaut secondaire. Évidemment, cela signifie que lors d'un défaut de cache, le numéro de ligne de cache est envoyé à tous les MSHR, pour comparaison. Les MSHR sont donc regroupés dans une mémoire associative, une sorte de mini-cache, faciliter l'implémentation. Lors d'un défaut de cache primaire, l'accès à un cache non-bloquant se fait comme suit : le processeur envoie une adresse au cache, accède à celui-ci, et détecte la survenue d'un défaut de cache. Il en profite alors pour attribuer une ligne de cache dans laquelle sera chargée ce défaut. L'attribution est très simple dans le cas des caches ''direct mapped'', ou associatifs par voie, pour lesquels l'attribution se fait assez simplement. Il mémorise alors cette information dans les MSHR, après avoir vérifié que le défaut de cache n'était pas un défaut secondaire. Lors des accès ultérieurs à une adresse proche, censée être dans la même ligne de cache, le processeur va encore une fois rencontrer un défaut de cache. Il va alors déterminer le numéro de la ligne de cache associée à l'adresse, et comparer ce numéro avec les MSHRs. Si un MSHR contient ce numéro, c'est signe que le défaut de cache est un défaut secondaire. La MHA réagit alors différemment selon le processeur considéré. Une première solution n'autorise pas les défauts de cache secondaires. Si l'un d'entre eux survient, le processeur est gelé par un ''pipeline stall'', une bulle de pipeline. Une autre solution fusionne les défauts de cache secondaires avec le défaut de cache primaire : tout cela ne correspond qu'à un seul défaut de cache pour lui. ===Les MSHR simples=== Un cache non-bloquant à '''MSHR simple''' contient juste plusieurs MSHR qui mémorisent juste un numéro de ligne de cache, un bit de validité, le champ de lecture/écriture, et l'adresse exacte de lecture/écriture. Avec cette organisation, il est possible d'avoir plusieurs défauts de cache séparés, mais à la condition que chaque défaut accède à une ligne de cache différente. Deux accès simultanés à une même ligne de cache ne sont pas possibles, les défauts de cache secondaires ne sont pas autorisés. Ainsi, chaque défaut de cache se voit attribuer son propre MSHR, chacun contient un numéro de ligne de cache différent. Pour comprendre pourquoi c'est impossible de gérer les défauts secondaires, il faut regarder le champ de lecture/écriture. Si on veut effectuer plusieurs écritures consécutives à la même adresse, le MSHR n'aura pas de quoi mémoriser les deux données à écrire. Il pourra mémoriser la première donnée à écrire, pas la seconde. Même chose lors d'une lecture : le champ lecture/écriture peut mémorisr la destination de la première lecture, pas de la seconde. L'avantage est que la MHA n'a besoin que des MSHR et de quelques circuits annexes. Les autres solutions rajoutent des circuits annexes pour gérer les défauts de cache secondaires, qui utilisent beaucoup de circuits. Le cout en circuit est donc élevé, mais le gain en performance est là. Passons maintenant aux caches non-bloquants qui autorisent les défauts de cache secondaire. La solution la plus simple consiste à utiliser ===Les MSHR adressés implicitement=== Avec les '''MSHR adressés implicitement''', il est possible de fusionner plusieurs accès mémoire à une même ligne de cache, mais sous une condition très importante : ces accès lisent/écrivent des mots mémoire différents. Par exemple, imaginons qu'une ligne de cache contienne 8 mots mémoire de 64 bits. Si un premier accès mémoire lit le mot mémoire numéro 7 (dernier mot mémoire de la ligne), et le second accès le mot mémoire numéro 3, alors la fusion est possible. Mais si deux accès mémoire veulent lire/écrire le mot mémoire numéro 7, alors la fusion n'est pas possible et le processeur se bloque, un ''pipeline stall'' survient. Un MSHR adressé implicitement est un MSHR simple, qui contient naturellement un numéro de ligne de cache (tag) et un bit de validité, sauf que le champ de lecture/écriture est dupliqué. Les différents champs lecture/écriture d'un MSHR sont regroupés dans une mémoire RAM/cache qui contient autant d'entrées qu'il y a de mot mémoire dans une ligne de cache. Chaque entrée de la table est associée à un mot mémoire de la ligne de cache et stocke des informations sur celui-ci. Une entrée mémorise, au minimum : * Un champ de lecture/écriture qui contient soit la destination de la lecture, soit la donnée à écrire. * Un bit R/W permettant d'interpréter correctement le champ de lecture/écriture. * Un bit de validité pour chaque entrée de la table, qui dit si un défaut de cache antérieur accède déjà à ce mot mémoire. La fusion de deux défauts de cache est ainsi assez simple. Un défaut de cache primaire/secondaire configure l'entrée associée au mot mémoire lu/écrit. Si un défaut secondaire ultérieur lit/écrit un mot mémoire différent (dont le bit de validité de l'entrée dans la table est à zéro), pas de conflit : il configure une autre entrée, vide. Mais s'il lit/écrit un mot mémoire pour lequel l'entrée de la table est occupée, il y a conflit, le processeur est bloqué par un ''pipeline stall'', une bulle de pipeline. Avec cette organisation, le nombre de MSHR indique combien de lignes de cache peuvent être lues en même temps depuis la mémoire. Quant au nombre d'entrées par MSHR, il détermine combien d'accès mémoires qui ne se recouvrent pas peuvent avoir lieu en même temps. ===Les MSHR adressés explicitement=== Les '''MSHR adressés explicitement''' n'ont pas les contraintes des MSHR adressés implicitement. Avec eux, il est possible d'avoir plusieurs défauts de cache pointant vers la même ligne de cache, mais aussi vers le même mot mémoire. De tels défauts de cache apparaissent sur les processeurs à exécution dans le désordre, mais sont très rares, voire inexistants, sur les processeurs ''in-order''. L'idée est encore que chaque MSHR est associée à une table mémoire qui mémorise des entrées. Cette table n'est autre qu'une mémoire FIFO. Sauf que cette fois-ci, une entrée n'est pas associée à un mot mémoire. Une entrée est associée à un défaut de cache. Une entrée mémorise là encore la destination de la lecture ou la donnée de l'écriture suivi du bit R/W, ainsi que d'un bit de validité par entrée, mais aussi : la position du mot mémoire lu dans la ligne de cache. C'est cette dernière information qui n'était pas présente dans les MSHR adressés implicitement. De plus, le nombre d'entrée par MSHR n'est pas égal au nombre de mots mémoires dans une ligne de cache, mais peut être arbitrairement grand. L'avantage d'un tel cache est qu'il est capable de traité les défauts secondaires concernant un même mot mémoire, et ce, car les accès à la ligne de cache concernée se produisent dans l'ordre de génération des défauts (la table mémoire est une FIFO). ===Les MSHRs inversés=== Généralement, plus on veut supporter de défauts de cache, plus le nombre de MSHR et d'entrées augmente. Mais au-delà d'un certain nombre d'entrées et de MSHR, les MSHR adressés implicitement et explicitement ont tendance à bouffer un peu trop de circuits. Utiliser une organisation un peu moins gourmande en circuits est donc une nécessité. Cette organisation plus économe se base sur des MSHR inversés. Les MSHR inversés ne contiennent qu'une seule entrée, en quelque sorte : au lieu d’utiliser n MSHR de m entrées chacun, on va utiliser n × m MSHR inversés. La différence, c'est que plusieurs MSHR peuvent contenir un tag identique, contrairement aux MSHR adressés implicitement et explicitement. Lorsqu'un défaut de cache a lieu, chaque MSHR est vérifié. Si jamais aucun MSHR ne contient de tag identique à celui utilisé par le défaut, un MSHR vide est choisi pour stocker ce défaut, et une requête de lecture en mémoire est lancée. Dans le cas contraire, un MSHR est réservé au défaut de cache, mais la requête n'est pas lancée. Quand la donnée est disponible, les MSHR correspondant à la ligne qui vient d'être chargée vont être utilisés un par un pour résoudre les défauts de cache en attente. ===Les MSHR intégrés au cache=== Certains chercheurs ont remarqué que pendant qu'une ligne de cache est en train de subir un défaut de cache, celle-ci reste inutilisée, et son contenu est destiné à être perdu une fois le défaut de cache résolu. Ils se sont dit que, plutôt que d'utiliser des MSHR séparés, il vaudrait mieux utiliser la ligne de cache pour stocker les informations sur les défauts de cache en attente dans cette ligne de cache. Pour éviter tout problème, il faut rajouter un bit dans les bits de contrôle de la ligne de cache, qui sert à indiquer que la ligne de cache est occupée : un défaut de cache a eu lieu dans cette ligne, et elle stocke donc des informations pour résoudre les défauts de cache. ==L'entrelacement mémoire== Nous venons de voir comment une mémoire cache peut gérer plusieurs accès mémoire simultanés. Il se trouve que ce genre d'optimisation dépasse largement le cadre du cache. Les mémoires RAM ne sont pas en reste, elles aussi ! Il est possible de pipeliner l'accès à une mémoire RAM, en utilisant des techniques dites d''''entrelacement'''. Précisons cependant que cette optimisation n'est pas utilisée sur les mémoires SDRAM ou DDR modernes, du moins pas sans modifications majeures. Mais divers ordinateurs assez anciens, dont des superordinateurs, ont utilisé cette technique. La technique demande d'utiliser plusieurs mémoires séparées. Sur les anciens ordinateurs, les mémoires en question sont des chips mémoire, à savoir des circuits intégrés de DRAM. Pour faire plus général, nous allons utiliser le terme de '''banques''', ou encore de bancs mémoire. La différence est qu'il existe des mémoires multi-banque, qui regroupent plusieurs banques indépendantes dans un seul boitier, dans un seul chip mémoire. En clair, de telles mémoires regroupent plusieurs sous-mémoires dans un seul circuit intégré. Il est aussi possible d'utiliser plusieurs chips mémoire séparés, chacun ayant une banque. Reste à combiner ces banques pour former un pseudo-pipeline. ===Utiliser plusieurs banques sans entrelacement=== Sans optimisation particulière, les adresses sont réparties dans les banques comme indiqué ci-dessous. Il s'agit de ce qui s'appelle un arrangement horizontal, et nous avions vu celui-ci dans le chapiutre sur les mémoires SDRAM. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Avec cet arrangement, les bits de poids fort de l'adresse sont utilisées pour sélectionner la banque adéquate, et le reste de l'adresse est envoyé sur le bus d'adresse. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Adresse de banque !! Adresse dans la banque |- | Quelques bits de poids fort || Reste de l'adresse |} L'entrelacement change cette répartition, afin d'accélérer les accès mémoire. L'entrelacement classique vise à améliorer les accès à des adresses mémoires consécutives, mais des formes plus évoluées d'entrelacement visent à optimiser des accès mémoire arbitraires. Voyons-les dans l'ordre, du plus simple au plus complexe. ===L'entrelacement classique=== Avec l''''entrelacement classique''', des adresses consécutives sont réparties dans des banques consécutives. Précisons que cette attribution des adresses n'implique absolument pas la mémoire virtuelle ou n'importe quel mécanisme dans le processeur. La répartition décide que telle adresse mémoire va dans telle banque, à telle adresse dans la banque. Elle est donc le fait du contrôleur mémoire, donc en dehors du processeur (les contrôleurs mémoires n’étaient pas encore intégrés dans le processeur à l'époque). [[File:Répartition des adresses dans une mémoire interleaved.png|centre|vignette|upright=1.5|Répartition des adresses dans une mémoire interleaved.]] L'entrelacement simple permet d'accélérer les accès mémoire qui se font à des adresses consécutives. Avec l'entrelacement, chaque accès mémoire tombe dans une banque mémoire différente, ce qui fait qu'on peut démarrer un nouvel accès mémoire à chaque cycle d'horloge. Pendant qu'une banque est occupée par un accès mémoire, on démarre le suivant dans une autre banque, et ainsi de suite. Le tout se termine soit quand on a épuisé toutes les banques libres, soit quand l'accès en rafales se termine. Pas besoin d'attendre que la mémoire ait fini sa lecture/écriture avant de démarrer la lecture/écriture suivante. [[File:Pipemining mémoire.png|centre|vignette|upright=2.5|Pipelining mémoire]] L'animation suivante illustre bien le processus, avec quatres banques. [[File:Interleaving.gif|centre|vignette|upright=2.5|Pipelining mémoire]] L'entrelacement simple utilise les bits de poids faible pour sélectionner la banque, et les bits de poids fort pour la case mémoire. [[File:Adresse mémoire d'une mémoire entrelacée.png|centre|vignette|upright=2|Adresse mémoire d'une mémoire entrelacée]] Précisons que le temps d'accès mémoire ne change pas beaucoup avec l'entrelacement. Par contre, on peut faire plus d'accès mémoire simultanés. On peut démarrer un accès mémoire par cycle d'horloge, mais l'accès en lui-même prend plusieurs cycles. Les mémoires à entrelacement ont donc un débit supérieur aux mémoires qui ne l'utilisent pas. L'entrelacement classique pourrait être utilisé pour accélérer les transferts entre mémoire RAM et cache, qui se font en blocs d'adresses consécutives. Mais de nos jours, il n'est pas utilisé dans cette optique. Il faut dire qu'il est redondant avec le mode rafale des mémoires DRAM. Les deux font globalement la même chose et on voit mal comment utiliser les deux en même temps. Le mode rafale étant plus simple à implémenter, et plus léger niveau utilisation du bus de commande mémoire, il est préféré à l'entrelacement. Par contre, l'entrelacement a eu son heure de gloire sur d'anciens processeurs qui n'avaient pas de mémoire cache, alors qu'ils disposaient d'un pipeline. L'exemple typique est celui des nombreux ''processeurs vectoriels'', que nous n'avons pas encore abordé à ce stade du cours. De tels processeurs avaient un pipeline, une unité de calcul fortement pipelinée, et parfois même de l'exécution dans le désordre et du renommage de registres ! ===L'entrelacement par décalage=== L'entrelacement simple est très utile pour les accès en rafle ou équivalents. Par contre, il existe de rares situations où il n'est pas optimal. Une de ces situations est celle des accès en enjambée (en ''stride''), où l'on accède en série à des adresses sont séparées par N mots mémoires. [[File:Accès par enjambées.png|centre|vignette|upright=2|Accès par enjambées.]] De tels accès surviennent quand un logiciel accède à des structures de données spécifiques, à savoir des tableaux de structures, des matrices, ou d'autres structures de données dans le genre. Un cas classique est celui du parcours d'une matrice colonne par colonne. Les matrices de nombres sont mémorisées ligne par ligne, ce qui fait que parcourir une colonne demande de passer d'une ligne à la suivante et de faire des grands sauts en mémoire RAM, mais tous espacés par la même distance. Avec l'entrelacement simple, les accès en enjambée sont moins performants que les accès à des adresses consécutives, mais cela ne fonctionne pas trop mal. Le pire cas est celui où l'on a N banques et où les données sont justement placées toutes les N adresses. Des accès consécutifs vont tous tomber dans la même banque, on ne peut plus accéder à des banques différentes en parallèle. Mais en dehors de ce cas, on voit une amélioration par rapport à la situation sans entrelacement. Pour obtenir des performances maximales pour les accès en enjambées, il faut répartir les mots mémoires dans la mémoire autrement. L'organisation idéale est la suivante. : Dans les explications qui vont suivre, la variable N représente le nombre de banques, qui sont numérotées de 0 à N-1. On commence par organiser les N premières adresses comme une mémoire entrelacée simple : l'adresse 0 correspond à la banque 0, l'adresse 1 à la banque 1, etc. Pour le bloc suivant, on décale tout d'une adresse, à savoir qu'on commence à remplir les banques à partir de la seconde, non de la première. Une fois la fin du bloc atteinte, on finit de remplir le bloc en repartant du début du bloc. Le troisième bloc d'adresse subit le même traitement, sauf qu'on commence à remplir à partir de la seconde banque. Et on poursuit l’assignation des adresses en décalant d'un cran en plus à chaque bloc. Ainsi, chaque bloc verra ses adresses décalées d'un cran en plus comparé au bloc précédent. Si jamais le décalage dépasse la fin d'un bloc, alors on reprend au début. [[File:Mémoire interleaved par décalage.png|centre|vignette|upright=1.5|Mémoire entrelacée par décalage.]] En faisant cela, on remarque que les banques situées à N adresses d'intervalle sont différentes. Dans l'exemple du dessus, nous avons ajouté un décalage de 1 à chaque nouveau bloc à remplir. Mais on aurait tout aussi bien pu prendre un décalage de 2, 3, etc. Dans tous les cas, on obtient un entrelacement par décalage. Ce décalage est appelé le pas d'entrelacement, noté P. Le calcul de l'adresse à envoyer à la banque, ainsi que la banque à sélectionner se fait en utilisant les formules suivantes : * adresse à envoyer à la banque = adresse totale / N ; * numéro de la banque = (adresse + décalage) modulo N ; * décalage = (adresse totale * P) mod N. Avec cet entrelacement par décalage, on peut prouver que la bande passante maximale est atteinte si le nombre de banques est un nombre premier. Seulement, utiliser un nombre de banques premier peut créer des trous dans la mémoire, des mots mémoires inadressables. Malgré ce défaut, la technique a été utilisée sur quelques ordinateurs, avec l'exemple notable du superordinateur ''Burroughs Scientific Processor''. Pour éviter cela, il y a plusieurs solutions. Par exemple, on peut faire en sorte que N et la taille d'une banque soient premiers entre eux : ils ne doivent pas avoir de diviseur commun. Mais en pratique, elles n'ont pas vraiment été implémentées dans une vraie machine, et sont restées à l'état de recherche, aussi je les passe sous silence. ===L'entrelacement pseudo-aléatoire=== Une dernière méthode de répartition consiste à répartir les adresses dans les banques de manière "pseudo-aléatoire". L'idée exacte est de faire passer l'adresse dans une '''fonction de hachage''', plus ou moins complexe. Pour rappel, une fonction de hachage prend une entrée de grande taille et fournit en sortie un résultat de petite taille. Idéalement, elles doivent donner des résultats aussi différents que possible pour des entrées similaires, histoire de simuler une sorte de pseudo-aléatoire. Ici, une adresse est transformée en un numéro de banque. Une fonction de hachage simple ne fait que permuter des bits de l'adresse pour obtenir son résultat. Elle ne fait qu'échanger des bits de place avant de couper l'adresse en deux morceaux : un pour la sélection de la banque, et un autre pour la sélection de l'adresse dans la banque. Cette permutation est fixe, et ne change pas suivant l'adresse. Des fonctions de hachage plus complexes font des XOR bit à bit entre certains bits de l'adresse. ==Les contrôleurs mémoires SDRAM/DDR optimisés== Après avoir vu le cas des mémoires RAM, nous allons nous concentrer sur le cas particulier des mémoires SDRAM. Les mémoires SDRAM et DDR modernes sont capables de gérer plusieurs accès simultanés à la mémoire RAM, et leurs contrôleurs mémoire en font tout autant. C'est une différence majeure avec les mémoires asynchrones FPM/EDO, qui n'acceptaient qu'un seul accès mémoire à la fois. Leur contrôleur mémoire n'acceptait qu'un seul accès mémoire à la fois, c'était un contrôleur mémoire bloquant. Les contrôleurs mémoires des SDRAM sont eux non-bloquants et peuvent encaisser une dizaine d'accès mémoire à la fois. Peu de choses sont connues sur les contrôleurs de SDRAM/DDR modernes, les fabricants ne donnant que peu de détails dessus. Les rares simulateurs qui tentent de décrire leur fonctionnement, comme DRAM SIM I et II, sont particulièrement simples et ne vont pas dans le détail. Néanmoins, le peu qu'on sait est tout de même instructif. ===Rappel sur les SDRAM : tampon de ligne et commandes=== Les mémoires SDRAM sont des mémoires à tampon de ligne. Elles font un accès mémoire en deux étapes. La première étape recopie une ligne de N * 64 bits dans un tampon interne à la SDRAM. La seconde étape sélectionne une colonne, à savoir une donnée de 64 bits, qui est soit envoyée sur le bus de données pour une lecture, soit modifiée par une écriture. [[File:Mémoire à tampon de ligne.png|centre|vignette|upright=2|Mémoire à tampon de ligne]] Les accès mémoire sont traduits par un séquenceur mémoire en une série de commandes mémoires, qui sont séparées par des délais mémoire de quelques cycles d'horloge. Les délais sont très précis, et sont à respecter à la lettre. Une lecture ou une écriture se fait en maximum trois commandes : une commande PRECHARGE qui ferme la ligne précédemment utilisée, une commande ACT qui précise l'adresse de la ligne, et une commande READ ou WRITE qui précise l'adresse de la colonne et éventuellement la donnée à écrire. : Pour être plus précis, la commande PRECHARGE précharge les lignes de bits du plan mémoire à une tension particulière, ce qui les vide de leur contenu. Mais c'est un détail sans importance pour ce qui va suivre. Les SDRAM permettent de se passer de la première étape si des accès consécutifs se font dans la même ligne. Une fois activée, la ligne reste ouverte et on peut accéder plusieurs fois de suite dedans. On a alors juste à préciser l'adresse de colonne dedans. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | bgcolor="#FFA0FF" | PRECHARGE || bgcolor="#FFA0FF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || bgcolor="#A0FFFF" | READ (2) || bgcolor="#A0FFFF" | READ (3) || || || bgcolor="#A0FFFF" | READ (4) || bgcolor="#A0FFFF" | READ (5) || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | Donnée READ 1 || ||bgcolor="#A0FFFF" | Donnée READ 2 | bgcolor="#A0FFFF" | Donnée READ 3 || || || bgcolor="#A0FFFF" | Donnée READ 4 || bgcolor="#A0FFFF" | Donnée READ 5 |} Les SDRAM sont parfois capables de démarrer une commande avant que la précédente soit terminée. Mais le respect des délais mémoire est très limitant, ce qui fait qu'on ne peut pas parler de réel pipeline, comme c'est le cas sur les processeurs. Parlons plutot de '''commandes anticipées'''. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | || bgcolor="#A0FFFF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || || || bgcolor="#FFA0FF" | READ (2) || || || || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 | bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 | |} Un dernier point est que les chips mémoires de SDRAM sont composés de plusieurs banques, chacune étant une sorte de mini-mémoire miniature. Chaque banque a son propre décodeur, son propre tampon de ligne, ses propres multiplexeurs de colonne, sa logique de rafraichissement mémoire, etc. C'est comme si une SDRAM regroupait plusieurs mémoires séparées dans un même circuit intégré. [[File:Arrangement vertical.jpg|centre|vignette|upright=2.5|Mémoire multi-banques.]] ===La mise en attente des accès mémoire=== Un contrôleur de SDRAM peut accepter plusieurs accès mémoire en même temps. Mais cela ne signifie pas que le contrôleur sera prêt à les traiter immédiatement. Pour éviter tout problème de disponibilité, le contrôleur met en attente les accès mémoire que le processeur lui envoie, pour les exécuter dès que possible. Les accès mémoire sont mis en attente dans une mémoire FIFO, histoire de les exécuter dans leur ordre d'arrivée, notamment pour renvoyer les lectures dans l'ordre demandé. Il y a aussi une mémoire FIFO pour les données à écrire et une FIFO pour les données lues. Cette dernière sert au cas où le cache ne soit pas disponible quand on lui envoie la donnée lue. [[File:Module d'interface avec la mémoire.png|centre|vignette|upright=2.5|Contrôleur mémoire avec mise en attente des requêtes processeur et autres optimisations.]] Des optimisations sont possibles dès la mémoire FIFO. Par exemple, on peut regrouper plusieurs accès à des données consécutives en un seul accès en rafale. Le contrôleur mémoire analyse les requêtes mises en attente et détecte si certaines se font à des adresses consécutives. Si c'est le cas, il fusionne ces requêtes en une lecture/écriture en rafale. Une telle optimisation est appelée la '''combinaison de lecture''' pour les lectures, et la '''combinaison d'écriture''' pour les écritures. Une autre optimisation possible est le réacheminement lecture sur écriture (''Store to load forwarding''). Il s'agit d'un équivalent de l’optimisation utilisée dans le cadre de la désambiguïsation mémoire. Quand une lecture demande à accéder à une donnée écrite récemment, il se peut que l'écriture ait été mise en attente dans le contrôleur mémoire. Dans ce cas, le contrôleur mémoire peut renvoyer la donnée directement depuis ses mémoires FIFOs, sans faire d'accès mémoire en lecture. Les deux optimisations précédentes ne paraissent pas payer de mine, mais elles deviennent plus compréhensible quand on sait que certains contrôleurs mémoire peuvent mettre en attente beaucoup de données. Par exemple, l'''Intel E8870 - ''Scalable Memory Controller'' peut mettre en attente 64 lignes de cache dans la mémoire FIFO, soit 8 kibioctets de RAM ! Deux pages mémoire si la mémoire virtuelle utilise des pages de 4 kibioctets ! ===Les optimisations liées aux commandes anticipées=== Les commandes anticipées permettent de faire un minimum de parallélisme mémoire. Même si elle parait très limitée, cette possibilité est décuplée par la présence de plusieurs banques dans la mémoire SDRAM. Il est en effet possible de faire plusieurs accès mémoire en même temps, dans des banques différentes. Il est possible de lancer un accès mémoire dans deux banques en même temps, par exemple. La seule contrainte est que la SDRAM est limitée à 4 banques actives en même temps. {|class="wikitable" |+ Pipelining basique sur les SDRAM |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 |- ! Banque Numéro 1 | || || || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || || |- ! Banque Numéro 2 | || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || |- ! Banque Numéro 3 | || || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || |} L'implémentation n'a rien de vraiment compliqué. Il suffit d'utiliser une mémoire FIFO par banque. Ainsi, les accès dans une même banque se feront en série, dans l'ordre d'arrivée, mais des accès à des banques différentes se feront dans le désordre. Cela ne pose pas de problème car les accès se font à des adresses différentes, ce qui fait qu'il n'y a pas de conflits majeurs. [[File:Gestion parallèle des banques.png|centre|vignette|upright=2.5|Gestion parallèle des banques.]] ===Le ré-ordonnancement des commandes mémoires=== Un contrôleur mémoire moderne est capable de changer l'ordre des requêtes mémoires, pour gagner en performances. En clair : il peut faire l'équivalent mémoire de l'exécution dans le désordre des processeurs haute performance. Une différence majeure est que le contrôleur mémoire ne remet pas les accès mémoire dans l'ordre avant de les envoyer au processeur. C'est le processeur qui remet en ordre les accès mémoire, dans sa ''load queue''. Pour cela, les requêtes mémoire sont "numérotées", à savoir qu'on leur attribue un identifiant binaire. Le contrôleur mémoire exécute les lectures/écritures dans le désordre, mais garde la trace de leur identifiant. Il sait dans quel ordre il exécute les accès mémoire et connait leur latence. Ce qui fait qu'il sait que la donnée lue à tel instant est associée à la requête mémoire numéro X. Pour les lectures, il renvoie la donnée lue avec l'identifiant. Le processeur sait alors à quelle lecture la donnée lue correspond et il se débrouille en interne pour gérer la situation. En clair : le processeur voit les accès mémoire dans le désordre, c'est lui qui les remet en ordre. Maintenant que ces précisions sont faites, posons cette question : quel est l'intérêt de faire les accès mémoire dans le désordre ? Accéder à des banques en parallèle est une réponse possible, mais le ré-ordonnancement est surtout utile si tous les accès mémoire atterrissent dans la même banque et dans la même ligne. Elle marche si plusieurs accès à une même ligne ne sont pas consécutifs, et qu'il y a des accès entre les deux. Après ré-ordonnancement, ces accès mémoire sont exécutés l'un à la suite de l'autre. Pour rendre le tout plus concret, voici un exemple. Imaginez que l'on sait les 3 accès mémoire suivants : * Une lecture ligne A ; * Une écriture ligne B ; * Une lecture ligne A. Sans réordonnancent, on doit émettre deux commandes PRECHARGE : une quand on passe de la ligne A à la ligne B, et une autre pour le passage inverse. pour éviter cela, le contrôleur mémoire peut retarder l'écriture, ou au contraire avancer la seconde lecture. le résultat est alors le suivant : * Lecture ligne A ; * Lecture ligne A ; * Une écriture ligne B. On a donc deux accès consécutifs à la même ligne, suivi par une écriture dans une autre ligne. La technique marche parce que l'on a un mix adéquat de lectures et d'écritures. Mais il existe des possibilités de réorganisation autres qui ne sont pas valides. Il faut par exemple prendre garde à éviter d'intervertir lectures et écritures à une même adresse, sous peine de problèmes. Encore une fois, il faut faire attention aux dépendances de données, encore une fois ! Ici des dépendances mémoires, mais les règles sont les mêmes. Le contrôleur mémoire teste les dépendances mémoires avant d'envoyer des commandes à la mémoire DDR/SDRAM. Il a existé des contrôleurs mémoires du temps des vieilles DDR qui implémentaient un ordonnancement très simples. Prenons l'exemple des coprocesseurs IO 81341 et 81342, qui étaient en réalité des ''chipsets'' intégrant un contrôleur SDRAM. Le ''chipset'' avait 5 ports : un pour les processeurs, un pour le pont sud (''southbridge'') et trois pour des canaux DMA. Pour le premier, il n'y avait pas de réordonnancement. Pour les autres, les lectures se font dans l'ordre, les écritures se font dans l'ordre, mais les écritures peuvent passer avant une lecture non-dépendante. L'implémentation était assez simple : le séquenceur était précédé par plusieurs mémoires FIFO. Le port processeur avait une seule mémoire FIFO, ce qui conservait l'ordre d'exécution des commandes mémoire. Pour les autres ports, le processeur avait vraisemblablement des files séparées pour les lectures et écritures. Le séquenceur mémoire vérifiait les dépendances mémoire de type RAW et autres. [[File:Double file d'attente pour les lectures et écritures.png|centre|vignette|upright=2|Double file d'attente pour les lectures et écritures]] L'implémentation était vraiment simple : elle n’exploitait même pas la présence de plusieurs banques ! De nos jours, les contrôleurs mémoires sont beaucoup plus évolués et sont capables d'exécution dans le désordre bien plus poussée. Les contrôleurs mémoires remplacent la FIFO par une sorte de fenêtre d'instruction dans laquelle ils poichent les accès mémoire selon leurs besoins. ==L'entrelacement sur les mémoires SDRAM== L''''entrelacement''' fonctionne sur les mémoires SDRAM, mais il doit être fortement modifié. Concrètement, tout ce qui a étré dit plus est inapplicable pour les SDRAM, du fait de la présence du tampon de ligne. Des adresses consécutives atterrissent déjà dans la même ligne, ce qui fait qu'on n'a pas besoin d'entrelacement au niveau d'une ligne. Et ne parlons pas de ce la présence de rangées et de canaux mémoire ! Cependant, il peut y avoir un entrelacement lié à la présence des banques SDRAM. L'entrelacement est géré juste avant le séquenceur mémoire. Le '''circuit d'entrelacement''' intervertit certains bits de l'adresse lors des accès mémoires. On trouve aussi des mémoires FIFOs pour mettre en attente les requêtes mémoire provenant du processeur. Elles permettent de recevoir plusieurs accès mémoire consécutifs. Si vous vous demandez comment plusieurs accès mémoire consécutifs peuvent avoir lieu, sachez que c'est une bonne question. Pour le moment, nous allons dire que ces accès proviennent du processeur ou de la mémoire cache, sans expliquer comment. Le contrôleur mémoire reçoit une série d'accès mémoire assez rapprochés, qu'il doit exécuter au mieux. [[File:Controleur mémoire d'une SDRAM avec entrelacement.png|centre|vignette|upright=2.5|Contrôleur mémoire d'une SDRAM avec entrelacement.]] Pour gérer l'entrelacement, le contrôleur de SDRAM prend en entrée l'adresse envoyée par le processeur, et la découpe en plusieurs champs : un pour sélectionner la banque adéquate, un autre pour la ligne, un autre pour la colonne, un autre pour la rangée. Une solution simple désactive l'entrelacement. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de banque || Adresse de ligne || Adresse de colonne |} Si cette solution fonctionne bien si les accès mémoire sont assez aléatoires, en pratique, mieux vaut utiliser l'entrelacement. ===L'entrelacement de banques/lignes=== Une première méthode d'entrelacement est l''''entrelacement de banques'''. Elle répartit deux lignes consécutives dans deux banques différentes. Pour comprendre l'idée, prenons un exemple. Imaginons une mémoire avec deux banques et 4 lignes. Imaginons qu'on parcoure/balaye la mémoire RAM en partant des adresses basses. Sans entrelacement de ligne, les accès se feront comme suit, de gauche à droite : {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Banque numéro 1 !! colspan="4" | Banque numéro 2 |- | Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 || Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 |} Avec l'entrelacement de banques, les accès se feront comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 |- | Ligne 1 || Ligne 1 || Ligne 2 || Ligne 2 || Ligne 3 || Ligne 3 || Ligne 4 || Ligne 4 |} Avec N banques, la première ligne ira dans la première banque, la seconde ligne dans la seconde, et ainsi de suite jusqu'à la énième banque qui va dans la banque numéro N. Et la ligne N+1 repart dans la première banque. L'avantage est que passer d'une ligne à la suivante est assez rapide. Au lieu de devoir fermer la première ligne et ouvrir la suivante on ouvrira directement la ligne suivante dans une banque différente, ce qui sera plus rapide. La commande PRECHARGE pourra être envoyée à la seconde banque en avance. Pour cela, l'adresse est découpée comme suit. {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne (précise à l'octet) |} ===L'entrelacement de rangées=== Il est aussi possible de faire la même chose, mais avec les rangées. Pour simplifier fortement, une rangée est simplement un chip mémoire. En réalité, les rangées ne sont pas des chip mémoire, mais un ensemble de chips mémoire regroupés ensemble histoire d'atteindre les 64 bits du bus de données. Par exemple, une rangée peut combiner 8 chips mémoire avec un bus de données de 8 bits chacun pour obtenir les 64 bits du bus de données d'une SDRAM. Mais nous passons ce détail sous silence dans les explications qui vont suivre, par souci de simplification. Pour faire comprendre l'entrelacement de rangée, le mieux est d'utiliser un exemple, le même que précédemment. Sans entrelacement de rangée, on change de chip mémoire une fois qu'on a balayé toutes ses adresses. {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Rangée numéro 1 !! colspan="4" | Rangée numéro 2 |- | Banque 1 || Banque 2 || Banque 3 || Banque 4 || Banque 1 || Banque 2 || Banque 3 || Banque 4 |} Avec l'entrelacement de ligne, on change de rangée dès qu'on change de banque/ligne. {|class="wikitable" |+ Adresse mémoire |- ! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 |- | Banque 1 || Banque 1 || Banque 2 || Banque 2 || Banque 3 || Banque 3 || Banque 4 || Banque 4 |} En clair, quand on a épuisé toutes les banques dans une même rangée, on passe à la rangée suivante au lieu de rester dans la même rangée. Notons que dans l'exemple précédent, on a combiné l'entrelacement de rangée et de banque, mais on aurait pu utiliser l'entrelacement de rangée seul. Mais ce n'est pas le cas le plus courant en pratique. Toujours est-il qu'en combinant entrelacement de rangée et de ligne, le découpage de l'adresse est le suivant : {|class="wikitable" |+ Adresse mémoire |- | Adresse de ligne || Numéro de rangée || Adresse de banque || Adresse de colonne (précise à l'octet) |} D'autres méthodes d'entrelacement sont possibles, mais elles sont plus complexes. Chaque contrôleur mémoire fait un peu à sa sauce. Ils permettent en général de choisir entre plusieurs entrelacements. Au minimum, ils permettent de désactiver l'entrelacement, d'activer un entrelacement de ligne, et d'autres entrelacements plus complexes, dont certains sont propriétaires et tenus secrets par le fabricant. ===L'entrelacement avec le ''dual channel''=== [[File:Dual channel slots.jpg|vignette|Slots mémoires avec ''dual channel''.]] L'usage du ''dual channel'' complique encore l'entrelacement. Là encore, il y a deux grandes solutions : avec et sans entrelacement des canaux mémoire. Rappelons le principe : deux barrettes de RAM sont accédées en parallèle. Et pour cela, il faut utiliser l'entrelacement. Typiquement, chaque barrette mémoire fournit 64 bits, ce qui fait que l'on peut accéder à 128 bits d'un seul coup, par exemple avec un accès en rafale. Sans ''dual channel'', la première barrette correspond à la moitié haute de la RAM, la seconde barrette correspond à la moitié basse. Avec ''dual channel'', une forme spécifique d'entrelacement est activée. Concrétement, deux blocs de 64 bits sont placés dans des canaux mémoire séparés. Idem avec du triple ou quadruple canal, mais c'est alors trois ou quatre blocs de 64 bits qui sont dispersés dans des canaux consécutifs. Le découpage de l'adresse est alors le suivant : {|class="wikitable" |+ Adresse mémoire |- ! colspan="6" | Sans entrelacement |- | Numéro de canal/barrette || Reste de l'adresse || Position de l'octet dans un bloc de 64 bits |- | colspan="6" | |- ! colspan="6" | Avec entrelacement |- | Reste de l'adresse || Numéro de canal/barrette || Position de l'octet dans un bloc de 64 bits |} [[File:Décodage d'adresse avec dual channel.png|centre|vignette|upright=2|Décodage d'adresse avec dual channel.]] Les contrôleurs de SDRAM précédents sont assez basiques et ne représentent pas les contrôleurs les plus évolués. Ils étaient utilisés sur les anciens PC, à une époque où ils étaient encore sur la carte mère du processeur. Mais de nos jours, le contrôleur mémoire est intégré au processeur. Et il incorpore de nombreuses optimisations afin de gagner en performances. Malheureusement, ces optimisations sont elles-mêmes dépendantes des optimisations intégrées dans le processeur, ce qui fait qu'on ne peut pas en parler ici. Elles demandent que le contrôleur mémoire reçoive plusieurs accès mémoire simultanés, ce qui n'a aucun sens à ce stade du cours. Aussi, nous allons les passer sous silence pour le moment. Nous les verrons dans le chapitre sur le parallélisme mémoire. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=La désambiguïsation mémoire | prevText=La désambiguïsation mémoire | next=Les processeurs superscalaires | nextText=Les processeurs superscalaires }} </noinclude> n0vtpzwkh9wkc9va52jah6hfyn47mqj 764727 764726 2026-04-23T22:21:58Z Mewtow 31375 /* Le ré-ordonnancement des commandes mémoires */ 764727 wikitext text/x-wiki Dans ce chapitre, nous allons voir les techniques qui permettent de gérer plusieurs accès mémoire simultanés directement au niveau du cache ou de la mémoire RAM. Par plusieurs accès mémoire simultanés, vous pensez sans doute à l'usage de cache multiports, voire de mémoires RAM multiport. C'est en effet une solution, mais ce n'est clairement pas la seule. Par exemple, il est possible de pipeliner l'accès au cache, voire à la mémoire RAM. Il existe de nombreuses techniques de '''parallélisme mémoire''', et nous allons les voir dans ce chapitre. Pour donner un exemple, il est possible de pipeliner les accès mémoire. Il est possible de pipeliner l'accès au cache et/ou l'accès à la mémoire, et nous verrons comment faire dans ce chapitre. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, qu'ils aient ou non un pipeline dynamique. Par contre, pipeliner la mémoire est une technique ancienne, aujourd'hui peu utilisée. Elle était utilisée sur des très vieux ordinateurs, qui avaient un pipeline mais pas de mémoire cache. Mais de nos jours, les mémoires SDRAM et DDR ne sont pas adaptées pour ça, il est très difficile de les pipeliner correctement. Le parallélisme mémoire est utile aussi bien sur des processeurs à émission dans l'ordre que dans le désordre. Par exemple, un processeur ''in-order'' avec des lectures non-bloquantes peut e, profiter. Il peut techniquement lancer une seconde lecture, après avoir rencontré une première lecture non-bloquante. En général, le nombre de lectures consécutives est cependant limité à moins d'une dizaine. À l'opposé, les processeurs sans lectures non-bloquantes profitent pas du parallélisme mémoire. À la rigueur, ils peuvent en profiter s'ils intègrent des techniques de préchargement. Pendant que le processeur exécute une lecture/écriture, il peut précharger une autre donnée en parallèle. Inutile de dire que sans pipeline processeur, le parallélisme mémoire ne sert pas à grand-chose. ==Le ''line fill buffer'' et les techniques associées== Lors d'un défaut de cache, le processeur doit attendre que toute la ligne de cache soit chargée avant d'être utilisable. Or, la taille d'une ligne de cache est supérieure à la largeur du bus mémoire, ce qui fait qu'une ligne de cache est chargée en plusieurs fois, morceaux par morceaux, mot mémoire par mot mémoire. Le chargement peut se faire directement dans le cache, mais ce n'est pas une solution très pratique. À la place, beaucoup de processeurs ajoutent une mémoire tampon entre la RAM et le cache, appelée le '''tampon de remplissage de ligne''' (''line-fill buffer'') dans les processeurs modernes. Lors d'un défaut de cache, le processeur charge la donnée de la RAM dans le tampon de remplissage de ligne, mot mémoire par mot mémoire. Une fois plein, le tampon de remplissage de ligne est recopié dans la ligne de cache. Le tampon de remplissage de ligne contient une ligne de cache, si ce n'est que certains bits de contrôle ne sont pas présents. Le tampon de remplissage de ligne contient un bit de validité pour chaque mot mémoire de la ligne de cache, qui indiquent si le mot mémoire a été chargé. Par exemple, prenons un processeur 64 bits, qui gère donc des mots mémoire de 8 octets, avec des lignes de cache 256 octets/32 mots mémoire. Le tampon de remplissage de ligne contiendra 32 bits de validité. Si le processeur a chargé les 6 premiers mots mémoire, les 6 premiers bits de validité seront mis à 1, les autres seront encore à zéro. Il faut noter que la disponibilité de la ligne complète se détermine assez facilement en faisant un ET logique entre tous les bits de validité. Le processeur sait ainsi quand la ligne de cache est disponible entièrement et donc quand la transférer dans le cache. La même chose existe avec une hiérarchie de cache, sauf que l'on trouve une mémoire tampon entre chaque niveau de cache. Il y a un tampon de remplissage de ligne entre le cache L1 et le cache L2, entre le cache L2 et le L3, etc. Si le cache ne gère qu'un seul défaut de cache à la fois, le ''fill line buffer'' est une mémoire très simple, qui ne mémorise qu'une seule ligne de cache. Et cela vaut aussi bien pour un cache bloquant que non-bloquant. Mais sur les caches capables de gérer plusieurs défauts simultanés, le tampon de remplissage de ligne est une mémoire de type FIFO ou LIFO, capable de mémoriser plusieurs lignes de cache. Notons que le tampon de remplissage de ligne est très utile pour implémenter certaines techniques, par exemple le contournement du cache. Nous avions vu dans le chapitre sur le cache que certains accès mémoire doivent contourner le cache, pour des raisons de cohérence des caches. C'est notamment nécessaire pour accéder aux périphériques, mais c'est aussi utile pour des raisons de performances dans des cas très spécifiques. Les accès qui contournent le cache se font directement dans le tampon de remplissage de ligne : le processeur écrit ou lit les données depuis ce tampon de remplissage de ligne, sans accéder au cache. ===L'''early restart'' et le ''critical word load''=== La présence du ''line buffer'' permet une optimisation assez intéressante, qui permet de réduire la latence des défauts de cache. L'optimisation consiste à lire un mot mémoire dans le tampon de remplissage de ligne, même si la ligne de cache complète n'a pas encore été chargée. Il existe deux manières de faire cela, qui portent les noms d'''early restart'' et de ''critical word load''. La première est la version la plus simple, la seconde est plus complexe mais plus performante. Avec la technique d''''''early restart''''', la ligne de cache est chargée normalement, en partant de son premier mot mémoire. Dès que le mot mémoire lu/écrit par le processeur est copié dans le ''line fill buffer'', il est envoyé au processeur immédiatement. Illustrons le tout par un exemple, où une ligne de cache fait 16 mots mémoire. Le processeur effectue une lecture, qui lit le 5ème mot mémoire. Sans ''early restart'', le processeur doit charger les 16 mots mémoire avant de faire la lecture dans le cache. Avec ''early restart'', le processeur reçoit la donnée dès que le 5ème mot mémoire est disponible. Le processeur doit attendre que les 4 premiers mots mémoire soient chargés, puis le 5ème mot mémoire arrive et est envoyé directement au processeur, il est lu directement depuis le ''line fill buffer''. Les 11 mots mémoire suivants sont ensuite chargés dans le cache pendant que le processeur fait des calculs dans son coin. Le ''critical word load'' est une optimisation de la technique précédente où le chargement de la ligne de cache commence directement à la donnée demandée par le processeur. Pour reprendre l'exemple précédent, où le processeur demande le 5ème mot mémoire sur 16, le ''critical word load'' charge le 5ème mot mémoire en premier et l'envoie au processeur, ce qui fait qu'il est chargé très rapidement. Pas besoin d'attendre que le processeur charge les 4 mots mémoire précédents comme avec l'''early restart''. Dans le détail, le processeur charge le 5ème mot mémoire en premier, puis charge les 11 suivants, et termine par les 4 mots mémoire du début. En clair, le ''critical word load'' commence par charger le mot mémoire lu, puis les blocs suivants, avant de revenir au début du bloc pour charger les blocs restants. Ainsi, la donnée demandée par le processeur sera la première disponible. Pour cela, l'organisation du tampon de remplissage de ligne est modifiée de manière à rendre cela possible. Il a une taille égale à une ligne de cache complète, qui contient elle-même plusieurs mots mémoire. Dans le ''line-fill buffer'', chaque mot mémoire est stocké avec un tag, qui indique l'adresse du mot mémoire stocké dans le ''line-fill buffer''. Le ''line-fill buffer'' est donc un cache un peu particulier, qui fonctionne comme un cache du point de vue du processeur, comme une mémoire FIFO pour les transferts avec le cache. Ainsi, un processeur qui veut lire dans le cache après un défaut peut accéder à la donnée directement depuis le tampon de remplissage de ligne, alors que la ligne de cache n'a pas encore été totalement recopiée en mémoire. ==Les caches pipelinés== Il est possible de pipeliner l'accès au cache, ce qui demande juste de rajouter des registres au bon endroits et quelques circuits de contrôle. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, même ceux avec un pipeline dynamique. Et l'implémentation peut se faire de plusieurs manières différentes. Il faut dire que découper une mémoire cache en plusieurs étapes peut se faire de plusieurs manière. Commençons par la plus simple. Beaucoup de processeurs des années 2000 avaient une fréquence peu élevée comparée aux standards d'aujourd'hui, ce qui fait que leur cache avait bien un temps d'accès d'un cycle d'horloge. Mais l'accès au cache se faisait en deux cycles d'horloge : un cycle pour calculer une adresse et un cycle pour l'accès mémoire proprement dit. Et ces deux étapes étaient pipelinées, à savoir que deux micro-opérations mémoire peuvent s'exécuter en même temps : une dans l'étage de calcul d'adresse, une autre dans le cache. L'unité mémoire était donc pipelinée, alors que l'accès au cache ne l'était pas. Les processeurs qui implémentaient cette technique regroupent les micro-architectures K5 et K6 d'AMD, les processeurs Intel de micro-architecture P6 (Pentium 2 et 3) et quelques autres. Il est aussi possible de pipeliner l'accès au cache lui-même. Avec un cache pipeliné, l'accès au cache ne se fait pas en un seul cycle, mais en plusieurs. Cependant, on peut lancer un nouvel accès au cache à chaque cycle d'horloge, comme un processeur pipeliné lance une nouvelle instruction à chaque cycle. L'implémentation est assez simple : il suffit d'ajouter des registres dans le cache. Pour cela, on profite que le cache est composé de plusieurs composants séparés, qui échangent des données dans un ordre bien précis, d'un composant à un autre. De plus, le trajet des informations dans un cache est linéaire, ce qui les rend parfois pour l'usage d'un pipeline. ===Le ''pipelining'' des caches ''direct-mapped''=== Voici ce qui se passe avec un cache directement adressé. Pour rappel ce genre de cache est conçu en combinant une mémoire RAM, généralement une SRAM, avec quelques circuits de comparaison et un MUX. L'accès au cache se fait globalement en deux étapes : la première lit la donnée et le ''tag'' dans la SRAM, et on utilise les deux dans une seconde étape. Nous avions vu il y a quelques chapitre comment pipeliner des mémoires, dans le chapitre sur les mémoires évoluées. Et bien ces méthodes peuvent s'utiliser pour la mémoire RAM interne au cache !L'idée est d'insérer un registre entre la sortie de la RAM et la suite du cache, pour en faire un cache pipéliné, à deux étages. Le premier étage lit dans la SRAM, le second fait le reste. L'implémentation sur les caches associatifs à plusieurs voies est globalement la même à quelques détails près. [[File:Cache directement adressé pipeliné - deux étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - deux étages]] Avec le circuit précédent, il est possible d'aller plus loin, cette fois en pipelinant l'accès à la mémoire RAM interne au cache. Pour comprendre comment, rappelons qu'une mémoire SRAM est composée d'un plan mémoire et d'un décodeur. L'accès à la mémoire demande d'abord que le décodeur fasse son travail pour sélectionner la case mémoire adéquate, puis ensuite la lecture ou écriture a lieu dans cette case mémoire. L'accès se fait donc en deux étapes successives séparées, on a juste à mettre un registre entre les deux. Il suffit donc de mettre un gros registre entre le décodeur et le plan mémoire. [[File:Cache directement adressé pipeliné - trois étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - trois étages]] Et on peut aller encore plus loin en découpant le décodeur en deux circuits séparés. En effet, rappelez-vous le chapitre sur les circuits de sélection : nous avions vu qu'il est possible de créer des décodeurs en assemblant des décodeurs plus petits, contrôlés par un circuit de prédécodage. Et bien on peut encore une ajouter un registre entre ce circuit de prédécodage et les petits décodeurs. : Théoriquement, toute l'adresse est fournie d'un seul coup au cache, la quasi-totalité des processeurs présentent l'adresse complète à un cache pipeliné. Mais le Pentium 4 fait autrement. Il faut noter que les premiers étages manipulent l'indice dans la SRAM, qui est dans les bits de poids faible, alors que les étages ultérieurs manipulent le ''tag'' qui est dans les bits de poids fort. Les concepteurs du Pentium 4 ont alors décidé de présenter les bits de poids faible lors du premier cycle d'accès au cache, puis ceux de poids fort au second cycle. Pour cela, l'ALU fonctionnait à une fréquence double de celle du processeur, tout comme le cache L1. Il n'y avait pas de pipeline proprement dit, mais cela réduisait grandement la latence d'accès au cache. ===Le ''pipelining'' des caches splittés/sériels=== Il est aussi possible de pipeliner un cache dits splittés, aussi appelés à accès sériel. Pour rappel, les caches à accès sériel vérifient si il y a succès ou défaut de cache, avant d'accéder aux lignes de cache en cas de succès. Ils font donc différemment des autres caches, qui accèdent à une ligne de cache, avant de déterminer s'il y a succès ou défaut en lisant le tag de la ligne de cache. Les caches sériels disposent de deux SRAM : une pour les tags des lignes de cache et une pour les données. Ils accèdent à la SRAM pour les tags, avant d’accéder à la SRAM des données en cas de succès de cache. Vu que l'accès se fait en deux étapes, une vérification des tags suivie de la lecture/écriture des données, il est facile à pipeliner. Pipeliner le cache permet de régler le problème des accès au cache L1, et elle est tout le temps utilisé sur les processeurs modernes. Mais que faire en cas de défaut de cache ? ==Les caches non bloquants== Un '''cache bloquant''' est un cache auquel le processeur ne peut pas accéder pendant un défaut de cache. Il faut attendre que la lecture ou écriture en RAM soit terminée avant de pouvoir utiliser de nouveau le cache. Un '''cache non bloquant''' n'a pas ce problème : on peut l'utiliser pendant un défaut de cache. Les caches non bloquants permettent de démarrer une nouvelle lecture ou écriture alors qu'une autre est en cours, ce qui permet d'exécuter plusieurs lectures ou écritures en même temps. ===Les ''Miss Handling Status Registers''=== Lors d'un défaut de cache, la mémoire RAM est consultée pendant le défaut de cache, mais le cache est inutilisé. Un défaut de cache n'utilise pas le cache, ce n'est qu'un accès en mémoire RAM, sur le bus mémoire (ou un accès aux niveaux de cache inférieurs, peu importe). Le cache en lui-même est laissé libre, rien n’empêche d'y accéder, il est en réalité intrinsèquement non-bloquant. Les caches, bloquants comme non-bloquants, sont en réalité composés d'une mémoire cache proprement dite, entourée de circuits qui servent d'interface entre le processeur et le cache lui-même. Et parmi les circuits tout autour, certains gèrent l'accès au cache lors d'un défaut de cache. Ils sont regroupés sous le terme de '''''Miss Handling Architecture''''' (MHA). La différence entre un cache bloquant et un cache non-bloquant est en réalité liée à l'implémentation de la MHA. Les caches bloquants coupent volontairement l'accès au cache lors d'un défaut, car les défauts sont plus simples à gérer ainsi. Le défaut de cache rapatrie une donnée depuis la RAM, qui sera écrite dans le cache. Et il ne faut pas qu'une tentative d'accès à cette donnée ait lieu avant qu'elle ne soit chargée. Mais ce blocage est général et touche tout le cache, alors que seule une ligne de cache est concernée par le défaut de cache. L'idée derrière un cache non-bloquant est que seule la ligne de cache est bloquée, mais les autres sont accessibles. L'idée est alors de mémoriser les lignes de cache concernées par le défaut de cache, afin d'en bloquer l'accès. À chaque accès, on vérifie si la ligne de cache est déjà réservée par un défaut de cache. La lecture/écriture est alors bloquée si c'est le cas, mais elle accepte les accès sinon. Pour cela, la ''Miss Handling Architecture'' contient des registres qui mémorisent des informations sur les défauts de cache en cours. Ils portent le nom de '''''miss status handling registers''''', que j’appellerais dorénavant MSHR, qui sont aussi appelés des '''''miss buffer'''''. Le contenu des MSHR varie beaucoup suivant le processeur, mais ces derniers stockent au minimum les informations suivantes : * Le numéro de la ligne de cache dans laquelle les données sont chargées. * Un bit de validité qui indique si le MSHR est vide ou pas, qui est mis à 0 quand le défaut de cache est résolu. * Un ou plusieurs champs de lecture/écriture, qui contiennent des informations sur la lecture/écriture. ** Pour une lecture, elle contient des informations sur la destination de la donnée, à savoir qui prévenir quand le défaut de cache est terminé. C'est parfois un nom/numéro de registre (celui dans lequel charger la donnée), mais c'est souvent le numéro de l'entrée dans le ''load/store queue''. ** Pour les écritures, elle contient la donnée à écrire, ou éventuellement un numéro de ''load/store queue'' où se trouve la donnée à écrire. Il faut noter que le bus mémoire ne peut gérer qu'un seul défaut de cache à la fois. Aussi, il est intéressant de regarder ce qui se passe lorsqu'un second défaut de cache survient, pendant qu'un premier est en cours. Dans ce cas, il y a deux réponses qui correspondent à deux types de caches non-bloquants, qui portent les noms barbares de caches de type succès après défaut et défaut après défaut. Sur le premier type, il ne peut pas y avoir plusieurs défauts de cache simultanés. Dès qu'un second défaut de cache survient, le cache stoppe son activité et on ne peut plus démarrer de nouvelle lecture/écriture, tant que le premier défaut de cache n'est pas résolu. Le second type est plus souple et autorise la survenue de plusieurs défauts de cache simultanés. Du moins, jusqu'à une certaine limite, car le cache ne peut supporter qu'un nombre limité d'accès mémoires simultanés (pipelinés). ===Les accès simultanés à une même ligne de cache=== Il arrive que le processeur fasse plusieurs accès mémoire simultanés à la même ligne de cache. Si la ligne de cache en question n'a pas encore été chargée dans le cache, alors on a plusieurs défauts de cache consécutifs pour la même ligne de cache, et le cache non-bloquant doit gérer la situation. Pour la suite, il va falloir faire une petite distinction entre les défauts primaires et secondaires. Imaginons qu'un défaut de cache ait lieu et demande à charger une donnée dans la ligne de cache numéro N. Il s'agit du premier défaut impliquant cette ligne de cache précise, ce qui lui vaut le nom de '''défaut de cache primaire'''. Mais par la suite, d'autres accès mémoire à la même ligne de cache ont lieu, alors que la ligne de cache n'est pas encore disponible. Dans ce cas, il s'agit de '''défauts de cache secondaires'''. Pour l'unité d'accès mémoire, les défauts de cache primaire et secondaire sont différents (ils prennent tous une entrée dans la ''load/store queue''). Mais pour le cache, ils ne correspondent qu'à un seul accès au cache : celui qui demande de charger la ligne de cache demandée. Les défauts de cache primaire et secondaire à la même ligne de cache se voient attribuer un MSHR unique. La gestion des défauts de cache secondaires dépend du cache non-bloquant. La solution la plus simple ne permet pas les défauts de cache secondaires. Le cache ne permet pas deux défauts de cache simultanés pour la même ligne de cache. Les autres solutions le permettent, en fusionnant des accès simultanés à la même ligne de cache en un seul au niveau des MSHR. Dans tous les cas, détecter les défauts de cache secondaires sont un problème qu'il faut détecter. La ''Miss Handling Architecture'' doit détecter les défauts de cache secondaire. Pour cela, elle procède comme suit. Lors de chaque défaut de cache, la MHA récupère le numéro de la ligne de cache associé. Il vérifie alors chaque MSHR pour vérifier s'il contient le numéro en question. S'il n'y a aucune correspondance dans les MSHR, alors c'est signe que le défaut de cache est un défaut primaire. Mais s'il y en a une, alors c'est un défaut secondaire. Évidemment, cela signifie que lors d'un défaut de cache, le numéro de ligne de cache est envoyé à tous les MSHR, pour comparaison. Les MSHR sont donc regroupés dans une mémoire associative, une sorte de mini-cache, faciliter l'implémentation. Lors d'un défaut de cache primaire, l'accès à un cache non-bloquant se fait comme suit : le processeur envoie une adresse au cache, accède à celui-ci, et détecte la survenue d'un défaut de cache. Il en profite alors pour attribuer une ligne de cache dans laquelle sera chargée ce défaut. L'attribution est très simple dans le cas des caches ''direct mapped'', ou associatifs par voie, pour lesquels l'attribution se fait assez simplement. Il mémorise alors cette information dans les MSHR, après avoir vérifié que le défaut de cache n'était pas un défaut secondaire. Lors des accès ultérieurs à une adresse proche, censée être dans la même ligne de cache, le processeur va encore une fois rencontrer un défaut de cache. Il va alors déterminer le numéro de la ligne de cache associée à l'adresse, et comparer ce numéro avec les MSHRs. Si un MSHR contient ce numéro, c'est signe que le défaut de cache est un défaut secondaire. La MHA réagit alors différemment selon le processeur considéré. Une première solution n'autorise pas les défauts de cache secondaires. Si l'un d'entre eux survient, le processeur est gelé par un ''pipeline stall'', une bulle de pipeline. Une autre solution fusionne les défauts de cache secondaires avec le défaut de cache primaire : tout cela ne correspond qu'à un seul défaut de cache pour lui. ===Les MSHR simples=== Un cache non-bloquant à '''MSHR simple''' contient juste plusieurs MSHR qui mémorisent juste un numéro de ligne de cache, un bit de validité, le champ de lecture/écriture, et l'adresse exacte de lecture/écriture. Avec cette organisation, il est possible d'avoir plusieurs défauts de cache séparés, mais à la condition que chaque défaut accède à une ligne de cache différente. Deux accès simultanés à une même ligne de cache ne sont pas possibles, les défauts de cache secondaires ne sont pas autorisés. Ainsi, chaque défaut de cache se voit attribuer son propre MSHR, chacun contient un numéro de ligne de cache différent. Pour comprendre pourquoi c'est impossible de gérer les défauts secondaires, il faut regarder le champ de lecture/écriture. Si on veut effectuer plusieurs écritures consécutives à la même adresse, le MSHR n'aura pas de quoi mémoriser les deux données à écrire. Il pourra mémoriser la première donnée à écrire, pas la seconde. Même chose lors d'une lecture : le champ lecture/écriture peut mémorisr la destination de la première lecture, pas de la seconde. L'avantage est que la MHA n'a besoin que des MSHR et de quelques circuits annexes. Les autres solutions rajoutent des circuits annexes pour gérer les défauts de cache secondaires, qui utilisent beaucoup de circuits. Le cout en circuit est donc élevé, mais le gain en performance est là. Passons maintenant aux caches non-bloquants qui autorisent les défauts de cache secondaire. La solution la plus simple consiste à utiliser ===Les MSHR adressés implicitement=== Avec les '''MSHR adressés implicitement''', il est possible de fusionner plusieurs accès mémoire à une même ligne de cache, mais sous une condition très importante : ces accès lisent/écrivent des mots mémoire différents. Par exemple, imaginons qu'une ligne de cache contienne 8 mots mémoire de 64 bits. Si un premier accès mémoire lit le mot mémoire numéro 7 (dernier mot mémoire de la ligne), et le second accès le mot mémoire numéro 3, alors la fusion est possible. Mais si deux accès mémoire veulent lire/écrire le mot mémoire numéro 7, alors la fusion n'est pas possible et le processeur se bloque, un ''pipeline stall'' survient. Un MSHR adressé implicitement est un MSHR simple, qui contient naturellement un numéro de ligne de cache (tag) et un bit de validité, sauf que le champ de lecture/écriture est dupliqué. Les différents champs lecture/écriture d'un MSHR sont regroupés dans une mémoire RAM/cache qui contient autant d'entrées qu'il y a de mot mémoire dans une ligne de cache. Chaque entrée de la table est associée à un mot mémoire de la ligne de cache et stocke des informations sur celui-ci. Une entrée mémorise, au minimum : * Un champ de lecture/écriture qui contient soit la destination de la lecture, soit la donnée à écrire. * Un bit R/W permettant d'interpréter correctement le champ de lecture/écriture. * Un bit de validité pour chaque entrée de la table, qui dit si un défaut de cache antérieur accède déjà à ce mot mémoire. La fusion de deux défauts de cache est ainsi assez simple. Un défaut de cache primaire/secondaire configure l'entrée associée au mot mémoire lu/écrit. Si un défaut secondaire ultérieur lit/écrit un mot mémoire différent (dont le bit de validité de l'entrée dans la table est à zéro), pas de conflit : il configure une autre entrée, vide. Mais s'il lit/écrit un mot mémoire pour lequel l'entrée de la table est occupée, il y a conflit, le processeur est bloqué par un ''pipeline stall'', une bulle de pipeline. Avec cette organisation, le nombre de MSHR indique combien de lignes de cache peuvent être lues en même temps depuis la mémoire. Quant au nombre d'entrées par MSHR, il détermine combien d'accès mémoires qui ne se recouvrent pas peuvent avoir lieu en même temps. ===Les MSHR adressés explicitement=== Les '''MSHR adressés explicitement''' n'ont pas les contraintes des MSHR adressés implicitement. Avec eux, il est possible d'avoir plusieurs défauts de cache pointant vers la même ligne de cache, mais aussi vers le même mot mémoire. De tels défauts de cache apparaissent sur les processeurs à exécution dans le désordre, mais sont très rares, voire inexistants, sur les processeurs ''in-order''. L'idée est encore que chaque MSHR est associée à une table mémoire qui mémorise des entrées. Cette table n'est autre qu'une mémoire FIFO. Sauf que cette fois-ci, une entrée n'est pas associée à un mot mémoire. Une entrée est associée à un défaut de cache. Une entrée mémorise là encore la destination de la lecture ou la donnée de l'écriture suivi du bit R/W, ainsi que d'un bit de validité par entrée, mais aussi : la position du mot mémoire lu dans la ligne de cache. C'est cette dernière information qui n'était pas présente dans les MSHR adressés implicitement. De plus, le nombre d'entrée par MSHR n'est pas égal au nombre de mots mémoires dans une ligne de cache, mais peut être arbitrairement grand. L'avantage d'un tel cache est qu'il est capable de traité les défauts secondaires concernant un même mot mémoire, et ce, car les accès à la ligne de cache concernée se produisent dans l'ordre de génération des défauts (la table mémoire est une FIFO). ===Les MSHRs inversés=== Généralement, plus on veut supporter de défauts de cache, plus le nombre de MSHR et d'entrées augmente. Mais au-delà d'un certain nombre d'entrées et de MSHR, les MSHR adressés implicitement et explicitement ont tendance à bouffer un peu trop de circuits. Utiliser une organisation un peu moins gourmande en circuits est donc une nécessité. Cette organisation plus économe se base sur des MSHR inversés. Les MSHR inversés ne contiennent qu'une seule entrée, en quelque sorte : au lieu d’utiliser n MSHR de m entrées chacun, on va utiliser n × m MSHR inversés. La différence, c'est que plusieurs MSHR peuvent contenir un tag identique, contrairement aux MSHR adressés implicitement et explicitement. Lorsqu'un défaut de cache a lieu, chaque MSHR est vérifié. Si jamais aucun MSHR ne contient de tag identique à celui utilisé par le défaut, un MSHR vide est choisi pour stocker ce défaut, et une requête de lecture en mémoire est lancée. Dans le cas contraire, un MSHR est réservé au défaut de cache, mais la requête n'est pas lancée. Quand la donnée est disponible, les MSHR correspondant à la ligne qui vient d'être chargée vont être utilisés un par un pour résoudre les défauts de cache en attente. ===Les MSHR intégrés au cache=== Certains chercheurs ont remarqué que pendant qu'une ligne de cache est en train de subir un défaut de cache, celle-ci reste inutilisée, et son contenu est destiné à être perdu une fois le défaut de cache résolu. Ils se sont dit que, plutôt que d'utiliser des MSHR séparés, il vaudrait mieux utiliser la ligne de cache pour stocker les informations sur les défauts de cache en attente dans cette ligne de cache. Pour éviter tout problème, il faut rajouter un bit dans les bits de contrôle de la ligne de cache, qui sert à indiquer que la ligne de cache est occupée : un défaut de cache a eu lieu dans cette ligne, et elle stocke donc des informations pour résoudre les défauts de cache. ==L'entrelacement mémoire== Nous venons de voir comment une mémoire cache peut gérer plusieurs accès mémoire simultanés. Il se trouve que ce genre d'optimisation dépasse largement le cadre du cache. Les mémoires RAM ne sont pas en reste, elles aussi ! Il est possible de pipeliner l'accès à une mémoire RAM, en utilisant des techniques dites d''''entrelacement'''. Précisons cependant que cette optimisation n'est pas utilisée sur les mémoires SDRAM ou DDR modernes, du moins pas sans modifications majeures. Mais divers ordinateurs assez anciens, dont des superordinateurs, ont utilisé cette technique. La technique demande d'utiliser plusieurs mémoires séparées. Sur les anciens ordinateurs, les mémoires en question sont des chips mémoire, à savoir des circuits intégrés de DRAM. Pour faire plus général, nous allons utiliser le terme de '''banques''', ou encore de bancs mémoire. La différence est qu'il existe des mémoires multi-banque, qui regroupent plusieurs banques indépendantes dans un seul boitier, dans un seul chip mémoire. En clair, de telles mémoires regroupent plusieurs sous-mémoires dans un seul circuit intégré. Il est aussi possible d'utiliser plusieurs chips mémoire séparés, chacun ayant une banque. Reste à combiner ces banques pour former un pseudo-pipeline. ===Utiliser plusieurs banques sans entrelacement=== Sans optimisation particulière, les adresses sont réparties dans les banques comme indiqué ci-dessous. Il s'agit de ce qui s'appelle un arrangement horizontal, et nous avions vu celui-ci dans le chapiutre sur les mémoires SDRAM. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Avec cet arrangement, les bits de poids fort de l'adresse sont utilisées pour sélectionner la banque adéquate, et le reste de l'adresse est envoyé sur le bus d'adresse. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Adresse de banque !! Adresse dans la banque |- | Quelques bits de poids fort || Reste de l'adresse |} L'entrelacement change cette répartition, afin d'accélérer les accès mémoire. L'entrelacement classique vise à améliorer les accès à des adresses mémoires consécutives, mais des formes plus évoluées d'entrelacement visent à optimiser des accès mémoire arbitraires. Voyons-les dans l'ordre, du plus simple au plus complexe. ===L'entrelacement classique=== Avec l''''entrelacement classique''', des adresses consécutives sont réparties dans des banques consécutives. Précisons que cette attribution des adresses n'implique absolument pas la mémoire virtuelle ou n'importe quel mécanisme dans le processeur. La répartition décide que telle adresse mémoire va dans telle banque, à telle adresse dans la banque. Elle est donc le fait du contrôleur mémoire, donc en dehors du processeur (les contrôleurs mémoires n’étaient pas encore intégrés dans le processeur à l'époque). [[File:Répartition des adresses dans une mémoire interleaved.png|centre|vignette|upright=1.5|Répartition des adresses dans une mémoire interleaved.]] L'entrelacement simple permet d'accélérer les accès mémoire qui se font à des adresses consécutives. Avec l'entrelacement, chaque accès mémoire tombe dans une banque mémoire différente, ce qui fait qu'on peut démarrer un nouvel accès mémoire à chaque cycle d'horloge. Pendant qu'une banque est occupée par un accès mémoire, on démarre le suivant dans une autre banque, et ainsi de suite. Le tout se termine soit quand on a épuisé toutes les banques libres, soit quand l'accès en rafales se termine. Pas besoin d'attendre que la mémoire ait fini sa lecture/écriture avant de démarrer la lecture/écriture suivante. [[File:Pipemining mémoire.png|centre|vignette|upright=2.5|Pipelining mémoire]] L'animation suivante illustre bien le processus, avec quatres banques. [[File:Interleaving.gif|centre|vignette|upright=2.5|Pipelining mémoire]] L'entrelacement simple utilise les bits de poids faible pour sélectionner la banque, et les bits de poids fort pour la case mémoire. [[File:Adresse mémoire d'une mémoire entrelacée.png|centre|vignette|upright=2|Adresse mémoire d'une mémoire entrelacée]] Précisons que le temps d'accès mémoire ne change pas beaucoup avec l'entrelacement. Par contre, on peut faire plus d'accès mémoire simultanés. On peut démarrer un accès mémoire par cycle d'horloge, mais l'accès en lui-même prend plusieurs cycles. Les mémoires à entrelacement ont donc un débit supérieur aux mémoires qui ne l'utilisent pas. L'entrelacement classique pourrait être utilisé pour accélérer les transferts entre mémoire RAM et cache, qui se font en blocs d'adresses consécutives. Mais de nos jours, il n'est pas utilisé dans cette optique. Il faut dire qu'il est redondant avec le mode rafale des mémoires DRAM. Les deux font globalement la même chose et on voit mal comment utiliser les deux en même temps. Le mode rafale étant plus simple à implémenter, et plus léger niveau utilisation du bus de commande mémoire, il est préféré à l'entrelacement. Par contre, l'entrelacement a eu son heure de gloire sur d'anciens processeurs qui n'avaient pas de mémoire cache, alors qu'ils disposaient d'un pipeline. L'exemple typique est celui des nombreux ''processeurs vectoriels'', que nous n'avons pas encore abordé à ce stade du cours. De tels processeurs avaient un pipeline, une unité de calcul fortement pipelinée, et parfois même de l'exécution dans le désordre et du renommage de registres ! ===L'entrelacement par décalage=== L'entrelacement simple est très utile pour les accès en rafle ou équivalents. Par contre, il existe de rares situations où il n'est pas optimal. Une de ces situations est celle des accès en enjambée (en ''stride''), où l'on accède en série à des adresses sont séparées par N mots mémoires. [[File:Accès par enjambées.png|centre|vignette|upright=2|Accès par enjambées.]] De tels accès surviennent quand un logiciel accède à des structures de données spécifiques, à savoir des tableaux de structures, des matrices, ou d'autres structures de données dans le genre. Un cas classique est celui du parcours d'une matrice colonne par colonne. Les matrices de nombres sont mémorisées ligne par ligne, ce qui fait que parcourir une colonne demande de passer d'une ligne à la suivante et de faire des grands sauts en mémoire RAM, mais tous espacés par la même distance. Avec l'entrelacement simple, les accès en enjambée sont moins performants que les accès à des adresses consécutives, mais cela ne fonctionne pas trop mal. Le pire cas est celui où l'on a N banques et où les données sont justement placées toutes les N adresses. Des accès consécutifs vont tous tomber dans la même banque, on ne peut plus accéder à des banques différentes en parallèle. Mais en dehors de ce cas, on voit une amélioration par rapport à la situation sans entrelacement. Pour obtenir des performances maximales pour les accès en enjambées, il faut répartir les mots mémoires dans la mémoire autrement. L'organisation idéale est la suivante. : Dans les explications qui vont suivre, la variable N représente le nombre de banques, qui sont numérotées de 0 à N-1. On commence par organiser les N premières adresses comme une mémoire entrelacée simple : l'adresse 0 correspond à la banque 0, l'adresse 1 à la banque 1, etc. Pour le bloc suivant, on décale tout d'une adresse, à savoir qu'on commence à remplir les banques à partir de la seconde, non de la première. Une fois la fin du bloc atteinte, on finit de remplir le bloc en repartant du début du bloc. Le troisième bloc d'adresse subit le même traitement, sauf qu'on commence à remplir à partir de la seconde banque. Et on poursuit l’assignation des adresses en décalant d'un cran en plus à chaque bloc. Ainsi, chaque bloc verra ses adresses décalées d'un cran en plus comparé au bloc précédent. Si jamais le décalage dépasse la fin d'un bloc, alors on reprend au début. [[File:Mémoire interleaved par décalage.png|centre|vignette|upright=1.5|Mémoire entrelacée par décalage.]] En faisant cela, on remarque que les banques situées à N adresses d'intervalle sont différentes. Dans l'exemple du dessus, nous avons ajouté un décalage de 1 à chaque nouveau bloc à remplir. Mais on aurait tout aussi bien pu prendre un décalage de 2, 3, etc. Dans tous les cas, on obtient un entrelacement par décalage. Ce décalage est appelé le pas d'entrelacement, noté P. Le calcul de l'adresse à envoyer à la banque, ainsi que la banque à sélectionner se fait en utilisant les formules suivantes : * adresse à envoyer à la banque = adresse totale / N ; * numéro de la banque = (adresse + décalage) modulo N ; * décalage = (adresse totale * P) mod N. Avec cet entrelacement par décalage, on peut prouver que la bande passante maximale est atteinte si le nombre de banques est un nombre premier. Seulement, utiliser un nombre de banques premier peut créer des trous dans la mémoire, des mots mémoires inadressables. Malgré ce défaut, la technique a été utilisée sur quelques ordinateurs, avec l'exemple notable du superordinateur ''Burroughs Scientific Processor''. Pour éviter cela, il y a plusieurs solutions. Par exemple, on peut faire en sorte que N et la taille d'une banque soient premiers entre eux : ils ne doivent pas avoir de diviseur commun. Mais en pratique, elles n'ont pas vraiment été implémentées dans une vraie machine, et sont restées à l'état de recherche, aussi je les passe sous silence. ===L'entrelacement pseudo-aléatoire=== Une dernière méthode de répartition consiste à répartir les adresses dans les banques de manière "pseudo-aléatoire". L'idée exacte est de faire passer l'adresse dans une '''fonction de hachage''', plus ou moins complexe. Pour rappel, une fonction de hachage prend une entrée de grande taille et fournit en sortie un résultat de petite taille. Idéalement, elles doivent donner des résultats aussi différents que possible pour des entrées similaires, histoire de simuler une sorte de pseudo-aléatoire. Ici, une adresse est transformée en un numéro de banque. Une fonction de hachage simple ne fait que permuter des bits de l'adresse pour obtenir son résultat. Elle ne fait qu'échanger des bits de place avant de couper l'adresse en deux morceaux : un pour la sélection de la banque, et un autre pour la sélection de l'adresse dans la banque. Cette permutation est fixe, et ne change pas suivant l'adresse. Des fonctions de hachage plus complexes font des XOR bit à bit entre certains bits de l'adresse. ==Les contrôleurs mémoires SDRAM/DDR optimisés== Après avoir vu le cas des mémoires RAM, nous allons nous concentrer sur le cas particulier des mémoires SDRAM. Les mémoires SDRAM et DDR modernes sont capables de gérer plusieurs accès simultanés à la mémoire RAM, et leurs contrôleurs mémoire en font tout autant. C'est une différence majeure avec les mémoires asynchrones FPM/EDO, qui n'acceptaient qu'un seul accès mémoire à la fois. Leur contrôleur mémoire n'acceptait qu'un seul accès mémoire à la fois, c'était un contrôleur mémoire bloquant. Les contrôleurs mémoires des SDRAM sont eux non-bloquants et peuvent encaisser une dizaine d'accès mémoire à la fois. Peu de choses sont connues sur les contrôleurs de SDRAM/DDR modernes, les fabricants ne donnant que peu de détails dessus. Les rares simulateurs qui tentent de décrire leur fonctionnement, comme DRAM SIM I et II, sont particulièrement simples et ne vont pas dans le détail. Néanmoins, le peu qu'on sait est tout de même instructif. ===Rappel sur les SDRAM : tampon de ligne et commandes=== Les mémoires SDRAM sont des mémoires à tampon de ligne. Elles font un accès mémoire en deux étapes. La première étape recopie une ligne de N * 64 bits dans un tampon interne à la SDRAM. La seconde étape sélectionne une colonne, à savoir une donnée de 64 bits, qui est soit envoyée sur le bus de données pour une lecture, soit modifiée par une écriture. [[File:Mémoire à tampon de ligne.png|centre|vignette|upright=2|Mémoire à tampon de ligne]] Les accès mémoire sont traduits par un séquenceur mémoire en une série de commandes mémoires, qui sont séparées par des délais mémoire de quelques cycles d'horloge. Les délais sont très précis, et sont à respecter à la lettre. Une lecture ou une écriture se fait en maximum trois commandes : une commande PRECHARGE qui ferme la ligne précédemment utilisée, une commande ACT qui précise l'adresse de la ligne, et une commande READ ou WRITE qui précise l'adresse de la colonne et éventuellement la donnée à écrire. : Pour être plus précis, la commande PRECHARGE précharge les lignes de bits du plan mémoire à une tension particulière, ce qui les vide de leur contenu. Mais c'est un détail sans importance pour ce qui va suivre. Les SDRAM permettent de se passer de la première étape si des accès consécutifs se font dans la même ligne. Une fois activée, la ligne reste ouverte et on peut accéder plusieurs fois de suite dedans. On a alors juste à préciser l'adresse de colonne dedans. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | bgcolor="#FFA0FF" | PRECHARGE || bgcolor="#FFA0FF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || bgcolor="#A0FFFF" | READ (2) || bgcolor="#A0FFFF" | READ (3) || || || bgcolor="#A0FFFF" | READ (4) || bgcolor="#A0FFFF" | READ (5) || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | Donnée READ 1 || ||bgcolor="#A0FFFF" | Donnée READ 2 | bgcolor="#A0FFFF" | Donnée READ 3 || || || bgcolor="#A0FFFF" | Donnée READ 4 || bgcolor="#A0FFFF" | Donnée READ 5 |} Les SDRAM sont parfois capables de démarrer une commande avant que la précédente soit terminée. Mais le respect des délais mémoire est très limitant, ce qui fait qu'on ne peut pas parler de réel pipeline, comme c'est le cas sur les processeurs. Parlons plutot de '''commandes anticipées'''. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | || bgcolor="#A0FFFF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || || || bgcolor="#FFA0FF" | READ (2) || || || || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 | bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 | |} Un dernier point est que les chips mémoires de SDRAM sont composés de plusieurs banques, chacune étant une sorte de mini-mémoire miniature. Chaque banque a son propre décodeur, son propre tampon de ligne, ses propres multiplexeurs de colonne, sa logique de rafraichissement mémoire, etc. C'est comme si une SDRAM regroupait plusieurs mémoires séparées dans un même circuit intégré. [[File:Arrangement vertical.jpg|centre|vignette|upright=2.5|Mémoire multi-banques.]] ===La mise en attente des accès mémoire=== Un contrôleur de SDRAM peut accepter plusieurs accès mémoire en même temps. Mais cela ne signifie pas que le contrôleur sera prêt à les traiter immédiatement. Pour éviter tout problème de disponibilité, le contrôleur met en attente les accès mémoire que le processeur lui envoie, pour les exécuter dès que possible. Les accès mémoire sont mis en attente dans une mémoire FIFO, histoire de les exécuter dans leur ordre d'arrivée, notamment pour renvoyer les lectures dans l'ordre demandé. Il y a aussi une mémoire FIFO pour les données à écrire et une FIFO pour les données lues. Cette dernière sert au cas où le cache ne soit pas disponible quand on lui envoie la donnée lue. [[File:Module d'interface avec la mémoire.png|centre|vignette|upright=2.5|Contrôleur mémoire avec mise en attente des requêtes processeur et autres optimisations.]] Des optimisations sont possibles dès la mémoire FIFO. Par exemple, on peut regrouper plusieurs accès à des données consécutives en un seul accès en rafale. Le contrôleur mémoire analyse les requêtes mises en attente et détecte si certaines se font à des adresses consécutives. Si c'est le cas, il fusionne ces requêtes en une lecture/écriture en rafale. Une telle optimisation est appelée la '''combinaison de lecture''' pour les lectures, et la '''combinaison d'écriture''' pour les écritures. Une autre optimisation possible est le réacheminement lecture sur écriture (''Store to load forwarding''). Il s'agit d'un équivalent de l’optimisation utilisée dans le cadre de la désambiguïsation mémoire. Quand une lecture demande à accéder à une donnée écrite récemment, il se peut que l'écriture ait été mise en attente dans le contrôleur mémoire. Dans ce cas, le contrôleur mémoire peut renvoyer la donnée directement depuis ses mémoires FIFOs, sans faire d'accès mémoire en lecture. Les deux optimisations précédentes ne paraissent pas payer de mine, mais elles deviennent plus compréhensible quand on sait que certains contrôleurs mémoire peuvent mettre en attente beaucoup de données. Par exemple, l'''Intel E8870 - ''Scalable Memory Controller'' peut mettre en attente 64 lignes de cache dans la mémoire FIFO, soit 8 kibioctets de RAM ! Deux pages mémoire si la mémoire virtuelle utilise des pages de 4 kibioctets ! ===Les optimisations liées aux commandes anticipées=== Les commandes anticipées permettent de faire un minimum de parallélisme mémoire. Même si elle parait très limitée, cette possibilité est décuplée par la présence de plusieurs banques dans la mémoire SDRAM. Il est en effet possible de faire plusieurs accès mémoire en même temps, dans des banques différentes. Il est possible de lancer un accès mémoire dans deux banques en même temps, par exemple. La seule contrainte est que la SDRAM est limitée à 4 banques actives en même temps. {|class="wikitable" |+ Pipelining basique sur les SDRAM |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 |- ! Banque Numéro 1 | || || || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || || |- ! Banque Numéro 2 | || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || |- ! Banque Numéro 3 | || || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || |} L'implémentation n'a rien de vraiment compliqué. Il suffit d'utiliser une mémoire FIFO par banque. Ainsi, les accès dans une même banque se feront en série, dans l'ordre d'arrivée, mais des accès à des banques différentes se feront dans le désordre. Cela ne pose pas de problème car les accès se font à des adresses différentes, ce qui fait qu'il n'y a pas de conflits majeurs. [[File:Gestion parallèle des banques.png|centre|vignette|upright=2.5|Gestion parallèle des banques.]] ===Le ré-ordonnancement des commandes mémoires=== Un contrôleur mémoire moderne est capable de changer l'ordre des requêtes mémoires, pour gagner en performances. En clair : il peut faire l'équivalent mémoire de l'exécution dans le désordre des processeurs haute performance. Une différence majeure est que le contrôleur mémoire ne remet pas les accès mémoire dans l'ordre avant de les envoyer au processeur. C'est le processeur qui remet en ordre les accès mémoire, dans sa ''load queue''. Pour cela, les requêtes mémoire sont "numérotées", à savoir qu'on leur attribue un identifiant binaire. Le contrôleur mémoire exécute les lectures/écritures dans le désordre, mais garde la trace de leur identifiant. Il sait dans quel ordre il exécute les accès mémoire et connait leur latence. Ce qui fait qu'il sait que la donnée lue à tel instant est associée à la requête mémoire numéro X. Pour les lectures, il renvoie la donnée lue avec l'identifiant. Le processeur sait alors à quelle lecture la donnée lue correspond et il se débrouille en interne pour gérer la situation. En clair : le processeur voit les accès mémoire dans le désordre, c'est lui qui les remet en ordre. Maintenant que ces précisions sont faites, posons cette question : quel est l'intérêt de faire les accès mémoire dans le désordre ? Accéder à des banques en parallèle est une réponse possible, mais le ré-ordonnancement est surtout utile si tous les accès mémoire atterrissent dans la même banque et dans la même ligne. Elle marche si plusieurs accès à une même ligne ne sont pas consécutifs, et qu'il y a des accès entre les deux. Après ré-ordonnancement, ces accès mémoire sont exécutés l'un à la suite de l'autre. Pour rendre le tout plus concret, voici un exemple. Imaginez que l'on sait les 3 accès mémoire suivants : * Une lecture ligne A ; * Une écriture ligne B ; * Une lecture ligne A. Sans réordonnancent, on doit émettre deux commandes PRECHARGE : une quand on passe de la ligne A à la ligne B, et une autre pour le passage inverse. pour éviter cela, le contrôleur mémoire peut retarder l'écriture, ou au contraire avancer la seconde lecture. le résultat est alors le suivant : * Lecture ligne A ; * Lecture ligne A ; * Une écriture ligne B. On a donc deux accès consécutifs à la même ligne, suivi par une écriture dans une autre ligne. La technique marche parce que l'on a un mix adéquat de lectures et d'écritures. Mais il existe des possibilités de réorganisation autres qui ne sont pas valides. Il faut par exemple prendre garde à éviter d'intervertir lectures et écritures à une même adresse, sous peine de problèmes. Encore une fois, il faut faire attention aux dépendances de données, encore une fois ! Ici des dépendances mémoires, mais les règles sont les mêmes. Le contrôleur mémoire teste les dépendances mémoires avant d'envoyer des commandes à la mémoire DDR/SDRAM. Il a existé des contrôleurs mémoires du temps des vieilles DDR qui implémentaient un ordonnancement très simples. Prenons l'exemple des coprocesseurs IO 81341 et 81342, qui étaient en réalité des ''chipsets'' intégrant un contrôleur SDRAM. Le ''chipset'' avait 5 ports : un pour les processeurs, un pour le pont sud (''southbridge'') et trois pour des canaux DMA. Pour le premier, il n'y avait pas de réordonnancement. Pour les autres, les lectures se font dans l'ordre, les écritures se font dans l'ordre, mais les écritures peuvent passer avant une lecture non-dépendante. L'implémentation était assez simple : le séquenceur était précédé par plusieurs mémoires FIFO. Le port processeur avait une seule mémoire FIFO, ce qui conservait l'ordre d'exécution des commandes mémoire. Pour les autres ports, le processeur avait vraisemblablement des files séparées pour les lectures et écritures. Le séquenceur mémoire vérifiait les dépendances mémoire de type RAW et autres. [[File:Double file d'attente pour les lectures et écritures.png|centre|vignette|upright=2.5|Double file d'attente pour les lectures et écritures]] L'implémentation était vraiment simple : elle n’exploitait même pas la présence de plusieurs banques ! De nos jours, les contrôleurs mémoires sont beaucoup plus évolués et sont capables d'exécution dans le désordre bien plus poussée. Les contrôleurs mémoires remplacent la FIFO par une sorte de fenêtre d'instruction dans laquelle ils poichent les accès mémoire selon leurs besoins. ==L'entrelacement sur les mémoires SDRAM== L''''entrelacement''' fonctionne sur les mémoires SDRAM, mais il doit être fortement modifié. Concrètement, tout ce qui a étré dit plus est inapplicable pour les SDRAM, du fait de la présence du tampon de ligne. Des adresses consécutives atterrissent déjà dans la même ligne, ce qui fait qu'on n'a pas besoin d'entrelacement au niveau d'une ligne. Et ne parlons pas de ce la présence de rangées et de canaux mémoire ! Cependant, il peut y avoir un entrelacement lié à la présence des banques SDRAM. L'entrelacement est géré juste avant le séquenceur mémoire. Le '''circuit d'entrelacement''' intervertit certains bits de l'adresse lors des accès mémoires. On trouve aussi des mémoires FIFOs pour mettre en attente les requêtes mémoire provenant du processeur. Elles permettent de recevoir plusieurs accès mémoire consécutifs. Si vous vous demandez comment plusieurs accès mémoire consécutifs peuvent avoir lieu, sachez que c'est une bonne question. Pour le moment, nous allons dire que ces accès proviennent du processeur ou de la mémoire cache, sans expliquer comment. Le contrôleur mémoire reçoit une série d'accès mémoire assez rapprochés, qu'il doit exécuter au mieux. [[File:Controleur mémoire d'une SDRAM avec entrelacement.png|centre|vignette|upright=2.5|Contrôleur mémoire d'une SDRAM avec entrelacement.]] Pour gérer l'entrelacement, le contrôleur de SDRAM prend en entrée l'adresse envoyée par le processeur, et la découpe en plusieurs champs : un pour sélectionner la banque adéquate, un autre pour la ligne, un autre pour la colonne, un autre pour la rangée. Une solution simple désactive l'entrelacement. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de banque || Adresse de ligne || Adresse de colonne |} Si cette solution fonctionne bien si les accès mémoire sont assez aléatoires, en pratique, mieux vaut utiliser l'entrelacement. ===L'entrelacement de banques/lignes=== Une première méthode d'entrelacement est l''''entrelacement de banques'''. Elle répartit deux lignes consécutives dans deux banques différentes. Pour comprendre l'idée, prenons un exemple. Imaginons une mémoire avec deux banques et 4 lignes. Imaginons qu'on parcoure/balaye la mémoire RAM en partant des adresses basses. Sans entrelacement de ligne, les accès se feront comme suit, de gauche à droite : {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Banque numéro 1 !! colspan="4" | Banque numéro 2 |- | Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 || Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 |} Avec l'entrelacement de banques, les accès se feront comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 |- | Ligne 1 || Ligne 1 || Ligne 2 || Ligne 2 || Ligne 3 || Ligne 3 || Ligne 4 || Ligne 4 |} Avec N banques, la première ligne ira dans la première banque, la seconde ligne dans la seconde, et ainsi de suite jusqu'à la énième banque qui va dans la banque numéro N. Et la ligne N+1 repart dans la première banque. L'avantage est que passer d'une ligne à la suivante est assez rapide. Au lieu de devoir fermer la première ligne et ouvrir la suivante on ouvrira directement la ligne suivante dans une banque différente, ce qui sera plus rapide. La commande PRECHARGE pourra être envoyée à la seconde banque en avance. Pour cela, l'adresse est découpée comme suit. {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne (précise à l'octet) |} ===L'entrelacement de rangées=== Il est aussi possible de faire la même chose, mais avec les rangées. Pour simplifier fortement, une rangée est simplement un chip mémoire. En réalité, les rangées ne sont pas des chip mémoire, mais un ensemble de chips mémoire regroupés ensemble histoire d'atteindre les 64 bits du bus de données. Par exemple, une rangée peut combiner 8 chips mémoire avec un bus de données de 8 bits chacun pour obtenir les 64 bits du bus de données d'une SDRAM. Mais nous passons ce détail sous silence dans les explications qui vont suivre, par souci de simplification. Pour faire comprendre l'entrelacement de rangée, le mieux est d'utiliser un exemple, le même que précédemment. Sans entrelacement de rangée, on change de chip mémoire une fois qu'on a balayé toutes ses adresses. {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Rangée numéro 1 !! colspan="4" | Rangée numéro 2 |- | Banque 1 || Banque 2 || Banque 3 || Banque 4 || Banque 1 || Banque 2 || Banque 3 || Banque 4 |} Avec l'entrelacement de ligne, on change de rangée dès qu'on change de banque/ligne. {|class="wikitable" |+ Adresse mémoire |- ! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 |- | Banque 1 || Banque 1 || Banque 2 || Banque 2 || Banque 3 || Banque 3 || Banque 4 || Banque 4 |} En clair, quand on a épuisé toutes les banques dans une même rangée, on passe à la rangée suivante au lieu de rester dans la même rangée. Notons que dans l'exemple précédent, on a combiné l'entrelacement de rangée et de banque, mais on aurait pu utiliser l'entrelacement de rangée seul. Mais ce n'est pas le cas le plus courant en pratique. Toujours est-il qu'en combinant entrelacement de rangée et de ligne, le découpage de l'adresse est le suivant : {|class="wikitable" |+ Adresse mémoire |- | Adresse de ligne || Numéro de rangée || Adresse de banque || Adresse de colonne (précise à l'octet) |} D'autres méthodes d'entrelacement sont possibles, mais elles sont plus complexes. Chaque contrôleur mémoire fait un peu à sa sauce. Ils permettent en général de choisir entre plusieurs entrelacements. Au minimum, ils permettent de désactiver l'entrelacement, d'activer un entrelacement de ligne, et d'autres entrelacements plus complexes, dont certains sont propriétaires et tenus secrets par le fabricant. ===L'entrelacement avec le ''dual channel''=== [[File:Dual channel slots.jpg|vignette|Slots mémoires avec ''dual channel''.]] L'usage du ''dual channel'' complique encore l'entrelacement. Là encore, il y a deux grandes solutions : avec et sans entrelacement des canaux mémoire. Rappelons le principe : deux barrettes de RAM sont accédées en parallèle. Et pour cela, il faut utiliser l'entrelacement. Typiquement, chaque barrette mémoire fournit 64 bits, ce qui fait que l'on peut accéder à 128 bits d'un seul coup, par exemple avec un accès en rafale. Sans ''dual channel'', la première barrette correspond à la moitié haute de la RAM, la seconde barrette correspond à la moitié basse. Avec ''dual channel'', une forme spécifique d'entrelacement est activée. Concrétement, deux blocs de 64 bits sont placés dans des canaux mémoire séparés. Idem avec du triple ou quadruple canal, mais c'est alors trois ou quatre blocs de 64 bits qui sont dispersés dans des canaux consécutifs. Le découpage de l'adresse est alors le suivant : {|class="wikitable" |+ Adresse mémoire |- ! colspan="6" | Sans entrelacement |- | Numéro de canal/barrette || Reste de l'adresse || Position de l'octet dans un bloc de 64 bits |- | colspan="6" | |- ! colspan="6" | Avec entrelacement |- | Reste de l'adresse || Numéro de canal/barrette || Position de l'octet dans un bloc de 64 bits |} [[File:Décodage d'adresse avec dual channel.png|centre|vignette|upright=2|Décodage d'adresse avec dual channel.]] Les contrôleurs de SDRAM précédents sont assez basiques et ne représentent pas les contrôleurs les plus évolués. Ils étaient utilisés sur les anciens PC, à une époque où ils étaient encore sur la carte mère du processeur. Mais de nos jours, le contrôleur mémoire est intégré au processeur. Et il incorpore de nombreuses optimisations afin de gagner en performances. Malheureusement, ces optimisations sont elles-mêmes dépendantes des optimisations intégrées dans le processeur, ce qui fait qu'on ne peut pas en parler ici. Elles demandent que le contrôleur mémoire reçoive plusieurs accès mémoire simultanés, ce qui n'a aucun sens à ce stade du cours. Aussi, nous allons les passer sous silence pour le moment. Nous les verrons dans le chapitre sur le parallélisme mémoire. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=La désambiguïsation mémoire | prevText=La désambiguïsation mémoire | next=Les processeurs superscalaires | nextText=Les processeurs superscalaires }} </noinclude> camhppv578w050txt4rermyia74s86w 764728 764727 2026-04-23T22:22:35Z Mewtow 31375 /* Les optimisations liées aux commandes anticipées */ 764728 wikitext text/x-wiki Dans ce chapitre, nous allons voir les techniques qui permettent de gérer plusieurs accès mémoire simultanés directement au niveau du cache ou de la mémoire RAM. Par plusieurs accès mémoire simultanés, vous pensez sans doute à l'usage de cache multiports, voire de mémoires RAM multiport. C'est en effet une solution, mais ce n'est clairement pas la seule. Par exemple, il est possible de pipeliner l'accès au cache, voire à la mémoire RAM. Il existe de nombreuses techniques de '''parallélisme mémoire''', et nous allons les voir dans ce chapitre. Pour donner un exemple, il est possible de pipeliner les accès mémoire. Il est possible de pipeliner l'accès au cache et/ou l'accès à la mémoire, et nous verrons comment faire dans ce chapitre. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, qu'ils aient ou non un pipeline dynamique. Par contre, pipeliner la mémoire est une technique ancienne, aujourd'hui peu utilisée. Elle était utilisée sur des très vieux ordinateurs, qui avaient un pipeline mais pas de mémoire cache. Mais de nos jours, les mémoires SDRAM et DDR ne sont pas adaptées pour ça, il est très difficile de les pipeliner correctement. Le parallélisme mémoire est utile aussi bien sur des processeurs à émission dans l'ordre que dans le désordre. Par exemple, un processeur ''in-order'' avec des lectures non-bloquantes peut e, profiter. Il peut techniquement lancer une seconde lecture, après avoir rencontré une première lecture non-bloquante. En général, le nombre de lectures consécutives est cependant limité à moins d'une dizaine. À l'opposé, les processeurs sans lectures non-bloquantes profitent pas du parallélisme mémoire. À la rigueur, ils peuvent en profiter s'ils intègrent des techniques de préchargement. Pendant que le processeur exécute une lecture/écriture, il peut précharger une autre donnée en parallèle. Inutile de dire que sans pipeline processeur, le parallélisme mémoire ne sert pas à grand-chose. ==Le ''line fill buffer'' et les techniques associées== Lors d'un défaut de cache, le processeur doit attendre que toute la ligne de cache soit chargée avant d'être utilisable. Or, la taille d'une ligne de cache est supérieure à la largeur du bus mémoire, ce qui fait qu'une ligne de cache est chargée en plusieurs fois, morceaux par morceaux, mot mémoire par mot mémoire. Le chargement peut se faire directement dans le cache, mais ce n'est pas une solution très pratique. À la place, beaucoup de processeurs ajoutent une mémoire tampon entre la RAM et le cache, appelée le '''tampon de remplissage de ligne''' (''line-fill buffer'') dans les processeurs modernes. Lors d'un défaut de cache, le processeur charge la donnée de la RAM dans le tampon de remplissage de ligne, mot mémoire par mot mémoire. Une fois plein, le tampon de remplissage de ligne est recopié dans la ligne de cache. Le tampon de remplissage de ligne contient une ligne de cache, si ce n'est que certains bits de contrôle ne sont pas présents. Le tampon de remplissage de ligne contient un bit de validité pour chaque mot mémoire de la ligne de cache, qui indiquent si le mot mémoire a été chargé. Par exemple, prenons un processeur 64 bits, qui gère donc des mots mémoire de 8 octets, avec des lignes de cache 256 octets/32 mots mémoire. Le tampon de remplissage de ligne contiendra 32 bits de validité. Si le processeur a chargé les 6 premiers mots mémoire, les 6 premiers bits de validité seront mis à 1, les autres seront encore à zéro. Il faut noter que la disponibilité de la ligne complète se détermine assez facilement en faisant un ET logique entre tous les bits de validité. Le processeur sait ainsi quand la ligne de cache est disponible entièrement et donc quand la transférer dans le cache. La même chose existe avec une hiérarchie de cache, sauf que l'on trouve une mémoire tampon entre chaque niveau de cache. Il y a un tampon de remplissage de ligne entre le cache L1 et le cache L2, entre le cache L2 et le L3, etc. Si le cache ne gère qu'un seul défaut de cache à la fois, le ''fill line buffer'' est une mémoire très simple, qui ne mémorise qu'une seule ligne de cache. Et cela vaut aussi bien pour un cache bloquant que non-bloquant. Mais sur les caches capables de gérer plusieurs défauts simultanés, le tampon de remplissage de ligne est une mémoire de type FIFO ou LIFO, capable de mémoriser plusieurs lignes de cache. Notons que le tampon de remplissage de ligne est très utile pour implémenter certaines techniques, par exemple le contournement du cache. Nous avions vu dans le chapitre sur le cache que certains accès mémoire doivent contourner le cache, pour des raisons de cohérence des caches. C'est notamment nécessaire pour accéder aux périphériques, mais c'est aussi utile pour des raisons de performances dans des cas très spécifiques. Les accès qui contournent le cache se font directement dans le tampon de remplissage de ligne : le processeur écrit ou lit les données depuis ce tampon de remplissage de ligne, sans accéder au cache. ===L'''early restart'' et le ''critical word load''=== La présence du ''line buffer'' permet une optimisation assez intéressante, qui permet de réduire la latence des défauts de cache. L'optimisation consiste à lire un mot mémoire dans le tampon de remplissage de ligne, même si la ligne de cache complète n'a pas encore été chargée. Il existe deux manières de faire cela, qui portent les noms d'''early restart'' et de ''critical word load''. La première est la version la plus simple, la seconde est plus complexe mais plus performante. Avec la technique d''''''early restart''''', la ligne de cache est chargée normalement, en partant de son premier mot mémoire. Dès que le mot mémoire lu/écrit par le processeur est copié dans le ''line fill buffer'', il est envoyé au processeur immédiatement. Illustrons le tout par un exemple, où une ligne de cache fait 16 mots mémoire. Le processeur effectue une lecture, qui lit le 5ème mot mémoire. Sans ''early restart'', le processeur doit charger les 16 mots mémoire avant de faire la lecture dans le cache. Avec ''early restart'', le processeur reçoit la donnée dès que le 5ème mot mémoire est disponible. Le processeur doit attendre que les 4 premiers mots mémoire soient chargés, puis le 5ème mot mémoire arrive et est envoyé directement au processeur, il est lu directement depuis le ''line fill buffer''. Les 11 mots mémoire suivants sont ensuite chargés dans le cache pendant que le processeur fait des calculs dans son coin. Le ''critical word load'' est une optimisation de la technique précédente où le chargement de la ligne de cache commence directement à la donnée demandée par le processeur. Pour reprendre l'exemple précédent, où le processeur demande le 5ème mot mémoire sur 16, le ''critical word load'' charge le 5ème mot mémoire en premier et l'envoie au processeur, ce qui fait qu'il est chargé très rapidement. Pas besoin d'attendre que le processeur charge les 4 mots mémoire précédents comme avec l'''early restart''. Dans le détail, le processeur charge le 5ème mot mémoire en premier, puis charge les 11 suivants, et termine par les 4 mots mémoire du début. En clair, le ''critical word load'' commence par charger le mot mémoire lu, puis les blocs suivants, avant de revenir au début du bloc pour charger les blocs restants. Ainsi, la donnée demandée par le processeur sera la première disponible. Pour cela, l'organisation du tampon de remplissage de ligne est modifiée de manière à rendre cela possible. Il a une taille égale à une ligne de cache complète, qui contient elle-même plusieurs mots mémoire. Dans le ''line-fill buffer'', chaque mot mémoire est stocké avec un tag, qui indique l'adresse du mot mémoire stocké dans le ''line-fill buffer''. Le ''line-fill buffer'' est donc un cache un peu particulier, qui fonctionne comme un cache du point de vue du processeur, comme une mémoire FIFO pour les transferts avec le cache. Ainsi, un processeur qui veut lire dans le cache après un défaut peut accéder à la donnée directement depuis le tampon de remplissage de ligne, alors que la ligne de cache n'a pas encore été totalement recopiée en mémoire. ==Les caches pipelinés== Il est possible de pipeliner l'accès au cache, ce qui demande juste de rajouter des registres au bon endroits et quelques circuits de contrôle. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, même ceux avec un pipeline dynamique. Et l'implémentation peut se faire de plusieurs manières différentes. Il faut dire que découper une mémoire cache en plusieurs étapes peut se faire de plusieurs manière. Commençons par la plus simple. Beaucoup de processeurs des années 2000 avaient une fréquence peu élevée comparée aux standards d'aujourd'hui, ce qui fait que leur cache avait bien un temps d'accès d'un cycle d'horloge. Mais l'accès au cache se faisait en deux cycles d'horloge : un cycle pour calculer une adresse et un cycle pour l'accès mémoire proprement dit. Et ces deux étapes étaient pipelinées, à savoir que deux micro-opérations mémoire peuvent s'exécuter en même temps : une dans l'étage de calcul d'adresse, une autre dans le cache. L'unité mémoire était donc pipelinée, alors que l'accès au cache ne l'était pas. Les processeurs qui implémentaient cette technique regroupent les micro-architectures K5 et K6 d'AMD, les processeurs Intel de micro-architecture P6 (Pentium 2 et 3) et quelques autres. Il est aussi possible de pipeliner l'accès au cache lui-même. Avec un cache pipeliné, l'accès au cache ne se fait pas en un seul cycle, mais en plusieurs. Cependant, on peut lancer un nouvel accès au cache à chaque cycle d'horloge, comme un processeur pipeliné lance une nouvelle instruction à chaque cycle. L'implémentation est assez simple : il suffit d'ajouter des registres dans le cache. Pour cela, on profite que le cache est composé de plusieurs composants séparés, qui échangent des données dans un ordre bien précis, d'un composant à un autre. De plus, le trajet des informations dans un cache est linéaire, ce qui les rend parfois pour l'usage d'un pipeline. ===Le ''pipelining'' des caches ''direct-mapped''=== Voici ce qui se passe avec un cache directement adressé. Pour rappel ce genre de cache est conçu en combinant une mémoire RAM, généralement une SRAM, avec quelques circuits de comparaison et un MUX. L'accès au cache se fait globalement en deux étapes : la première lit la donnée et le ''tag'' dans la SRAM, et on utilise les deux dans une seconde étape. Nous avions vu il y a quelques chapitre comment pipeliner des mémoires, dans le chapitre sur les mémoires évoluées. Et bien ces méthodes peuvent s'utiliser pour la mémoire RAM interne au cache !L'idée est d'insérer un registre entre la sortie de la RAM et la suite du cache, pour en faire un cache pipéliné, à deux étages. Le premier étage lit dans la SRAM, le second fait le reste. L'implémentation sur les caches associatifs à plusieurs voies est globalement la même à quelques détails près. [[File:Cache directement adressé pipeliné - deux étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - deux étages]] Avec le circuit précédent, il est possible d'aller plus loin, cette fois en pipelinant l'accès à la mémoire RAM interne au cache. Pour comprendre comment, rappelons qu'une mémoire SRAM est composée d'un plan mémoire et d'un décodeur. L'accès à la mémoire demande d'abord que le décodeur fasse son travail pour sélectionner la case mémoire adéquate, puis ensuite la lecture ou écriture a lieu dans cette case mémoire. L'accès se fait donc en deux étapes successives séparées, on a juste à mettre un registre entre les deux. Il suffit donc de mettre un gros registre entre le décodeur et le plan mémoire. [[File:Cache directement adressé pipeliné - trois étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - trois étages]] Et on peut aller encore plus loin en découpant le décodeur en deux circuits séparés. En effet, rappelez-vous le chapitre sur les circuits de sélection : nous avions vu qu'il est possible de créer des décodeurs en assemblant des décodeurs plus petits, contrôlés par un circuit de prédécodage. Et bien on peut encore une ajouter un registre entre ce circuit de prédécodage et les petits décodeurs. : Théoriquement, toute l'adresse est fournie d'un seul coup au cache, la quasi-totalité des processeurs présentent l'adresse complète à un cache pipeliné. Mais le Pentium 4 fait autrement. Il faut noter que les premiers étages manipulent l'indice dans la SRAM, qui est dans les bits de poids faible, alors que les étages ultérieurs manipulent le ''tag'' qui est dans les bits de poids fort. Les concepteurs du Pentium 4 ont alors décidé de présenter les bits de poids faible lors du premier cycle d'accès au cache, puis ceux de poids fort au second cycle. Pour cela, l'ALU fonctionnait à une fréquence double de celle du processeur, tout comme le cache L1. Il n'y avait pas de pipeline proprement dit, mais cela réduisait grandement la latence d'accès au cache. ===Le ''pipelining'' des caches splittés/sériels=== Il est aussi possible de pipeliner un cache dits splittés, aussi appelés à accès sériel. Pour rappel, les caches à accès sériel vérifient si il y a succès ou défaut de cache, avant d'accéder aux lignes de cache en cas de succès. Ils font donc différemment des autres caches, qui accèdent à une ligne de cache, avant de déterminer s'il y a succès ou défaut en lisant le tag de la ligne de cache. Les caches sériels disposent de deux SRAM : une pour les tags des lignes de cache et une pour les données. Ils accèdent à la SRAM pour les tags, avant d’accéder à la SRAM des données en cas de succès de cache. Vu que l'accès se fait en deux étapes, une vérification des tags suivie de la lecture/écriture des données, il est facile à pipeliner. Pipeliner le cache permet de régler le problème des accès au cache L1, et elle est tout le temps utilisé sur les processeurs modernes. Mais que faire en cas de défaut de cache ? ==Les caches non bloquants== Un '''cache bloquant''' est un cache auquel le processeur ne peut pas accéder pendant un défaut de cache. Il faut attendre que la lecture ou écriture en RAM soit terminée avant de pouvoir utiliser de nouveau le cache. Un '''cache non bloquant''' n'a pas ce problème : on peut l'utiliser pendant un défaut de cache. Les caches non bloquants permettent de démarrer une nouvelle lecture ou écriture alors qu'une autre est en cours, ce qui permet d'exécuter plusieurs lectures ou écritures en même temps. ===Les ''Miss Handling Status Registers''=== Lors d'un défaut de cache, la mémoire RAM est consultée pendant le défaut de cache, mais le cache est inutilisé. Un défaut de cache n'utilise pas le cache, ce n'est qu'un accès en mémoire RAM, sur le bus mémoire (ou un accès aux niveaux de cache inférieurs, peu importe). Le cache en lui-même est laissé libre, rien n’empêche d'y accéder, il est en réalité intrinsèquement non-bloquant. Les caches, bloquants comme non-bloquants, sont en réalité composés d'une mémoire cache proprement dite, entourée de circuits qui servent d'interface entre le processeur et le cache lui-même. Et parmi les circuits tout autour, certains gèrent l'accès au cache lors d'un défaut de cache. Ils sont regroupés sous le terme de '''''Miss Handling Architecture''''' (MHA). La différence entre un cache bloquant et un cache non-bloquant est en réalité liée à l'implémentation de la MHA. Les caches bloquants coupent volontairement l'accès au cache lors d'un défaut, car les défauts sont plus simples à gérer ainsi. Le défaut de cache rapatrie une donnée depuis la RAM, qui sera écrite dans le cache. Et il ne faut pas qu'une tentative d'accès à cette donnée ait lieu avant qu'elle ne soit chargée. Mais ce blocage est général et touche tout le cache, alors que seule une ligne de cache est concernée par le défaut de cache. L'idée derrière un cache non-bloquant est que seule la ligne de cache est bloquée, mais les autres sont accessibles. L'idée est alors de mémoriser les lignes de cache concernées par le défaut de cache, afin d'en bloquer l'accès. À chaque accès, on vérifie si la ligne de cache est déjà réservée par un défaut de cache. La lecture/écriture est alors bloquée si c'est le cas, mais elle accepte les accès sinon. Pour cela, la ''Miss Handling Architecture'' contient des registres qui mémorisent des informations sur les défauts de cache en cours. Ils portent le nom de '''''miss status handling registers''''', que j’appellerais dorénavant MSHR, qui sont aussi appelés des '''''miss buffer'''''. Le contenu des MSHR varie beaucoup suivant le processeur, mais ces derniers stockent au minimum les informations suivantes : * Le numéro de la ligne de cache dans laquelle les données sont chargées. * Un bit de validité qui indique si le MSHR est vide ou pas, qui est mis à 0 quand le défaut de cache est résolu. * Un ou plusieurs champs de lecture/écriture, qui contiennent des informations sur la lecture/écriture. ** Pour une lecture, elle contient des informations sur la destination de la donnée, à savoir qui prévenir quand le défaut de cache est terminé. C'est parfois un nom/numéro de registre (celui dans lequel charger la donnée), mais c'est souvent le numéro de l'entrée dans le ''load/store queue''. ** Pour les écritures, elle contient la donnée à écrire, ou éventuellement un numéro de ''load/store queue'' où se trouve la donnée à écrire. Il faut noter que le bus mémoire ne peut gérer qu'un seul défaut de cache à la fois. Aussi, il est intéressant de regarder ce qui se passe lorsqu'un second défaut de cache survient, pendant qu'un premier est en cours. Dans ce cas, il y a deux réponses qui correspondent à deux types de caches non-bloquants, qui portent les noms barbares de caches de type succès après défaut et défaut après défaut. Sur le premier type, il ne peut pas y avoir plusieurs défauts de cache simultanés. Dès qu'un second défaut de cache survient, le cache stoppe son activité et on ne peut plus démarrer de nouvelle lecture/écriture, tant que le premier défaut de cache n'est pas résolu. Le second type est plus souple et autorise la survenue de plusieurs défauts de cache simultanés. Du moins, jusqu'à une certaine limite, car le cache ne peut supporter qu'un nombre limité d'accès mémoires simultanés (pipelinés). ===Les accès simultanés à une même ligne de cache=== Il arrive que le processeur fasse plusieurs accès mémoire simultanés à la même ligne de cache. Si la ligne de cache en question n'a pas encore été chargée dans le cache, alors on a plusieurs défauts de cache consécutifs pour la même ligne de cache, et le cache non-bloquant doit gérer la situation. Pour la suite, il va falloir faire une petite distinction entre les défauts primaires et secondaires. Imaginons qu'un défaut de cache ait lieu et demande à charger une donnée dans la ligne de cache numéro N. Il s'agit du premier défaut impliquant cette ligne de cache précise, ce qui lui vaut le nom de '''défaut de cache primaire'''. Mais par la suite, d'autres accès mémoire à la même ligne de cache ont lieu, alors que la ligne de cache n'est pas encore disponible. Dans ce cas, il s'agit de '''défauts de cache secondaires'''. Pour l'unité d'accès mémoire, les défauts de cache primaire et secondaire sont différents (ils prennent tous une entrée dans la ''load/store queue''). Mais pour le cache, ils ne correspondent qu'à un seul accès au cache : celui qui demande de charger la ligne de cache demandée. Les défauts de cache primaire et secondaire à la même ligne de cache se voient attribuer un MSHR unique. La gestion des défauts de cache secondaires dépend du cache non-bloquant. La solution la plus simple ne permet pas les défauts de cache secondaires. Le cache ne permet pas deux défauts de cache simultanés pour la même ligne de cache. Les autres solutions le permettent, en fusionnant des accès simultanés à la même ligne de cache en un seul au niveau des MSHR. Dans tous les cas, détecter les défauts de cache secondaires sont un problème qu'il faut détecter. La ''Miss Handling Architecture'' doit détecter les défauts de cache secondaire. Pour cela, elle procède comme suit. Lors de chaque défaut de cache, la MHA récupère le numéro de la ligne de cache associé. Il vérifie alors chaque MSHR pour vérifier s'il contient le numéro en question. S'il n'y a aucune correspondance dans les MSHR, alors c'est signe que le défaut de cache est un défaut primaire. Mais s'il y en a une, alors c'est un défaut secondaire. Évidemment, cela signifie que lors d'un défaut de cache, le numéro de ligne de cache est envoyé à tous les MSHR, pour comparaison. Les MSHR sont donc regroupés dans une mémoire associative, une sorte de mini-cache, faciliter l'implémentation. Lors d'un défaut de cache primaire, l'accès à un cache non-bloquant se fait comme suit : le processeur envoie une adresse au cache, accède à celui-ci, et détecte la survenue d'un défaut de cache. Il en profite alors pour attribuer une ligne de cache dans laquelle sera chargée ce défaut. L'attribution est très simple dans le cas des caches ''direct mapped'', ou associatifs par voie, pour lesquels l'attribution se fait assez simplement. Il mémorise alors cette information dans les MSHR, après avoir vérifié que le défaut de cache n'était pas un défaut secondaire. Lors des accès ultérieurs à une adresse proche, censée être dans la même ligne de cache, le processeur va encore une fois rencontrer un défaut de cache. Il va alors déterminer le numéro de la ligne de cache associée à l'adresse, et comparer ce numéro avec les MSHRs. Si un MSHR contient ce numéro, c'est signe que le défaut de cache est un défaut secondaire. La MHA réagit alors différemment selon le processeur considéré. Une première solution n'autorise pas les défauts de cache secondaires. Si l'un d'entre eux survient, le processeur est gelé par un ''pipeline stall'', une bulle de pipeline. Une autre solution fusionne les défauts de cache secondaires avec le défaut de cache primaire : tout cela ne correspond qu'à un seul défaut de cache pour lui. ===Les MSHR simples=== Un cache non-bloquant à '''MSHR simple''' contient juste plusieurs MSHR qui mémorisent juste un numéro de ligne de cache, un bit de validité, le champ de lecture/écriture, et l'adresse exacte de lecture/écriture. Avec cette organisation, il est possible d'avoir plusieurs défauts de cache séparés, mais à la condition que chaque défaut accède à une ligne de cache différente. Deux accès simultanés à une même ligne de cache ne sont pas possibles, les défauts de cache secondaires ne sont pas autorisés. Ainsi, chaque défaut de cache se voit attribuer son propre MSHR, chacun contient un numéro de ligne de cache différent. Pour comprendre pourquoi c'est impossible de gérer les défauts secondaires, il faut regarder le champ de lecture/écriture. Si on veut effectuer plusieurs écritures consécutives à la même adresse, le MSHR n'aura pas de quoi mémoriser les deux données à écrire. Il pourra mémoriser la première donnée à écrire, pas la seconde. Même chose lors d'une lecture : le champ lecture/écriture peut mémorisr la destination de la première lecture, pas de la seconde. L'avantage est que la MHA n'a besoin que des MSHR et de quelques circuits annexes. Les autres solutions rajoutent des circuits annexes pour gérer les défauts de cache secondaires, qui utilisent beaucoup de circuits. Le cout en circuit est donc élevé, mais le gain en performance est là. Passons maintenant aux caches non-bloquants qui autorisent les défauts de cache secondaire. La solution la plus simple consiste à utiliser ===Les MSHR adressés implicitement=== Avec les '''MSHR adressés implicitement''', il est possible de fusionner plusieurs accès mémoire à une même ligne de cache, mais sous une condition très importante : ces accès lisent/écrivent des mots mémoire différents. Par exemple, imaginons qu'une ligne de cache contienne 8 mots mémoire de 64 bits. Si un premier accès mémoire lit le mot mémoire numéro 7 (dernier mot mémoire de la ligne), et le second accès le mot mémoire numéro 3, alors la fusion est possible. Mais si deux accès mémoire veulent lire/écrire le mot mémoire numéro 7, alors la fusion n'est pas possible et le processeur se bloque, un ''pipeline stall'' survient. Un MSHR adressé implicitement est un MSHR simple, qui contient naturellement un numéro de ligne de cache (tag) et un bit de validité, sauf que le champ de lecture/écriture est dupliqué. Les différents champs lecture/écriture d'un MSHR sont regroupés dans une mémoire RAM/cache qui contient autant d'entrées qu'il y a de mot mémoire dans une ligne de cache. Chaque entrée de la table est associée à un mot mémoire de la ligne de cache et stocke des informations sur celui-ci. Une entrée mémorise, au minimum : * Un champ de lecture/écriture qui contient soit la destination de la lecture, soit la donnée à écrire. * Un bit R/W permettant d'interpréter correctement le champ de lecture/écriture. * Un bit de validité pour chaque entrée de la table, qui dit si un défaut de cache antérieur accède déjà à ce mot mémoire. La fusion de deux défauts de cache est ainsi assez simple. Un défaut de cache primaire/secondaire configure l'entrée associée au mot mémoire lu/écrit. Si un défaut secondaire ultérieur lit/écrit un mot mémoire différent (dont le bit de validité de l'entrée dans la table est à zéro), pas de conflit : il configure une autre entrée, vide. Mais s'il lit/écrit un mot mémoire pour lequel l'entrée de la table est occupée, il y a conflit, le processeur est bloqué par un ''pipeline stall'', une bulle de pipeline. Avec cette organisation, le nombre de MSHR indique combien de lignes de cache peuvent être lues en même temps depuis la mémoire. Quant au nombre d'entrées par MSHR, il détermine combien d'accès mémoires qui ne se recouvrent pas peuvent avoir lieu en même temps. ===Les MSHR adressés explicitement=== Les '''MSHR adressés explicitement''' n'ont pas les contraintes des MSHR adressés implicitement. Avec eux, il est possible d'avoir plusieurs défauts de cache pointant vers la même ligne de cache, mais aussi vers le même mot mémoire. De tels défauts de cache apparaissent sur les processeurs à exécution dans le désordre, mais sont très rares, voire inexistants, sur les processeurs ''in-order''. L'idée est encore que chaque MSHR est associée à une table mémoire qui mémorise des entrées. Cette table n'est autre qu'une mémoire FIFO. Sauf que cette fois-ci, une entrée n'est pas associée à un mot mémoire. Une entrée est associée à un défaut de cache. Une entrée mémorise là encore la destination de la lecture ou la donnée de l'écriture suivi du bit R/W, ainsi que d'un bit de validité par entrée, mais aussi : la position du mot mémoire lu dans la ligne de cache. C'est cette dernière information qui n'était pas présente dans les MSHR adressés implicitement. De plus, le nombre d'entrée par MSHR n'est pas égal au nombre de mots mémoires dans une ligne de cache, mais peut être arbitrairement grand. L'avantage d'un tel cache est qu'il est capable de traité les défauts secondaires concernant un même mot mémoire, et ce, car les accès à la ligne de cache concernée se produisent dans l'ordre de génération des défauts (la table mémoire est une FIFO). ===Les MSHRs inversés=== Généralement, plus on veut supporter de défauts de cache, plus le nombre de MSHR et d'entrées augmente. Mais au-delà d'un certain nombre d'entrées et de MSHR, les MSHR adressés implicitement et explicitement ont tendance à bouffer un peu trop de circuits. Utiliser une organisation un peu moins gourmande en circuits est donc une nécessité. Cette organisation plus économe se base sur des MSHR inversés. Les MSHR inversés ne contiennent qu'une seule entrée, en quelque sorte : au lieu d’utiliser n MSHR de m entrées chacun, on va utiliser n × m MSHR inversés. La différence, c'est que plusieurs MSHR peuvent contenir un tag identique, contrairement aux MSHR adressés implicitement et explicitement. Lorsqu'un défaut de cache a lieu, chaque MSHR est vérifié. Si jamais aucun MSHR ne contient de tag identique à celui utilisé par le défaut, un MSHR vide est choisi pour stocker ce défaut, et une requête de lecture en mémoire est lancée. Dans le cas contraire, un MSHR est réservé au défaut de cache, mais la requête n'est pas lancée. Quand la donnée est disponible, les MSHR correspondant à la ligne qui vient d'être chargée vont être utilisés un par un pour résoudre les défauts de cache en attente. ===Les MSHR intégrés au cache=== Certains chercheurs ont remarqué que pendant qu'une ligne de cache est en train de subir un défaut de cache, celle-ci reste inutilisée, et son contenu est destiné à être perdu une fois le défaut de cache résolu. Ils se sont dit que, plutôt que d'utiliser des MSHR séparés, il vaudrait mieux utiliser la ligne de cache pour stocker les informations sur les défauts de cache en attente dans cette ligne de cache. Pour éviter tout problème, il faut rajouter un bit dans les bits de contrôle de la ligne de cache, qui sert à indiquer que la ligne de cache est occupée : un défaut de cache a eu lieu dans cette ligne, et elle stocke donc des informations pour résoudre les défauts de cache. ==L'entrelacement mémoire== Nous venons de voir comment une mémoire cache peut gérer plusieurs accès mémoire simultanés. Il se trouve que ce genre d'optimisation dépasse largement le cadre du cache. Les mémoires RAM ne sont pas en reste, elles aussi ! Il est possible de pipeliner l'accès à une mémoire RAM, en utilisant des techniques dites d''''entrelacement'''. Précisons cependant que cette optimisation n'est pas utilisée sur les mémoires SDRAM ou DDR modernes, du moins pas sans modifications majeures. Mais divers ordinateurs assez anciens, dont des superordinateurs, ont utilisé cette technique. La technique demande d'utiliser plusieurs mémoires séparées. Sur les anciens ordinateurs, les mémoires en question sont des chips mémoire, à savoir des circuits intégrés de DRAM. Pour faire plus général, nous allons utiliser le terme de '''banques''', ou encore de bancs mémoire. La différence est qu'il existe des mémoires multi-banque, qui regroupent plusieurs banques indépendantes dans un seul boitier, dans un seul chip mémoire. En clair, de telles mémoires regroupent plusieurs sous-mémoires dans un seul circuit intégré. Il est aussi possible d'utiliser plusieurs chips mémoire séparés, chacun ayant une banque. Reste à combiner ces banques pour former un pseudo-pipeline. ===Utiliser plusieurs banques sans entrelacement=== Sans optimisation particulière, les adresses sont réparties dans les banques comme indiqué ci-dessous. Il s'agit de ce qui s'appelle un arrangement horizontal, et nous avions vu celui-ci dans le chapiutre sur les mémoires SDRAM. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Avec cet arrangement, les bits de poids fort de l'adresse sont utilisées pour sélectionner la banque adéquate, et le reste de l'adresse est envoyé sur le bus d'adresse. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Adresse de banque !! Adresse dans la banque |- | Quelques bits de poids fort || Reste de l'adresse |} L'entrelacement change cette répartition, afin d'accélérer les accès mémoire. L'entrelacement classique vise à améliorer les accès à des adresses mémoires consécutives, mais des formes plus évoluées d'entrelacement visent à optimiser des accès mémoire arbitraires. Voyons-les dans l'ordre, du plus simple au plus complexe. ===L'entrelacement classique=== Avec l''''entrelacement classique''', des adresses consécutives sont réparties dans des banques consécutives. Précisons que cette attribution des adresses n'implique absolument pas la mémoire virtuelle ou n'importe quel mécanisme dans le processeur. La répartition décide que telle adresse mémoire va dans telle banque, à telle adresse dans la banque. Elle est donc le fait du contrôleur mémoire, donc en dehors du processeur (les contrôleurs mémoires n’étaient pas encore intégrés dans le processeur à l'époque). [[File:Répartition des adresses dans une mémoire interleaved.png|centre|vignette|upright=1.5|Répartition des adresses dans une mémoire interleaved.]] L'entrelacement simple permet d'accélérer les accès mémoire qui se font à des adresses consécutives. Avec l'entrelacement, chaque accès mémoire tombe dans une banque mémoire différente, ce qui fait qu'on peut démarrer un nouvel accès mémoire à chaque cycle d'horloge. Pendant qu'une banque est occupée par un accès mémoire, on démarre le suivant dans une autre banque, et ainsi de suite. Le tout se termine soit quand on a épuisé toutes les banques libres, soit quand l'accès en rafales se termine. Pas besoin d'attendre que la mémoire ait fini sa lecture/écriture avant de démarrer la lecture/écriture suivante. [[File:Pipemining mémoire.png|centre|vignette|upright=2.5|Pipelining mémoire]] L'animation suivante illustre bien le processus, avec quatres banques. [[File:Interleaving.gif|centre|vignette|upright=2.5|Pipelining mémoire]] L'entrelacement simple utilise les bits de poids faible pour sélectionner la banque, et les bits de poids fort pour la case mémoire. [[File:Adresse mémoire d'une mémoire entrelacée.png|centre|vignette|upright=2|Adresse mémoire d'une mémoire entrelacée]] Précisons que le temps d'accès mémoire ne change pas beaucoup avec l'entrelacement. Par contre, on peut faire plus d'accès mémoire simultanés. On peut démarrer un accès mémoire par cycle d'horloge, mais l'accès en lui-même prend plusieurs cycles. Les mémoires à entrelacement ont donc un débit supérieur aux mémoires qui ne l'utilisent pas. L'entrelacement classique pourrait être utilisé pour accélérer les transferts entre mémoire RAM et cache, qui se font en blocs d'adresses consécutives. Mais de nos jours, il n'est pas utilisé dans cette optique. Il faut dire qu'il est redondant avec le mode rafale des mémoires DRAM. Les deux font globalement la même chose et on voit mal comment utiliser les deux en même temps. Le mode rafale étant plus simple à implémenter, et plus léger niveau utilisation du bus de commande mémoire, il est préféré à l'entrelacement. Par contre, l'entrelacement a eu son heure de gloire sur d'anciens processeurs qui n'avaient pas de mémoire cache, alors qu'ils disposaient d'un pipeline. L'exemple typique est celui des nombreux ''processeurs vectoriels'', que nous n'avons pas encore abordé à ce stade du cours. De tels processeurs avaient un pipeline, une unité de calcul fortement pipelinée, et parfois même de l'exécution dans le désordre et du renommage de registres ! ===L'entrelacement par décalage=== L'entrelacement simple est très utile pour les accès en rafle ou équivalents. Par contre, il existe de rares situations où il n'est pas optimal. Une de ces situations est celle des accès en enjambée (en ''stride''), où l'on accède en série à des adresses sont séparées par N mots mémoires. [[File:Accès par enjambées.png|centre|vignette|upright=2|Accès par enjambées.]] De tels accès surviennent quand un logiciel accède à des structures de données spécifiques, à savoir des tableaux de structures, des matrices, ou d'autres structures de données dans le genre. Un cas classique est celui du parcours d'une matrice colonne par colonne. Les matrices de nombres sont mémorisées ligne par ligne, ce qui fait que parcourir une colonne demande de passer d'une ligne à la suivante et de faire des grands sauts en mémoire RAM, mais tous espacés par la même distance. Avec l'entrelacement simple, les accès en enjambée sont moins performants que les accès à des adresses consécutives, mais cela ne fonctionne pas trop mal. Le pire cas est celui où l'on a N banques et où les données sont justement placées toutes les N adresses. Des accès consécutifs vont tous tomber dans la même banque, on ne peut plus accéder à des banques différentes en parallèle. Mais en dehors de ce cas, on voit une amélioration par rapport à la situation sans entrelacement. Pour obtenir des performances maximales pour les accès en enjambées, il faut répartir les mots mémoires dans la mémoire autrement. L'organisation idéale est la suivante. : Dans les explications qui vont suivre, la variable N représente le nombre de banques, qui sont numérotées de 0 à N-1. On commence par organiser les N premières adresses comme une mémoire entrelacée simple : l'adresse 0 correspond à la banque 0, l'adresse 1 à la banque 1, etc. Pour le bloc suivant, on décale tout d'une adresse, à savoir qu'on commence à remplir les banques à partir de la seconde, non de la première. Une fois la fin du bloc atteinte, on finit de remplir le bloc en repartant du début du bloc. Le troisième bloc d'adresse subit le même traitement, sauf qu'on commence à remplir à partir de la seconde banque. Et on poursuit l’assignation des adresses en décalant d'un cran en plus à chaque bloc. Ainsi, chaque bloc verra ses adresses décalées d'un cran en plus comparé au bloc précédent. Si jamais le décalage dépasse la fin d'un bloc, alors on reprend au début. [[File:Mémoire interleaved par décalage.png|centre|vignette|upright=1.5|Mémoire entrelacée par décalage.]] En faisant cela, on remarque que les banques situées à N adresses d'intervalle sont différentes. Dans l'exemple du dessus, nous avons ajouté un décalage de 1 à chaque nouveau bloc à remplir. Mais on aurait tout aussi bien pu prendre un décalage de 2, 3, etc. Dans tous les cas, on obtient un entrelacement par décalage. Ce décalage est appelé le pas d'entrelacement, noté P. Le calcul de l'adresse à envoyer à la banque, ainsi que la banque à sélectionner se fait en utilisant les formules suivantes : * adresse à envoyer à la banque = adresse totale / N ; * numéro de la banque = (adresse + décalage) modulo N ; * décalage = (adresse totale * P) mod N. Avec cet entrelacement par décalage, on peut prouver que la bande passante maximale est atteinte si le nombre de banques est un nombre premier. Seulement, utiliser un nombre de banques premier peut créer des trous dans la mémoire, des mots mémoires inadressables. Malgré ce défaut, la technique a été utilisée sur quelques ordinateurs, avec l'exemple notable du superordinateur ''Burroughs Scientific Processor''. Pour éviter cela, il y a plusieurs solutions. Par exemple, on peut faire en sorte que N et la taille d'une banque soient premiers entre eux : ils ne doivent pas avoir de diviseur commun. Mais en pratique, elles n'ont pas vraiment été implémentées dans une vraie machine, et sont restées à l'état de recherche, aussi je les passe sous silence. ===L'entrelacement pseudo-aléatoire=== Une dernière méthode de répartition consiste à répartir les adresses dans les banques de manière "pseudo-aléatoire". L'idée exacte est de faire passer l'adresse dans une '''fonction de hachage''', plus ou moins complexe. Pour rappel, une fonction de hachage prend une entrée de grande taille et fournit en sortie un résultat de petite taille. Idéalement, elles doivent donner des résultats aussi différents que possible pour des entrées similaires, histoire de simuler une sorte de pseudo-aléatoire. Ici, une adresse est transformée en un numéro de banque. Une fonction de hachage simple ne fait que permuter des bits de l'adresse pour obtenir son résultat. Elle ne fait qu'échanger des bits de place avant de couper l'adresse en deux morceaux : un pour la sélection de la banque, et un autre pour la sélection de l'adresse dans la banque. Cette permutation est fixe, et ne change pas suivant l'adresse. Des fonctions de hachage plus complexes font des XOR bit à bit entre certains bits de l'adresse. ==Les contrôleurs mémoires SDRAM/DDR optimisés== Après avoir vu le cas des mémoires RAM, nous allons nous concentrer sur le cas particulier des mémoires SDRAM. Les mémoires SDRAM et DDR modernes sont capables de gérer plusieurs accès simultanés à la mémoire RAM, et leurs contrôleurs mémoire en font tout autant. C'est une différence majeure avec les mémoires asynchrones FPM/EDO, qui n'acceptaient qu'un seul accès mémoire à la fois. Leur contrôleur mémoire n'acceptait qu'un seul accès mémoire à la fois, c'était un contrôleur mémoire bloquant. Les contrôleurs mémoires des SDRAM sont eux non-bloquants et peuvent encaisser une dizaine d'accès mémoire à la fois. Peu de choses sont connues sur les contrôleurs de SDRAM/DDR modernes, les fabricants ne donnant que peu de détails dessus. Les rares simulateurs qui tentent de décrire leur fonctionnement, comme DRAM SIM I et II, sont particulièrement simples et ne vont pas dans le détail. Néanmoins, le peu qu'on sait est tout de même instructif. ===Rappel sur les SDRAM : tampon de ligne et commandes=== Les mémoires SDRAM sont des mémoires à tampon de ligne. Elles font un accès mémoire en deux étapes. La première étape recopie une ligne de N * 64 bits dans un tampon interne à la SDRAM. La seconde étape sélectionne une colonne, à savoir une donnée de 64 bits, qui est soit envoyée sur le bus de données pour une lecture, soit modifiée par une écriture. [[File:Mémoire à tampon de ligne.png|centre|vignette|upright=2|Mémoire à tampon de ligne]] Les accès mémoire sont traduits par un séquenceur mémoire en une série de commandes mémoires, qui sont séparées par des délais mémoire de quelques cycles d'horloge. Les délais sont très précis, et sont à respecter à la lettre. Une lecture ou une écriture se fait en maximum trois commandes : une commande PRECHARGE qui ferme la ligne précédemment utilisée, une commande ACT qui précise l'adresse de la ligne, et une commande READ ou WRITE qui précise l'adresse de la colonne et éventuellement la donnée à écrire. : Pour être plus précis, la commande PRECHARGE précharge les lignes de bits du plan mémoire à une tension particulière, ce qui les vide de leur contenu. Mais c'est un détail sans importance pour ce qui va suivre. Les SDRAM permettent de se passer de la première étape si des accès consécutifs se font dans la même ligne. Une fois activée, la ligne reste ouverte et on peut accéder plusieurs fois de suite dedans. On a alors juste à préciser l'adresse de colonne dedans. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | bgcolor="#FFA0FF" | PRECHARGE || bgcolor="#FFA0FF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || bgcolor="#A0FFFF" | READ (2) || bgcolor="#A0FFFF" | READ (3) || || || bgcolor="#A0FFFF" | READ (4) || bgcolor="#A0FFFF" | READ (5) || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | Donnée READ 1 || ||bgcolor="#A0FFFF" | Donnée READ 2 | bgcolor="#A0FFFF" | Donnée READ 3 || || || bgcolor="#A0FFFF" | Donnée READ 4 || bgcolor="#A0FFFF" | Donnée READ 5 |} Les SDRAM sont parfois capables de démarrer une commande avant que la précédente soit terminée. Mais le respect des délais mémoire est très limitant, ce qui fait qu'on ne peut pas parler de réel pipeline, comme c'est le cas sur les processeurs. Parlons plutot de '''commandes anticipées'''. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | || bgcolor="#A0FFFF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || || || bgcolor="#FFA0FF" | READ (2) || || || || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 | bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 | |} Un dernier point est que les chips mémoires de SDRAM sont composés de plusieurs banques, chacune étant une sorte de mini-mémoire miniature. Chaque banque a son propre décodeur, son propre tampon de ligne, ses propres multiplexeurs de colonne, sa logique de rafraichissement mémoire, etc. C'est comme si une SDRAM regroupait plusieurs mémoires séparées dans un même circuit intégré. [[File:Arrangement vertical.jpg|centre|vignette|upright=2.5|Mémoire multi-banques.]] ===La mise en attente des accès mémoire=== Un contrôleur de SDRAM peut accepter plusieurs accès mémoire en même temps. Mais cela ne signifie pas que le contrôleur sera prêt à les traiter immédiatement. Pour éviter tout problème de disponibilité, le contrôleur met en attente les accès mémoire que le processeur lui envoie, pour les exécuter dès que possible. Les accès mémoire sont mis en attente dans une mémoire FIFO, histoire de les exécuter dans leur ordre d'arrivée, notamment pour renvoyer les lectures dans l'ordre demandé. Il y a aussi une mémoire FIFO pour les données à écrire et une FIFO pour les données lues. Cette dernière sert au cas où le cache ne soit pas disponible quand on lui envoie la donnée lue. [[File:Module d'interface avec la mémoire.png|centre|vignette|upright=2.5|Contrôleur mémoire avec mise en attente des requêtes processeur et autres optimisations.]] Des optimisations sont possibles dès la mémoire FIFO. Par exemple, on peut regrouper plusieurs accès à des données consécutives en un seul accès en rafale. Le contrôleur mémoire analyse les requêtes mises en attente et détecte si certaines se font à des adresses consécutives. Si c'est le cas, il fusionne ces requêtes en une lecture/écriture en rafale. Une telle optimisation est appelée la '''combinaison de lecture''' pour les lectures, et la '''combinaison d'écriture''' pour les écritures. Une autre optimisation possible est le réacheminement lecture sur écriture (''Store to load forwarding''). Il s'agit d'un équivalent de l’optimisation utilisée dans le cadre de la désambiguïsation mémoire. Quand une lecture demande à accéder à une donnée écrite récemment, il se peut que l'écriture ait été mise en attente dans le contrôleur mémoire. Dans ce cas, le contrôleur mémoire peut renvoyer la donnée directement depuis ses mémoires FIFOs, sans faire d'accès mémoire en lecture. Les deux optimisations précédentes ne paraissent pas payer de mine, mais elles deviennent plus compréhensible quand on sait que certains contrôleurs mémoire peuvent mettre en attente beaucoup de données. Par exemple, l'''Intel E8870 - ''Scalable Memory Controller'' peut mettre en attente 64 lignes de cache dans la mémoire FIFO, soit 8 kibioctets de RAM ! Deux pages mémoire si la mémoire virtuelle utilise des pages de 4 kibioctets ! ===Les optimisations liées à la présence de plusieurs banques=== Les commandes anticipées permettent de faire un minimum de parallélisme mémoire. Même si elle parait très limitée, cette possibilité est décuplée par la présence de plusieurs banques dans la mémoire SDRAM. Il est en effet possible de faire plusieurs accès mémoire en même temps, dans des banques différentes. Il est possible de lancer un accès mémoire dans deux banques en même temps, par exemple. La seule contrainte est que la SDRAM est limitée à 4 banques actives en même temps. {|class="wikitable" |+ Pipelining basique sur les SDRAM |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 |- ! Banque Numéro 1 | || || || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || || |- ! Banque Numéro 2 | || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || |- ! Banque Numéro 3 | || || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || |} L'implémentation n'a rien de vraiment compliqué. Il suffit d'utiliser une mémoire FIFO par banque. Ainsi, les accès dans une même banque se feront en série, dans l'ordre d'arrivée, mais des accès à des banques différentes se feront dans le désordre. Cela ne pose pas de problème car les accès se font à des adresses différentes, ce qui fait qu'il n'y a pas de conflits majeurs. [[File:Gestion parallèle des banques.png|centre|vignette|upright=2.5|Gestion parallèle des banques.]] ===Le ré-ordonnancement des commandes mémoires=== Un contrôleur mémoire moderne est capable de changer l'ordre des requêtes mémoires, pour gagner en performances. En clair : il peut faire l'équivalent mémoire de l'exécution dans le désordre des processeurs haute performance. Une différence majeure est que le contrôleur mémoire ne remet pas les accès mémoire dans l'ordre avant de les envoyer au processeur. C'est le processeur qui remet en ordre les accès mémoire, dans sa ''load queue''. Pour cela, les requêtes mémoire sont "numérotées", à savoir qu'on leur attribue un identifiant binaire. Le contrôleur mémoire exécute les lectures/écritures dans le désordre, mais garde la trace de leur identifiant. Il sait dans quel ordre il exécute les accès mémoire et connait leur latence. Ce qui fait qu'il sait que la donnée lue à tel instant est associée à la requête mémoire numéro X. Pour les lectures, il renvoie la donnée lue avec l'identifiant. Le processeur sait alors à quelle lecture la donnée lue correspond et il se débrouille en interne pour gérer la situation. En clair : le processeur voit les accès mémoire dans le désordre, c'est lui qui les remet en ordre. Maintenant que ces précisions sont faites, posons cette question : quel est l'intérêt de faire les accès mémoire dans le désordre ? Accéder à des banques en parallèle est une réponse possible, mais le ré-ordonnancement est surtout utile si tous les accès mémoire atterrissent dans la même banque et dans la même ligne. Elle marche si plusieurs accès à une même ligne ne sont pas consécutifs, et qu'il y a des accès entre les deux. Après ré-ordonnancement, ces accès mémoire sont exécutés l'un à la suite de l'autre. Pour rendre le tout plus concret, voici un exemple. Imaginez que l'on sait les 3 accès mémoire suivants : * Une lecture ligne A ; * Une écriture ligne B ; * Une lecture ligne A. Sans réordonnancent, on doit émettre deux commandes PRECHARGE : une quand on passe de la ligne A à la ligne B, et une autre pour le passage inverse. pour éviter cela, le contrôleur mémoire peut retarder l'écriture, ou au contraire avancer la seconde lecture. le résultat est alors le suivant : * Lecture ligne A ; * Lecture ligne A ; * Une écriture ligne B. On a donc deux accès consécutifs à la même ligne, suivi par une écriture dans une autre ligne. La technique marche parce que l'on a un mix adéquat de lectures et d'écritures. Mais il existe des possibilités de réorganisation autres qui ne sont pas valides. Il faut par exemple prendre garde à éviter d'intervertir lectures et écritures à une même adresse, sous peine de problèmes. Encore une fois, il faut faire attention aux dépendances de données, encore une fois ! Ici des dépendances mémoires, mais les règles sont les mêmes. Le contrôleur mémoire teste les dépendances mémoires avant d'envoyer des commandes à la mémoire DDR/SDRAM. Il a existé des contrôleurs mémoires du temps des vieilles DDR qui implémentaient un ordonnancement très simples. Prenons l'exemple des coprocesseurs IO 81341 et 81342, qui étaient en réalité des ''chipsets'' intégrant un contrôleur SDRAM. Le ''chipset'' avait 5 ports : un pour les processeurs, un pour le pont sud (''southbridge'') et trois pour des canaux DMA. Pour le premier, il n'y avait pas de réordonnancement. Pour les autres, les lectures se font dans l'ordre, les écritures se font dans l'ordre, mais les écritures peuvent passer avant une lecture non-dépendante. L'implémentation était assez simple : le séquenceur était précédé par plusieurs mémoires FIFO. Le port processeur avait une seule mémoire FIFO, ce qui conservait l'ordre d'exécution des commandes mémoire. Pour les autres ports, le processeur avait vraisemblablement des files séparées pour les lectures et écritures. Le séquenceur mémoire vérifiait les dépendances mémoire de type RAW et autres. [[File:Double file d'attente pour les lectures et écritures.png|centre|vignette|upright=2.5|Double file d'attente pour les lectures et écritures]] L'implémentation était vraiment simple : elle n’exploitait même pas la présence de plusieurs banques ! De nos jours, les contrôleurs mémoires sont beaucoup plus évolués et sont capables d'exécution dans le désordre bien plus poussée. Les contrôleurs mémoires remplacent la FIFO par une sorte de fenêtre d'instruction dans laquelle ils poichent les accès mémoire selon leurs besoins. ==L'entrelacement sur les mémoires SDRAM== L''''entrelacement''' fonctionne sur les mémoires SDRAM, mais il doit être fortement modifié. Concrètement, tout ce qui a étré dit plus est inapplicable pour les SDRAM, du fait de la présence du tampon de ligne. Des adresses consécutives atterrissent déjà dans la même ligne, ce qui fait qu'on n'a pas besoin d'entrelacement au niveau d'une ligne. Et ne parlons pas de ce la présence de rangées et de canaux mémoire ! Cependant, il peut y avoir un entrelacement lié à la présence des banques SDRAM. L'entrelacement est géré juste avant le séquenceur mémoire. Le '''circuit d'entrelacement''' intervertit certains bits de l'adresse lors des accès mémoires. On trouve aussi des mémoires FIFOs pour mettre en attente les requêtes mémoire provenant du processeur. Elles permettent de recevoir plusieurs accès mémoire consécutifs. Si vous vous demandez comment plusieurs accès mémoire consécutifs peuvent avoir lieu, sachez que c'est une bonne question. Pour le moment, nous allons dire que ces accès proviennent du processeur ou de la mémoire cache, sans expliquer comment. Le contrôleur mémoire reçoit une série d'accès mémoire assez rapprochés, qu'il doit exécuter au mieux. [[File:Controleur mémoire d'une SDRAM avec entrelacement.png|centre|vignette|upright=2.5|Contrôleur mémoire d'une SDRAM avec entrelacement.]] Pour gérer l'entrelacement, le contrôleur de SDRAM prend en entrée l'adresse envoyée par le processeur, et la découpe en plusieurs champs : un pour sélectionner la banque adéquate, un autre pour la ligne, un autre pour la colonne, un autre pour la rangée. Une solution simple désactive l'entrelacement. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de banque || Adresse de ligne || Adresse de colonne |} Si cette solution fonctionne bien si les accès mémoire sont assez aléatoires, en pratique, mieux vaut utiliser l'entrelacement. ===L'entrelacement de banques/lignes=== Une première méthode d'entrelacement est l''''entrelacement de banques'''. Elle répartit deux lignes consécutives dans deux banques différentes. Pour comprendre l'idée, prenons un exemple. Imaginons une mémoire avec deux banques et 4 lignes. Imaginons qu'on parcoure/balaye la mémoire RAM en partant des adresses basses. Sans entrelacement de ligne, les accès se feront comme suit, de gauche à droite : {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Banque numéro 1 !! colspan="4" | Banque numéro 2 |- | Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 || Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 |} Avec l'entrelacement de banques, les accès se feront comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 |- | Ligne 1 || Ligne 1 || Ligne 2 || Ligne 2 || Ligne 3 || Ligne 3 || Ligne 4 || Ligne 4 |} Avec N banques, la première ligne ira dans la première banque, la seconde ligne dans la seconde, et ainsi de suite jusqu'à la énième banque qui va dans la banque numéro N. Et la ligne N+1 repart dans la première banque. L'avantage est que passer d'une ligne à la suivante est assez rapide. Au lieu de devoir fermer la première ligne et ouvrir la suivante on ouvrira directement la ligne suivante dans une banque différente, ce qui sera plus rapide. La commande PRECHARGE pourra être envoyée à la seconde banque en avance. Pour cela, l'adresse est découpée comme suit. {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne (précise à l'octet) |} ===L'entrelacement de rangées=== Il est aussi possible de faire la même chose, mais avec les rangées. Pour simplifier fortement, une rangée est simplement un chip mémoire. En réalité, les rangées ne sont pas des chip mémoire, mais un ensemble de chips mémoire regroupés ensemble histoire d'atteindre les 64 bits du bus de données. Par exemple, une rangée peut combiner 8 chips mémoire avec un bus de données de 8 bits chacun pour obtenir les 64 bits du bus de données d'une SDRAM. Mais nous passons ce détail sous silence dans les explications qui vont suivre, par souci de simplification. Pour faire comprendre l'entrelacement de rangée, le mieux est d'utiliser un exemple, le même que précédemment. Sans entrelacement de rangée, on change de chip mémoire une fois qu'on a balayé toutes ses adresses. {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Rangée numéro 1 !! colspan="4" | Rangée numéro 2 |- | Banque 1 || Banque 2 || Banque 3 || Banque 4 || Banque 1 || Banque 2 || Banque 3 || Banque 4 |} Avec l'entrelacement de ligne, on change de rangée dès qu'on change de banque/ligne. {|class="wikitable" |+ Adresse mémoire |- ! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 |- | Banque 1 || Banque 1 || Banque 2 || Banque 2 || Banque 3 || Banque 3 || Banque 4 || Banque 4 |} En clair, quand on a épuisé toutes les banques dans une même rangée, on passe à la rangée suivante au lieu de rester dans la même rangée. Notons que dans l'exemple précédent, on a combiné l'entrelacement de rangée et de banque, mais on aurait pu utiliser l'entrelacement de rangée seul. Mais ce n'est pas le cas le plus courant en pratique. Toujours est-il qu'en combinant entrelacement de rangée et de ligne, le découpage de l'adresse est le suivant : {|class="wikitable" |+ Adresse mémoire |- | Adresse de ligne || Numéro de rangée || Adresse de banque || Adresse de colonne (précise à l'octet) |} D'autres méthodes d'entrelacement sont possibles, mais elles sont plus complexes. Chaque contrôleur mémoire fait un peu à sa sauce. Ils permettent en général de choisir entre plusieurs entrelacements. Au minimum, ils permettent de désactiver l'entrelacement, d'activer un entrelacement de ligne, et d'autres entrelacements plus complexes, dont certains sont propriétaires et tenus secrets par le fabricant. ===L'entrelacement avec le ''dual channel''=== [[File:Dual channel slots.jpg|vignette|Slots mémoires avec ''dual channel''.]] L'usage du ''dual channel'' complique encore l'entrelacement. Là encore, il y a deux grandes solutions : avec et sans entrelacement des canaux mémoire. Rappelons le principe : deux barrettes de RAM sont accédées en parallèle. Et pour cela, il faut utiliser l'entrelacement. Typiquement, chaque barrette mémoire fournit 64 bits, ce qui fait que l'on peut accéder à 128 bits d'un seul coup, par exemple avec un accès en rafale. Sans ''dual channel'', la première barrette correspond à la moitié haute de la RAM, la seconde barrette correspond à la moitié basse. Avec ''dual channel'', une forme spécifique d'entrelacement est activée. Concrétement, deux blocs de 64 bits sont placés dans des canaux mémoire séparés. Idem avec du triple ou quadruple canal, mais c'est alors trois ou quatre blocs de 64 bits qui sont dispersés dans des canaux consécutifs. Le découpage de l'adresse est alors le suivant : {|class="wikitable" |+ Adresse mémoire |- ! colspan="6" | Sans entrelacement |- | Numéro de canal/barrette || Reste de l'adresse || Position de l'octet dans un bloc de 64 bits |- | colspan="6" | |- ! colspan="6" | Avec entrelacement |- | Reste de l'adresse || Numéro de canal/barrette || Position de l'octet dans un bloc de 64 bits |} [[File:Décodage d'adresse avec dual channel.png|centre|vignette|upright=2|Décodage d'adresse avec dual channel.]] Les contrôleurs de SDRAM précédents sont assez basiques et ne représentent pas les contrôleurs les plus évolués. Ils étaient utilisés sur les anciens PC, à une époque où ils étaient encore sur la carte mère du processeur. Mais de nos jours, le contrôleur mémoire est intégré au processeur. Et il incorpore de nombreuses optimisations afin de gagner en performances. Malheureusement, ces optimisations sont elles-mêmes dépendantes des optimisations intégrées dans le processeur, ce qui fait qu'on ne peut pas en parler ici. Elles demandent que le contrôleur mémoire reçoive plusieurs accès mémoire simultanés, ce qui n'a aucun sens à ce stade du cours. Aussi, nous allons les passer sous silence pour le moment. Nous les verrons dans le chapitre sur le parallélisme mémoire. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=La désambiguïsation mémoire | prevText=La désambiguïsation mémoire | next=Les processeurs superscalaires | nextText=Les processeurs superscalaires }} </noinclude> jy5dqbv4wbrm6gnavmfj1vm5r0i03b7 764729 764728 2026-04-23T22:22:49Z Mewtow 31375 /* La mise en attente des accès mémoire */ 764729 wikitext text/x-wiki Dans ce chapitre, nous allons voir les techniques qui permettent de gérer plusieurs accès mémoire simultanés directement au niveau du cache ou de la mémoire RAM. Par plusieurs accès mémoire simultanés, vous pensez sans doute à l'usage de cache multiports, voire de mémoires RAM multiport. C'est en effet une solution, mais ce n'est clairement pas la seule. Par exemple, il est possible de pipeliner l'accès au cache, voire à la mémoire RAM. Il existe de nombreuses techniques de '''parallélisme mémoire''', et nous allons les voir dans ce chapitre. Pour donner un exemple, il est possible de pipeliner les accès mémoire. Il est possible de pipeliner l'accès au cache et/ou l'accès à la mémoire, et nous verrons comment faire dans ce chapitre. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, qu'ils aient ou non un pipeline dynamique. Par contre, pipeliner la mémoire est une technique ancienne, aujourd'hui peu utilisée. Elle était utilisée sur des très vieux ordinateurs, qui avaient un pipeline mais pas de mémoire cache. Mais de nos jours, les mémoires SDRAM et DDR ne sont pas adaptées pour ça, il est très difficile de les pipeliner correctement. Le parallélisme mémoire est utile aussi bien sur des processeurs à émission dans l'ordre que dans le désordre. Par exemple, un processeur ''in-order'' avec des lectures non-bloquantes peut e, profiter. Il peut techniquement lancer une seconde lecture, après avoir rencontré une première lecture non-bloquante. En général, le nombre de lectures consécutives est cependant limité à moins d'une dizaine. À l'opposé, les processeurs sans lectures non-bloquantes profitent pas du parallélisme mémoire. À la rigueur, ils peuvent en profiter s'ils intègrent des techniques de préchargement. Pendant que le processeur exécute une lecture/écriture, il peut précharger une autre donnée en parallèle. Inutile de dire que sans pipeline processeur, le parallélisme mémoire ne sert pas à grand-chose. ==Le ''line fill buffer'' et les techniques associées== Lors d'un défaut de cache, le processeur doit attendre que toute la ligne de cache soit chargée avant d'être utilisable. Or, la taille d'une ligne de cache est supérieure à la largeur du bus mémoire, ce qui fait qu'une ligne de cache est chargée en plusieurs fois, morceaux par morceaux, mot mémoire par mot mémoire. Le chargement peut se faire directement dans le cache, mais ce n'est pas une solution très pratique. À la place, beaucoup de processeurs ajoutent une mémoire tampon entre la RAM et le cache, appelée le '''tampon de remplissage de ligne''' (''line-fill buffer'') dans les processeurs modernes. Lors d'un défaut de cache, le processeur charge la donnée de la RAM dans le tampon de remplissage de ligne, mot mémoire par mot mémoire. Une fois plein, le tampon de remplissage de ligne est recopié dans la ligne de cache. Le tampon de remplissage de ligne contient une ligne de cache, si ce n'est que certains bits de contrôle ne sont pas présents. Le tampon de remplissage de ligne contient un bit de validité pour chaque mot mémoire de la ligne de cache, qui indiquent si le mot mémoire a été chargé. Par exemple, prenons un processeur 64 bits, qui gère donc des mots mémoire de 8 octets, avec des lignes de cache 256 octets/32 mots mémoire. Le tampon de remplissage de ligne contiendra 32 bits de validité. Si le processeur a chargé les 6 premiers mots mémoire, les 6 premiers bits de validité seront mis à 1, les autres seront encore à zéro. Il faut noter que la disponibilité de la ligne complète se détermine assez facilement en faisant un ET logique entre tous les bits de validité. Le processeur sait ainsi quand la ligne de cache est disponible entièrement et donc quand la transférer dans le cache. La même chose existe avec une hiérarchie de cache, sauf que l'on trouve une mémoire tampon entre chaque niveau de cache. Il y a un tampon de remplissage de ligne entre le cache L1 et le cache L2, entre le cache L2 et le L3, etc. Si le cache ne gère qu'un seul défaut de cache à la fois, le ''fill line buffer'' est une mémoire très simple, qui ne mémorise qu'une seule ligne de cache. Et cela vaut aussi bien pour un cache bloquant que non-bloquant. Mais sur les caches capables de gérer plusieurs défauts simultanés, le tampon de remplissage de ligne est une mémoire de type FIFO ou LIFO, capable de mémoriser plusieurs lignes de cache. Notons que le tampon de remplissage de ligne est très utile pour implémenter certaines techniques, par exemple le contournement du cache. Nous avions vu dans le chapitre sur le cache que certains accès mémoire doivent contourner le cache, pour des raisons de cohérence des caches. C'est notamment nécessaire pour accéder aux périphériques, mais c'est aussi utile pour des raisons de performances dans des cas très spécifiques. Les accès qui contournent le cache se font directement dans le tampon de remplissage de ligne : le processeur écrit ou lit les données depuis ce tampon de remplissage de ligne, sans accéder au cache. ===L'''early restart'' et le ''critical word load''=== La présence du ''line buffer'' permet une optimisation assez intéressante, qui permet de réduire la latence des défauts de cache. L'optimisation consiste à lire un mot mémoire dans le tampon de remplissage de ligne, même si la ligne de cache complète n'a pas encore été chargée. Il existe deux manières de faire cela, qui portent les noms d'''early restart'' et de ''critical word load''. La première est la version la plus simple, la seconde est plus complexe mais plus performante. Avec la technique d''''''early restart''''', la ligne de cache est chargée normalement, en partant de son premier mot mémoire. Dès que le mot mémoire lu/écrit par le processeur est copié dans le ''line fill buffer'', il est envoyé au processeur immédiatement. Illustrons le tout par un exemple, où une ligne de cache fait 16 mots mémoire. Le processeur effectue une lecture, qui lit le 5ème mot mémoire. Sans ''early restart'', le processeur doit charger les 16 mots mémoire avant de faire la lecture dans le cache. Avec ''early restart'', le processeur reçoit la donnée dès que le 5ème mot mémoire est disponible. Le processeur doit attendre que les 4 premiers mots mémoire soient chargés, puis le 5ème mot mémoire arrive et est envoyé directement au processeur, il est lu directement depuis le ''line fill buffer''. Les 11 mots mémoire suivants sont ensuite chargés dans le cache pendant que le processeur fait des calculs dans son coin. Le ''critical word load'' est une optimisation de la technique précédente où le chargement de la ligne de cache commence directement à la donnée demandée par le processeur. Pour reprendre l'exemple précédent, où le processeur demande le 5ème mot mémoire sur 16, le ''critical word load'' charge le 5ème mot mémoire en premier et l'envoie au processeur, ce qui fait qu'il est chargé très rapidement. Pas besoin d'attendre que le processeur charge les 4 mots mémoire précédents comme avec l'''early restart''. Dans le détail, le processeur charge le 5ème mot mémoire en premier, puis charge les 11 suivants, et termine par les 4 mots mémoire du début. En clair, le ''critical word load'' commence par charger le mot mémoire lu, puis les blocs suivants, avant de revenir au début du bloc pour charger les blocs restants. Ainsi, la donnée demandée par le processeur sera la première disponible. Pour cela, l'organisation du tampon de remplissage de ligne est modifiée de manière à rendre cela possible. Il a une taille égale à une ligne de cache complète, qui contient elle-même plusieurs mots mémoire. Dans le ''line-fill buffer'', chaque mot mémoire est stocké avec un tag, qui indique l'adresse du mot mémoire stocké dans le ''line-fill buffer''. Le ''line-fill buffer'' est donc un cache un peu particulier, qui fonctionne comme un cache du point de vue du processeur, comme une mémoire FIFO pour les transferts avec le cache. Ainsi, un processeur qui veut lire dans le cache après un défaut peut accéder à la donnée directement depuis le tampon de remplissage de ligne, alors que la ligne de cache n'a pas encore été totalement recopiée en mémoire. ==Les caches pipelinés== Il est possible de pipeliner l'accès au cache, ce qui demande juste de rajouter des registres au bon endroits et quelques circuits de contrôle. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, même ceux avec un pipeline dynamique. Et l'implémentation peut se faire de plusieurs manières différentes. Il faut dire que découper une mémoire cache en plusieurs étapes peut se faire de plusieurs manière. Commençons par la plus simple. Beaucoup de processeurs des années 2000 avaient une fréquence peu élevée comparée aux standards d'aujourd'hui, ce qui fait que leur cache avait bien un temps d'accès d'un cycle d'horloge. Mais l'accès au cache se faisait en deux cycles d'horloge : un cycle pour calculer une adresse et un cycle pour l'accès mémoire proprement dit. Et ces deux étapes étaient pipelinées, à savoir que deux micro-opérations mémoire peuvent s'exécuter en même temps : une dans l'étage de calcul d'adresse, une autre dans le cache. L'unité mémoire était donc pipelinée, alors que l'accès au cache ne l'était pas. Les processeurs qui implémentaient cette technique regroupent les micro-architectures K5 et K6 d'AMD, les processeurs Intel de micro-architecture P6 (Pentium 2 et 3) et quelques autres. Il est aussi possible de pipeliner l'accès au cache lui-même. Avec un cache pipeliné, l'accès au cache ne se fait pas en un seul cycle, mais en plusieurs. Cependant, on peut lancer un nouvel accès au cache à chaque cycle d'horloge, comme un processeur pipeliné lance une nouvelle instruction à chaque cycle. L'implémentation est assez simple : il suffit d'ajouter des registres dans le cache. Pour cela, on profite que le cache est composé de plusieurs composants séparés, qui échangent des données dans un ordre bien précis, d'un composant à un autre. De plus, le trajet des informations dans un cache est linéaire, ce qui les rend parfois pour l'usage d'un pipeline. ===Le ''pipelining'' des caches ''direct-mapped''=== Voici ce qui se passe avec un cache directement adressé. Pour rappel ce genre de cache est conçu en combinant une mémoire RAM, généralement une SRAM, avec quelques circuits de comparaison et un MUX. L'accès au cache se fait globalement en deux étapes : la première lit la donnée et le ''tag'' dans la SRAM, et on utilise les deux dans une seconde étape. Nous avions vu il y a quelques chapitre comment pipeliner des mémoires, dans le chapitre sur les mémoires évoluées. Et bien ces méthodes peuvent s'utiliser pour la mémoire RAM interne au cache !L'idée est d'insérer un registre entre la sortie de la RAM et la suite du cache, pour en faire un cache pipéliné, à deux étages. Le premier étage lit dans la SRAM, le second fait le reste. L'implémentation sur les caches associatifs à plusieurs voies est globalement la même à quelques détails près. [[File:Cache directement adressé pipeliné - deux étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - deux étages]] Avec le circuit précédent, il est possible d'aller plus loin, cette fois en pipelinant l'accès à la mémoire RAM interne au cache. Pour comprendre comment, rappelons qu'une mémoire SRAM est composée d'un plan mémoire et d'un décodeur. L'accès à la mémoire demande d'abord que le décodeur fasse son travail pour sélectionner la case mémoire adéquate, puis ensuite la lecture ou écriture a lieu dans cette case mémoire. L'accès se fait donc en deux étapes successives séparées, on a juste à mettre un registre entre les deux. Il suffit donc de mettre un gros registre entre le décodeur et le plan mémoire. [[File:Cache directement adressé pipeliné - trois étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - trois étages]] Et on peut aller encore plus loin en découpant le décodeur en deux circuits séparés. En effet, rappelez-vous le chapitre sur les circuits de sélection : nous avions vu qu'il est possible de créer des décodeurs en assemblant des décodeurs plus petits, contrôlés par un circuit de prédécodage. Et bien on peut encore une ajouter un registre entre ce circuit de prédécodage et les petits décodeurs. : Théoriquement, toute l'adresse est fournie d'un seul coup au cache, la quasi-totalité des processeurs présentent l'adresse complète à un cache pipeliné. Mais le Pentium 4 fait autrement. Il faut noter que les premiers étages manipulent l'indice dans la SRAM, qui est dans les bits de poids faible, alors que les étages ultérieurs manipulent le ''tag'' qui est dans les bits de poids fort. Les concepteurs du Pentium 4 ont alors décidé de présenter les bits de poids faible lors du premier cycle d'accès au cache, puis ceux de poids fort au second cycle. Pour cela, l'ALU fonctionnait à une fréquence double de celle du processeur, tout comme le cache L1. Il n'y avait pas de pipeline proprement dit, mais cela réduisait grandement la latence d'accès au cache. ===Le ''pipelining'' des caches splittés/sériels=== Il est aussi possible de pipeliner un cache dits splittés, aussi appelés à accès sériel. Pour rappel, les caches à accès sériel vérifient si il y a succès ou défaut de cache, avant d'accéder aux lignes de cache en cas de succès. Ils font donc différemment des autres caches, qui accèdent à une ligne de cache, avant de déterminer s'il y a succès ou défaut en lisant le tag de la ligne de cache. Les caches sériels disposent de deux SRAM : une pour les tags des lignes de cache et une pour les données. Ils accèdent à la SRAM pour les tags, avant d’accéder à la SRAM des données en cas de succès de cache. Vu que l'accès se fait en deux étapes, une vérification des tags suivie de la lecture/écriture des données, il est facile à pipeliner. Pipeliner le cache permet de régler le problème des accès au cache L1, et elle est tout le temps utilisé sur les processeurs modernes. Mais que faire en cas de défaut de cache ? ==Les caches non bloquants== Un '''cache bloquant''' est un cache auquel le processeur ne peut pas accéder pendant un défaut de cache. Il faut attendre que la lecture ou écriture en RAM soit terminée avant de pouvoir utiliser de nouveau le cache. Un '''cache non bloquant''' n'a pas ce problème : on peut l'utiliser pendant un défaut de cache. Les caches non bloquants permettent de démarrer une nouvelle lecture ou écriture alors qu'une autre est en cours, ce qui permet d'exécuter plusieurs lectures ou écritures en même temps. ===Les ''Miss Handling Status Registers''=== Lors d'un défaut de cache, la mémoire RAM est consultée pendant le défaut de cache, mais le cache est inutilisé. Un défaut de cache n'utilise pas le cache, ce n'est qu'un accès en mémoire RAM, sur le bus mémoire (ou un accès aux niveaux de cache inférieurs, peu importe). Le cache en lui-même est laissé libre, rien n’empêche d'y accéder, il est en réalité intrinsèquement non-bloquant. Les caches, bloquants comme non-bloquants, sont en réalité composés d'une mémoire cache proprement dite, entourée de circuits qui servent d'interface entre le processeur et le cache lui-même. Et parmi les circuits tout autour, certains gèrent l'accès au cache lors d'un défaut de cache. Ils sont regroupés sous le terme de '''''Miss Handling Architecture''''' (MHA). La différence entre un cache bloquant et un cache non-bloquant est en réalité liée à l'implémentation de la MHA. Les caches bloquants coupent volontairement l'accès au cache lors d'un défaut, car les défauts sont plus simples à gérer ainsi. Le défaut de cache rapatrie une donnée depuis la RAM, qui sera écrite dans le cache. Et il ne faut pas qu'une tentative d'accès à cette donnée ait lieu avant qu'elle ne soit chargée. Mais ce blocage est général et touche tout le cache, alors que seule une ligne de cache est concernée par le défaut de cache. L'idée derrière un cache non-bloquant est que seule la ligne de cache est bloquée, mais les autres sont accessibles. L'idée est alors de mémoriser les lignes de cache concernées par le défaut de cache, afin d'en bloquer l'accès. À chaque accès, on vérifie si la ligne de cache est déjà réservée par un défaut de cache. La lecture/écriture est alors bloquée si c'est le cas, mais elle accepte les accès sinon. Pour cela, la ''Miss Handling Architecture'' contient des registres qui mémorisent des informations sur les défauts de cache en cours. Ils portent le nom de '''''miss status handling registers''''', que j’appellerais dorénavant MSHR, qui sont aussi appelés des '''''miss buffer'''''. Le contenu des MSHR varie beaucoup suivant le processeur, mais ces derniers stockent au minimum les informations suivantes : * Le numéro de la ligne de cache dans laquelle les données sont chargées. * Un bit de validité qui indique si le MSHR est vide ou pas, qui est mis à 0 quand le défaut de cache est résolu. * Un ou plusieurs champs de lecture/écriture, qui contiennent des informations sur la lecture/écriture. ** Pour une lecture, elle contient des informations sur la destination de la donnée, à savoir qui prévenir quand le défaut de cache est terminé. C'est parfois un nom/numéro de registre (celui dans lequel charger la donnée), mais c'est souvent le numéro de l'entrée dans le ''load/store queue''. ** Pour les écritures, elle contient la donnée à écrire, ou éventuellement un numéro de ''load/store queue'' où se trouve la donnée à écrire. Il faut noter que le bus mémoire ne peut gérer qu'un seul défaut de cache à la fois. Aussi, il est intéressant de regarder ce qui se passe lorsqu'un second défaut de cache survient, pendant qu'un premier est en cours. Dans ce cas, il y a deux réponses qui correspondent à deux types de caches non-bloquants, qui portent les noms barbares de caches de type succès après défaut et défaut après défaut. Sur le premier type, il ne peut pas y avoir plusieurs défauts de cache simultanés. Dès qu'un second défaut de cache survient, le cache stoppe son activité et on ne peut plus démarrer de nouvelle lecture/écriture, tant que le premier défaut de cache n'est pas résolu. Le second type est plus souple et autorise la survenue de plusieurs défauts de cache simultanés. Du moins, jusqu'à une certaine limite, car le cache ne peut supporter qu'un nombre limité d'accès mémoires simultanés (pipelinés). ===Les accès simultanés à une même ligne de cache=== Il arrive que le processeur fasse plusieurs accès mémoire simultanés à la même ligne de cache. Si la ligne de cache en question n'a pas encore été chargée dans le cache, alors on a plusieurs défauts de cache consécutifs pour la même ligne de cache, et le cache non-bloquant doit gérer la situation. Pour la suite, il va falloir faire une petite distinction entre les défauts primaires et secondaires. Imaginons qu'un défaut de cache ait lieu et demande à charger une donnée dans la ligne de cache numéro N. Il s'agit du premier défaut impliquant cette ligne de cache précise, ce qui lui vaut le nom de '''défaut de cache primaire'''. Mais par la suite, d'autres accès mémoire à la même ligne de cache ont lieu, alors que la ligne de cache n'est pas encore disponible. Dans ce cas, il s'agit de '''défauts de cache secondaires'''. Pour l'unité d'accès mémoire, les défauts de cache primaire et secondaire sont différents (ils prennent tous une entrée dans la ''load/store queue''). Mais pour le cache, ils ne correspondent qu'à un seul accès au cache : celui qui demande de charger la ligne de cache demandée. Les défauts de cache primaire et secondaire à la même ligne de cache se voient attribuer un MSHR unique. La gestion des défauts de cache secondaires dépend du cache non-bloquant. La solution la plus simple ne permet pas les défauts de cache secondaires. Le cache ne permet pas deux défauts de cache simultanés pour la même ligne de cache. Les autres solutions le permettent, en fusionnant des accès simultanés à la même ligne de cache en un seul au niveau des MSHR. Dans tous les cas, détecter les défauts de cache secondaires sont un problème qu'il faut détecter. La ''Miss Handling Architecture'' doit détecter les défauts de cache secondaire. Pour cela, elle procède comme suit. Lors de chaque défaut de cache, la MHA récupère le numéro de la ligne de cache associé. Il vérifie alors chaque MSHR pour vérifier s'il contient le numéro en question. S'il n'y a aucune correspondance dans les MSHR, alors c'est signe que le défaut de cache est un défaut primaire. Mais s'il y en a une, alors c'est un défaut secondaire. Évidemment, cela signifie que lors d'un défaut de cache, le numéro de ligne de cache est envoyé à tous les MSHR, pour comparaison. Les MSHR sont donc regroupés dans une mémoire associative, une sorte de mini-cache, faciliter l'implémentation. Lors d'un défaut de cache primaire, l'accès à un cache non-bloquant se fait comme suit : le processeur envoie une adresse au cache, accède à celui-ci, et détecte la survenue d'un défaut de cache. Il en profite alors pour attribuer une ligne de cache dans laquelle sera chargée ce défaut. L'attribution est très simple dans le cas des caches ''direct mapped'', ou associatifs par voie, pour lesquels l'attribution se fait assez simplement. Il mémorise alors cette information dans les MSHR, après avoir vérifié que le défaut de cache n'était pas un défaut secondaire. Lors des accès ultérieurs à une adresse proche, censée être dans la même ligne de cache, le processeur va encore une fois rencontrer un défaut de cache. Il va alors déterminer le numéro de la ligne de cache associée à l'adresse, et comparer ce numéro avec les MSHRs. Si un MSHR contient ce numéro, c'est signe que le défaut de cache est un défaut secondaire. La MHA réagit alors différemment selon le processeur considéré. Une première solution n'autorise pas les défauts de cache secondaires. Si l'un d'entre eux survient, le processeur est gelé par un ''pipeline stall'', une bulle de pipeline. Une autre solution fusionne les défauts de cache secondaires avec le défaut de cache primaire : tout cela ne correspond qu'à un seul défaut de cache pour lui. ===Les MSHR simples=== Un cache non-bloquant à '''MSHR simple''' contient juste plusieurs MSHR qui mémorisent juste un numéro de ligne de cache, un bit de validité, le champ de lecture/écriture, et l'adresse exacte de lecture/écriture. Avec cette organisation, il est possible d'avoir plusieurs défauts de cache séparés, mais à la condition que chaque défaut accède à une ligne de cache différente. Deux accès simultanés à une même ligne de cache ne sont pas possibles, les défauts de cache secondaires ne sont pas autorisés. Ainsi, chaque défaut de cache se voit attribuer son propre MSHR, chacun contient un numéro de ligne de cache différent. Pour comprendre pourquoi c'est impossible de gérer les défauts secondaires, il faut regarder le champ de lecture/écriture. Si on veut effectuer plusieurs écritures consécutives à la même adresse, le MSHR n'aura pas de quoi mémoriser les deux données à écrire. Il pourra mémoriser la première donnée à écrire, pas la seconde. Même chose lors d'une lecture : le champ lecture/écriture peut mémorisr la destination de la première lecture, pas de la seconde. L'avantage est que la MHA n'a besoin que des MSHR et de quelques circuits annexes. Les autres solutions rajoutent des circuits annexes pour gérer les défauts de cache secondaires, qui utilisent beaucoup de circuits. Le cout en circuit est donc élevé, mais le gain en performance est là. Passons maintenant aux caches non-bloquants qui autorisent les défauts de cache secondaire. La solution la plus simple consiste à utiliser ===Les MSHR adressés implicitement=== Avec les '''MSHR adressés implicitement''', il est possible de fusionner plusieurs accès mémoire à une même ligne de cache, mais sous une condition très importante : ces accès lisent/écrivent des mots mémoire différents. Par exemple, imaginons qu'une ligne de cache contienne 8 mots mémoire de 64 bits. Si un premier accès mémoire lit le mot mémoire numéro 7 (dernier mot mémoire de la ligne), et le second accès le mot mémoire numéro 3, alors la fusion est possible. Mais si deux accès mémoire veulent lire/écrire le mot mémoire numéro 7, alors la fusion n'est pas possible et le processeur se bloque, un ''pipeline stall'' survient. Un MSHR adressé implicitement est un MSHR simple, qui contient naturellement un numéro de ligne de cache (tag) et un bit de validité, sauf que le champ de lecture/écriture est dupliqué. Les différents champs lecture/écriture d'un MSHR sont regroupés dans une mémoire RAM/cache qui contient autant d'entrées qu'il y a de mot mémoire dans une ligne de cache. Chaque entrée de la table est associée à un mot mémoire de la ligne de cache et stocke des informations sur celui-ci. Une entrée mémorise, au minimum : * Un champ de lecture/écriture qui contient soit la destination de la lecture, soit la donnée à écrire. * Un bit R/W permettant d'interpréter correctement le champ de lecture/écriture. * Un bit de validité pour chaque entrée de la table, qui dit si un défaut de cache antérieur accède déjà à ce mot mémoire. La fusion de deux défauts de cache est ainsi assez simple. Un défaut de cache primaire/secondaire configure l'entrée associée au mot mémoire lu/écrit. Si un défaut secondaire ultérieur lit/écrit un mot mémoire différent (dont le bit de validité de l'entrée dans la table est à zéro), pas de conflit : il configure une autre entrée, vide. Mais s'il lit/écrit un mot mémoire pour lequel l'entrée de la table est occupée, il y a conflit, le processeur est bloqué par un ''pipeline stall'', une bulle de pipeline. Avec cette organisation, le nombre de MSHR indique combien de lignes de cache peuvent être lues en même temps depuis la mémoire. Quant au nombre d'entrées par MSHR, il détermine combien d'accès mémoires qui ne se recouvrent pas peuvent avoir lieu en même temps. ===Les MSHR adressés explicitement=== Les '''MSHR adressés explicitement''' n'ont pas les contraintes des MSHR adressés implicitement. Avec eux, il est possible d'avoir plusieurs défauts de cache pointant vers la même ligne de cache, mais aussi vers le même mot mémoire. De tels défauts de cache apparaissent sur les processeurs à exécution dans le désordre, mais sont très rares, voire inexistants, sur les processeurs ''in-order''. L'idée est encore que chaque MSHR est associée à une table mémoire qui mémorise des entrées. Cette table n'est autre qu'une mémoire FIFO. Sauf que cette fois-ci, une entrée n'est pas associée à un mot mémoire. Une entrée est associée à un défaut de cache. Une entrée mémorise là encore la destination de la lecture ou la donnée de l'écriture suivi du bit R/W, ainsi que d'un bit de validité par entrée, mais aussi : la position du mot mémoire lu dans la ligne de cache. C'est cette dernière information qui n'était pas présente dans les MSHR adressés implicitement. De plus, le nombre d'entrée par MSHR n'est pas égal au nombre de mots mémoires dans une ligne de cache, mais peut être arbitrairement grand. L'avantage d'un tel cache est qu'il est capable de traité les défauts secondaires concernant un même mot mémoire, et ce, car les accès à la ligne de cache concernée se produisent dans l'ordre de génération des défauts (la table mémoire est une FIFO). ===Les MSHRs inversés=== Généralement, plus on veut supporter de défauts de cache, plus le nombre de MSHR et d'entrées augmente. Mais au-delà d'un certain nombre d'entrées et de MSHR, les MSHR adressés implicitement et explicitement ont tendance à bouffer un peu trop de circuits. Utiliser une organisation un peu moins gourmande en circuits est donc une nécessité. Cette organisation plus économe se base sur des MSHR inversés. Les MSHR inversés ne contiennent qu'une seule entrée, en quelque sorte : au lieu d’utiliser n MSHR de m entrées chacun, on va utiliser n × m MSHR inversés. La différence, c'est que plusieurs MSHR peuvent contenir un tag identique, contrairement aux MSHR adressés implicitement et explicitement. Lorsqu'un défaut de cache a lieu, chaque MSHR est vérifié. Si jamais aucun MSHR ne contient de tag identique à celui utilisé par le défaut, un MSHR vide est choisi pour stocker ce défaut, et une requête de lecture en mémoire est lancée. Dans le cas contraire, un MSHR est réservé au défaut de cache, mais la requête n'est pas lancée. Quand la donnée est disponible, les MSHR correspondant à la ligne qui vient d'être chargée vont être utilisés un par un pour résoudre les défauts de cache en attente. ===Les MSHR intégrés au cache=== Certains chercheurs ont remarqué que pendant qu'une ligne de cache est en train de subir un défaut de cache, celle-ci reste inutilisée, et son contenu est destiné à être perdu une fois le défaut de cache résolu. Ils se sont dit que, plutôt que d'utiliser des MSHR séparés, il vaudrait mieux utiliser la ligne de cache pour stocker les informations sur les défauts de cache en attente dans cette ligne de cache. Pour éviter tout problème, il faut rajouter un bit dans les bits de contrôle de la ligne de cache, qui sert à indiquer que la ligne de cache est occupée : un défaut de cache a eu lieu dans cette ligne, et elle stocke donc des informations pour résoudre les défauts de cache. ==L'entrelacement mémoire== Nous venons de voir comment une mémoire cache peut gérer plusieurs accès mémoire simultanés. Il se trouve que ce genre d'optimisation dépasse largement le cadre du cache. Les mémoires RAM ne sont pas en reste, elles aussi ! Il est possible de pipeliner l'accès à une mémoire RAM, en utilisant des techniques dites d''''entrelacement'''. Précisons cependant que cette optimisation n'est pas utilisée sur les mémoires SDRAM ou DDR modernes, du moins pas sans modifications majeures. Mais divers ordinateurs assez anciens, dont des superordinateurs, ont utilisé cette technique. La technique demande d'utiliser plusieurs mémoires séparées. Sur les anciens ordinateurs, les mémoires en question sont des chips mémoire, à savoir des circuits intégrés de DRAM. Pour faire plus général, nous allons utiliser le terme de '''banques''', ou encore de bancs mémoire. La différence est qu'il existe des mémoires multi-banque, qui regroupent plusieurs banques indépendantes dans un seul boitier, dans un seul chip mémoire. En clair, de telles mémoires regroupent plusieurs sous-mémoires dans un seul circuit intégré. Il est aussi possible d'utiliser plusieurs chips mémoire séparés, chacun ayant une banque. Reste à combiner ces banques pour former un pseudo-pipeline. ===Utiliser plusieurs banques sans entrelacement=== Sans optimisation particulière, les adresses sont réparties dans les banques comme indiqué ci-dessous. Il s'agit de ce qui s'appelle un arrangement horizontal, et nous avions vu celui-ci dans le chapiutre sur les mémoires SDRAM. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Avec cet arrangement, les bits de poids fort de l'adresse sont utilisées pour sélectionner la banque adéquate, et le reste de l'adresse est envoyé sur le bus d'adresse. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Adresse de banque !! Adresse dans la banque |- | Quelques bits de poids fort || Reste de l'adresse |} L'entrelacement change cette répartition, afin d'accélérer les accès mémoire. L'entrelacement classique vise à améliorer les accès à des adresses mémoires consécutives, mais des formes plus évoluées d'entrelacement visent à optimiser des accès mémoire arbitraires. Voyons-les dans l'ordre, du plus simple au plus complexe. ===L'entrelacement classique=== Avec l''''entrelacement classique''', des adresses consécutives sont réparties dans des banques consécutives. Précisons que cette attribution des adresses n'implique absolument pas la mémoire virtuelle ou n'importe quel mécanisme dans le processeur. La répartition décide que telle adresse mémoire va dans telle banque, à telle adresse dans la banque. Elle est donc le fait du contrôleur mémoire, donc en dehors du processeur (les contrôleurs mémoires n’étaient pas encore intégrés dans le processeur à l'époque). [[File:Répartition des adresses dans une mémoire interleaved.png|centre|vignette|upright=1.5|Répartition des adresses dans une mémoire interleaved.]] L'entrelacement simple permet d'accélérer les accès mémoire qui se font à des adresses consécutives. Avec l'entrelacement, chaque accès mémoire tombe dans une banque mémoire différente, ce qui fait qu'on peut démarrer un nouvel accès mémoire à chaque cycle d'horloge. Pendant qu'une banque est occupée par un accès mémoire, on démarre le suivant dans une autre banque, et ainsi de suite. Le tout se termine soit quand on a épuisé toutes les banques libres, soit quand l'accès en rafales se termine. Pas besoin d'attendre que la mémoire ait fini sa lecture/écriture avant de démarrer la lecture/écriture suivante. [[File:Pipemining mémoire.png|centre|vignette|upright=2.5|Pipelining mémoire]] L'animation suivante illustre bien le processus, avec quatres banques. [[File:Interleaving.gif|centre|vignette|upright=2.5|Pipelining mémoire]] L'entrelacement simple utilise les bits de poids faible pour sélectionner la banque, et les bits de poids fort pour la case mémoire. [[File:Adresse mémoire d'une mémoire entrelacée.png|centre|vignette|upright=2|Adresse mémoire d'une mémoire entrelacée]] Précisons que le temps d'accès mémoire ne change pas beaucoup avec l'entrelacement. Par contre, on peut faire plus d'accès mémoire simultanés. On peut démarrer un accès mémoire par cycle d'horloge, mais l'accès en lui-même prend plusieurs cycles. Les mémoires à entrelacement ont donc un débit supérieur aux mémoires qui ne l'utilisent pas. L'entrelacement classique pourrait être utilisé pour accélérer les transferts entre mémoire RAM et cache, qui se font en blocs d'adresses consécutives. Mais de nos jours, il n'est pas utilisé dans cette optique. Il faut dire qu'il est redondant avec le mode rafale des mémoires DRAM. Les deux font globalement la même chose et on voit mal comment utiliser les deux en même temps. Le mode rafale étant plus simple à implémenter, et plus léger niveau utilisation du bus de commande mémoire, il est préféré à l'entrelacement. Par contre, l'entrelacement a eu son heure de gloire sur d'anciens processeurs qui n'avaient pas de mémoire cache, alors qu'ils disposaient d'un pipeline. L'exemple typique est celui des nombreux ''processeurs vectoriels'', que nous n'avons pas encore abordé à ce stade du cours. De tels processeurs avaient un pipeline, une unité de calcul fortement pipelinée, et parfois même de l'exécution dans le désordre et du renommage de registres ! ===L'entrelacement par décalage=== L'entrelacement simple est très utile pour les accès en rafle ou équivalents. Par contre, il existe de rares situations où il n'est pas optimal. Une de ces situations est celle des accès en enjambée (en ''stride''), où l'on accède en série à des adresses sont séparées par N mots mémoires. [[File:Accès par enjambées.png|centre|vignette|upright=2|Accès par enjambées.]] De tels accès surviennent quand un logiciel accède à des structures de données spécifiques, à savoir des tableaux de structures, des matrices, ou d'autres structures de données dans le genre. Un cas classique est celui du parcours d'une matrice colonne par colonne. Les matrices de nombres sont mémorisées ligne par ligne, ce qui fait que parcourir une colonne demande de passer d'une ligne à la suivante et de faire des grands sauts en mémoire RAM, mais tous espacés par la même distance. Avec l'entrelacement simple, les accès en enjambée sont moins performants que les accès à des adresses consécutives, mais cela ne fonctionne pas trop mal. Le pire cas est celui où l'on a N banques et où les données sont justement placées toutes les N adresses. Des accès consécutifs vont tous tomber dans la même banque, on ne peut plus accéder à des banques différentes en parallèle. Mais en dehors de ce cas, on voit une amélioration par rapport à la situation sans entrelacement. Pour obtenir des performances maximales pour les accès en enjambées, il faut répartir les mots mémoires dans la mémoire autrement. L'organisation idéale est la suivante. : Dans les explications qui vont suivre, la variable N représente le nombre de banques, qui sont numérotées de 0 à N-1. On commence par organiser les N premières adresses comme une mémoire entrelacée simple : l'adresse 0 correspond à la banque 0, l'adresse 1 à la banque 1, etc. Pour le bloc suivant, on décale tout d'une adresse, à savoir qu'on commence à remplir les banques à partir de la seconde, non de la première. Une fois la fin du bloc atteinte, on finit de remplir le bloc en repartant du début du bloc. Le troisième bloc d'adresse subit le même traitement, sauf qu'on commence à remplir à partir de la seconde banque. Et on poursuit l’assignation des adresses en décalant d'un cran en plus à chaque bloc. Ainsi, chaque bloc verra ses adresses décalées d'un cran en plus comparé au bloc précédent. Si jamais le décalage dépasse la fin d'un bloc, alors on reprend au début. [[File:Mémoire interleaved par décalage.png|centre|vignette|upright=1.5|Mémoire entrelacée par décalage.]] En faisant cela, on remarque que les banques situées à N adresses d'intervalle sont différentes. Dans l'exemple du dessus, nous avons ajouté un décalage de 1 à chaque nouveau bloc à remplir. Mais on aurait tout aussi bien pu prendre un décalage de 2, 3, etc. Dans tous les cas, on obtient un entrelacement par décalage. Ce décalage est appelé le pas d'entrelacement, noté P. Le calcul de l'adresse à envoyer à la banque, ainsi que la banque à sélectionner se fait en utilisant les formules suivantes : * adresse à envoyer à la banque = adresse totale / N ; * numéro de la banque = (adresse + décalage) modulo N ; * décalage = (adresse totale * P) mod N. Avec cet entrelacement par décalage, on peut prouver que la bande passante maximale est atteinte si le nombre de banques est un nombre premier. Seulement, utiliser un nombre de banques premier peut créer des trous dans la mémoire, des mots mémoires inadressables. Malgré ce défaut, la technique a été utilisée sur quelques ordinateurs, avec l'exemple notable du superordinateur ''Burroughs Scientific Processor''. Pour éviter cela, il y a plusieurs solutions. Par exemple, on peut faire en sorte que N et la taille d'une banque soient premiers entre eux : ils ne doivent pas avoir de diviseur commun. Mais en pratique, elles n'ont pas vraiment été implémentées dans une vraie machine, et sont restées à l'état de recherche, aussi je les passe sous silence. ===L'entrelacement pseudo-aléatoire=== Une dernière méthode de répartition consiste à répartir les adresses dans les banques de manière "pseudo-aléatoire". L'idée exacte est de faire passer l'adresse dans une '''fonction de hachage''', plus ou moins complexe. Pour rappel, une fonction de hachage prend une entrée de grande taille et fournit en sortie un résultat de petite taille. Idéalement, elles doivent donner des résultats aussi différents que possible pour des entrées similaires, histoire de simuler une sorte de pseudo-aléatoire. Ici, une adresse est transformée en un numéro de banque. Une fonction de hachage simple ne fait que permuter des bits de l'adresse pour obtenir son résultat. Elle ne fait qu'échanger des bits de place avant de couper l'adresse en deux morceaux : un pour la sélection de la banque, et un autre pour la sélection de l'adresse dans la banque. Cette permutation est fixe, et ne change pas suivant l'adresse. Des fonctions de hachage plus complexes font des XOR bit à bit entre certains bits de l'adresse. ==Les contrôleurs mémoires SDRAM/DDR optimisés== Après avoir vu le cas des mémoires RAM, nous allons nous concentrer sur le cas particulier des mémoires SDRAM. Les mémoires SDRAM et DDR modernes sont capables de gérer plusieurs accès simultanés à la mémoire RAM, et leurs contrôleurs mémoire en font tout autant. C'est une différence majeure avec les mémoires asynchrones FPM/EDO, qui n'acceptaient qu'un seul accès mémoire à la fois. Leur contrôleur mémoire n'acceptait qu'un seul accès mémoire à la fois, c'était un contrôleur mémoire bloquant. Les contrôleurs mémoires des SDRAM sont eux non-bloquants et peuvent encaisser une dizaine d'accès mémoire à la fois. Peu de choses sont connues sur les contrôleurs de SDRAM/DDR modernes, les fabricants ne donnant que peu de détails dessus. Les rares simulateurs qui tentent de décrire leur fonctionnement, comme DRAM SIM I et II, sont particulièrement simples et ne vont pas dans le détail. Néanmoins, le peu qu'on sait est tout de même instructif. ===Rappel sur les SDRAM : tampon de ligne et commandes=== Les mémoires SDRAM sont des mémoires à tampon de ligne. Elles font un accès mémoire en deux étapes. La première étape recopie une ligne de N * 64 bits dans un tampon interne à la SDRAM. La seconde étape sélectionne une colonne, à savoir une donnée de 64 bits, qui est soit envoyée sur le bus de données pour une lecture, soit modifiée par une écriture. [[File:Mémoire à tampon de ligne.png|centre|vignette|upright=2|Mémoire à tampon de ligne]] Les accès mémoire sont traduits par un séquenceur mémoire en une série de commandes mémoires, qui sont séparées par des délais mémoire de quelques cycles d'horloge. Les délais sont très précis, et sont à respecter à la lettre. Une lecture ou une écriture se fait en maximum trois commandes : une commande PRECHARGE qui ferme la ligne précédemment utilisée, une commande ACT qui précise l'adresse de la ligne, et une commande READ ou WRITE qui précise l'adresse de la colonne et éventuellement la donnée à écrire. : Pour être plus précis, la commande PRECHARGE précharge les lignes de bits du plan mémoire à une tension particulière, ce qui les vide de leur contenu. Mais c'est un détail sans importance pour ce qui va suivre. Les SDRAM permettent de se passer de la première étape si des accès consécutifs se font dans la même ligne. Une fois activée, la ligne reste ouverte et on peut accéder plusieurs fois de suite dedans. On a alors juste à préciser l'adresse de colonne dedans. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | bgcolor="#FFA0FF" | PRECHARGE || bgcolor="#FFA0FF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || bgcolor="#A0FFFF" | READ (2) || bgcolor="#A0FFFF" | READ (3) || || || bgcolor="#A0FFFF" | READ (4) || bgcolor="#A0FFFF" | READ (5) || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | Donnée READ 1 || ||bgcolor="#A0FFFF" | Donnée READ 2 | bgcolor="#A0FFFF" | Donnée READ 3 || || || bgcolor="#A0FFFF" | Donnée READ 4 || bgcolor="#A0FFFF" | Donnée READ 5 |} Les SDRAM sont parfois capables de démarrer une commande avant que la précédente soit terminée. Mais le respect des délais mémoire est très limitant, ce qui fait qu'on ne peut pas parler de réel pipeline, comme c'est le cas sur les processeurs. Parlons plutot de '''commandes anticipées'''. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | || bgcolor="#A0FFFF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || || || bgcolor="#FFA0FF" | READ (2) || || || || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 | bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 | |} Un dernier point est que les chips mémoires de SDRAM sont composés de plusieurs banques, chacune étant une sorte de mini-mémoire miniature. Chaque banque a son propre décodeur, son propre tampon de ligne, ses propres multiplexeurs de colonne, sa logique de rafraichissement mémoire, etc. C'est comme si une SDRAM regroupait plusieurs mémoires séparées dans un même circuit intégré. [[File:Arrangement vertical.jpg|centre|vignette|upright=2.5|Mémoire multi-banques.]] ===La mise en attente des accès mémoire=== Un contrôleur de SDRAM peut accepter plusieurs accès mémoire en même temps. Mais cela ne signifie pas que le contrôleur sera prêt à les traiter immédiatement. Pour éviter tout problème de disponibilité, le contrôleur met en attente les accès mémoire que le processeur lui envoie, pour les exécuter dès que possible. Les accès mémoire sont mis en attente dans une mémoire FIFO, histoire de les exécuter dans leur ordre d'arrivée, notamment pour renvoyer les lectures dans l'ordre demandé. Il y a aussi une mémoire FIFO pour les données à écrire et une FIFO pour les données lues. Cette dernière sert au cas où le cache ne soit pas disponible quand on lui envoie la donnée lue. [[File:Module d'interface avec la mémoire.png|centre|vignette|upright=2.5|Contrôleur mémoire avec mise en attente des requêtes processeur et autres optimisations.]] Des optimisations sont possibles dès la mémoire FIFO. Par exemple, on peut regrouper plusieurs accès à des données consécutives en un seul accès en rafale. Le contrôleur mémoire analyse les requêtes mises en attente et détecte si certaines se font à des adresses consécutives. Si c'est le cas, il fusionne ces requêtes en une lecture/écriture en rafale. Une telle optimisation est appelée la '''combinaison de lecture''' pour les lectures, et la '''combinaison d'écriture''' pour les écritures. Une autre optimisation possible est le réacheminement lecture sur écriture (''Store to load forwarding''). Il s'agit d'un équivalent de l’optimisation utilisée dans le cadre de la désambiguïsation mémoire. Quand une lecture demande à accéder à une donnée écrite récemment, il se peut que l'écriture ait été mise en attente dans le contrôleur mémoire. Dans ce cas, le contrôleur mémoire peut renvoyer la donnée directement depuis ses mémoires FIFOs, sans faire d'accès mémoire en lecture. Les deux optimisations précédentes ne paraissent pas payer de mine, mais elles deviennent plus compréhensible quand on sait que certains contrôleurs mémoire peuvent mettre en attente beaucoup de données. Par exemple, l'''Intel E8870 - Scalable Memory Controller'' peut mettre en attente 64 lignes de cache dans la mémoire FIFO, soit 8 kibioctets de RAM ! Deux pages mémoire si la mémoire virtuelle utilise des pages de 4 kibioctets ! ===Les optimisations liées à la présence de plusieurs banques=== Les commandes anticipées permettent de faire un minimum de parallélisme mémoire. Même si elle parait très limitée, cette possibilité est décuplée par la présence de plusieurs banques dans la mémoire SDRAM. Il est en effet possible de faire plusieurs accès mémoire en même temps, dans des banques différentes. Il est possible de lancer un accès mémoire dans deux banques en même temps, par exemple. La seule contrainte est que la SDRAM est limitée à 4 banques actives en même temps. {|class="wikitable" |+ Pipelining basique sur les SDRAM |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 |- ! Banque Numéro 1 | || || || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || || |- ! Banque Numéro 2 | || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || |- ! Banque Numéro 3 | || || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || |} L'implémentation n'a rien de vraiment compliqué. Il suffit d'utiliser une mémoire FIFO par banque. Ainsi, les accès dans une même banque se feront en série, dans l'ordre d'arrivée, mais des accès à des banques différentes se feront dans le désordre. Cela ne pose pas de problème car les accès se font à des adresses différentes, ce qui fait qu'il n'y a pas de conflits majeurs. [[File:Gestion parallèle des banques.png|centre|vignette|upright=2.5|Gestion parallèle des banques.]] ===Le ré-ordonnancement des commandes mémoires=== Un contrôleur mémoire moderne est capable de changer l'ordre des requêtes mémoires, pour gagner en performances. En clair : il peut faire l'équivalent mémoire de l'exécution dans le désordre des processeurs haute performance. Une différence majeure est que le contrôleur mémoire ne remet pas les accès mémoire dans l'ordre avant de les envoyer au processeur. C'est le processeur qui remet en ordre les accès mémoire, dans sa ''load queue''. Pour cela, les requêtes mémoire sont "numérotées", à savoir qu'on leur attribue un identifiant binaire. Le contrôleur mémoire exécute les lectures/écritures dans le désordre, mais garde la trace de leur identifiant. Il sait dans quel ordre il exécute les accès mémoire et connait leur latence. Ce qui fait qu'il sait que la donnée lue à tel instant est associée à la requête mémoire numéro X. Pour les lectures, il renvoie la donnée lue avec l'identifiant. Le processeur sait alors à quelle lecture la donnée lue correspond et il se débrouille en interne pour gérer la situation. En clair : le processeur voit les accès mémoire dans le désordre, c'est lui qui les remet en ordre. Maintenant que ces précisions sont faites, posons cette question : quel est l'intérêt de faire les accès mémoire dans le désordre ? Accéder à des banques en parallèle est une réponse possible, mais le ré-ordonnancement est surtout utile si tous les accès mémoire atterrissent dans la même banque et dans la même ligne. Elle marche si plusieurs accès à une même ligne ne sont pas consécutifs, et qu'il y a des accès entre les deux. Après ré-ordonnancement, ces accès mémoire sont exécutés l'un à la suite de l'autre. Pour rendre le tout plus concret, voici un exemple. Imaginez que l'on sait les 3 accès mémoire suivants : * Une lecture ligne A ; * Une écriture ligne B ; * Une lecture ligne A. Sans réordonnancent, on doit émettre deux commandes PRECHARGE : une quand on passe de la ligne A à la ligne B, et une autre pour le passage inverse. pour éviter cela, le contrôleur mémoire peut retarder l'écriture, ou au contraire avancer la seconde lecture. le résultat est alors le suivant : * Lecture ligne A ; * Lecture ligne A ; * Une écriture ligne B. On a donc deux accès consécutifs à la même ligne, suivi par une écriture dans une autre ligne. La technique marche parce que l'on a un mix adéquat de lectures et d'écritures. Mais il existe des possibilités de réorganisation autres qui ne sont pas valides. Il faut par exemple prendre garde à éviter d'intervertir lectures et écritures à une même adresse, sous peine de problèmes. Encore une fois, il faut faire attention aux dépendances de données, encore une fois ! Ici des dépendances mémoires, mais les règles sont les mêmes. Le contrôleur mémoire teste les dépendances mémoires avant d'envoyer des commandes à la mémoire DDR/SDRAM. Il a existé des contrôleurs mémoires du temps des vieilles DDR qui implémentaient un ordonnancement très simples. Prenons l'exemple des coprocesseurs IO 81341 et 81342, qui étaient en réalité des ''chipsets'' intégrant un contrôleur SDRAM. Le ''chipset'' avait 5 ports : un pour les processeurs, un pour le pont sud (''southbridge'') et trois pour des canaux DMA. Pour le premier, il n'y avait pas de réordonnancement. Pour les autres, les lectures se font dans l'ordre, les écritures se font dans l'ordre, mais les écritures peuvent passer avant une lecture non-dépendante. L'implémentation était assez simple : le séquenceur était précédé par plusieurs mémoires FIFO. Le port processeur avait une seule mémoire FIFO, ce qui conservait l'ordre d'exécution des commandes mémoire. Pour les autres ports, le processeur avait vraisemblablement des files séparées pour les lectures et écritures. Le séquenceur mémoire vérifiait les dépendances mémoire de type RAW et autres. [[File:Double file d'attente pour les lectures et écritures.png|centre|vignette|upright=2.5|Double file d'attente pour les lectures et écritures]] L'implémentation était vraiment simple : elle n’exploitait même pas la présence de plusieurs banques ! De nos jours, les contrôleurs mémoires sont beaucoup plus évolués et sont capables d'exécution dans le désordre bien plus poussée. Les contrôleurs mémoires remplacent la FIFO par une sorte de fenêtre d'instruction dans laquelle ils poichent les accès mémoire selon leurs besoins. ==L'entrelacement sur les mémoires SDRAM== L''''entrelacement''' fonctionne sur les mémoires SDRAM, mais il doit être fortement modifié. Concrètement, tout ce qui a étré dit plus est inapplicable pour les SDRAM, du fait de la présence du tampon de ligne. Des adresses consécutives atterrissent déjà dans la même ligne, ce qui fait qu'on n'a pas besoin d'entrelacement au niveau d'une ligne. Et ne parlons pas de ce la présence de rangées et de canaux mémoire ! Cependant, il peut y avoir un entrelacement lié à la présence des banques SDRAM. L'entrelacement est géré juste avant le séquenceur mémoire. Le '''circuit d'entrelacement''' intervertit certains bits de l'adresse lors des accès mémoires. On trouve aussi des mémoires FIFOs pour mettre en attente les requêtes mémoire provenant du processeur. Elles permettent de recevoir plusieurs accès mémoire consécutifs. Si vous vous demandez comment plusieurs accès mémoire consécutifs peuvent avoir lieu, sachez que c'est une bonne question. Pour le moment, nous allons dire que ces accès proviennent du processeur ou de la mémoire cache, sans expliquer comment. Le contrôleur mémoire reçoit une série d'accès mémoire assez rapprochés, qu'il doit exécuter au mieux. [[File:Controleur mémoire d'une SDRAM avec entrelacement.png|centre|vignette|upright=2.5|Contrôleur mémoire d'une SDRAM avec entrelacement.]] Pour gérer l'entrelacement, le contrôleur de SDRAM prend en entrée l'adresse envoyée par le processeur, et la découpe en plusieurs champs : un pour sélectionner la banque adéquate, un autre pour la ligne, un autre pour la colonne, un autre pour la rangée. Une solution simple désactive l'entrelacement. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de banque || Adresse de ligne || Adresse de colonne |} Si cette solution fonctionne bien si les accès mémoire sont assez aléatoires, en pratique, mieux vaut utiliser l'entrelacement. ===L'entrelacement de banques/lignes=== Une première méthode d'entrelacement est l''''entrelacement de banques'''. Elle répartit deux lignes consécutives dans deux banques différentes. Pour comprendre l'idée, prenons un exemple. Imaginons une mémoire avec deux banques et 4 lignes. Imaginons qu'on parcoure/balaye la mémoire RAM en partant des adresses basses. Sans entrelacement de ligne, les accès se feront comme suit, de gauche à droite : {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Banque numéro 1 !! colspan="4" | Banque numéro 2 |- | Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 || Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 |} Avec l'entrelacement de banques, les accès se feront comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 |- | Ligne 1 || Ligne 1 || Ligne 2 || Ligne 2 || Ligne 3 || Ligne 3 || Ligne 4 || Ligne 4 |} Avec N banques, la première ligne ira dans la première banque, la seconde ligne dans la seconde, et ainsi de suite jusqu'à la énième banque qui va dans la banque numéro N. Et la ligne N+1 repart dans la première banque. L'avantage est que passer d'une ligne à la suivante est assez rapide. Au lieu de devoir fermer la première ligne et ouvrir la suivante on ouvrira directement la ligne suivante dans une banque différente, ce qui sera plus rapide. La commande PRECHARGE pourra être envoyée à la seconde banque en avance. Pour cela, l'adresse est découpée comme suit. {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne (précise à l'octet) |} ===L'entrelacement de rangées=== Il est aussi possible de faire la même chose, mais avec les rangées. Pour simplifier fortement, une rangée est simplement un chip mémoire. En réalité, les rangées ne sont pas des chip mémoire, mais un ensemble de chips mémoire regroupés ensemble histoire d'atteindre les 64 bits du bus de données. Par exemple, une rangée peut combiner 8 chips mémoire avec un bus de données de 8 bits chacun pour obtenir les 64 bits du bus de données d'une SDRAM. Mais nous passons ce détail sous silence dans les explications qui vont suivre, par souci de simplification. Pour faire comprendre l'entrelacement de rangée, le mieux est d'utiliser un exemple, le même que précédemment. Sans entrelacement de rangée, on change de chip mémoire une fois qu'on a balayé toutes ses adresses. {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Rangée numéro 1 !! colspan="4" | Rangée numéro 2 |- | Banque 1 || Banque 2 || Banque 3 || Banque 4 || Banque 1 || Banque 2 || Banque 3 || Banque 4 |} Avec l'entrelacement de ligne, on change de rangée dès qu'on change de banque/ligne. {|class="wikitable" |+ Adresse mémoire |- ! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 |- | Banque 1 || Banque 1 || Banque 2 || Banque 2 || Banque 3 || Banque 3 || Banque 4 || Banque 4 |} En clair, quand on a épuisé toutes les banques dans une même rangée, on passe à la rangée suivante au lieu de rester dans la même rangée. Notons que dans l'exemple précédent, on a combiné l'entrelacement de rangée et de banque, mais on aurait pu utiliser l'entrelacement de rangée seul. Mais ce n'est pas le cas le plus courant en pratique. Toujours est-il qu'en combinant entrelacement de rangée et de ligne, le découpage de l'adresse est le suivant : {|class="wikitable" |+ Adresse mémoire |- | Adresse de ligne || Numéro de rangée || Adresse de banque || Adresse de colonne (précise à l'octet) |} D'autres méthodes d'entrelacement sont possibles, mais elles sont plus complexes. Chaque contrôleur mémoire fait un peu à sa sauce. Ils permettent en général de choisir entre plusieurs entrelacements. Au minimum, ils permettent de désactiver l'entrelacement, d'activer un entrelacement de ligne, et d'autres entrelacements plus complexes, dont certains sont propriétaires et tenus secrets par le fabricant. ===L'entrelacement avec le ''dual channel''=== [[File:Dual channel slots.jpg|vignette|Slots mémoires avec ''dual channel''.]] L'usage du ''dual channel'' complique encore l'entrelacement. Là encore, il y a deux grandes solutions : avec et sans entrelacement des canaux mémoire. Rappelons le principe : deux barrettes de RAM sont accédées en parallèle. Et pour cela, il faut utiliser l'entrelacement. Typiquement, chaque barrette mémoire fournit 64 bits, ce qui fait que l'on peut accéder à 128 bits d'un seul coup, par exemple avec un accès en rafale. Sans ''dual channel'', la première barrette correspond à la moitié haute de la RAM, la seconde barrette correspond à la moitié basse. Avec ''dual channel'', une forme spécifique d'entrelacement est activée. Concrétement, deux blocs de 64 bits sont placés dans des canaux mémoire séparés. Idem avec du triple ou quadruple canal, mais c'est alors trois ou quatre blocs de 64 bits qui sont dispersés dans des canaux consécutifs. Le découpage de l'adresse est alors le suivant : {|class="wikitable" |+ Adresse mémoire |- ! colspan="6" | Sans entrelacement |- | Numéro de canal/barrette || Reste de l'adresse || Position de l'octet dans un bloc de 64 bits |- | colspan="6" | |- ! colspan="6" | Avec entrelacement |- | Reste de l'adresse || Numéro de canal/barrette || Position de l'octet dans un bloc de 64 bits |} [[File:Décodage d'adresse avec dual channel.png|centre|vignette|upright=2|Décodage d'adresse avec dual channel.]] Les contrôleurs de SDRAM précédents sont assez basiques et ne représentent pas les contrôleurs les plus évolués. Ils étaient utilisés sur les anciens PC, à une époque où ils étaient encore sur la carte mère du processeur. Mais de nos jours, le contrôleur mémoire est intégré au processeur. Et il incorpore de nombreuses optimisations afin de gagner en performances. Malheureusement, ces optimisations sont elles-mêmes dépendantes des optimisations intégrées dans le processeur, ce qui fait qu'on ne peut pas en parler ici. Elles demandent que le contrôleur mémoire reçoive plusieurs accès mémoire simultanés, ce qui n'a aucun sens à ce stade du cours. Aussi, nous allons les passer sous silence pour le moment. Nous les verrons dans le chapitre sur le parallélisme mémoire. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=La désambiguïsation mémoire | prevText=La désambiguïsation mémoire | next=Les processeurs superscalaires | nextText=Les processeurs superscalaires }} </noinclude> jcbo7qbyxsrj6q3bm3gbfuts9ab5cvy 764730 764729 2026-04-23T22:26:30Z Mewtow 31375 /* Les contrôleurs mémoires SDRAM/DDR optimisés */ 764730 wikitext text/x-wiki Dans ce chapitre, nous allons voir les techniques qui permettent de gérer plusieurs accès mémoire simultanés directement au niveau du cache ou de la mémoire RAM. Par plusieurs accès mémoire simultanés, vous pensez sans doute à l'usage de cache multiports, voire de mémoires RAM multiport. C'est en effet une solution, mais ce n'est clairement pas la seule. Par exemple, il est possible de pipeliner l'accès au cache, voire à la mémoire RAM. Il existe de nombreuses techniques de '''parallélisme mémoire''', et nous allons les voir dans ce chapitre. Pour donner un exemple, il est possible de pipeliner les accès mémoire. Il est possible de pipeliner l'accès au cache et/ou l'accès à la mémoire, et nous verrons comment faire dans ce chapitre. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, qu'ils aient ou non un pipeline dynamique. Par contre, pipeliner la mémoire est une technique ancienne, aujourd'hui peu utilisée. Elle était utilisée sur des très vieux ordinateurs, qui avaient un pipeline mais pas de mémoire cache. Mais de nos jours, les mémoires SDRAM et DDR ne sont pas adaptées pour ça, il est très difficile de les pipeliner correctement. Le parallélisme mémoire est utile aussi bien sur des processeurs à émission dans l'ordre que dans le désordre. Par exemple, un processeur ''in-order'' avec des lectures non-bloquantes peut e, profiter. Il peut techniquement lancer une seconde lecture, après avoir rencontré une première lecture non-bloquante. En général, le nombre de lectures consécutives est cependant limité à moins d'une dizaine. À l'opposé, les processeurs sans lectures non-bloquantes profitent pas du parallélisme mémoire. À la rigueur, ils peuvent en profiter s'ils intègrent des techniques de préchargement. Pendant que le processeur exécute une lecture/écriture, il peut précharger une autre donnée en parallèle. Inutile de dire que sans pipeline processeur, le parallélisme mémoire ne sert pas à grand-chose. ==Le ''line fill buffer'' et les techniques associées== Lors d'un défaut de cache, le processeur doit attendre que toute la ligne de cache soit chargée avant d'être utilisable. Or, la taille d'une ligne de cache est supérieure à la largeur du bus mémoire, ce qui fait qu'une ligne de cache est chargée en plusieurs fois, morceaux par morceaux, mot mémoire par mot mémoire. Le chargement peut se faire directement dans le cache, mais ce n'est pas une solution très pratique. À la place, beaucoup de processeurs ajoutent une mémoire tampon entre la RAM et le cache, appelée le '''tampon de remplissage de ligne''' (''line-fill buffer'') dans les processeurs modernes. Lors d'un défaut de cache, le processeur charge la donnée de la RAM dans le tampon de remplissage de ligne, mot mémoire par mot mémoire. Une fois plein, le tampon de remplissage de ligne est recopié dans la ligne de cache. Le tampon de remplissage de ligne contient une ligne de cache, si ce n'est que certains bits de contrôle ne sont pas présents. Le tampon de remplissage de ligne contient un bit de validité pour chaque mot mémoire de la ligne de cache, qui indiquent si le mot mémoire a été chargé. Par exemple, prenons un processeur 64 bits, qui gère donc des mots mémoire de 8 octets, avec des lignes de cache 256 octets/32 mots mémoire. Le tampon de remplissage de ligne contiendra 32 bits de validité. Si le processeur a chargé les 6 premiers mots mémoire, les 6 premiers bits de validité seront mis à 1, les autres seront encore à zéro. Il faut noter que la disponibilité de la ligne complète se détermine assez facilement en faisant un ET logique entre tous les bits de validité. Le processeur sait ainsi quand la ligne de cache est disponible entièrement et donc quand la transférer dans le cache. La même chose existe avec une hiérarchie de cache, sauf que l'on trouve une mémoire tampon entre chaque niveau de cache. Il y a un tampon de remplissage de ligne entre le cache L1 et le cache L2, entre le cache L2 et le L3, etc. Si le cache ne gère qu'un seul défaut de cache à la fois, le ''fill line buffer'' est une mémoire très simple, qui ne mémorise qu'une seule ligne de cache. Et cela vaut aussi bien pour un cache bloquant que non-bloquant. Mais sur les caches capables de gérer plusieurs défauts simultanés, le tampon de remplissage de ligne est une mémoire de type FIFO ou LIFO, capable de mémoriser plusieurs lignes de cache. Notons que le tampon de remplissage de ligne est très utile pour implémenter certaines techniques, par exemple le contournement du cache. Nous avions vu dans le chapitre sur le cache que certains accès mémoire doivent contourner le cache, pour des raisons de cohérence des caches. C'est notamment nécessaire pour accéder aux périphériques, mais c'est aussi utile pour des raisons de performances dans des cas très spécifiques. Les accès qui contournent le cache se font directement dans le tampon de remplissage de ligne : le processeur écrit ou lit les données depuis ce tampon de remplissage de ligne, sans accéder au cache. ===L'''early restart'' et le ''critical word load''=== La présence du ''line buffer'' permet une optimisation assez intéressante, qui permet de réduire la latence des défauts de cache. L'optimisation consiste à lire un mot mémoire dans le tampon de remplissage de ligne, même si la ligne de cache complète n'a pas encore été chargée. Il existe deux manières de faire cela, qui portent les noms d'''early restart'' et de ''critical word load''. La première est la version la plus simple, la seconde est plus complexe mais plus performante. Avec la technique d''''''early restart''''', la ligne de cache est chargée normalement, en partant de son premier mot mémoire. Dès que le mot mémoire lu/écrit par le processeur est copié dans le ''line fill buffer'', il est envoyé au processeur immédiatement. Illustrons le tout par un exemple, où une ligne de cache fait 16 mots mémoire. Le processeur effectue une lecture, qui lit le 5ème mot mémoire. Sans ''early restart'', le processeur doit charger les 16 mots mémoire avant de faire la lecture dans le cache. Avec ''early restart'', le processeur reçoit la donnée dès que le 5ème mot mémoire est disponible. Le processeur doit attendre que les 4 premiers mots mémoire soient chargés, puis le 5ème mot mémoire arrive et est envoyé directement au processeur, il est lu directement depuis le ''line fill buffer''. Les 11 mots mémoire suivants sont ensuite chargés dans le cache pendant que le processeur fait des calculs dans son coin. Le ''critical word load'' est une optimisation de la technique précédente où le chargement de la ligne de cache commence directement à la donnée demandée par le processeur. Pour reprendre l'exemple précédent, où le processeur demande le 5ème mot mémoire sur 16, le ''critical word load'' charge le 5ème mot mémoire en premier et l'envoie au processeur, ce qui fait qu'il est chargé très rapidement. Pas besoin d'attendre que le processeur charge les 4 mots mémoire précédents comme avec l'''early restart''. Dans le détail, le processeur charge le 5ème mot mémoire en premier, puis charge les 11 suivants, et termine par les 4 mots mémoire du début. En clair, le ''critical word load'' commence par charger le mot mémoire lu, puis les blocs suivants, avant de revenir au début du bloc pour charger les blocs restants. Ainsi, la donnée demandée par le processeur sera la première disponible. Pour cela, l'organisation du tampon de remplissage de ligne est modifiée de manière à rendre cela possible. Il a une taille égale à une ligne de cache complète, qui contient elle-même plusieurs mots mémoire. Dans le ''line-fill buffer'', chaque mot mémoire est stocké avec un tag, qui indique l'adresse du mot mémoire stocké dans le ''line-fill buffer''. Le ''line-fill buffer'' est donc un cache un peu particulier, qui fonctionne comme un cache du point de vue du processeur, comme une mémoire FIFO pour les transferts avec le cache. Ainsi, un processeur qui veut lire dans le cache après un défaut peut accéder à la donnée directement depuis le tampon de remplissage de ligne, alors que la ligne de cache n'a pas encore été totalement recopiée en mémoire. ==Les caches pipelinés== Il est possible de pipeliner l'accès au cache, ce qui demande juste de rajouter des registres au bon endroits et quelques circuits de contrôle. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, même ceux avec un pipeline dynamique. Et l'implémentation peut se faire de plusieurs manières différentes. Il faut dire que découper une mémoire cache en plusieurs étapes peut se faire de plusieurs manière. Commençons par la plus simple. Beaucoup de processeurs des années 2000 avaient une fréquence peu élevée comparée aux standards d'aujourd'hui, ce qui fait que leur cache avait bien un temps d'accès d'un cycle d'horloge. Mais l'accès au cache se faisait en deux cycles d'horloge : un cycle pour calculer une adresse et un cycle pour l'accès mémoire proprement dit. Et ces deux étapes étaient pipelinées, à savoir que deux micro-opérations mémoire peuvent s'exécuter en même temps : une dans l'étage de calcul d'adresse, une autre dans le cache. L'unité mémoire était donc pipelinée, alors que l'accès au cache ne l'était pas. Les processeurs qui implémentaient cette technique regroupent les micro-architectures K5 et K6 d'AMD, les processeurs Intel de micro-architecture P6 (Pentium 2 et 3) et quelques autres. Il est aussi possible de pipeliner l'accès au cache lui-même. Avec un cache pipeliné, l'accès au cache ne se fait pas en un seul cycle, mais en plusieurs. Cependant, on peut lancer un nouvel accès au cache à chaque cycle d'horloge, comme un processeur pipeliné lance une nouvelle instruction à chaque cycle. L'implémentation est assez simple : il suffit d'ajouter des registres dans le cache. Pour cela, on profite que le cache est composé de plusieurs composants séparés, qui échangent des données dans un ordre bien précis, d'un composant à un autre. De plus, le trajet des informations dans un cache est linéaire, ce qui les rend parfois pour l'usage d'un pipeline. ===Le ''pipelining'' des caches ''direct-mapped''=== Voici ce qui se passe avec un cache directement adressé. Pour rappel ce genre de cache est conçu en combinant une mémoire RAM, généralement une SRAM, avec quelques circuits de comparaison et un MUX. L'accès au cache se fait globalement en deux étapes : la première lit la donnée et le ''tag'' dans la SRAM, et on utilise les deux dans une seconde étape. Nous avions vu il y a quelques chapitre comment pipeliner des mémoires, dans le chapitre sur les mémoires évoluées. Et bien ces méthodes peuvent s'utiliser pour la mémoire RAM interne au cache !L'idée est d'insérer un registre entre la sortie de la RAM et la suite du cache, pour en faire un cache pipéliné, à deux étages. Le premier étage lit dans la SRAM, le second fait le reste. L'implémentation sur les caches associatifs à plusieurs voies est globalement la même à quelques détails près. [[File:Cache directement adressé pipeliné - deux étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - deux étages]] Avec le circuit précédent, il est possible d'aller plus loin, cette fois en pipelinant l'accès à la mémoire RAM interne au cache. Pour comprendre comment, rappelons qu'une mémoire SRAM est composée d'un plan mémoire et d'un décodeur. L'accès à la mémoire demande d'abord que le décodeur fasse son travail pour sélectionner la case mémoire adéquate, puis ensuite la lecture ou écriture a lieu dans cette case mémoire. L'accès se fait donc en deux étapes successives séparées, on a juste à mettre un registre entre les deux. Il suffit donc de mettre un gros registre entre le décodeur et le plan mémoire. [[File:Cache directement adressé pipeliné - trois étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - trois étages]] Et on peut aller encore plus loin en découpant le décodeur en deux circuits séparés. En effet, rappelez-vous le chapitre sur les circuits de sélection : nous avions vu qu'il est possible de créer des décodeurs en assemblant des décodeurs plus petits, contrôlés par un circuit de prédécodage. Et bien on peut encore une ajouter un registre entre ce circuit de prédécodage et les petits décodeurs. : Théoriquement, toute l'adresse est fournie d'un seul coup au cache, la quasi-totalité des processeurs présentent l'adresse complète à un cache pipeliné. Mais le Pentium 4 fait autrement. Il faut noter que les premiers étages manipulent l'indice dans la SRAM, qui est dans les bits de poids faible, alors que les étages ultérieurs manipulent le ''tag'' qui est dans les bits de poids fort. Les concepteurs du Pentium 4 ont alors décidé de présenter les bits de poids faible lors du premier cycle d'accès au cache, puis ceux de poids fort au second cycle. Pour cela, l'ALU fonctionnait à une fréquence double de celle du processeur, tout comme le cache L1. Il n'y avait pas de pipeline proprement dit, mais cela réduisait grandement la latence d'accès au cache. ===Le ''pipelining'' des caches splittés/sériels=== Il est aussi possible de pipeliner un cache dits splittés, aussi appelés à accès sériel. Pour rappel, les caches à accès sériel vérifient si il y a succès ou défaut de cache, avant d'accéder aux lignes de cache en cas de succès. Ils font donc différemment des autres caches, qui accèdent à une ligne de cache, avant de déterminer s'il y a succès ou défaut en lisant le tag de la ligne de cache. Les caches sériels disposent de deux SRAM : une pour les tags des lignes de cache et une pour les données. Ils accèdent à la SRAM pour les tags, avant d’accéder à la SRAM des données en cas de succès de cache. Vu que l'accès se fait en deux étapes, une vérification des tags suivie de la lecture/écriture des données, il est facile à pipeliner. Pipeliner le cache permet de régler le problème des accès au cache L1, et elle est tout le temps utilisé sur les processeurs modernes. Mais que faire en cas de défaut de cache ? ==Les caches non bloquants== Un '''cache bloquant''' est un cache auquel le processeur ne peut pas accéder pendant un défaut de cache. Il faut attendre que la lecture ou écriture en RAM soit terminée avant de pouvoir utiliser de nouveau le cache. Un '''cache non bloquant''' n'a pas ce problème : on peut l'utiliser pendant un défaut de cache. Les caches non bloquants permettent de démarrer une nouvelle lecture ou écriture alors qu'une autre est en cours, ce qui permet d'exécuter plusieurs lectures ou écritures en même temps. ===Les ''Miss Handling Status Registers''=== Lors d'un défaut de cache, la mémoire RAM est consultée pendant le défaut de cache, mais le cache est inutilisé. Un défaut de cache n'utilise pas le cache, ce n'est qu'un accès en mémoire RAM, sur le bus mémoire (ou un accès aux niveaux de cache inférieurs, peu importe). Le cache en lui-même est laissé libre, rien n’empêche d'y accéder, il est en réalité intrinsèquement non-bloquant. Les caches, bloquants comme non-bloquants, sont en réalité composés d'une mémoire cache proprement dite, entourée de circuits qui servent d'interface entre le processeur et le cache lui-même. Et parmi les circuits tout autour, certains gèrent l'accès au cache lors d'un défaut de cache. Ils sont regroupés sous le terme de '''''Miss Handling Architecture''''' (MHA). La différence entre un cache bloquant et un cache non-bloquant est en réalité liée à l'implémentation de la MHA. Les caches bloquants coupent volontairement l'accès au cache lors d'un défaut, car les défauts sont plus simples à gérer ainsi. Le défaut de cache rapatrie une donnée depuis la RAM, qui sera écrite dans le cache. Et il ne faut pas qu'une tentative d'accès à cette donnée ait lieu avant qu'elle ne soit chargée. Mais ce blocage est général et touche tout le cache, alors que seule une ligne de cache est concernée par le défaut de cache. L'idée derrière un cache non-bloquant est que seule la ligne de cache est bloquée, mais les autres sont accessibles. L'idée est alors de mémoriser les lignes de cache concernées par le défaut de cache, afin d'en bloquer l'accès. À chaque accès, on vérifie si la ligne de cache est déjà réservée par un défaut de cache. La lecture/écriture est alors bloquée si c'est le cas, mais elle accepte les accès sinon. Pour cela, la ''Miss Handling Architecture'' contient des registres qui mémorisent des informations sur les défauts de cache en cours. Ils portent le nom de '''''miss status handling registers''''', que j’appellerais dorénavant MSHR, qui sont aussi appelés des '''''miss buffer'''''. Le contenu des MSHR varie beaucoup suivant le processeur, mais ces derniers stockent au minimum les informations suivantes : * Le numéro de la ligne de cache dans laquelle les données sont chargées. * Un bit de validité qui indique si le MSHR est vide ou pas, qui est mis à 0 quand le défaut de cache est résolu. * Un ou plusieurs champs de lecture/écriture, qui contiennent des informations sur la lecture/écriture. ** Pour une lecture, elle contient des informations sur la destination de la donnée, à savoir qui prévenir quand le défaut de cache est terminé. C'est parfois un nom/numéro de registre (celui dans lequel charger la donnée), mais c'est souvent le numéro de l'entrée dans le ''load/store queue''. ** Pour les écritures, elle contient la donnée à écrire, ou éventuellement un numéro de ''load/store queue'' où se trouve la donnée à écrire. Il faut noter que le bus mémoire ne peut gérer qu'un seul défaut de cache à la fois. Aussi, il est intéressant de regarder ce qui se passe lorsqu'un second défaut de cache survient, pendant qu'un premier est en cours. Dans ce cas, il y a deux réponses qui correspondent à deux types de caches non-bloquants, qui portent les noms barbares de caches de type succès après défaut et défaut après défaut. Sur le premier type, il ne peut pas y avoir plusieurs défauts de cache simultanés. Dès qu'un second défaut de cache survient, le cache stoppe son activité et on ne peut plus démarrer de nouvelle lecture/écriture, tant que le premier défaut de cache n'est pas résolu. Le second type est plus souple et autorise la survenue de plusieurs défauts de cache simultanés. Du moins, jusqu'à une certaine limite, car le cache ne peut supporter qu'un nombre limité d'accès mémoires simultanés (pipelinés). ===Les accès simultanés à une même ligne de cache=== Il arrive que le processeur fasse plusieurs accès mémoire simultanés à la même ligne de cache. Si la ligne de cache en question n'a pas encore été chargée dans le cache, alors on a plusieurs défauts de cache consécutifs pour la même ligne de cache, et le cache non-bloquant doit gérer la situation. Pour la suite, il va falloir faire une petite distinction entre les défauts primaires et secondaires. Imaginons qu'un défaut de cache ait lieu et demande à charger une donnée dans la ligne de cache numéro N. Il s'agit du premier défaut impliquant cette ligne de cache précise, ce qui lui vaut le nom de '''défaut de cache primaire'''. Mais par la suite, d'autres accès mémoire à la même ligne de cache ont lieu, alors que la ligne de cache n'est pas encore disponible. Dans ce cas, il s'agit de '''défauts de cache secondaires'''. Pour l'unité d'accès mémoire, les défauts de cache primaire et secondaire sont différents (ils prennent tous une entrée dans la ''load/store queue''). Mais pour le cache, ils ne correspondent qu'à un seul accès au cache : celui qui demande de charger la ligne de cache demandée. Les défauts de cache primaire et secondaire à la même ligne de cache se voient attribuer un MSHR unique. La gestion des défauts de cache secondaires dépend du cache non-bloquant. La solution la plus simple ne permet pas les défauts de cache secondaires. Le cache ne permet pas deux défauts de cache simultanés pour la même ligne de cache. Les autres solutions le permettent, en fusionnant des accès simultanés à la même ligne de cache en un seul au niveau des MSHR. Dans tous les cas, détecter les défauts de cache secondaires sont un problème qu'il faut détecter. La ''Miss Handling Architecture'' doit détecter les défauts de cache secondaire. Pour cela, elle procède comme suit. Lors de chaque défaut de cache, la MHA récupère le numéro de la ligne de cache associé. Il vérifie alors chaque MSHR pour vérifier s'il contient le numéro en question. S'il n'y a aucune correspondance dans les MSHR, alors c'est signe que le défaut de cache est un défaut primaire. Mais s'il y en a une, alors c'est un défaut secondaire. Évidemment, cela signifie que lors d'un défaut de cache, le numéro de ligne de cache est envoyé à tous les MSHR, pour comparaison. Les MSHR sont donc regroupés dans une mémoire associative, une sorte de mini-cache, faciliter l'implémentation. Lors d'un défaut de cache primaire, l'accès à un cache non-bloquant se fait comme suit : le processeur envoie une adresse au cache, accède à celui-ci, et détecte la survenue d'un défaut de cache. Il en profite alors pour attribuer une ligne de cache dans laquelle sera chargée ce défaut. L'attribution est très simple dans le cas des caches ''direct mapped'', ou associatifs par voie, pour lesquels l'attribution se fait assez simplement. Il mémorise alors cette information dans les MSHR, après avoir vérifié que le défaut de cache n'était pas un défaut secondaire. Lors des accès ultérieurs à une adresse proche, censée être dans la même ligne de cache, le processeur va encore une fois rencontrer un défaut de cache. Il va alors déterminer le numéro de la ligne de cache associée à l'adresse, et comparer ce numéro avec les MSHRs. Si un MSHR contient ce numéro, c'est signe que le défaut de cache est un défaut secondaire. La MHA réagit alors différemment selon le processeur considéré. Une première solution n'autorise pas les défauts de cache secondaires. Si l'un d'entre eux survient, le processeur est gelé par un ''pipeline stall'', une bulle de pipeline. Une autre solution fusionne les défauts de cache secondaires avec le défaut de cache primaire : tout cela ne correspond qu'à un seul défaut de cache pour lui. ===Les MSHR simples=== Un cache non-bloquant à '''MSHR simple''' contient juste plusieurs MSHR qui mémorisent juste un numéro de ligne de cache, un bit de validité, le champ de lecture/écriture, et l'adresse exacte de lecture/écriture. Avec cette organisation, il est possible d'avoir plusieurs défauts de cache séparés, mais à la condition que chaque défaut accède à une ligne de cache différente. Deux accès simultanés à une même ligne de cache ne sont pas possibles, les défauts de cache secondaires ne sont pas autorisés. Ainsi, chaque défaut de cache se voit attribuer son propre MSHR, chacun contient un numéro de ligne de cache différent. Pour comprendre pourquoi c'est impossible de gérer les défauts secondaires, il faut regarder le champ de lecture/écriture. Si on veut effectuer plusieurs écritures consécutives à la même adresse, le MSHR n'aura pas de quoi mémoriser les deux données à écrire. Il pourra mémoriser la première donnée à écrire, pas la seconde. Même chose lors d'une lecture : le champ lecture/écriture peut mémorisr la destination de la première lecture, pas de la seconde. L'avantage est que la MHA n'a besoin que des MSHR et de quelques circuits annexes. Les autres solutions rajoutent des circuits annexes pour gérer les défauts de cache secondaires, qui utilisent beaucoup de circuits. Le cout en circuit est donc élevé, mais le gain en performance est là. Passons maintenant aux caches non-bloquants qui autorisent les défauts de cache secondaire. La solution la plus simple consiste à utiliser ===Les MSHR adressés implicitement=== Avec les '''MSHR adressés implicitement''', il est possible de fusionner plusieurs accès mémoire à une même ligne de cache, mais sous une condition très importante : ces accès lisent/écrivent des mots mémoire différents. Par exemple, imaginons qu'une ligne de cache contienne 8 mots mémoire de 64 bits. Si un premier accès mémoire lit le mot mémoire numéro 7 (dernier mot mémoire de la ligne), et le second accès le mot mémoire numéro 3, alors la fusion est possible. Mais si deux accès mémoire veulent lire/écrire le mot mémoire numéro 7, alors la fusion n'est pas possible et le processeur se bloque, un ''pipeline stall'' survient. Un MSHR adressé implicitement est un MSHR simple, qui contient naturellement un numéro de ligne de cache (tag) et un bit de validité, sauf que le champ de lecture/écriture est dupliqué. Les différents champs lecture/écriture d'un MSHR sont regroupés dans une mémoire RAM/cache qui contient autant d'entrées qu'il y a de mot mémoire dans une ligne de cache. Chaque entrée de la table est associée à un mot mémoire de la ligne de cache et stocke des informations sur celui-ci. Une entrée mémorise, au minimum : * Un champ de lecture/écriture qui contient soit la destination de la lecture, soit la donnée à écrire. * Un bit R/W permettant d'interpréter correctement le champ de lecture/écriture. * Un bit de validité pour chaque entrée de la table, qui dit si un défaut de cache antérieur accède déjà à ce mot mémoire. La fusion de deux défauts de cache est ainsi assez simple. Un défaut de cache primaire/secondaire configure l'entrée associée au mot mémoire lu/écrit. Si un défaut secondaire ultérieur lit/écrit un mot mémoire différent (dont le bit de validité de l'entrée dans la table est à zéro), pas de conflit : il configure une autre entrée, vide. Mais s'il lit/écrit un mot mémoire pour lequel l'entrée de la table est occupée, il y a conflit, le processeur est bloqué par un ''pipeline stall'', une bulle de pipeline. Avec cette organisation, le nombre de MSHR indique combien de lignes de cache peuvent être lues en même temps depuis la mémoire. Quant au nombre d'entrées par MSHR, il détermine combien d'accès mémoires qui ne se recouvrent pas peuvent avoir lieu en même temps. ===Les MSHR adressés explicitement=== Les '''MSHR adressés explicitement''' n'ont pas les contraintes des MSHR adressés implicitement. Avec eux, il est possible d'avoir plusieurs défauts de cache pointant vers la même ligne de cache, mais aussi vers le même mot mémoire. De tels défauts de cache apparaissent sur les processeurs à exécution dans le désordre, mais sont très rares, voire inexistants, sur les processeurs ''in-order''. L'idée est encore que chaque MSHR est associée à une table mémoire qui mémorise des entrées. Cette table n'est autre qu'une mémoire FIFO. Sauf que cette fois-ci, une entrée n'est pas associée à un mot mémoire. Une entrée est associée à un défaut de cache. Une entrée mémorise là encore la destination de la lecture ou la donnée de l'écriture suivi du bit R/W, ainsi que d'un bit de validité par entrée, mais aussi : la position du mot mémoire lu dans la ligne de cache. C'est cette dernière information qui n'était pas présente dans les MSHR adressés implicitement. De plus, le nombre d'entrée par MSHR n'est pas égal au nombre de mots mémoires dans une ligne de cache, mais peut être arbitrairement grand. L'avantage d'un tel cache est qu'il est capable de traité les défauts secondaires concernant un même mot mémoire, et ce, car les accès à la ligne de cache concernée se produisent dans l'ordre de génération des défauts (la table mémoire est une FIFO). ===Les MSHRs inversés=== Généralement, plus on veut supporter de défauts de cache, plus le nombre de MSHR et d'entrées augmente. Mais au-delà d'un certain nombre d'entrées et de MSHR, les MSHR adressés implicitement et explicitement ont tendance à bouffer un peu trop de circuits. Utiliser une organisation un peu moins gourmande en circuits est donc une nécessité. Cette organisation plus économe se base sur des MSHR inversés. Les MSHR inversés ne contiennent qu'une seule entrée, en quelque sorte : au lieu d’utiliser n MSHR de m entrées chacun, on va utiliser n × m MSHR inversés. La différence, c'est que plusieurs MSHR peuvent contenir un tag identique, contrairement aux MSHR adressés implicitement et explicitement. Lorsqu'un défaut de cache a lieu, chaque MSHR est vérifié. Si jamais aucun MSHR ne contient de tag identique à celui utilisé par le défaut, un MSHR vide est choisi pour stocker ce défaut, et une requête de lecture en mémoire est lancée. Dans le cas contraire, un MSHR est réservé au défaut de cache, mais la requête n'est pas lancée. Quand la donnée est disponible, les MSHR correspondant à la ligne qui vient d'être chargée vont être utilisés un par un pour résoudre les défauts de cache en attente. ===Les MSHR intégrés au cache=== Certains chercheurs ont remarqué que pendant qu'une ligne de cache est en train de subir un défaut de cache, celle-ci reste inutilisée, et son contenu est destiné à être perdu une fois le défaut de cache résolu. Ils se sont dit que, plutôt que d'utiliser des MSHR séparés, il vaudrait mieux utiliser la ligne de cache pour stocker les informations sur les défauts de cache en attente dans cette ligne de cache. Pour éviter tout problème, il faut rajouter un bit dans les bits de contrôle de la ligne de cache, qui sert à indiquer que la ligne de cache est occupée : un défaut de cache a eu lieu dans cette ligne, et elle stocke donc des informations pour résoudre les défauts de cache. ==L'entrelacement mémoire== Nous venons de voir comment une mémoire cache peut gérer plusieurs accès mémoire simultanés. Il se trouve que ce genre d'optimisation dépasse largement le cadre du cache. Les mémoires RAM ne sont pas en reste, elles aussi ! Il est possible de pipeliner l'accès à une mémoire RAM, en utilisant des techniques dites d''''entrelacement'''. Précisons cependant que cette optimisation n'est pas utilisée sur les mémoires SDRAM ou DDR modernes, du moins pas sans modifications majeures. Mais divers ordinateurs assez anciens, dont des superordinateurs, ont utilisé cette technique. La technique demande d'utiliser plusieurs mémoires séparées. Sur les anciens ordinateurs, les mémoires en question sont des chips mémoire, à savoir des circuits intégrés de DRAM. Pour faire plus général, nous allons utiliser le terme de '''banques''', ou encore de bancs mémoire. La différence est qu'il existe des mémoires multi-banque, qui regroupent plusieurs banques indépendantes dans un seul boitier, dans un seul chip mémoire. En clair, de telles mémoires regroupent plusieurs sous-mémoires dans un seul circuit intégré. Il est aussi possible d'utiliser plusieurs chips mémoire séparés, chacun ayant une banque. Reste à combiner ces banques pour former un pseudo-pipeline. ===Utiliser plusieurs banques sans entrelacement=== Sans optimisation particulière, les adresses sont réparties dans les banques comme indiqué ci-dessous. Il s'agit de ce qui s'appelle un arrangement horizontal, et nous avions vu celui-ci dans le chapiutre sur les mémoires SDRAM. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Avec cet arrangement, les bits de poids fort de l'adresse sont utilisées pour sélectionner la banque adéquate, et le reste de l'adresse est envoyé sur le bus d'adresse. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Adresse de banque !! Adresse dans la banque |- | Quelques bits de poids fort || Reste de l'adresse |} L'entrelacement change cette répartition, afin d'accélérer les accès mémoire. L'entrelacement classique vise à améliorer les accès à des adresses mémoires consécutives, mais des formes plus évoluées d'entrelacement visent à optimiser des accès mémoire arbitraires. Voyons-les dans l'ordre, du plus simple au plus complexe. ===L'entrelacement classique=== Avec l''''entrelacement classique''', des adresses consécutives sont réparties dans des banques consécutives. Précisons que cette attribution des adresses n'implique absolument pas la mémoire virtuelle ou n'importe quel mécanisme dans le processeur. La répartition décide que telle adresse mémoire va dans telle banque, à telle adresse dans la banque. Elle est donc le fait du contrôleur mémoire, donc en dehors du processeur (les contrôleurs mémoires n’étaient pas encore intégrés dans le processeur à l'époque). [[File:Répartition des adresses dans une mémoire interleaved.png|centre|vignette|upright=1.5|Répartition des adresses dans une mémoire interleaved.]] L'entrelacement simple permet d'accélérer les accès mémoire qui se font à des adresses consécutives. Avec l'entrelacement, chaque accès mémoire tombe dans une banque mémoire différente, ce qui fait qu'on peut démarrer un nouvel accès mémoire à chaque cycle d'horloge. Pendant qu'une banque est occupée par un accès mémoire, on démarre le suivant dans une autre banque, et ainsi de suite. Le tout se termine soit quand on a épuisé toutes les banques libres, soit quand l'accès en rafales se termine. Pas besoin d'attendre que la mémoire ait fini sa lecture/écriture avant de démarrer la lecture/écriture suivante. [[File:Pipemining mémoire.png|centre|vignette|upright=2.5|Pipelining mémoire]] L'animation suivante illustre bien le processus, avec quatres banques. [[File:Interleaving.gif|centre|vignette|upright=2.5|Pipelining mémoire]] L'entrelacement simple utilise les bits de poids faible pour sélectionner la banque, et les bits de poids fort pour la case mémoire. [[File:Adresse mémoire d'une mémoire entrelacée.png|centre|vignette|upright=2|Adresse mémoire d'une mémoire entrelacée]] Précisons que le temps d'accès mémoire ne change pas beaucoup avec l'entrelacement. Par contre, on peut faire plus d'accès mémoire simultanés. On peut démarrer un accès mémoire par cycle d'horloge, mais l'accès en lui-même prend plusieurs cycles. Les mémoires à entrelacement ont donc un débit supérieur aux mémoires qui ne l'utilisent pas. L'entrelacement classique pourrait être utilisé pour accélérer les transferts entre mémoire RAM et cache, qui se font en blocs d'adresses consécutives. Mais de nos jours, il n'est pas utilisé dans cette optique. Il faut dire qu'il est redondant avec le mode rafale des mémoires DRAM. Les deux font globalement la même chose et on voit mal comment utiliser les deux en même temps. Le mode rafale étant plus simple à implémenter, et plus léger niveau utilisation du bus de commande mémoire, il est préféré à l'entrelacement. Par contre, l'entrelacement a eu son heure de gloire sur d'anciens processeurs qui n'avaient pas de mémoire cache, alors qu'ils disposaient d'un pipeline. L'exemple typique est celui des nombreux ''processeurs vectoriels'', que nous n'avons pas encore abordé à ce stade du cours. De tels processeurs avaient un pipeline, une unité de calcul fortement pipelinée, et parfois même de l'exécution dans le désordre et du renommage de registres ! ===L'entrelacement par décalage=== L'entrelacement simple est très utile pour les accès en rafle ou équivalents. Par contre, il existe de rares situations où il n'est pas optimal. Une de ces situations est celle des accès en enjambée (en ''stride''), où l'on accède en série à des adresses sont séparées par N mots mémoires. [[File:Accès par enjambées.png|centre|vignette|upright=2|Accès par enjambées.]] De tels accès surviennent quand un logiciel accède à des structures de données spécifiques, à savoir des tableaux de structures, des matrices, ou d'autres structures de données dans le genre. Un cas classique est celui du parcours d'une matrice colonne par colonne. Les matrices de nombres sont mémorisées ligne par ligne, ce qui fait que parcourir une colonne demande de passer d'une ligne à la suivante et de faire des grands sauts en mémoire RAM, mais tous espacés par la même distance. Avec l'entrelacement simple, les accès en enjambée sont moins performants que les accès à des adresses consécutives, mais cela ne fonctionne pas trop mal. Le pire cas est celui où l'on a N banques et où les données sont justement placées toutes les N adresses. Des accès consécutifs vont tous tomber dans la même banque, on ne peut plus accéder à des banques différentes en parallèle. Mais en dehors de ce cas, on voit une amélioration par rapport à la situation sans entrelacement. Pour obtenir des performances maximales pour les accès en enjambées, il faut répartir les mots mémoires dans la mémoire autrement. L'organisation idéale est la suivante. : Dans les explications qui vont suivre, la variable N représente le nombre de banques, qui sont numérotées de 0 à N-1. On commence par organiser les N premières adresses comme une mémoire entrelacée simple : l'adresse 0 correspond à la banque 0, l'adresse 1 à la banque 1, etc. Pour le bloc suivant, on décale tout d'une adresse, à savoir qu'on commence à remplir les banques à partir de la seconde, non de la première. Une fois la fin du bloc atteinte, on finit de remplir le bloc en repartant du début du bloc. Le troisième bloc d'adresse subit le même traitement, sauf qu'on commence à remplir à partir de la seconde banque. Et on poursuit l’assignation des adresses en décalant d'un cran en plus à chaque bloc. Ainsi, chaque bloc verra ses adresses décalées d'un cran en plus comparé au bloc précédent. Si jamais le décalage dépasse la fin d'un bloc, alors on reprend au début. [[File:Mémoire interleaved par décalage.png|centre|vignette|upright=1.5|Mémoire entrelacée par décalage.]] En faisant cela, on remarque que les banques situées à N adresses d'intervalle sont différentes. Dans l'exemple du dessus, nous avons ajouté un décalage de 1 à chaque nouveau bloc à remplir. Mais on aurait tout aussi bien pu prendre un décalage de 2, 3, etc. Dans tous les cas, on obtient un entrelacement par décalage. Ce décalage est appelé le pas d'entrelacement, noté P. Le calcul de l'adresse à envoyer à la banque, ainsi que la banque à sélectionner se fait en utilisant les formules suivantes : * adresse à envoyer à la banque = adresse totale / N ; * numéro de la banque = (adresse + décalage) modulo N ; * décalage = (adresse totale * P) mod N. Avec cet entrelacement par décalage, on peut prouver que la bande passante maximale est atteinte si le nombre de banques est un nombre premier. Seulement, utiliser un nombre de banques premier peut créer des trous dans la mémoire, des mots mémoires inadressables. Malgré ce défaut, la technique a été utilisée sur quelques ordinateurs, avec l'exemple notable du superordinateur ''Burroughs Scientific Processor''. Pour éviter cela, il y a plusieurs solutions. Par exemple, on peut faire en sorte que N et la taille d'une banque soient premiers entre eux : ils ne doivent pas avoir de diviseur commun. Mais en pratique, elles n'ont pas vraiment été implémentées dans une vraie machine, et sont restées à l'état de recherche, aussi je les passe sous silence. ===L'entrelacement pseudo-aléatoire=== Une dernière méthode de répartition consiste à répartir les adresses dans les banques de manière "pseudo-aléatoire". L'idée exacte est de faire passer l'adresse dans une '''fonction de hachage''', plus ou moins complexe. Pour rappel, une fonction de hachage prend une entrée de grande taille et fournit en sortie un résultat de petite taille. Idéalement, elles doivent donner des résultats aussi différents que possible pour des entrées similaires, histoire de simuler une sorte de pseudo-aléatoire. Ici, une adresse est transformée en un numéro de banque. Une fonction de hachage simple ne fait que permuter des bits de l'adresse pour obtenir son résultat. Elle ne fait qu'échanger des bits de place avant de couper l'adresse en deux morceaux : un pour la sélection de la banque, et un autre pour la sélection de l'adresse dans la banque. Cette permutation est fixe, et ne change pas suivant l'adresse. Des fonctions de hachage plus complexes font des XOR bit à bit entre certains bits de l'adresse. ==Les contrôleurs mémoires SDRAM/DDR optimisés== Après avoir vu le cas des mémoires RAM, nous allons nous concentrer sur le cas particulier des mémoires SDRAM. Les mémoires SDRAM et DDR modernes sont capables de gérer plusieurs accès simultanés à la mémoire RAM, et leurs contrôleurs mémoire en font tout autant. C'est une différence majeure avec les mémoires asynchrones FPM/EDO, qui n'acceptaient qu'un seul accès mémoire à la fois. Leur contrôleur mémoire n'acceptait qu'un seul accès mémoire à la fois, c'était un contrôleur mémoire bloquant. Les contrôleurs mémoires des SDRAM sont eux non-bloquants et peuvent encaisser une dizaine d'accès mémoire à la fois. Peu de choses sont connues sur les contrôleurs de SDRAM/DDR modernes, les fabricants ne donnant que peu de détails dessus. Les rares simulateurs qui tentent de décrire leur fonctionnement, comme DRAM SIM I et II, sont particulièrement simples et ne vont pas dans le détail. Néanmoins, le peu qu'on sait est tout de même instructif. ===Rappel sur les SDRAM : tampon de ligne et commandes=== Les mémoires SDRAM sont des mémoires à tampon de ligne. Elles font un accès mémoire en deux étapes. La première étape recopie une ligne de N * 64 bits dans un tampon interne à la SDRAM. La seconde étape sélectionne une colonne, à savoir une donnée de 64 bits, qui est soit envoyée sur le bus de données pour une lecture, soit modifiée par une écriture. [[File:Mémoire à tampon de ligne.png|centre|vignette|upright=2|Mémoire à tampon de ligne]] Les accès mémoire sont traduits par un séquenceur mémoire en une série de commandes mémoires, qui sont séparées par des délais mémoire de quelques cycles d'horloge. Les délais sont très précis, et sont à respecter à la lettre. Une lecture ou une écriture se fait en maximum trois commandes : une commande PRECHARGE qui ferme la ligne précédemment utilisée, une commande ACT qui précise l'adresse de la ligne, et une commande READ ou WRITE qui précise l'adresse de la colonne et éventuellement la donnée à écrire. : Pour être plus précis, la commande PRECHARGE précharge les lignes de bits du plan mémoire à une tension particulière, ce qui les vide de leur contenu. Mais c'est un détail sans importance pour ce qui va suivre. Les SDRAM permettent de se passer de la première étape si des accès consécutifs se font dans la même ligne. Une fois activée, la ligne reste ouverte et on peut accéder plusieurs fois de suite dedans. On a alors juste à préciser l'adresse de colonne dedans. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | bgcolor="#FFA0FF" | PRECHARGE || bgcolor="#FFA0FF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || bgcolor="#A0FFFF" | READ (2) || bgcolor="#A0FFFF" | READ (3) || || || bgcolor="#A0FFFF" | READ (4) || bgcolor="#A0FFFF" | READ (5) || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | Donnée READ 1 || ||bgcolor="#A0FFFF" | Donnée READ 2 | bgcolor="#A0FFFF" | Donnée READ 3 || || || bgcolor="#A0FFFF" | Donnée READ 4 || bgcolor="#A0FFFF" | Donnée READ 5 |} Les SDRAM sont parfois capables de démarrer une commande avant que la précédente soit terminée. Mais le respect des délais mémoire est très limitant, ce qui fait qu'on ne peut pas parler de réel pipeline, comme c'est le cas sur les processeurs. Parlons plutot de '''commandes anticipées'''. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | || bgcolor="#A0FFFF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || || || bgcolor="#FFA0FF" | READ (2) || || || || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 | bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 | |} Un dernier point est que les chips mémoires de SDRAM sont composés de plusieurs banques, chacune étant une sorte de mini-mémoire miniature. Chaque banque a son propre décodeur, son propre tampon de ligne, ses propres multiplexeurs de colonne, sa logique de rafraichissement mémoire, etc. C'est comme si une SDRAM regroupait plusieurs mémoires séparées dans un même circuit intégré. [[File:Arrangement vertical.jpg|centre|vignette|upright=2.5|Mémoire multi-banques.]] ===La mise en attente des accès mémoire=== Un contrôleur de SDRAM peut accepter plusieurs accès mémoire en même temps. Mais cela ne signifie pas que le contrôleur sera prêt à les traiter immédiatement. Pour éviter tout problème de disponibilité, le contrôleur met en attente les accès mémoire que le processeur lui envoie, pour les exécuter dès que possible. Les accès mémoire sont mis en attente dans une mémoire FIFO, histoire de les exécuter dans leur ordre d'arrivée, notamment pour renvoyer les lectures dans l'ordre demandé. Il y a aussi une mémoire FIFO pour les données à écrire et une FIFO pour les données lues. Cette dernière sert au cas où le cache ne soit pas disponible quand on lui envoie la donnée lue. [[File:Module d'interface avec la mémoire.png|centre|vignette|upright=2.5|Contrôleur mémoire avec mise en attente des requêtes processeur et autres optimisations.]] Des optimisations sont possibles dès la mémoire FIFO. Par exemple, on peut regrouper plusieurs accès à des données consécutives en un seul accès en rafale. Le contrôleur mémoire analyse les requêtes mises en attente et détecte si certaines se font à des adresses consécutives. Si c'est le cas, il fusionne ces requêtes en une lecture/écriture en rafale. Une telle optimisation est appelée la '''combinaison de lecture''' pour les lectures, et la '''combinaison d'écriture''' pour les écritures. Une autre optimisation possible est le réacheminement lecture sur écriture (''Store to load forwarding''). Il s'agit d'un équivalent de l’optimisation utilisée dans le cadre de la désambiguïsation mémoire. Quand une lecture demande à accéder à une donnée écrite récemment, il se peut que l'écriture ait été mise en attente dans le contrôleur mémoire. Dans ce cas, le contrôleur mémoire peut renvoyer la donnée directement depuis ses mémoires FIFOs, sans faire d'accès mémoire en lecture. Les deux optimisations précédentes ne paraissent pas payer de mine, mais elles deviennent plus compréhensible quand on sait que certains contrôleurs mémoire peuvent mettre en attente beaucoup de données. Par exemple, l'''Intel E8870 - Scalable Memory Controller'' peut mettre en attente 64 lignes de cache dans la mémoire FIFO, soit 8 kibioctets de RAM ! Deux pages mémoire si la mémoire virtuelle utilise des pages de 4 kibioctets ! ===Le ré-ordonnancement des commandes mémoires=== Un contrôleur mémoire moderne est capable de changer l'ordre des requêtes mémoires, pour gagner en performances. En clair : il peut faire l'équivalent mémoire de l'exécution dans le désordre des processeurs haute performance. Une différence majeure est que le contrôleur mémoire ne remet pas les accès mémoire dans l'ordre avant de les envoyer au processeur. C'est le processeur qui remet en ordre les accès mémoire, dans sa ''load queue''. Pour cela, les requêtes mémoire sont "numérotées", à savoir qu'on leur attribue un identifiant binaire. Le contrôleur mémoire exécute les lectures/écritures dans le désordre, mais garde la trace de leur identifiant. Il sait dans quel ordre il exécute les accès mémoire et connait leur latence. Ce qui fait qu'il sait que la donnée lue à tel instant est associée à la requête mémoire numéro X. Pour les lectures, il renvoie la donnée lue avec l'identifiant. Le processeur sait alors à quelle lecture la donnée lue correspond et il se débrouille en interne pour gérer la situation. En clair : le processeur voit les accès mémoire dans le désordre, c'est lui qui les remet en ordre. Maintenant que ces précisions sont faites, posons cette question : quel est l'intérêt de faire les accès mémoire dans le désordre ? Accéder à des banques en parallèle est une réponse possible, mais le ré-ordonnancement est surtout utile si tous les accès mémoire atterrissent dans la même banque et dans la même ligne. Elle marche si plusieurs accès à une même ligne ne sont pas consécutifs, et qu'il y a des accès entre les deux. Après ré-ordonnancement, ces accès mémoire sont exécutés l'un à la suite de l'autre. Pour rendre le tout plus concret, voici un exemple. Imaginez que l'on sait les 3 accès mémoire suivants : * Une lecture ligne A ; * Une écriture ligne B ; * Une lecture ligne A. Sans réordonnancent, on doit émettre deux commandes PRECHARGE : une quand on passe de la ligne A à la ligne B, et une autre pour le passage inverse. pour éviter cela, le contrôleur mémoire peut retarder l'écriture, ou au contraire avancer la seconde lecture. le résultat est alors le suivant : * Lecture ligne A ; * Lecture ligne A ; * Une écriture ligne B. On a donc deux accès consécutifs à la même ligne, suivi par une écriture dans une autre ligne. La technique marche parce que l'on a un mix adéquat de lectures et d'écritures. Mais il existe des possibilités de réorganisation autres qui ne sont pas valides. Il faut par exemple prendre garde à éviter d'intervertir lectures et écritures à une même adresse, sous peine de problèmes. Encore une fois, il faut faire attention aux dépendances de données, encore une fois ! Ici des dépendances mémoires, mais les règles sont les mêmes. Le contrôleur mémoire teste les dépendances mémoires avant d'envoyer des commandes à la mémoire DDR/SDRAM. Il a existé des contrôleurs mémoires du temps des vieilles DDR qui implémentaient un ordonnancement très simples. Prenons l'exemple des coprocesseurs IO 81341 et 81342, qui étaient en réalité des ''chipsets'' intégrant un contrôleur SDRAM. Le ''chipset'' avait 5 ports : un pour les processeurs, un pour le pont sud (''southbridge'') et trois pour des canaux DMA. Pour le premier, il n'y avait pas de réordonnancement. Pour les autres, les lectures se font dans l'ordre, les écritures se font dans l'ordre, mais les écritures peuvent passer avant une lecture non-dépendante. L'implémentation était assez simple : le séquenceur était précédé par plusieurs mémoires FIFO. Le port processeur avait une seule mémoire FIFO, ce qui conservait l'ordre d'exécution des commandes mémoire. Pour les autres ports, le processeur avait vraisemblablement des files séparées pour les lectures et écritures. Le séquenceur mémoire vérifiait les dépendances mémoire de type RAW et autres. [[File:Double file d'attente pour les lectures et écritures.png|centre|vignette|upright=2.5|Double file d'attente pour les lectures et écritures]] L'implémentation était vraiment simple : elle n’exploitait même pas la présence de plusieurs banques ! De nos jours, les contrôleurs mémoires sont beaucoup plus évolués et sont capables d'exécution dans le désordre bien plus poussée. Les contrôleurs mémoires remplacent la FIFO par une sorte de fenêtre d'instruction dans laquelle ils poichent les accès mémoire selon leurs besoins. ===Les optimisations liées à la présence de plusieurs banques=== Les optimisations précédentes sont décuplées par la présence de plusieurs banques dans la mémoire SDRAM. Il est en effet possible de faire plusieurs accès mémoire en même temps, dans des banques différentes. Il est possible de lancer un accès mémoire dans deux banques en même temps, par exemple. La seule contrainte est que la SDRAM est limitée à 4 banques actives en même temps. {|class="wikitable" |+ Pipelining basique sur les SDRAM |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 |- ! Banque Numéro 1 | || || || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || || |- ! Banque Numéro 2 | || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || |- ! Banque Numéro 3 | || || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || |} L'implémentation n'a rien de vraiment compliqué. Il suffit d'utiliser une mémoire FIFO par banque. Ainsi, les accès dans une même banque se feront en série, dans l'ordre d'arrivée, mais des accès à des banques différentes se feront dans le désordre. Cela ne pose pas de problème car les accès se font à des adresses différentes, ce qui fait qu'il n'y a pas de conflits majeurs. [[File:Gestion parallèle des banques.png|centre|vignette|upright=2.5|Gestion parallèle des banques.]] ==L'entrelacement sur les mémoires SDRAM== L''''entrelacement''' fonctionne sur les mémoires SDRAM, mais il doit être fortement modifié. Concrètement, tout ce qui a étré dit plus est inapplicable pour les SDRAM, du fait de la présence du tampon de ligne. Des adresses consécutives atterrissent déjà dans la même ligne, ce qui fait qu'on n'a pas besoin d'entrelacement au niveau d'une ligne. Et ne parlons pas de ce la présence de rangées et de canaux mémoire ! Cependant, il peut y avoir un entrelacement lié à la présence des banques SDRAM. L'entrelacement est géré juste avant le séquenceur mémoire. Le '''circuit d'entrelacement''' intervertit certains bits de l'adresse lors des accès mémoires. On trouve aussi des mémoires FIFOs pour mettre en attente les requêtes mémoire provenant du processeur. Elles permettent de recevoir plusieurs accès mémoire consécutifs. Si vous vous demandez comment plusieurs accès mémoire consécutifs peuvent avoir lieu, sachez que c'est une bonne question. Pour le moment, nous allons dire que ces accès proviennent du processeur ou de la mémoire cache, sans expliquer comment. Le contrôleur mémoire reçoit une série d'accès mémoire assez rapprochés, qu'il doit exécuter au mieux. [[File:Controleur mémoire d'une SDRAM avec entrelacement.png|centre|vignette|upright=2.5|Contrôleur mémoire d'une SDRAM avec entrelacement.]] Pour gérer l'entrelacement, le contrôleur de SDRAM prend en entrée l'adresse envoyée par le processeur, et la découpe en plusieurs champs : un pour sélectionner la banque adéquate, un autre pour la ligne, un autre pour la colonne, un autre pour la rangée. Une solution simple désactive l'entrelacement. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de banque || Adresse de ligne || Adresse de colonne |} Si cette solution fonctionne bien si les accès mémoire sont assez aléatoires, en pratique, mieux vaut utiliser l'entrelacement. ===L'entrelacement de banques/lignes=== Une première méthode d'entrelacement est l''''entrelacement de banques'''. Elle répartit deux lignes consécutives dans deux banques différentes. Pour comprendre l'idée, prenons un exemple. Imaginons une mémoire avec deux banques et 4 lignes. Imaginons qu'on parcoure/balaye la mémoire RAM en partant des adresses basses. Sans entrelacement de ligne, les accès se feront comme suit, de gauche à droite : {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Banque numéro 1 !! colspan="4" | Banque numéro 2 |- | Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 || Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 |} Avec l'entrelacement de banques, les accès se feront comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 |- | Ligne 1 || Ligne 1 || Ligne 2 || Ligne 2 || Ligne 3 || Ligne 3 || Ligne 4 || Ligne 4 |} Avec N banques, la première ligne ira dans la première banque, la seconde ligne dans la seconde, et ainsi de suite jusqu'à la énième banque qui va dans la banque numéro N. Et la ligne N+1 repart dans la première banque. L'avantage est que passer d'une ligne à la suivante est assez rapide. Au lieu de devoir fermer la première ligne et ouvrir la suivante on ouvrira directement la ligne suivante dans une banque différente, ce qui sera plus rapide. La commande PRECHARGE pourra être envoyée à la seconde banque en avance. Pour cela, l'adresse est découpée comme suit. {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne (précise à l'octet) |} ===L'entrelacement de rangées=== Il est aussi possible de faire la même chose, mais avec les rangées. Pour simplifier fortement, une rangée est simplement un chip mémoire. En réalité, les rangées ne sont pas des chip mémoire, mais un ensemble de chips mémoire regroupés ensemble histoire d'atteindre les 64 bits du bus de données. Par exemple, une rangée peut combiner 8 chips mémoire avec un bus de données de 8 bits chacun pour obtenir les 64 bits du bus de données d'une SDRAM. Mais nous passons ce détail sous silence dans les explications qui vont suivre, par souci de simplification. Pour faire comprendre l'entrelacement de rangée, le mieux est d'utiliser un exemple, le même que précédemment. Sans entrelacement de rangée, on change de chip mémoire une fois qu'on a balayé toutes ses adresses. {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Rangée numéro 1 !! colspan="4" | Rangée numéro 2 |- | Banque 1 || Banque 2 || Banque 3 || Banque 4 || Banque 1 || Banque 2 || Banque 3 || Banque 4 |} Avec l'entrelacement de ligne, on change de rangée dès qu'on change de banque/ligne. {|class="wikitable" |+ Adresse mémoire |- ! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 |- | Banque 1 || Banque 1 || Banque 2 || Banque 2 || Banque 3 || Banque 3 || Banque 4 || Banque 4 |} En clair, quand on a épuisé toutes les banques dans une même rangée, on passe à la rangée suivante au lieu de rester dans la même rangée. Notons que dans l'exemple précédent, on a combiné l'entrelacement de rangée et de banque, mais on aurait pu utiliser l'entrelacement de rangée seul. Mais ce n'est pas le cas le plus courant en pratique. Toujours est-il qu'en combinant entrelacement de rangée et de ligne, le découpage de l'adresse est le suivant : {|class="wikitable" |+ Adresse mémoire |- | Adresse de ligne || Numéro de rangée || Adresse de banque || Adresse de colonne (précise à l'octet) |} D'autres méthodes d'entrelacement sont possibles, mais elles sont plus complexes. Chaque contrôleur mémoire fait un peu à sa sauce. Ils permettent en général de choisir entre plusieurs entrelacements. Au minimum, ils permettent de désactiver l'entrelacement, d'activer un entrelacement de ligne, et d'autres entrelacements plus complexes, dont certains sont propriétaires et tenus secrets par le fabricant. ===L'entrelacement avec le ''dual channel''=== [[File:Dual channel slots.jpg|vignette|Slots mémoires avec ''dual channel''.]] L'usage du ''dual channel'' complique encore l'entrelacement. Là encore, il y a deux grandes solutions : avec et sans entrelacement des canaux mémoire. Rappelons le principe : deux barrettes de RAM sont accédées en parallèle. Et pour cela, il faut utiliser l'entrelacement. Typiquement, chaque barrette mémoire fournit 64 bits, ce qui fait que l'on peut accéder à 128 bits d'un seul coup, par exemple avec un accès en rafale. Sans ''dual channel'', la première barrette correspond à la moitié haute de la RAM, la seconde barrette correspond à la moitié basse. Avec ''dual channel'', une forme spécifique d'entrelacement est activée. Concrétement, deux blocs de 64 bits sont placés dans des canaux mémoire séparés. Idem avec du triple ou quadruple canal, mais c'est alors trois ou quatre blocs de 64 bits qui sont dispersés dans des canaux consécutifs. Le découpage de l'adresse est alors le suivant : {|class="wikitable" |+ Adresse mémoire |- ! colspan="6" | Sans entrelacement |- | Numéro de canal/barrette || Reste de l'adresse || Position de l'octet dans un bloc de 64 bits |- | colspan="6" | |- ! colspan="6" | Avec entrelacement |- | Reste de l'adresse || Numéro de canal/barrette || Position de l'octet dans un bloc de 64 bits |} [[File:Décodage d'adresse avec dual channel.png|centre|vignette|upright=2|Décodage d'adresse avec dual channel.]] Les contrôleurs de SDRAM précédents sont assez basiques et ne représentent pas les contrôleurs les plus évolués. Ils étaient utilisés sur les anciens PC, à une époque où ils étaient encore sur la carte mère du processeur. Mais de nos jours, le contrôleur mémoire est intégré au processeur. Et il incorpore de nombreuses optimisations afin de gagner en performances. Malheureusement, ces optimisations sont elles-mêmes dépendantes des optimisations intégrées dans le processeur, ce qui fait qu'on ne peut pas en parler ici. Elles demandent que le contrôleur mémoire reçoive plusieurs accès mémoire simultanés, ce qui n'a aucun sens à ce stade du cours. Aussi, nous allons les passer sous silence pour le moment. Nous les verrons dans le chapitre sur le parallélisme mémoire. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=La désambiguïsation mémoire | prevText=La désambiguïsation mémoire | next=Les processeurs superscalaires | nextText=Les processeurs superscalaires }} </noinclude> 7w9udm0vzjp9fx84a3x9c3f61zkt4eh 764731 764730 2026-04-23T22:26:42Z Mewtow 31375 /* Le ré-ordonnancement des commandes mémoires */ 764731 wikitext text/x-wiki Dans ce chapitre, nous allons voir les techniques qui permettent de gérer plusieurs accès mémoire simultanés directement au niveau du cache ou de la mémoire RAM. Par plusieurs accès mémoire simultanés, vous pensez sans doute à l'usage de cache multiports, voire de mémoires RAM multiport. C'est en effet une solution, mais ce n'est clairement pas la seule. Par exemple, il est possible de pipeliner l'accès au cache, voire à la mémoire RAM. Il existe de nombreuses techniques de '''parallélisme mémoire''', et nous allons les voir dans ce chapitre. Pour donner un exemple, il est possible de pipeliner les accès mémoire. Il est possible de pipeliner l'accès au cache et/ou l'accès à la mémoire, et nous verrons comment faire dans ce chapitre. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, qu'ils aient ou non un pipeline dynamique. Par contre, pipeliner la mémoire est une technique ancienne, aujourd'hui peu utilisée. Elle était utilisée sur des très vieux ordinateurs, qui avaient un pipeline mais pas de mémoire cache. Mais de nos jours, les mémoires SDRAM et DDR ne sont pas adaptées pour ça, il est très difficile de les pipeliner correctement. Le parallélisme mémoire est utile aussi bien sur des processeurs à émission dans l'ordre que dans le désordre. Par exemple, un processeur ''in-order'' avec des lectures non-bloquantes peut e, profiter. Il peut techniquement lancer une seconde lecture, après avoir rencontré une première lecture non-bloquante. En général, le nombre de lectures consécutives est cependant limité à moins d'une dizaine. À l'opposé, les processeurs sans lectures non-bloquantes profitent pas du parallélisme mémoire. À la rigueur, ils peuvent en profiter s'ils intègrent des techniques de préchargement. Pendant que le processeur exécute une lecture/écriture, il peut précharger une autre donnée en parallèle. Inutile de dire que sans pipeline processeur, le parallélisme mémoire ne sert pas à grand-chose. ==Le ''line fill buffer'' et les techniques associées== Lors d'un défaut de cache, le processeur doit attendre que toute la ligne de cache soit chargée avant d'être utilisable. Or, la taille d'une ligne de cache est supérieure à la largeur du bus mémoire, ce qui fait qu'une ligne de cache est chargée en plusieurs fois, morceaux par morceaux, mot mémoire par mot mémoire. Le chargement peut se faire directement dans le cache, mais ce n'est pas une solution très pratique. À la place, beaucoup de processeurs ajoutent une mémoire tampon entre la RAM et le cache, appelée le '''tampon de remplissage de ligne''' (''line-fill buffer'') dans les processeurs modernes. Lors d'un défaut de cache, le processeur charge la donnée de la RAM dans le tampon de remplissage de ligne, mot mémoire par mot mémoire. Une fois plein, le tampon de remplissage de ligne est recopié dans la ligne de cache. Le tampon de remplissage de ligne contient une ligne de cache, si ce n'est que certains bits de contrôle ne sont pas présents. Le tampon de remplissage de ligne contient un bit de validité pour chaque mot mémoire de la ligne de cache, qui indiquent si le mot mémoire a été chargé. Par exemple, prenons un processeur 64 bits, qui gère donc des mots mémoire de 8 octets, avec des lignes de cache 256 octets/32 mots mémoire. Le tampon de remplissage de ligne contiendra 32 bits de validité. Si le processeur a chargé les 6 premiers mots mémoire, les 6 premiers bits de validité seront mis à 1, les autres seront encore à zéro. Il faut noter que la disponibilité de la ligne complète se détermine assez facilement en faisant un ET logique entre tous les bits de validité. Le processeur sait ainsi quand la ligne de cache est disponible entièrement et donc quand la transférer dans le cache. La même chose existe avec une hiérarchie de cache, sauf que l'on trouve une mémoire tampon entre chaque niveau de cache. Il y a un tampon de remplissage de ligne entre le cache L1 et le cache L2, entre le cache L2 et le L3, etc. Si le cache ne gère qu'un seul défaut de cache à la fois, le ''fill line buffer'' est une mémoire très simple, qui ne mémorise qu'une seule ligne de cache. Et cela vaut aussi bien pour un cache bloquant que non-bloquant. Mais sur les caches capables de gérer plusieurs défauts simultanés, le tampon de remplissage de ligne est une mémoire de type FIFO ou LIFO, capable de mémoriser plusieurs lignes de cache. Notons que le tampon de remplissage de ligne est très utile pour implémenter certaines techniques, par exemple le contournement du cache. Nous avions vu dans le chapitre sur le cache que certains accès mémoire doivent contourner le cache, pour des raisons de cohérence des caches. C'est notamment nécessaire pour accéder aux périphériques, mais c'est aussi utile pour des raisons de performances dans des cas très spécifiques. Les accès qui contournent le cache se font directement dans le tampon de remplissage de ligne : le processeur écrit ou lit les données depuis ce tampon de remplissage de ligne, sans accéder au cache. ===L'''early restart'' et le ''critical word load''=== La présence du ''line buffer'' permet une optimisation assez intéressante, qui permet de réduire la latence des défauts de cache. L'optimisation consiste à lire un mot mémoire dans le tampon de remplissage de ligne, même si la ligne de cache complète n'a pas encore été chargée. Il existe deux manières de faire cela, qui portent les noms d'''early restart'' et de ''critical word load''. La première est la version la plus simple, la seconde est plus complexe mais plus performante. Avec la technique d''''''early restart''''', la ligne de cache est chargée normalement, en partant de son premier mot mémoire. Dès que le mot mémoire lu/écrit par le processeur est copié dans le ''line fill buffer'', il est envoyé au processeur immédiatement. Illustrons le tout par un exemple, où une ligne de cache fait 16 mots mémoire. Le processeur effectue une lecture, qui lit le 5ème mot mémoire. Sans ''early restart'', le processeur doit charger les 16 mots mémoire avant de faire la lecture dans le cache. Avec ''early restart'', le processeur reçoit la donnée dès que le 5ème mot mémoire est disponible. Le processeur doit attendre que les 4 premiers mots mémoire soient chargés, puis le 5ème mot mémoire arrive et est envoyé directement au processeur, il est lu directement depuis le ''line fill buffer''. Les 11 mots mémoire suivants sont ensuite chargés dans le cache pendant que le processeur fait des calculs dans son coin. Le ''critical word load'' est une optimisation de la technique précédente où le chargement de la ligne de cache commence directement à la donnée demandée par le processeur. Pour reprendre l'exemple précédent, où le processeur demande le 5ème mot mémoire sur 16, le ''critical word load'' charge le 5ème mot mémoire en premier et l'envoie au processeur, ce qui fait qu'il est chargé très rapidement. Pas besoin d'attendre que le processeur charge les 4 mots mémoire précédents comme avec l'''early restart''. Dans le détail, le processeur charge le 5ème mot mémoire en premier, puis charge les 11 suivants, et termine par les 4 mots mémoire du début. En clair, le ''critical word load'' commence par charger le mot mémoire lu, puis les blocs suivants, avant de revenir au début du bloc pour charger les blocs restants. Ainsi, la donnée demandée par le processeur sera la première disponible. Pour cela, l'organisation du tampon de remplissage de ligne est modifiée de manière à rendre cela possible. Il a une taille égale à une ligne de cache complète, qui contient elle-même plusieurs mots mémoire. Dans le ''line-fill buffer'', chaque mot mémoire est stocké avec un tag, qui indique l'adresse du mot mémoire stocké dans le ''line-fill buffer''. Le ''line-fill buffer'' est donc un cache un peu particulier, qui fonctionne comme un cache du point de vue du processeur, comme une mémoire FIFO pour les transferts avec le cache. Ainsi, un processeur qui veut lire dans le cache après un défaut peut accéder à la donnée directement depuis le tampon de remplissage de ligne, alors que la ligne de cache n'a pas encore été totalement recopiée en mémoire. ==Les caches pipelinés== Il est possible de pipeliner l'accès au cache, ce qui demande juste de rajouter des registres au bon endroits et quelques circuits de contrôle. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, même ceux avec un pipeline dynamique. Et l'implémentation peut se faire de plusieurs manières différentes. Il faut dire que découper une mémoire cache en plusieurs étapes peut se faire de plusieurs manière. Commençons par la plus simple. Beaucoup de processeurs des années 2000 avaient une fréquence peu élevée comparée aux standards d'aujourd'hui, ce qui fait que leur cache avait bien un temps d'accès d'un cycle d'horloge. Mais l'accès au cache se faisait en deux cycles d'horloge : un cycle pour calculer une adresse et un cycle pour l'accès mémoire proprement dit. Et ces deux étapes étaient pipelinées, à savoir que deux micro-opérations mémoire peuvent s'exécuter en même temps : une dans l'étage de calcul d'adresse, une autre dans le cache. L'unité mémoire était donc pipelinée, alors que l'accès au cache ne l'était pas. Les processeurs qui implémentaient cette technique regroupent les micro-architectures K5 et K6 d'AMD, les processeurs Intel de micro-architecture P6 (Pentium 2 et 3) et quelques autres. Il est aussi possible de pipeliner l'accès au cache lui-même. Avec un cache pipeliné, l'accès au cache ne se fait pas en un seul cycle, mais en plusieurs. Cependant, on peut lancer un nouvel accès au cache à chaque cycle d'horloge, comme un processeur pipeliné lance une nouvelle instruction à chaque cycle. L'implémentation est assez simple : il suffit d'ajouter des registres dans le cache. Pour cela, on profite que le cache est composé de plusieurs composants séparés, qui échangent des données dans un ordre bien précis, d'un composant à un autre. De plus, le trajet des informations dans un cache est linéaire, ce qui les rend parfois pour l'usage d'un pipeline. ===Le ''pipelining'' des caches ''direct-mapped''=== Voici ce qui se passe avec un cache directement adressé. Pour rappel ce genre de cache est conçu en combinant une mémoire RAM, généralement une SRAM, avec quelques circuits de comparaison et un MUX. L'accès au cache se fait globalement en deux étapes : la première lit la donnée et le ''tag'' dans la SRAM, et on utilise les deux dans une seconde étape. Nous avions vu il y a quelques chapitre comment pipeliner des mémoires, dans le chapitre sur les mémoires évoluées. Et bien ces méthodes peuvent s'utiliser pour la mémoire RAM interne au cache !L'idée est d'insérer un registre entre la sortie de la RAM et la suite du cache, pour en faire un cache pipéliné, à deux étages. Le premier étage lit dans la SRAM, le second fait le reste. L'implémentation sur les caches associatifs à plusieurs voies est globalement la même à quelques détails près. [[File:Cache directement adressé pipeliné - deux étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - deux étages]] Avec le circuit précédent, il est possible d'aller plus loin, cette fois en pipelinant l'accès à la mémoire RAM interne au cache. Pour comprendre comment, rappelons qu'une mémoire SRAM est composée d'un plan mémoire et d'un décodeur. L'accès à la mémoire demande d'abord que le décodeur fasse son travail pour sélectionner la case mémoire adéquate, puis ensuite la lecture ou écriture a lieu dans cette case mémoire. L'accès se fait donc en deux étapes successives séparées, on a juste à mettre un registre entre les deux. Il suffit donc de mettre un gros registre entre le décodeur et le plan mémoire. [[File:Cache directement adressé pipeliné - trois étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - trois étages]] Et on peut aller encore plus loin en découpant le décodeur en deux circuits séparés. En effet, rappelez-vous le chapitre sur les circuits de sélection : nous avions vu qu'il est possible de créer des décodeurs en assemblant des décodeurs plus petits, contrôlés par un circuit de prédécodage. Et bien on peut encore une ajouter un registre entre ce circuit de prédécodage et les petits décodeurs. : Théoriquement, toute l'adresse est fournie d'un seul coup au cache, la quasi-totalité des processeurs présentent l'adresse complète à un cache pipeliné. Mais le Pentium 4 fait autrement. Il faut noter que les premiers étages manipulent l'indice dans la SRAM, qui est dans les bits de poids faible, alors que les étages ultérieurs manipulent le ''tag'' qui est dans les bits de poids fort. Les concepteurs du Pentium 4 ont alors décidé de présenter les bits de poids faible lors du premier cycle d'accès au cache, puis ceux de poids fort au second cycle. Pour cela, l'ALU fonctionnait à une fréquence double de celle du processeur, tout comme le cache L1. Il n'y avait pas de pipeline proprement dit, mais cela réduisait grandement la latence d'accès au cache. ===Le ''pipelining'' des caches splittés/sériels=== Il est aussi possible de pipeliner un cache dits splittés, aussi appelés à accès sériel. Pour rappel, les caches à accès sériel vérifient si il y a succès ou défaut de cache, avant d'accéder aux lignes de cache en cas de succès. Ils font donc différemment des autres caches, qui accèdent à une ligne de cache, avant de déterminer s'il y a succès ou défaut en lisant le tag de la ligne de cache. Les caches sériels disposent de deux SRAM : une pour les tags des lignes de cache et une pour les données. Ils accèdent à la SRAM pour les tags, avant d’accéder à la SRAM des données en cas de succès de cache. Vu que l'accès se fait en deux étapes, une vérification des tags suivie de la lecture/écriture des données, il est facile à pipeliner. Pipeliner le cache permet de régler le problème des accès au cache L1, et elle est tout le temps utilisé sur les processeurs modernes. Mais que faire en cas de défaut de cache ? ==Les caches non bloquants== Un '''cache bloquant''' est un cache auquel le processeur ne peut pas accéder pendant un défaut de cache. Il faut attendre que la lecture ou écriture en RAM soit terminée avant de pouvoir utiliser de nouveau le cache. Un '''cache non bloquant''' n'a pas ce problème : on peut l'utiliser pendant un défaut de cache. Les caches non bloquants permettent de démarrer une nouvelle lecture ou écriture alors qu'une autre est en cours, ce qui permet d'exécuter plusieurs lectures ou écritures en même temps. ===Les ''Miss Handling Status Registers''=== Lors d'un défaut de cache, la mémoire RAM est consultée pendant le défaut de cache, mais le cache est inutilisé. Un défaut de cache n'utilise pas le cache, ce n'est qu'un accès en mémoire RAM, sur le bus mémoire (ou un accès aux niveaux de cache inférieurs, peu importe). Le cache en lui-même est laissé libre, rien n’empêche d'y accéder, il est en réalité intrinsèquement non-bloquant. Les caches, bloquants comme non-bloquants, sont en réalité composés d'une mémoire cache proprement dite, entourée de circuits qui servent d'interface entre le processeur et le cache lui-même. Et parmi les circuits tout autour, certains gèrent l'accès au cache lors d'un défaut de cache. Ils sont regroupés sous le terme de '''''Miss Handling Architecture''''' (MHA). La différence entre un cache bloquant et un cache non-bloquant est en réalité liée à l'implémentation de la MHA. Les caches bloquants coupent volontairement l'accès au cache lors d'un défaut, car les défauts sont plus simples à gérer ainsi. Le défaut de cache rapatrie une donnée depuis la RAM, qui sera écrite dans le cache. Et il ne faut pas qu'une tentative d'accès à cette donnée ait lieu avant qu'elle ne soit chargée. Mais ce blocage est général et touche tout le cache, alors que seule une ligne de cache est concernée par le défaut de cache. L'idée derrière un cache non-bloquant est que seule la ligne de cache est bloquée, mais les autres sont accessibles. L'idée est alors de mémoriser les lignes de cache concernées par le défaut de cache, afin d'en bloquer l'accès. À chaque accès, on vérifie si la ligne de cache est déjà réservée par un défaut de cache. La lecture/écriture est alors bloquée si c'est le cas, mais elle accepte les accès sinon. Pour cela, la ''Miss Handling Architecture'' contient des registres qui mémorisent des informations sur les défauts de cache en cours. Ils portent le nom de '''''miss status handling registers''''', que j’appellerais dorénavant MSHR, qui sont aussi appelés des '''''miss buffer'''''. Le contenu des MSHR varie beaucoup suivant le processeur, mais ces derniers stockent au minimum les informations suivantes : * Le numéro de la ligne de cache dans laquelle les données sont chargées. * Un bit de validité qui indique si le MSHR est vide ou pas, qui est mis à 0 quand le défaut de cache est résolu. * Un ou plusieurs champs de lecture/écriture, qui contiennent des informations sur la lecture/écriture. ** Pour une lecture, elle contient des informations sur la destination de la donnée, à savoir qui prévenir quand le défaut de cache est terminé. C'est parfois un nom/numéro de registre (celui dans lequel charger la donnée), mais c'est souvent le numéro de l'entrée dans le ''load/store queue''. ** Pour les écritures, elle contient la donnée à écrire, ou éventuellement un numéro de ''load/store queue'' où se trouve la donnée à écrire. Il faut noter que le bus mémoire ne peut gérer qu'un seul défaut de cache à la fois. Aussi, il est intéressant de regarder ce qui se passe lorsqu'un second défaut de cache survient, pendant qu'un premier est en cours. Dans ce cas, il y a deux réponses qui correspondent à deux types de caches non-bloquants, qui portent les noms barbares de caches de type succès après défaut et défaut après défaut. Sur le premier type, il ne peut pas y avoir plusieurs défauts de cache simultanés. Dès qu'un second défaut de cache survient, le cache stoppe son activité et on ne peut plus démarrer de nouvelle lecture/écriture, tant que le premier défaut de cache n'est pas résolu. Le second type est plus souple et autorise la survenue de plusieurs défauts de cache simultanés. Du moins, jusqu'à une certaine limite, car le cache ne peut supporter qu'un nombre limité d'accès mémoires simultanés (pipelinés). ===Les accès simultanés à une même ligne de cache=== Il arrive que le processeur fasse plusieurs accès mémoire simultanés à la même ligne de cache. Si la ligne de cache en question n'a pas encore été chargée dans le cache, alors on a plusieurs défauts de cache consécutifs pour la même ligne de cache, et le cache non-bloquant doit gérer la situation. Pour la suite, il va falloir faire une petite distinction entre les défauts primaires et secondaires. Imaginons qu'un défaut de cache ait lieu et demande à charger une donnée dans la ligne de cache numéro N. Il s'agit du premier défaut impliquant cette ligne de cache précise, ce qui lui vaut le nom de '''défaut de cache primaire'''. Mais par la suite, d'autres accès mémoire à la même ligne de cache ont lieu, alors que la ligne de cache n'est pas encore disponible. Dans ce cas, il s'agit de '''défauts de cache secondaires'''. Pour l'unité d'accès mémoire, les défauts de cache primaire et secondaire sont différents (ils prennent tous une entrée dans la ''load/store queue''). Mais pour le cache, ils ne correspondent qu'à un seul accès au cache : celui qui demande de charger la ligne de cache demandée. Les défauts de cache primaire et secondaire à la même ligne de cache se voient attribuer un MSHR unique. La gestion des défauts de cache secondaires dépend du cache non-bloquant. La solution la plus simple ne permet pas les défauts de cache secondaires. Le cache ne permet pas deux défauts de cache simultanés pour la même ligne de cache. Les autres solutions le permettent, en fusionnant des accès simultanés à la même ligne de cache en un seul au niveau des MSHR. Dans tous les cas, détecter les défauts de cache secondaires sont un problème qu'il faut détecter. La ''Miss Handling Architecture'' doit détecter les défauts de cache secondaire. Pour cela, elle procède comme suit. Lors de chaque défaut de cache, la MHA récupère le numéro de la ligne de cache associé. Il vérifie alors chaque MSHR pour vérifier s'il contient le numéro en question. S'il n'y a aucune correspondance dans les MSHR, alors c'est signe que le défaut de cache est un défaut primaire. Mais s'il y en a une, alors c'est un défaut secondaire. Évidemment, cela signifie que lors d'un défaut de cache, le numéro de ligne de cache est envoyé à tous les MSHR, pour comparaison. Les MSHR sont donc regroupés dans une mémoire associative, une sorte de mini-cache, faciliter l'implémentation. Lors d'un défaut de cache primaire, l'accès à un cache non-bloquant se fait comme suit : le processeur envoie une adresse au cache, accède à celui-ci, et détecte la survenue d'un défaut de cache. Il en profite alors pour attribuer une ligne de cache dans laquelle sera chargée ce défaut. L'attribution est très simple dans le cas des caches ''direct mapped'', ou associatifs par voie, pour lesquels l'attribution se fait assez simplement. Il mémorise alors cette information dans les MSHR, après avoir vérifié que le défaut de cache n'était pas un défaut secondaire. Lors des accès ultérieurs à une adresse proche, censée être dans la même ligne de cache, le processeur va encore une fois rencontrer un défaut de cache. Il va alors déterminer le numéro de la ligne de cache associée à l'adresse, et comparer ce numéro avec les MSHRs. Si un MSHR contient ce numéro, c'est signe que le défaut de cache est un défaut secondaire. La MHA réagit alors différemment selon le processeur considéré. Une première solution n'autorise pas les défauts de cache secondaires. Si l'un d'entre eux survient, le processeur est gelé par un ''pipeline stall'', une bulle de pipeline. Une autre solution fusionne les défauts de cache secondaires avec le défaut de cache primaire : tout cela ne correspond qu'à un seul défaut de cache pour lui. ===Les MSHR simples=== Un cache non-bloquant à '''MSHR simple''' contient juste plusieurs MSHR qui mémorisent juste un numéro de ligne de cache, un bit de validité, le champ de lecture/écriture, et l'adresse exacte de lecture/écriture. Avec cette organisation, il est possible d'avoir plusieurs défauts de cache séparés, mais à la condition que chaque défaut accède à une ligne de cache différente. Deux accès simultanés à une même ligne de cache ne sont pas possibles, les défauts de cache secondaires ne sont pas autorisés. Ainsi, chaque défaut de cache se voit attribuer son propre MSHR, chacun contient un numéro de ligne de cache différent. Pour comprendre pourquoi c'est impossible de gérer les défauts secondaires, il faut regarder le champ de lecture/écriture. Si on veut effectuer plusieurs écritures consécutives à la même adresse, le MSHR n'aura pas de quoi mémoriser les deux données à écrire. Il pourra mémoriser la première donnée à écrire, pas la seconde. Même chose lors d'une lecture : le champ lecture/écriture peut mémorisr la destination de la première lecture, pas de la seconde. L'avantage est que la MHA n'a besoin que des MSHR et de quelques circuits annexes. Les autres solutions rajoutent des circuits annexes pour gérer les défauts de cache secondaires, qui utilisent beaucoup de circuits. Le cout en circuit est donc élevé, mais le gain en performance est là. Passons maintenant aux caches non-bloquants qui autorisent les défauts de cache secondaire. La solution la plus simple consiste à utiliser ===Les MSHR adressés implicitement=== Avec les '''MSHR adressés implicitement''', il est possible de fusionner plusieurs accès mémoire à une même ligne de cache, mais sous une condition très importante : ces accès lisent/écrivent des mots mémoire différents. Par exemple, imaginons qu'une ligne de cache contienne 8 mots mémoire de 64 bits. Si un premier accès mémoire lit le mot mémoire numéro 7 (dernier mot mémoire de la ligne), et le second accès le mot mémoire numéro 3, alors la fusion est possible. Mais si deux accès mémoire veulent lire/écrire le mot mémoire numéro 7, alors la fusion n'est pas possible et le processeur se bloque, un ''pipeline stall'' survient. Un MSHR adressé implicitement est un MSHR simple, qui contient naturellement un numéro de ligne de cache (tag) et un bit de validité, sauf que le champ de lecture/écriture est dupliqué. Les différents champs lecture/écriture d'un MSHR sont regroupés dans une mémoire RAM/cache qui contient autant d'entrées qu'il y a de mot mémoire dans une ligne de cache. Chaque entrée de la table est associée à un mot mémoire de la ligne de cache et stocke des informations sur celui-ci. Une entrée mémorise, au minimum : * Un champ de lecture/écriture qui contient soit la destination de la lecture, soit la donnée à écrire. * Un bit R/W permettant d'interpréter correctement le champ de lecture/écriture. * Un bit de validité pour chaque entrée de la table, qui dit si un défaut de cache antérieur accède déjà à ce mot mémoire. La fusion de deux défauts de cache est ainsi assez simple. Un défaut de cache primaire/secondaire configure l'entrée associée au mot mémoire lu/écrit. Si un défaut secondaire ultérieur lit/écrit un mot mémoire différent (dont le bit de validité de l'entrée dans la table est à zéro), pas de conflit : il configure une autre entrée, vide. Mais s'il lit/écrit un mot mémoire pour lequel l'entrée de la table est occupée, il y a conflit, le processeur est bloqué par un ''pipeline stall'', une bulle de pipeline. Avec cette organisation, le nombre de MSHR indique combien de lignes de cache peuvent être lues en même temps depuis la mémoire. Quant au nombre d'entrées par MSHR, il détermine combien d'accès mémoires qui ne se recouvrent pas peuvent avoir lieu en même temps. ===Les MSHR adressés explicitement=== Les '''MSHR adressés explicitement''' n'ont pas les contraintes des MSHR adressés implicitement. Avec eux, il est possible d'avoir plusieurs défauts de cache pointant vers la même ligne de cache, mais aussi vers le même mot mémoire. De tels défauts de cache apparaissent sur les processeurs à exécution dans le désordre, mais sont très rares, voire inexistants, sur les processeurs ''in-order''. L'idée est encore que chaque MSHR est associée à une table mémoire qui mémorise des entrées. Cette table n'est autre qu'une mémoire FIFO. Sauf que cette fois-ci, une entrée n'est pas associée à un mot mémoire. Une entrée est associée à un défaut de cache. Une entrée mémorise là encore la destination de la lecture ou la donnée de l'écriture suivi du bit R/W, ainsi que d'un bit de validité par entrée, mais aussi : la position du mot mémoire lu dans la ligne de cache. C'est cette dernière information qui n'était pas présente dans les MSHR adressés implicitement. De plus, le nombre d'entrée par MSHR n'est pas égal au nombre de mots mémoires dans une ligne de cache, mais peut être arbitrairement grand. L'avantage d'un tel cache est qu'il est capable de traité les défauts secondaires concernant un même mot mémoire, et ce, car les accès à la ligne de cache concernée se produisent dans l'ordre de génération des défauts (la table mémoire est une FIFO). ===Les MSHRs inversés=== Généralement, plus on veut supporter de défauts de cache, plus le nombre de MSHR et d'entrées augmente. Mais au-delà d'un certain nombre d'entrées et de MSHR, les MSHR adressés implicitement et explicitement ont tendance à bouffer un peu trop de circuits. Utiliser une organisation un peu moins gourmande en circuits est donc une nécessité. Cette organisation plus économe se base sur des MSHR inversés. Les MSHR inversés ne contiennent qu'une seule entrée, en quelque sorte : au lieu d’utiliser n MSHR de m entrées chacun, on va utiliser n × m MSHR inversés. La différence, c'est que plusieurs MSHR peuvent contenir un tag identique, contrairement aux MSHR adressés implicitement et explicitement. Lorsqu'un défaut de cache a lieu, chaque MSHR est vérifié. Si jamais aucun MSHR ne contient de tag identique à celui utilisé par le défaut, un MSHR vide est choisi pour stocker ce défaut, et une requête de lecture en mémoire est lancée. Dans le cas contraire, un MSHR est réservé au défaut de cache, mais la requête n'est pas lancée. Quand la donnée est disponible, les MSHR correspondant à la ligne qui vient d'être chargée vont être utilisés un par un pour résoudre les défauts de cache en attente. ===Les MSHR intégrés au cache=== Certains chercheurs ont remarqué que pendant qu'une ligne de cache est en train de subir un défaut de cache, celle-ci reste inutilisée, et son contenu est destiné à être perdu une fois le défaut de cache résolu. Ils se sont dit que, plutôt que d'utiliser des MSHR séparés, il vaudrait mieux utiliser la ligne de cache pour stocker les informations sur les défauts de cache en attente dans cette ligne de cache. Pour éviter tout problème, il faut rajouter un bit dans les bits de contrôle de la ligne de cache, qui sert à indiquer que la ligne de cache est occupée : un défaut de cache a eu lieu dans cette ligne, et elle stocke donc des informations pour résoudre les défauts de cache. ==L'entrelacement mémoire== Nous venons de voir comment une mémoire cache peut gérer plusieurs accès mémoire simultanés. Il se trouve que ce genre d'optimisation dépasse largement le cadre du cache. Les mémoires RAM ne sont pas en reste, elles aussi ! Il est possible de pipeliner l'accès à une mémoire RAM, en utilisant des techniques dites d''''entrelacement'''. Précisons cependant que cette optimisation n'est pas utilisée sur les mémoires SDRAM ou DDR modernes, du moins pas sans modifications majeures. Mais divers ordinateurs assez anciens, dont des superordinateurs, ont utilisé cette technique. La technique demande d'utiliser plusieurs mémoires séparées. Sur les anciens ordinateurs, les mémoires en question sont des chips mémoire, à savoir des circuits intégrés de DRAM. Pour faire plus général, nous allons utiliser le terme de '''banques''', ou encore de bancs mémoire. La différence est qu'il existe des mémoires multi-banque, qui regroupent plusieurs banques indépendantes dans un seul boitier, dans un seul chip mémoire. En clair, de telles mémoires regroupent plusieurs sous-mémoires dans un seul circuit intégré. Il est aussi possible d'utiliser plusieurs chips mémoire séparés, chacun ayant une banque. Reste à combiner ces banques pour former un pseudo-pipeline. ===Utiliser plusieurs banques sans entrelacement=== Sans optimisation particulière, les adresses sont réparties dans les banques comme indiqué ci-dessous. Il s'agit de ce qui s'appelle un arrangement horizontal, et nous avions vu celui-ci dans le chapiutre sur les mémoires SDRAM. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Avec cet arrangement, les bits de poids fort de l'adresse sont utilisées pour sélectionner la banque adéquate, et le reste de l'adresse est envoyé sur le bus d'adresse. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Adresse de banque !! Adresse dans la banque |- | Quelques bits de poids fort || Reste de l'adresse |} L'entrelacement change cette répartition, afin d'accélérer les accès mémoire. L'entrelacement classique vise à améliorer les accès à des adresses mémoires consécutives, mais des formes plus évoluées d'entrelacement visent à optimiser des accès mémoire arbitraires. Voyons-les dans l'ordre, du plus simple au plus complexe. ===L'entrelacement classique=== Avec l''''entrelacement classique''', des adresses consécutives sont réparties dans des banques consécutives. Précisons que cette attribution des adresses n'implique absolument pas la mémoire virtuelle ou n'importe quel mécanisme dans le processeur. La répartition décide que telle adresse mémoire va dans telle banque, à telle adresse dans la banque. Elle est donc le fait du contrôleur mémoire, donc en dehors du processeur (les contrôleurs mémoires n’étaient pas encore intégrés dans le processeur à l'époque). [[File:Répartition des adresses dans une mémoire interleaved.png|centre|vignette|upright=1.5|Répartition des adresses dans une mémoire interleaved.]] L'entrelacement simple permet d'accélérer les accès mémoire qui se font à des adresses consécutives. Avec l'entrelacement, chaque accès mémoire tombe dans une banque mémoire différente, ce qui fait qu'on peut démarrer un nouvel accès mémoire à chaque cycle d'horloge. Pendant qu'une banque est occupée par un accès mémoire, on démarre le suivant dans une autre banque, et ainsi de suite. Le tout se termine soit quand on a épuisé toutes les banques libres, soit quand l'accès en rafales se termine. Pas besoin d'attendre que la mémoire ait fini sa lecture/écriture avant de démarrer la lecture/écriture suivante. [[File:Pipemining mémoire.png|centre|vignette|upright=2.5|Pipelining mémoire]] L'animation suivante illustre bien le processus, avec quatres banques. [[File:Interleaving.gif|centre|vignette|upright=2.5|Pipelining mémoire]] L'entrelacement simple utilise les bits de poids faible pour sélectionner la banque, et les bits de poids fort pour la case mémoire. [[File:Adresse mémoire d'une mémoire entrelacée.png|centre|vignette|upright=2|Adresse mémoire d'une mémoire entrelacée]] Précisons que le temps d'accès mémoire ne change pas beaucoup avec l'entrelacement. Par contre, on peut faire plus d'accès mémoire simultanés. On peut démarrer un accès mémoire par cycle d'horloge, mais l'accès en lui-même prend plusieurs cycles. Les mémoires à entrelacement ont donc un débit supérieur aux mémoires qui ne l'utilisent pas. L'entrelacement classique pourrait être utilisé pour accélérer les transferts entre mémoire RAM et cache, qui se font en blocs d'adresses consécutives. Mais de nos jours, il n'est pas utilisé dans cette optique. Il faut dire qu'il est redondant avec le mode rafale des mémoires DRAM. Les deux font globalement la même chose et on voit mal comment utiliser les deux en même temps. Le mode rafale étant plus simple à implémenter, et plus léger niveau utilisation du bus de commande mémoire, il est préféré à l'entrelacement. Par contre, l'entrelacement a eu son heure de gloire sur d'anciens processeurs qui n'avaient pas de mémoire cache, alors qu'ils disposaient d'un pipeline. L'exemple typique est celui des nombreux ''processeurs vectoriels'', que nous n'avons pas encore abordé à ce stade du cours. De tels processeurs avaient un pipeline, une unité de calcul fortement pipelinée, et parfois même de l'exécution dans le désordre et du renommage de registres ! ===L'entrelacement par décalage=== L'entrelacement simple est très utile pour les accès en rafle ou équivalents. Par contre, il existe de rares situations où il n'est pas optimal. Une de ces situations est celle des accès en enjambée (en ''stride''), où l'on accède en série à des adresses sont séparées par N mots mémoires. [[File:Accès par enjambées.png|centre|vignette|upright=2|Accès par enjambées.]] De tels accès surviennent quand un logiciel accède à des structures de données spécifiques, à savoir des tableaux de structures, des matrices, ou d'autres structures de données dans le genre. Un cas classique est celui du parcours d'une matrice colonne par colonne. Les matrices de nombres sont mémorisées ligne par ligne, ce qui fait que parcourir une colonne demande de passer d'une ligne à la suivante et de faire des grands sauts en mémoire RAM, mais tous espacés par la même distance. Avec l'entrelacement simple, les accès en enjambée sont moins performants que les accès à des adresses consécutives, mais cela ne fonctionne pas trop mal. Le pire cas est celui où l'on a N banques et où les données sont justement placées toutes les N adresses. Des accès consécutifs vont tous tomber dans la même banque, on ne peut plus accéder à des banques différentes en parallèle. Mais en dehors de ce cas, on voit une amélioration par rapport à la situation sans entrelacement. Pour obtenir des performances maximales pour les accès en enjambées, il faut répartir les mots mémoires dans la mémoire autrement. L'organisation idéale est la suivante. : Dans les explications qui vont suivre, la variable N représente le nombre de banques, qui sont numérotées de 0 à N-1. On commence par organiser les N premières adresses comme une mémoire entrelacée simple : l'adresse 0 correspond à la banque 0, l'adresse 1 à la banque 1, etc. Pour le bloc suivant, on décale tout d'une adresse, à savoir qu'on commence à remplir les banques à partir de la seconde, non de la première. Une fois la fin du bloc atteinte, on finit de remplir le bloc en repartant du début du bloc. Le troisième bloc d'adresse subit le même traitement, sauf qu'on commence à remplir à partir de la seconde banque. Et on poursuit l’assignation des adresses en décalant d'un cran en plus à chaque bloc. Ainsi, chaque bloc verra ses adresses décalées d'un cran en plus comparé au bloc précédent. Si jamais le décalage dépasse la fin d'un bloc, alors on reprend au début. [[File:Mémoire interleaved par décalage.png|centre|vignette|upright=1.5|Mémoire entrelacée par décalage.]] En faisant cela, on remarque que les banques situées à N adresses d'intervalle sont différentes. Dans l'exemple du dessus, nous avons ajouté un décalage de 1 à chaque nouveau bloc à remplir. Mais on aurait tout aussi bien pu prendre un décalage de 2, 3, etc. Dans tous les cas, on obtient un entrelacement par décalage. Ce décalage est appelé le pas d'entrelacement, noté P. Le calcul de l'adresse à envoyer à la banque, ainsi que la banque à sélectionner se fait en utilisant les formules suivantes : * adresse à envoyer à la banque = adresse totale / N ; * numéro de la banque = (adresse + décalage) modulo N ; * décalage = (adresse totale * P) mod N. Avec cet entrelacement par décalage, on peut prouver que la bande passante maximale est atteinte si le nombre de banques est un nombre premier. Seulement, utiliser un nombre de banques premier peut créer des trous dans la mémoire, des mots mémoires inadressables. Malgré ce défaut, la technique a été utilisée sur quelques ordinateurs, avec l'exemple notable du superordinateur ''Burroughs Scientific Processor''. Pour éviter cela, il y a plusieurs solutions. Par exemple, on peut faire en sorte que N et la taille d'une banque soient premiers entre eux : ils ne doivent pas avoir de diviseur commun. Mais en pratique, elles n'ont pas vraiment été implémentées dans une vraie machine, et sont restées à l'état de recherche, aussi je les passe sous silence. ===L'entrelacement pseudo-aléatoire=== Une dernière méthode de répartition consiste à répartir les adresses dans les banques de manière "pseudo-aléatoire". L'idée exacte est de faire passer l'adresse dans une '''fonction de hachage''', plus ou moins complexe. Pour rappel, une fonction de hachage prend une entrée de grande taille et fournit en sortie un résultat de petite taille. Idéalement, elles doivent donner des résultats aussi différents que possible pour des entrées similaires, histoire de simuler une sorte de pseudo-aléatoire. Ici, une adresse est transformée en un numéro de banque. Une fonction de hachage simple ne fait que permuter des bits de l'adresse pour obtenir son résultat. Elle ne fait qu'échanger des bits de place avant de couper l'adresse en deux morceaux : un pour la sélection de la banque, et un autre pour la sélection de l'adresse dans la banque. Cette permutation est fixe, et ne change pas suivant l'adresse. Des fonctions de hachage plus complexes font des XOR bit à bit entre certains bits de l'adresse. ==Les contrôleurs mémoires SDRAM/DDR optimisés== Après avoir vu le cas des mémoires RAM, nous allons nous concentrer sur le cas particulier des mémoires SDRAM. Les mémoires SDRAM et DDR modernes sont capables de gérer plusieurs accès simultanés à la mémoire RAM, et leurs contrôleurs mémoire en font tout autant. C'est une différence majeure avec les mémoires asynchrones FPM/EDO, qui n'acceptaient qu'un seul accès mémoire à la fois. Leur contrôleur mémoire n'acceptait qu'un seul accès mémoire à la fois, c'était un contrôleur mémoire bloquant. Les contrôleurs mémoires des SDRAM sont eux non-bloquants et peuvent encaisser une dizaine d'accès mémoire à la fois. Peu de choses sont connues sur les contrôleurs de SDRAM/DDR modernes, les fabricants ne donnant que peu de détails dessus. Les rares simulateurs qui tentent de décrire leur fonctionnement, comme DRAM SIM I et II, sont particulièrement simples et ne vont pas dans le détail. Néanmoins, le peu qu'on sait est tout de même instructif. ===Rappel sur les SDRAM : tampon de ligne et commandes=== Les mémoires SDRAM sont des mémoires à tampon de ligne. Elles font un accès mémoire en deux étapes. La première étape recopie une ligne de N * 64 bits dans un tampon interne à la SDRAM. La seconde étape sélectionne une colonne, à savoir une donnée de 64 bits, qui est soit envoyée sur le bus de données pour une lecture, soit modifiée par une écriture. [[File:Mémoire à tampon de ligne.png|centre|vignette|upright=2|Mémoire à tampon de ligne]] Les accès mémoire sont traduits par un séquenceur mémoire en une série de commandes mémoires, qui sont séparées par des délais mémoire de quelques cycles d'horloge. Les délais sont très précis, et sont à respecter à la lettre. Une lecture ou une écriture se fait en maximum trois commandes : une commande PRECHARGE qui ferme la ligne précédemment utilisée, une commande ACT qui précise l'adresse de la ligne, et une commande READ ou WRITE qui précise l'adresse de la colonne et éventuellement la donnée à écrire. : Pour être plus précis, la commande PRECHARGE précharge les lignes de bits du plan mémoire à une tension particulière, ce qui les vide de leur contenu. Mais c'est un détail sans importance pour ce qui va suivre. Les SDRAM permettent de se passer de la première étape si des accès consécutifs se font dans la même ligne. Une fois activée, la ligne reste ouverte et on peut accéder plusieurs fois de suite dedans. On a alors juste à préciser l'adresse de colonne dedans. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | bgcolor="#FFA0FF" | PRECHARGE || bgcolor="#FFA0FF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || bgcolor="#A0FFFF" | READ (2) || bgcolor="#A0FFFF" | READ (3) || || || bgcolor="#A0FFFF" | READ (4) || bgcolor="#A0FFFF" | READ (5) || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | Donnée READ 1 || ||bgcolor="#A0FFFF" | Donnée READ 2 | bgcolor="#A0FFFF" | Donnée READ 3 || || || bgcolor="#A0FFFF" | Donnée READ 4 || bgcolor="#A0FFFF" | Donnée READ 5 |} Les SDRAM sont parfois capables de démarrer une commande avant que la précédente soit terminée. Mais le respect des délais mémoire est très limitant, ce qui fait qu'on ne peut pas parler de réel pipeline, comme c'est le cas sur les processeurs. Parlons plutot de '''commandes anticipées'''. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | || bgcolor="#A0FFFF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || || || bgcolor="#FFA0FF" | READ (2) || || || || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 | bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 | |} Un dernier point est que les chips mémoires de SDRAM sont composés de plusieurs banques, chacune étant une sorte de mini-mémoire miniature. Chaque banque a son propre décodeur, son propre tampon de ligne, ses propres multiplexeurs de colonne, sa logique de rafraichissement mémoire, etc. C'est comme si une SDRAM regroupait plusieurs mémoires séparées dans un même circuit intégré. [[File:Arrangement vertical.jpg|centre|vignette|upright=2.5|Mémoire multi-banques.]] ===La mise en attente des accès mémoire=== Un contrôleur de SDRAM peut accepter plusieurs accès mémoire en même temps. Mais cela ne signifie pas que le contrôleur sera prêt à les traiter immédiatement. Pour éviter tout problème de disponibilité, le contrôleur met en attente les accès mémoire que le processeur lui envoie, pour les exécuter dès que possible. Les accès mémoire sont mis en attente dans une mémoire FIFO, histoire de les exécuter dans leur ordre d'arrivée, notamment pour renvoyer les lectures dans l'ordre demandé. Il y a aussi une mémoire FIFO pour les données à écrire et une FIFO pour les données lues. Cette dernière sert au cas où le cache ne soit pas disponible quand on lui envoie la donnée lue. [[File:Module d'interface avec la mémoire.png|centre|vignette|upright=2.5|Contrôleur mémoire avec mise en attente des requêtes processeur et autres optimisations.]] Des optimisations sont possibles dès la mémoire FIFO. Par exemple, on peut regrouper plusieurs accès à des données consécutives en un seul accès en rafale. Le contrôleur mémoire analyse les requêtes mises en attente et détecte si certaines se font à des adresses consécutives. Si c'est le cas, il fusionne ces requêtes en une lecture/écriture en rafale. Une telle optimisation est appelée la '''combinaison de lecture''' pour les lectures, et la '''combinaison d'écriture''' pour les écritures. Une autre optimisation possible est le réacheminement lecture sur écriture (''Store to load forwarding''). Il s'agit d'un équivalent de l’optimisation utilisée dans le cadre de la désambiguïsation mémoire. Quand une lecture demande à accéder à une donnée écrite récemment, il se peut que l'écriture ait été mise en attente dans le contrôleur mémoire. Dans ce cas, le contrôleur mémoire peut renvoyer la donnée directement depuis ses mémoires FIFOs, sans faire d'accès mémoire en lecture. Les deux optimisations précédentes ne paraissent pas payer de mine, mais elles deviennent plus compréhensible quand on sait que certains contrôleurs mémoire peuvent mettre en attente beaucoup de données. Par exemple, l'''Intel E8870 - Scalable Memory Controller'' peut mettre en attente 64 lignes de cache dans la mémoire FIFO, soit 8 kibioctets de RAM ! Deux pages mémoire si la mémoire virtuelle utilise des pages de 4 kibioctets ! ===Le ré-ordonnancement des commandes mémoires=== Un contrôleur mémoire moderne est capable de changer l'ordre des requêtes mémoires, pour gagner en performances. En clair : il peut faire l'équivalent mémoire de l'exécution dans le désordre des processeurs haute performance. Une différence majeure est que le contrôleur mémoire ne remet pas les accès mémoire dans l'ordre avant de les envoyer au processeur. C'est le processeur qui remet en ordre les accès mémoire, dans sa ''load queue''. Pour cela, les requêtes mémoire sont "numérotées", à savoir qu'on leur attribue un identifiant binaire. Le contrôleur mémoire exécute les lectures/écritures dans le désordre, mais garde la trace de leur identifiant. Il sait dans quel ordre il exécute les accès mémoire et connait leur latence. Ce qui fait qu'il sait que la donnée lue à tel instant est associée à la requête mémoire numéro X. Pour les lectures, il renvoie la donnée lue avec l'identifiant. Le processeur sait alors à quelle lecture la donnée lue correspond et il se débrouille en interne pour gérer la situation. En clair : le processeur voit les accès mémoire dans le désordre, c'est lui qui les remet en ordre. Maintenant que ces précisions sont faites, posons cette question : quel est l'intérêt de faire les accès mémoire dans le désordre ? Le ré-ordonnancement est surtout utile si tous les accès mémoire atterrissent dans la même banque et dans la même ligne. Elle marche si plusieurs accès à une même ligne ne sont pas consécutifs, et qu'il y a des accès entre les deux. Après ré-ordonnancement, ces accès mémoire sont exécutés l'un à la suite de l'autre. Pour rendre le tout plus concret, voici un exemple. Imaginez que l'on sait les 3 accès mémoire suivants : * Une lecture ligne A ; * Une écriture ligne B ; * Une lecture ligne A. Sans réordonnancent, on doit émettre deux commandes PRECHARGE : une quand on passe de la ligne A à la ligne B, et une autre pour le passage inverse. pour éviter cela, le contrôleur mémoire peut retarder l'écriture, ou au contraire avancer la seconde lecture. le résultat est alors le suivant : * Lecture ligne A ; * Lecture ligne A ; * Une écriture ligne B. On a donc deux accès consécutifs à la même ligne, suivi par une écriture dans une autre ligne. La technique marche parce que l'on a un mix adéquat de lectures et d'écritures. Mais il existe des possibilités de réorganisation autres qui ne sont pas valides. Il faut par exemple prendre garde à éviter d'intervertir lectures et écritures à une même adresse, sous peine de problèmes. Encore une fois, il faut faire attention aux dépendances de données, encore une fois ! Ici des dépendances mémoires, mais les règles sont les mêmes. Le contrôleur mémoire teste les dépendances mémoires avant d'envoyer des commandes à la mémoire DDR/SDRAM. Il a existé des contrôleurs mémoires du temps des vieilles DDR qui implémentaient un ordonnancement très simples. Prenons l'exemple des coprocesseurs IO 81341 et 81342, qui étaient en réalité des ''chipsets'' intégrant un contrôleur SDRAM. Le ''chipset'' avait 5 ports : un pour les processeurs, un pour le pont sud (''southbridge'') et trois pour des canaux DMA. Pour le premier, il n'y avait pas de réordonnancement. Pour les autres, les lectures se font dans l'ordre, les écritures se font dans l'ordre, mais les écritures peuvent passer avant une lecture non-dépendante. L'implémentation était assez simple : le séquenceur était précédé par plusieurs mémoires FIFO. Le port processeur avait une seule mémoire FIFO, ce qui conservait l'ordre d'exécution des commandes mémoire. Pour les autres ports, le processeur avait vraisemblablement des files séparées pour les lectures et écritures. Le séquenceur mémoire vérifiait les dépendances mémoire de type RAW et autres. [[File:Double file d'attente pour les lectures et écritures.png|centre|vignette|upright=2.5|Double file d'attente pour les lectures et écritures]] L'implémentation était vraiment simple : elle n’exploitait même pas la présence de plusieurs banques ! De nos jours, les contrôleurs mémoires sont beaucoup plus évolués et sont capables d'exécution dans le désordre bien plus poussée. Les contrôleurs mémoires remplacent la FIFO par une sorte de fenêtre d'instruction dans laquelle ils poichent les accès mémoire selon leurs besoins. ===Les optimisations liées à la présence de plusieurs banques=== Les optimisations précédentes sont décuplées par la présence de plusieurs banques dans la mémoire SDRAM. Il est en effet possible de faire plusieurs accès mémoire en même temps, dans des banques différentes. Il est possible de lancer un accès mémoire dans deux banques en même temps, par exemple. La seule contrainte est que la SDRAM est limitée à 4 banques actives en même temps. {|class="wikitable" |+ Pipelining basique sur les SDRAM |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 |- ! Banque Numéro 1 | || || || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || || |- ! Banque Numéro 2 | || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || |- ! Banque Numéro 3 | || || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || |} L'implémentation n'a rien de vraiment compliqué. Il suffit d'utiliser une mémoire FIFO par banque. Ainsi, les accès dans une même banque se feront en série, dans l'ordre d'arrivée, mais des accès à des banques différentes se feront dans le désordre. Cela ne pose pas de problème car les accès se font à des adresses différentes, ce qui fait qu'il n'y a pas de conflits majeurs. [[File:Gestion parallèle des banques.png|centre|vignette|upright=2.5|Gestion parallèle des banques.]] ==L'entrelacement sur les mémoires SDRAM== L''''entrelacement''' fonctionne sur les mémoires SDRAM, mais il doit être fortement modifié. Concrètement, tout ce qui a étré dit plus est inapplicable pour les SDRAM, du fait de la présence du tampon de ligne. Des adresses consécutives atterrissent déjà dans la même ligne, ce qui fait qu'on n'a pas besoin d'entrelacement au niveau d'une ligne. Et ne parlons pas de ce la présence de rangées et de canaux mémoire ! Cependant, il peut y avoir un entrelacement lié à la présence des banques SDRAM. L'entrelacement est géré juste avant le séquenceur mémoire. Le '''circuit d'entrelacement''' intervertit certains bits de l'adresse lors des accès mémoires. On trouve aussi des mémoires FIFOs pour mettre en attente les requêtes mémoire provenant du processeur. Elles permettent de recevoir plusieurs accès mémoire consécutifs. Si vous vous demandez comment plusieurs accès mémoire consécutifs peuvent avoir lieu, sachez que c'est une bonne question. Pour le moment, nous allons dire que ces accès proviennent du processeur ou de la mémoire cache, sans expliquer comment. Le contrôleur mémoire reçoit une série d'accès mémoire assez rapprochés, qu'il doit exécuter au mieux. [[File:Controleur mémoire d'une SDRAM avec entrelacement.png|centre|vignette|upright=2.5|Contrôleur mémoire d'une SDRAM avec entrelacement.]] Pour gérer l'entrelacement, le contrôleur de SDRAM prend en entrée l'adresse envoyée par le processeur, et la découpe en plusieurs champs : un pour sélectionner la banque adéquate, un autre pour la ligne, un autre pour la colonne, un autre pour la rangée. Une solution simple désactive l'entrelacement. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de banque || Adresse de ligne || Adresse de colonne |} Si cette solution fonctionne bien si les accès mémoire sont assez aléatoires, en pratique, mieux vaut utiliser l'entrelacement. ===L'entrelacement de banques/lignes=== Une première méthode d'entrelacement est l''''entrelacement de banques'''. Elle répartit deux lignes consécutives dans deux banques différentes. Pour comprendre l'idée, prenons un exemple. Imaginons une mémoire avec deux banques et 4 lignes. Imaginons qu'on parcoure/balaye la mémoire RAM en partant des adresses basses. Sans entrelacement de ligne, les accès se feront comme suit, de gauche à droite : {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Banque numéro 1 !! colspan="4" | Banque numéro 2 |- | Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 || Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 |} Avec l'entrelacement de banques, les accès se feront comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 |- | Ligne 1 || Ligne 1 || Ligne 2 || Ligne 2 || Ligne 3 || Ligne 3 || Ligne 4 || Ligne 4 |} Avec N banques, la première ligne ira dans la première banque, la seconde ligne dans la seconde, et ainsi de suite jusqu'à la énième banque qui va dans la banque numéro N. Et la ligne N+1 repart dans la première banque. L'avantage est que passer d'une ligne à la suivante est assez rapide. Au lieu de devoir fermer la première ligne et ouvrir la suivante on ouvrira directement la ligne suivante dans une banque différente, ce qui sera plus rapide. La commande PRECHARGE pourra être envoyée à la seconde banque en avance. Pour cela, l'adresse est découpée comme suit. {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne (précise à l'octet) |} ===L'entrelacement de rangées=== Il est aussi possible de faire la même chose, mais avec les rangées. Pour simplifier fortement, une rangée est simplement un chip mémoire. En réalité, les rangées ne sont pas des chip mémoire, mais un ensemble de chips mémoire regroupés ensemble histoire d'atteindre les 64 bits du bus de données. Par exemple, une rangée peut combiner 8 chips mémoire avec un bus de données de 8 bits chacun pour obtenir les 64 bits du bus de données d'une SDRAM. Mais nous passons ce détail sous silence dans les explications qui vont suivre, par souci de simplification. Pour faire comprendre l'entrelacement de rangée, le mieux est d'utiliser un exemple, le même que précédemment. Sans entrelacement de rangée, on change de chip mémoire une fois qu'on a balayé toutes ses adresses. {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Rangée numéro 1 !! colspan="4" | Rangée numéro 2 |- | Banque 1 || Banque 2 || Banque 3 || Banque 4 || Banque 1 || Banque 2 || Banque 3 || Banque 4 |} Avec l'entrelacement de ligne, on change de rangée dès qu'on change de banque/ligne. {|class="wikitable" |+ Adresse mémoire |- ! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 |- | Banque 1 || Banque 1 || Banque 2 || Banque 2 || Banque 3 || Banque 3 || Banque 4 || Banque 4 |} En clair, quand on a épuisé toutes les banques dans une même rangée, on passe à la rangée suivante au lieu de rester dans la même rangée. Notons que dans l'exemple précédent, on a combiné l'entrelacement de rangée et de banque, mais on aurait pu utiliser l'entrelacement de rangée seul. Mais ce n'est pas le cas le plus courant en pratique. Toujours est-il qu'en combinant entrelacement de rangée et de ligne, le découpage de l'adresse est le suivant : {|class="wikitable" |+ Adresse mémoire |- | Adresse de ligne || Numéro de rangée || Adresse de banque || Adresse de colonne (précise à l'octet) |} D'autres méthodes d'entrelacement sont possibles, mais elles sont plus complexes. Chaque contrôleur mémoire fait un peu à sa sauce. Ils permettent en général de choisir entre plusieurs entrelacements. Au minimum, ils permettent de désactiver l'entrelacement, d'activer un entrelacement de ligne, et d'autres entrelacements plus complexes, dont certains sont propriétaires et tenus secrets par le fabricant. ===L'entrelacement avec le ''dual channel''=== [[File:Dual channel slots.jpg|vignette|Slots mémoires avec ''dual channel''.]] L'usage du ''dual channel'' complique encore l'entrelacement. Là encore, il y a deux grandes solutions : avec et sans entrelacement des canaux mémoire. Rappelons le principe : deux barrettes de RAM sont accédées en parallèle. Et pour cela, il faut utiliser l'entrelacement. Typiquement, chaque barrette mémoire fournit 64 bits, ce qui fait que l'on peut accéder à 128 bits d'un seul coup, par exemple avec un accès en rafale. Sans ''dual channel'', la première barrette correspond à la moitié haute de la RAM, la seconde barrette correspond à la moitié basse. Avec ''dual channel'', une forme spécifique d'entrelacement est activée. Concrétement, deux blocs de 64 bits sont placés dans des canaux mémoire séparés. Idem avec du triple ou quadruple canal, mais c'est alors trois ou quatre blocs de 64 bits qui sont dispersés dans des canaux consécutifs. Le découpage de l'adresse est alors le suivant : {|class="wikitable" |+ Adresse mémoire |- ! colspan="6" | Sans entrelacement |- | Numéro de canal/barrette || Reste de l'adresse || Position de l'octet dans un bloc de 64 bits |- | colspan="6" | |- ! colspan="6" | Avec entrelacement |- | Reste de l'adresse || Numéro de canal/barrette || Position de l'octet dans un bloc de 64 bits |} [[File:Décodage d'adresse avec dual channel.png|centre|vignette|upright=2|Décodage d'adresse avec dual channel.]] Les contrôleurs de SDRAM précédents sont assez basiques et ne représentent pas les contrôleurs les plus évolués. Ils étaient utilisés sur les anciens PC, à une époque où ils étaient encore sur la carte mère du processeur. Mais de nos jours, le contrôleur mémoire est intégré au processeur. Et il incorpore de nombreuses optimisations afin de gagner en performances. Malheureusement, ces optimisations sont elles-mêmes dépendantes des optimisations intégrées dans le processeur, ce qui fait qu'on ne peut pas en parler ici. Elles demandent que le contrôleur mémoire reçoive plusieurs accès mémoire simultanés, ce qui n'a aucun sens à ce stade du cours. Aussi, nous allons les passer sous silence pour le moment. Nous les verrons dans le chapitre sur le parallélisme mémoire. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=La désambiguïsation mémoire | prevText=La désambiguïsation mémoire | next=Les processeurs superscalaires | nextText=Les processeurs superscalaires }} </noinclude> gho957okoaj0e2rx5og6njuwxqizhmx 764732 764731 2026-04-23T22:44:37Z Mewtow 31375 /* Le ré-ordonnancement des commandes mémoires */ 764732 wikitext text/x-wiki Dans ce chapitre, nous allons voir les techniques qui permettent de gérer plusieurs accès mémoire simultanés directement au niveau du cache ou de la mémoire RAM. Par plusieurs accès mémoire simultanés, vous pensez sans doute à l'usage de cache multiports, voire de mémoires RAM multiport. C'est en effet une solution, mais ce n'est clairement pas la seule. Par exemple, il est possible de pipeliner l'accès au cache, voire à la mémoire RAM. Il existe de nombreuses techniques de '''parallélisme mémoire''', et nous allons les voir dans ce chapitre. Pour donner un exemple, il est possible de pipeliner les accès mémoire. Il est possible de pipeliner l'accès au cache et/ou l'accès à la mémoire, et nous verrons comment faire dans ce chapitre. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, qu'ils aient ou non un pipeline dynamique. Par contre, pipeliner la mémoire est une technique ancienne, aujourd'hui peu utilisée. Elle était utilisée sur des très vieux ordinateurs, qui avaient un pipeline mais pas de mémoire cache. Mais de nos jours, les mémoires SDRAM et DDR ne sont pas adaptées pour ça, il est très difficile de les pipeliner correctement. Le parallélisme mémoire est utile aussi bien sur des processeurs à émission dans l'ordre que dans le désordre. Par exemple, un processeur ''in-order'' avec des lectures non-bloquantes peut e, profiter. Il peut techniquement lancer une seconde lecture, après avoir rencontré une première lecture non-bloquante. En général, le nombre de lectures consécutives est cependant limité à moins d'une dizaine. À l'opposé, les processeurs sans lectures non-bloquantes profitent pas du parallélisme mémoire. À la rigueur, ils peuvent en profiter s'ils intègrent des techniques de préchargement. Pendant que le processeur exécute une lecture/écriture, il peut précharger une autre donnée en parallèle. Inutile de dire que sans pipeline processeur, le parallélisme mémoire ne sert pas à grand-chose. ==Le ''line fill buffer'' et les techniques associées== Lors d'un défaut de cache, le processeur doit attendre que toute la ligne de cache soit chargée avant d'être utilisable. Or, la taille d'une ligne de cache est supérieure à la largeur du bus mémoire, ce qui fait qu'une ligne de cache est chargée en plusieurs fois, morceaux par morceaux, mot mémoire par mot mémoire. Le chargement peut se faire directement dans le cache, mais ce n'est pas une solution très pratique. À la place, beaucoup de processeurs ajoutent une mémoire tampon entre la RAM et le cache, appelée le '''tampon de remplissage de ligne''' (''line-fill buffer'') dans les processeurs modernes. Lors d'un défaut de cache, le processeur charge la donnée de la RAM dans le tampon de remplissage de ligne, mot mémoire par mot mémoire. Une fois plein, le tampon de remplissage de ligne est recopié dans la ligne de cache. Le tampon de remplissage de ligne contient une ligne de cache, si ce n'est que certains bits de contrôle ne sont pas présents. Le tampon de remplissage de ligne contient un bit de validité pour chaque mot mémoire de la ligne de cache, qui indiquent si le mot mémoire a été chargé. Par exemple, prenons un processeur 64 bits, qui gère donc des mots mémoire de 8 octets, avec des lignes de cache 256 octets/32 mots mémoire. Le tampon de remplissage de ligne contiendra 32 bits de validité. Si le processeur a chargé les 6 premiers mots mémoire, les 6 premiers bits de validité seront mis à 1, les autres seront encore à zéro. Il faut noter que la disponibilité de la ligne complète se détermine assez facilement en faisant un ET logique entre tous les bits de validité. Le processeur sait ainsi quand la ligne de cache est disponible entièrement et donc quand la transférer dans le cache. La même chose existe avec une hiérarchie de cache, sauf que l'on trouve une mémoire tampon entre chaque niveau de cache. Il y a un tampon de remplissage de ligne entre le cache L1 et le cache L2, entre le cache L2 et le L3, etc. Si le cache ne gère qu'un seul défaut de cache à la fois, le ''fill line buffer'' est une mémoire très simple, qui ne mémorise qu'une seule ligne de cache. Et cela vaut aussi bien pour un cache bloquant que non-bloquant. Mais sur les caches capables de gérer plusieurs défauts simultanés, le tampon de remplissage de ligne est une mémoire de type FIFO ou LIFO, capable de mémoriser plusieurs lignes de cache. Notons que le tampon de remplissage de ligne est très utile pour implémenter certaines techniques, par exemple le contournement du cache. Nous avions vu dans le chapitre sur le cache que certains accès mémoire doivent contourner le cache, pour des raisons de cohérence des caches. C'est notamment nécessaire pour accéder aux périphériques, mais c'est aussi utile pour des raisons de performances dans des cas très spécifiques. Les accès qui contournent le cache se font directement dans le tampon de remplissage de ligne : le processeur écrit ou lit les données depuis ce tampon de remplissage de ligne, sans accéder au cache. ===L'''early restart'' et le ''critical word load''=== La présence du ''line buffer'' permet une optimisation assez intéressante, qui permet de réduire la latence des défauts de cache. L'optimisation consiste à lire un mot mémoire dans le tampon de remplissage de ligne, même si la ligne de cache complète n'a pas encore été chargée. Il existe deux manières de faire cela, qui portent les noms d'''early restart'' et de ''critical word load''. La première est la version la plus simple, la seconde est plus complexe mais plus performante. Avec la technique d''''''early restart''''', la ligne de cache est chargée normalement, en partant de son premier mot mémoire. Dès que le mot mémoire lu/écrit par le processeur est copié dans le ''line fill buffer'', il est envoyé au processeur immédiatement. Illustrons le tout par un exemple, où une ligne de cache fait 16 mots mémoire. Le processeur effectue une lecture, qui lit le 5ème mot mémoire. Sans ''early restart'', le processeur doit charger les 16 mots mémoire avant de faire la lecture dans le cache. Avec ''early restart'', le processeur reçoit la donnée dès que le 5ème mot mémoire est disponible. Le processeur doit attendre que les 4 premiers mots mémoire soient chargés, puis le 5ème mot mémoire arrive et est envoyé directement au processeur, il est lu directement depuis le ''line fill buffer''. Les 11 mots mémoire suivants sont ensuite chargés dans le cache pendant que le processeur fait des calculs dans son coin. Le ''critical word load'' est une optimisation de la technique précédente où le chargement de la ligne de cache commence directement à la donnée demandée par le processeur. Pour reprendre l'exemple précédent, où le processeur demande le 5ème mot mémoire sur 16, le ''critical word load'' charge le 5ème mot mémoire en premier et l'envoie au processeur, ce qui fait qu'il est chargé très rapidement. Pas besoin d'attendre que le processeur charge les 4 mots mémoire précédents comme avec l'''early restart''. Dans le détail, le processeur charge le 5ème mot mémoire en premier, puis charge les 11 suivants, et termine par les 4 mots mémoire du début. En clair, le ''critical word load'' commence par charger le mot mémoire lu, puis les blocs suivants, avant de revenir au début du bloc pour charger les blocs restants. Ainsi, la donnée demandée par le processeur sera la première disponible. Pour cela, l'organisation du tampon de remplissage de ligne est modifiée de manière à rendre cela possible. Il a une taille égale à une ligne de cache complète, qui contient elle-même plusieurs mots mémoire. Dans le ''line-fill buffer'', chaque mot mémoire est stocké avec un tag, qui indique l'adresse du mot mémoire stocké dans le ''line-fill buffer''. Le ''line-fill buffer'' est donc un cache un peu particulier, qui fonctionne comme un cache du point de vue du processeur, comme une mémoire FIFO pour les transferts avec le cache. Ainsi, un processeur qui veut lire dans le cache après un défaut peut accéder à la donnée directement depuis le tampon de remplissage de ligne, alors que la ligne de cache n'a pas encore été totalement recopiée en mémoire. ==Les caches pipelinés== Il est possible de pipeliner l'accès au cache, ce qui demande juste de rajouter des registres au bon endroits et quelques circuits de contrôle. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, même ceux avec un pipeline dynamique. Et l'implémentation peut se faire de plusieurs manières différentes. Il faut dire que découper une mémoire cache en plusieurs étapes peut se faire de plusieurs manière. Commençons par la plus simple. Beaucoup de processeurs des années 2000 avaient une fréquence peu élevée comparée aux standards d'aujourd'hui, ce qui fait que leur cache avait bien un temps d'accès d'un cycle d'horloge. Mais l'accès au cache se faisait en deux cycles d'horloge : un cycle pour calculer une adresse et un cycle pour l'accès mémoire proprement dit. Et ces deux étapes étaient pipelinées, à savoir que deux micro-opérations mémoire peuvent s'exécuter en même temps : une dans l'étage de calcul d'adresse, une autre dans le cache. L'unité mémoire était donc pipelinée, alors que l'accès au cache ne l'était pas. Les processeurs qui implémentaient cette technique regroupent les micro-architectures K5 et K6 d'AMD, les processeurs Intel de micro-architecture P6 (Pentium 2 et 3) et quelques autres. Il est aussi possible de pipeliner l'accès au cache lui-même. Avec un cache pipeliné, l'accès au cache ne se fait pas en un seul cycle, mais en plusieurs. Cependant, on peut lancer un nouvel accès au cache à chaque cycle d'horloge, comme un processeur pipeliné lance une nouvelle instruction à chaque cycle. L'implémentation est assez simple : il suffit d'ajouter des registres dans le cache. Pour cela, on profite que le cache est composé de plusieurs composants séparés, qui échangent des données dans un ordre bien précis, d'un composant à un autre. De plus, le trajet des informations dans un cache est linéaire, ce qui les rend parfois pour l'usage d'un pipeline. ===Le ''pipelining'' des caches ''direct-mapped''=== Voici ce qui se passe avec un cache directement adressé. Pour rappel ce genre de cache est conçu en combinant une mémoire RAM, généralement une SRAM, avec quelques circuits de comparaison et un MUX. L'accès au cache se fait globalement en deux étapes : la première lit la donnée et le ''tag'' dans la SRAM, et on utilise les deux dans une seconde étape. Nous avions vu il y a quelques chapitre comment pipeliner des mémoires, dans le chapitre sur les mémoires évoluées. Et bien ces méthodes peuvent s'utiliser pour la mémoire RAM interne au cache !L'idée est d'insérer un registre entre la sortie de la RAM et la suite du cache, pour en faire un cache pipéliné, à deux étages. Le premier étage lit dans la SRAM, le second fait le reste. L'implémentation sur les caches associatifs à plusieurs voies est globalement la même à quelques détails près. [[File:Cache directement adressé pipeliné - deux étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - deux étages]] Avec le circuit précédent, il est possible d'aller plus loin, cette fois en pipelinant l'accès à la mémoire RAM interne au cache. Pour comprendre comment, rappelons qu'une mémoire SRAM est composée d'un plan mémoire et d'un décodeur. L'accès à la mémoire demande d'abord que le décodeur fasse son travail pour sélectionner la case mémoire adéquate, puis ensuite la lecture ou écriture a lieu dans cette case mémoire. L'accès se fait donc en deux étapes successives séparées, on a juste à mettre un registre entre les deux. Il suffit donc de mettre un gros registre entre le décodeur et le plan mémoire. [[File:Cache directement adressé pipeliné - trois étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - trois étages]] Et on peut aller encore plus loin en découpant le décodeur en deux circuits séparés. En effet, rappelez-vous le chapitre sur les circuits de sélection : nous avions vu qu'il est possible de créer des décodeurs en assemblant des décodeurs plus petits, contrôlés par un circuit de prédécodage. Et bien on peut encore une ajouter un registre entre ce circuit de prédécodage et les petits décodeurs. : Théoriquement, toute l'adresse est fournie d'un seul coup au cache, la quasi-totalité des processeurs présentent l'adresse complète à un cache pipeliné. Mais le Pentium 4 fait autrement. Il faut noter que les premiers étages manipulent l'indice dans la SRAM, qui est dans les bits de poids faible, alors que les étages ultérieurs manipulent le ''tag'' qui est dans les bits de poids fort. Les concepteurs du Pentium 4 ont alors décidé de présenter les bits de poids faible lors du premier cycle d'accès au cache, puis ceux de poids fort au second cycle. Pour cela, l'ALU fonctionnait à une fréquence double de celle du processeur, tout comme le cache L1. Il n'y avait pas de pipeline proprement dit, mais cela réduisait grandement la latence d'accès au cache. ===Le ''pipelining'' des caches splittés/sériels=== Il est aussi possible de pipeliner un cache dits splittés, aussi appelés à accès sériel. Pour rappel, les caches à accès sériel vérifient si il y a succès ou défaut de cache, avant d'accéder aux lignes de cache en cas de succès. Ils font donc différemment des autres caches, qui accèdent à une ligne de cache, avant de déterminer s'il y a succès ou défaut en lisant le tag de la ligne de cache. Les caches sériels disposent de deux SRAM : une pour les tags des lignes de cache et une pour les données. Ils accèdent à la SRAM pour les tags, avant d’accéder à la SRAM des données en cas de succès de cache. Vu que l'accès se fait en deux étapes, une vérification des tags suivie de la lecture/écriture des données, il est facile à pipeliner. Pipeliner le cache permet de régler le problème des accès au cache L1, et elle est tout le temps utilisé sur les processeurs modernes. Mais que faire en cas de défaut de cache ? ==Les caches non bloquants== Un '''cache bloquant''' est un cache auquel le processeur ne peut pas accéder pendant un défaut de cache. Il faut attendre que la lecture ou écriture en RAM soit terminée avant de pouvoir utiliser de nouveau le cache. Un '''cache non bloquant''' n'a pas ce problème : on peut l'utiliser pendant un défaut de cache. Les caches non bloquants permettent de démarrer une nouvelle lecture ou écriture alors qu'une autre est en cours, ce qui permet d'exécuter plusieurs lectures ou écritures en même temps. ===Les ''Miss Handling Status Registers''=== Lors d'un défaut de cache, la mémoire RAM est consultée pendant le défaut de cache, mais le cache est inutilisé. Un défaut de cache n'utilise pas le cache, ce n'est qu'un accès en mémoire RAM, sur le bus mémoire (ou un accès aux niveaux de cache inférieurs, peu importe). Le cache en lui-même est laissé libre, rien n’empêche d'y accéder, il est en réalité intrinsèquement non-bloquant. Les caches, bloquants comme non-bloquants, sont en réalité composés d'une mémoire cache proprement dite, entourée de circuits qui servent d'interface entre le processeur et le cache lui-même. Et parmi les circuits tout autour, certains gèrent l'accès au cache lors d'un défaut de cache. Ils sont regroupés sous le terme de '''''Miss Handling Architecture''''' (MHA). La différence entre un cache bloquant et un cache non-bloquant est en réalité liée à l'implémentation de la MHA. Les caches bloquants coupent volontairement l'accès au cache lors d'un défaut, car les défauts sont plus simples à gérer ainsi. Le défaut de cache rapatrie une donnée depuis la RAM, qui sera écrite dans le cache. Et il ne faut pas qu'une tentative d'accès à cette donnée ait lieu avant qu'elle ne soit chargée. Mais ce blocage est général et touche tout le cache, alors que seule une ligne de cache est concernée par le défaut de cache. L'idée derrière un cache non-bloquant est que seule la ligne de cache est bloquée, mais les autres sont accessibles. L'idée est alors de mémoriser les lignes de cache concernées par le défaut de cache, afin d'en bloquer l'accès. À chaque accès, on vérifie si la ligne de cache est déjà réservée par un défaut de cache. La lecture/écriture est alors bloquée si c'est le cas, mais elle accepte les accès sinon. Pour cela, la ''Miss Handling Architecture'' contient des registres qui mémorisent des informations sur les défauts de cache en cours. Ils portent le nom de '''''miss status handling registers''''', que j’appellerais dorénavant MSHR, qui sont aussi appelés des '''''miss buffer'''''. Le contenu des MSHR varie beaucoup suivant le processeur, mais ces derniers stockent au minimum les informations suivantes : * Le numéro de la ligne de cache dans laquelle les données sont chargées. * Un bit de validité qui indique si le MSHR est vide ou pas, qui est mis à 0 quand le défaut de cache est résolu. * Un ou plusieurs champs de lecture/écriture, qui contiennent des informations sur la lecture/écriture. ** Pour une lecture, elle contient des informations sur la destination de la donnée, à savoir qui prévenir quand le défaut de cache est terminé. C'est parfois un nom/numéro de registre (celui dans lequel charger la donnée), mais c'est souvent le numéro de l'entrée dans le ''load/store queue''. ** Pour les écritures, elle contient la donnée à écrire, ou éventuellement un numéro de ''load/store queue'' où se trouve la donnée à écrire. Il faut noter que le bus mémoire ne peut gérer qu'un seul défaut de cache à la fois. Aussi, il est intéressant de regarder ce qui se passe lorsqu'un second défaut de cache survient, pendant qu'un premier est en cours. Dans ce cas, il y a deux réponses qui correspondent à deux types de caches non-bloquants, qui portent les noms barbares de caches de type succès après défaut et défaut après défaut. Sur le premier type, il ne peut pas y avoir plusieurs défauts de cache simultanés. Dès qu'un second défaut de cache survient, le cache stoppe son activité et on ne peut plus démarrer de nouvelle lecture/écriture, tant que le premier défaut de cache n'est pas résolu. Le second type est plus souple et autorise la survenue de plusieurs défauts de cache simultanés. Du moins, jusqu'à une certaine limite, car le cache ne peut supporter qu'un nombre limité d'accès mémoires simultanés (pipelinés). ===Les accès simultanés à une même ligne de cache=== Il arrive que le processeur fasse plusieurs accès mémoire simultanés à la même ligne de cache. Si la ligne de cache en question n'a pas encore été chargée dans le cache, alors on a plusieurs défauts de cache consécutifs pour la même ligne de cache, et le cache non-bloquant doit gérer la situation. Pour la suite, il va falloir faire une petite distinction entre les défauts primaires et secondaires. Imaginons qu'un défaut de cache ait lieu et demande à charger une donnée dans la ligne de cache numéro N. Il s'agit du premier défaut impliquant cette ligne de cache précise, ce qui lui vaut le nom de '''défaut de cache primaire'''. Mais par la suite, d'autres accès mémoire à la même ligne de cache ont lieu, alors que la ligne de cache n'est pas encore disponible. Dans ce cas, il s'agit de '''défauts de cache secondaires'''. Pour l'unité d'accès mémoire, les défauts de cache primaire et secondaire sont différents (ils prennent tous une entrée dans la ''load/store queue''). Mais pour le cache, ils ne correspondent qu'à un seul accès au cache : celui qui demande de charger la ligne de cache demandée. Les défauts de cache primaire et secondaire à la même ligne de cache se voient attribuer un MSHR unique. La gestion des défauts de cache secondaires dépend du cache non-bloquant. La solution la plus simple ne permet pas les défauts de cache secondaires. Le cache ne permet pas deux défauts de cache simultanés pour la même ligne de cache. Les autres solutions le permettent, en fusionnant des accès simultanés à la même ligne de cache en un seul au niveau des MSHR. Dans tous les cas, détecter les défauts de cache secondaires sont un problème qu'il faut détecter. La ''Miss Handling Architecture'' doit détecter les défauts de cache secondaire. Pour cela, elle procède comme suit. Lors de chaque défaut de cache, la MHA récupère le numéro de la ligne de cache associé. Il vérifie alors chaque MSHR pour vérifier s'il contient le numéro en question. S'il n'y a aucune correspondance dans les MSHR, alors c'est signe que le défaut de cache est un défaut primaire. Mais s'il y en a une, alors c'est un défaut secondaire. Évidemment, cela signifie que lors d'un défaut de cache, le numéro de ligne de cache est envoyé à tous les MSHR, pour comparaison. Les MSHR sont donc regroupés dans une mémoire associative, une sorte de mini-cache, faciliter l'implémentation. Lors d'un défaut de cache primaire, l'accès à un cache non-bloquant se fait comme suit : le processeur envoie une adresse au cache, accède à celui-ci, et détecte la survenue d'un défaut de cache. Il en profite alors pour attribuer une ligne de cache dans laquelle sera chargée ce défaut. L'attribution est très simple dans le cas des caches ''direct mapped'', ou associatifs par voie, pour lesquels l'attribution se fait assez simplement. Il mémorise alors cette information dans les MSHR, après avoir vérifié que le défaut de cache n'était pas un défaut secondaire. Lors des accès ultérieurs à une adresse proche, censée être dans la même ligne de cache, le processeur va encore une fois rencontrer un défaut de cache. Il va alors déterminer le numéro de la ligne de cache associée à l'adresse, et comparer ce numéro avec les MSHRs. Si un MSHR contient ce numéro, c'est signe que le défaut de cache est un défaut secondaire. La MHA réagit alors différemment selon le processeur considéré. Une première solution n'autorise pas les défauts de cache secondaires. Si l'un d'entre eux survient, le processeur est gelé par un ''pipeline stall'', une bulle de pipeline. Une autre solution fusionne les défauts de cache secondaires avec le défaut de cache primaire : tout cela ne correspond qu'à un seul défaut de cache pour lui. ===Les MSHR simples=== Un cache non-bloquant à '''MSHR simple''' contient juste plusieurs MSHR qui mémorisent juste un numéro de ligne de cache, un bit de validité, le champ de lecture/écriture, et l'adresse exacte de lecture/écriture. Avec cette organisation, il est possible d'avoir plusieurs défauts de cache séparés, mais à la condition que chaque défaut accède à une ligne de cache différente. Deux accès simultanés à une même ligne de cache ne sont pas possibles, les défauts de cache secondaires ne sont pas autorisés. Ainsi, chaque défaut de cache se voit attribuer son propre MSHR, chacun contient un numéro de ligne de cache différent. Pour comprendre pourquoi c'est impossible de gérer les défauts secondaires, il faut regarder le champ de lecture/écriture. Si on veut effectuer plusieurs écritures consécutives à la même adresse, le MSHR n'aura pas de quoi mémoriser les deux données à écrire. Il pourra mémoriser la première donnée à écrire, pas la seconde. Même chose lors d'une lecture : le champ lecture/écriture peut mémorisr la destination de la première lecture, pas de la seconde. L'avantage est que la MHA n'a besoin que des MSHR et de quelques circuits annexes. Les autres solutions rajoutent des circuits annexes pour gérer les défauts de cache secondaires, qui utilisent beaucoup de circuits. Le cout en circuit est donc élevé, mais le gain en performance est là. Passons maintenant aux caches non-bloquants qui autorisent les défauts de cache secondaire. La solution la plus simple consiste à utiliser ===Les MSHR adressés implicitement=== Avec les '''MSHR adressés implicitement''', il est possible de fusionner plusieurs accès mémoire à une même ligne de cache, mais sous une condition très importante : ces accès lisent/écrivent des mots mémoire différents. Par exemple, imaginons qu'une ligne de cache contienne 8 mots mémoire de 64 bits. Si un premier accès mémoire lit le mot mémoire numéro 7 (dernier mot mémoire de la ligne), et le second accès le mot mémoire numéro 3, alors la fusion est possible. Mais si deux accès mémoire veulent lire/écrire le mot mémoire numéro 7, alors la fusion n'est pas possible et le processeur se bloque, un ''pipeline stall'' survient. Un MSHR adressé implicitement est un MSHR simple, qui contient naturellement un numéro de ligne de cache (tag) et un bit de validité, sauf que le champ de lecture/écriture est dupliqué. Les différents champs lecture/écriture d'un MSHR sont regroupés dans une mémoire RAM/cache qui contient autant d'entrées qu'il y a de mot mémoire dans une ligne de cache. Chaque entrée de la table est associée à un mot mémoire de la ligne de cache et stocke des informations sur celui-ci. Une entrée mémorise, au minimum : * Un champ de lecture/écriture qui contient soit la destination de la lecture, soit la donnée à écrire. * Un bit R/W permettant d'interpréter correctement le champ de lecture/écriture. * Un bit de validité pour chaque entrée de la table, qui dit si un défaut de cache antérieur accède déjà à ce mot mémoire. La fusion de deux défauts de cache est ainsi assez simple. Un défaut de cache primaire/secondaire configure l'entrée associée au mot mémoire lu/écrit. Si un défaut secondaire ultérieur lit/écrit un mot mémoire différent (dont le bit de validité de l'entrée dans la table est à zéro), pas de conflit : il configure une autre entrée, vide. Mais s'il lit/écrit un mot mémoire pour lequel l'entrée de la table est occupée, il y a conflit, le processeur est bloqué par un ''pipeline stall'', une bulle de pipeline. Avec cette organisation, le nombre de MSHR indique combien de lignes de cache peuvent être lues en même temps depuis la mémoire. Quant au nombre d'entrées par MSHR, il détermine combien d'accès mémoires qui ne se recouvrent pas peuvent avoir lieu en même temps. ===Les MSHR adressés explicitement=== Les '''MSHR adressés explicitement''' n'ont pas les contraintes des MSHR adressés implicitement. Avec eux, il est possible d'avoir plusieurs défauts de cache pointant vers la même ligne de cache, mais aussi vers le même mot mémoire. De tels défauts de cache apparaissent sur les processeurs à exécution dans le désordre, mais sont très rares, voire inexistants, sur les processeurs ''in-order''. L'idée est encore que chaque MSHR est associée à une table mémoire qui mémorise des entrées. Cette table n'est autre qu'une mémoire FIFO. Sauf que cette fois-ci, une entrée n'est pas associée à un mot mémoire. Une entrée est associée à un défaut de cache. Une entrée mémorise là encore la destination de la lecture ou la donnée de l'écriture suivi du bit R/W, ainsi que d'un bit de validité par entrée, mais aussi : la position du mot mémoire lu dans la ligne de cache. C'est cette dernière information qui n'était pas présente dans les MSHR adressés implicitement. De plus, le nombre d'entrée par MSHR n'est pas égal au nombre de mots mémoires dans une ligne de cache, mais peut être arbitrairement grand. L'avantage d'un tel cache est qu'il est capable de traité les défauts secondaires concernant un même mot mémoire, et ce, car les accès à la ligne de cache concernée se produisent dans l'ordre de génération des défauts (la table mémoire est une FIFO). ===Les MSHRs inversés=== Généralement, plus on veut supporter de défauts de cache, plus le nombre de MSHR et d'entrées augmente. Mais au-delà d'un certain nombre d'entrées et de MSHR, les MSHR adressés implicitement et explicitement ont tendance à bouffer un peu trop de circuits. Utiliser une organisation un peu moins gourmande en circuits est donc une nécessité. Cette organisation plus économe se base sur des MSHR inversés. Les MSHR inversés ne contiennent qu'une seule entrée, en quelque sorte : au lieu d’utiliser n MSHR de m entrées chacun, on va utiliser n × m MSHR inversés. La différence, c'est que plusieurs MSHR peuvent contenir un tag identique, contrairement aux MSHR adressés implicitement et explicitement. Lorsqu'un défaut de cache a lieu, chaque MSHR est vérifié. Si jamais aucun MSHR ne contient de tag identique à celui utilisé par le défaut, un MSHR vide est choisi pour stocker ce défaut, et une requête de lecture en mémoire est lancée. Dans le cas contraire, un MSHR est réservé au défaut de cache, mais la requête n'est pas lancée. Quand la donnée est disponible, les MSHR correspondant à la ligne qui vient d'être chargée vont être utilisés un par un pour résoudre les défauts de cache en attente. ===Les MSHR intégrés au cache=== Certains chercheurs ont remarqué que pendant qu'une ligne de cache est en train de subir un défaut de cache, celle-ci reste inutilisée, et son contenu est destiné à être perdu une fois le défaut de cache résolu. Ils se sont dit que, plutôt que d'utiliser des MSHR séparés, il vaudrait mieux utiliser la ligne de cache pour stocker les informations sur les défauts de cache en attente dans cette ligne de cache. Pour éviter tout problème, il faut rajouter un bit dans les bits de contrôle de la ligne de cache, qui sert à indiquer que la ligne de cache est occupée : un défaut de cache a eu lieu dans cette ligne, et elle stocke donc des informations pour résoudre les défauts de cache. ==L'entrelacement mémoire== Nous venons de voir comment une mémoire cache peut gérer plusieurs accès mémoire simultanés. Il se trouve que ce genre d'optimisation dépasse largement le cadre du cache. Les mémoires RAM ne sont pas en reste, elles aussi ! Il est possible de pipeliner l'accès à une mémoire RAM, en utilisant des techniques dites d''''entrelacement'''. Précisons cependant que cette optimisation n'est pas utilisée sur les mémoires SDRAM ou DDR modernes, du moins pas sans modifications majeures. Mais divers ordinateurs assez anciens, dont des superordinateurs, ont utilisé cette technique. La technique demande d'utiliser plusieurs mémoires séparées. Sur les anciens ordinateurs, les mémoires en question sont des chips mémoire, à savoir des circuits intégrés de DRAM. Pour faire plus général, nous allons utiliser le terme de '''banques''', ou encore de bancs mémoire. La différence est qu'il existe des mémoires multi-banque, qui regroupent plusieurs banques indépendantes dans un seul boitier, dans un seul chip mémoire. En clair, de telles mémoires regroupent plusieurs sous-mémoires dans un seul circuit intégré. Il est aussi possible d'utiliser plusieurs chips mémoire séparés, chacun ayant une banque. Reste à combiner ces banques pour former un pseudo-pipeline. ===Utiliser plusieurs banques sans entrelacement=== Sans optimisation particulière, les adresses sont réparties dans les banques comme indiqué ci-dessous. Il s'agit de ce qui s'appelle un arrangement horizontal, et nous avions vu celui-ci dans le chapiutre sur les mémoires SDRAM. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Avec cet arrangement, les bits de poids fort de l'adresse sont utilisées pour sélectionner la banque adéquate, et le reste de l'adresse est envoyé sur le bus d'adresse. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Adresse de banque !! Adresse dans la banque |- | Quelques bits de poids fort || Reste de l'adresse |} L'entrelacement change cette répartition, afin d'accélérer les accès mémoire. L'entrelacement classique vise à améliorer les accès à des adresses mémoires consécutives, mais des formes plus évoluées d'entrelacement visent à optimiser des accès mémoire arbitraires. Voyons-les dans l'ordre, du plus simple au plus complexe. ===L'entrelacement classique=== Avec l''''entrelacement classique''', des adresses consécutives sont réparties dans des banques consécutives. Précisons que cette attribution des adresses n'implique absolument pas la mémoire virtuelle ou n'importe quel mécanisme dans le processeur. La répartition décide que telle adresse mémoire va dans telle banque, à telle adresse dans la banque. Elle est donc le fait du contrôleur mémoire, donc en dehors du processeur (les contrôleurs mémoires n’étaient pas encore intégrés dans le processeur à l'époque). [[File:Répartition des adresses dans une mémoire interleaved.png|centre|vignette|upright=1.5|Répartition des adresses dans une mémoire interleaved.]] L'entrelacement simple permet d'accélérer les accès mémoire qui se font à des adresses consécutives. Avec l'entrelacement, chaque accès mémoire tombe dans une banque mémoire différente, ce qui fait qu'on peut démarrer un nouvel accès mémoire à chaque cycle d'horloge. Pendant qu'une banque est occupée par un accès mémoire, on démarre le suivant dans une autre banque, et ainsi de suite. Le tout se termine soit quand on a épuisé toutes les banques libres, soit quand l'accès en rafales se termine. Pas besoin d'attendre que la mémoire ait fini sa lecture/écriture avant de démarrer la lecture/écriture suivante. [[File:Pipemining mémoire.png|centre|vignette|upright=2.5|Pipelining mémoire]] L'animation suivante illustre bien le processus, avec quatres banques. [[File:Interleaving.gif|centre|vignette|upright=2.5|Pipelining mémoire]] L'entrelacement simple utilise les bits de poids faible pour sélectionner la banque, et les bits de poids fort pour la case mémoire. [[File:Adresse mémoire d'une mémoire entrelacée.png|centre|vignette|upright=2|Adresse mémoire d'une mémoire entrelacée]] Précisons que le temps d'accès mémoire ne change pas beaucoup avec l'entrelacement. Par contre, on peut faire plus d'accès mémoire simultanés. On peut démarrer un accès mémoire par cycle d'horloge, mais l'accès en lui-même prend plusieurs cycles. Les mémoires à entrelacement ont donc un débit supérieur aux mémoires qui ne l'utilisent pas. L'entrelacement classique pourrait être utilisé pour accélérer les transferts entre mémoire RAM et cache, qui se font en blocs d'adresses consécutives. Mais de nos jours, il n'est pas utilisé dans cette optique. Il faut dire qu'il est redondant avec le mode rafale des mémoires DRAM. Les deux font globalement la même chose et on voit mal comment utiliser les deux en même temps. Le mode rafale étant plus simple à implémenter, et plus léger niveau utilisation du bus de commande mémoire, il est préféré à l'entrelacement. Par contre, l'entrelacement a eu son heure de gloire sur d'anciens processeurs qui n'avaient pas de mémoire cache, alors qu'ils disposaient d'un pipeline. L'exemple typique est celui des nombreux ''processeurs vectoriels'', que nous n'avons pas encore abordé à ce stade du cours. De tels processeurs avaient un pipeline, une unité de calcul fortement pipelinée, et parfois même de l'exécution dans le désordre et du renommage de registres ! ===L'entrelacement par décalage=== L'entrelacement simple est très utile pour les accès en rafle ou équivalents. Par contre, il existe de rares situations où il n'est pas optimal. Une de ces situations est celle des accès en enjambée (en ''stride''), où l'on accède en série à des adresses sont séparées par N mots mémoires. [[File:Accès par enjambées.png|centre|vignette|upright=2|Accès par enjambées.]] De tels accès surviennent quand un logiciel accède à des structures de données spécifiques, à savoir des tableaux de structures, des matrices, ou d'autres structures de données dans le genre. Un cas classique est celui du parcours d'une matrice colonne par colonne. Les matrices de nombres sont mémorisées ligne par ligne, ce qui fait que parcourir une colonne demande de passer d'une ligne à la suivante et de faire des grands sauts en mémoire RAM, mais tous espacés par la même distance. Avec l'entrelacement simple, les accès en enjambée sont moins performants que les accès à des adresses consécutives, mais cela ne fonctionne pas trop mal. Le pire cas est celui où l'on a N banques et où les données sont justement placées toutes les N adresses. Des accès consécutifs vont tous tomber dans la même banque, on ne peut plus accéder à des banques différentes en parallèle. Mais en dehors de ce cas, on voit une amélioration par rapport à la situation sans entrelacement. Pour obtenir des performances maximales pour les accès en enjambées, il faut répartir les mots mémoires dans la mémoire autrement. L'organisation idéale est la suivante. : Dans les explications qui vont suivre, la variable N représente le nombre de banques, qui sont numérotées de 0 à N-1. On commence par organiser les N premières adresses comme une mémoire entrelacée simple : l'adresse 0 correspond à la banque 0, l'adresse 1 à la banque 1, etc. Pour le bloc suivant, on décale tout d'une adresse, à savoir qu'on commence à remplir les banques à partir de la seconde, non de la première. Une fois la fin du bloc atteinte, on finit de remplir le bloc en repartant du début du bloc. Le troisième bloc d'adresse subit le même traitement, sauf qu'on commence à remplir à partir de la seconde banque. Et on poursuit l’assignation des adresses en décalant d'un cran en plus à chaque bloc. Ainsi, chaque bloc verra ses adresses décalées d'un cran en plus comparé au bloc précédent. Si jamais le décalage dépasse la fin d'un bloc, alors on reprend au début. [[File:Mémoire interleaved par décalage.png|centre|vignette|upright=1.5|Mémoire entrelacée par décalage.]] En faisant cela, on remarque que les banques situées à N adresses d'intervalle sont différentes. Dans l'exemple du dessus, nous avons ajouté un décalage de 1 à chaque nouveau bloc à remplir. Mais on aurait tout aussi bien pu prendre un décalage de 2, 3, etc. Dans tous les cas, on obtient un entrelacement par décalage. Ce décalage est appelé le pas d'entrelacement, noté P. Le calcul de l'adresse à envoyer à la banque, ainsi que la banque à sélectionner se fait en utilisant les formules suivantes : * adresse à envoyer à la banque = adresse totale / N ; * numéro de la banque = (adresse + décalage) modulo N ; * décalage = (adresse totale * P) mod N. Avec cet entrelacement par décalage, on peut prouver que la bande passante maximale est atteinte si le nombre de banques est un nombre premier. Seulement, utiliser un nombre de banques premier peut créer des trous dans la mémoire, des mots mémoires inadressables. Malgré ce défaut, la technique a été utilisée sur quelques ordinateurs, avec l'exemple notable du superordinateur ''Burroughs Scientific Processor''. Pour éviter cela, il y a plusieurs solutions. Par exemple, on peut faire en sorte que N et la taille d'une banque soient premiers entre eux : ils ne doivent pas avoir de diviseur commun. Mais en pratique, elles n'ont pas vraiment été implémentées dans une vraie machine, et sont restées à l'état de recherche, aussi je les passe sous silence. ===L'entrelacement pseudo-aléatoire=== Une dernière méthode de répartition consiste à répartir les adresses dans les banques de manière "pseudo-aléatoire". L'idée exacte est de faire passer l'adresse dans une '''fonction de hachage''', plus ou moins complexe. Pour rappel, une fonction de hachage prend une entrée de grande taille et fournit en sortie un résultat de petite taille. Idéalement, elles doivent donner des résultats aussi différents que possible pour des entrées similaires, histoire de simuler une sorte de pseudo-aléatoire. Ici, une adresse est transformée en un numéro de banque. Une fonction de hachage simple ne fait que permuter des bits de l'adresse pour obtenir son résultat. Elle ne fait qu'échanger des bits de place avant de couper l'adresse en deux morceaux : un pour la sélection de la banque, et un autre pour la sélection de l'adresse dans la banque. Cette permutation est fixe, et ne change pas suivant l'adresse. Des fonctions de hachage plus complexes font des XOR bit à bit entre certains bits de l'adresse. ==Les contrôleurs mémoires SDRAM/DDR optimisés== Après avoir vu le cas des mémoires RAM, nous allons nous concentrer sur le cas particulier des mémoires SDRAM. Les mémoires SDRAM et DDR modernes sont capables de gérer plusieurs accès simultanés à la mémoire RAM, et leurs contrôleurs mémoire en font tout autant. C'est une différence majeure avec les mémoires asynchrones FPM/EDO, qui n'acceptaient qu'un seul accès mémoire à la fois. Leur contrôleur mémoire n'acceptait qu'un seul accès mémoire à la fois, c'était un contrôleur mémoire bloquant. Les contrôleurs mémoires des SDRAM sont eux non-bloquants et peuvent encaisser une dizaine d'accès mémoire à la fois. Peu de choses sont connues sur les contrôleurs de SDRAM/DDR modernes, les fabricants ne donnant que peu de détails dessus. Les rares simulateurs qui tentent de décrire leur fonctionnement, comme DRAM SIM I et II, sont particulièrement simples et ne vont pas dans le détail. Néanmoins, le peu qu'on sait est tout de même instructif. ===Rappel sur les SDRAM : tampon de ligne et commandes=== Les mémoires SDRAM sont des mémoires à tampon de ligne. Elles font un accès mémoire en deux étapes. La première étape recopie une ligne de N * 64 bits dans un tampon interne à la SDRAM. La seconde étape sélectionne une colonne, à savoir une donnée de 64 bits, qui est soit envoyée sur le bus de données pour une lecture, soit modifiée par une écriture. [[File:Mémoire à tampon de ligne.png|centre|vignette|upright=2|Mémoire à tampon de ligne]] Les accès mémoire sont traduits par un séquenceur mémoire en une série de commandes mémoires, qui sont séparées par des délais mémoire de quelques cycles d'horloge. Les délais sont très précis, et sont à respecter à la lettre. Une lecture ou une écriture se fait en maximum trois commandes : une commande PRECHARGE qui ferme la ligne précédemment utilisée, une commande ACT qui précise l'adresse de la ligne, et une commande READ ou WRITE qui précise l'adresse de la colonne et éventuellement la donnée à écrire. : Pour être plus précis, la commande PRECHARGE précharge les lignes de bits du plan mémoire à une tension particulière, ce qui les vide de leur contenu. Mais c'est un détail sans importance pour ce qui va suivre. Les SDRAM permettent de se passer de la première étape si des accès consécutifs se font dans la même ligne. Une fois activée, la ligne reste ouverte et on peut accéder plusieurs fois de suite dedans. On a alors juste à préciser l'adresse de colonne dedans. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | bgcolor="#FFA0FF" | PRECHARGE || bgcolor="#FFA0FF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || bgcolor="#A0FFFF" | READ (2) || bgcolor="#A0FFFF" | READ (3) || || || bgcolor="#A0FFFF" | READ (4) || bgcolor="#A0FFFF" | READ (5) || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | Donnée READ 1 || ||bgcolor="#A0FFFF" | Donnée READ 2 | bgcolor="#A0FFFF" | Donnée READ 3 || || || bgcolor="#A0FFFF" | Donnée READ 4 || bgcolor="#A0FFFF" | Donnée READ 5 |} Les SDRAM sont parfois capables de démarrer une commande avant que la précédente soit terminée. Mais le respect des délais mémoire est très limitant, ce qui fait qu'on ne peut pas parler de réel pipeline, comme c'est le cas sur les processeurs. Parlons plutot de '''commandes anticipées'''. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | || bgcolor="#A0FFFF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || || || bgcolor="#FFA0FF" | READ (2) || || || || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 | bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 | |} Un dernier point est que les chips mémoires de SDRAM sont composés de plusieurs banques, chacune étant une sorte de mini-mémoire miniature. Chaque banque a son propre décodeur, son propre tampon de ligne, ses propres multiplexeurs de colonne, sa logique de rafraichissement mémoire, etc. C'est comme si une SDRAM regroupait plusieurs mémoires séparées dans un même circuit intégré. [[File:Arrangement vertical.jpg|centre|vignette|upright=2.5|Mémoire multi-banques.]] ===La mise en attente des accès mémoire=== Un contrôleur de SDRAM peut accepter plusieurs accès mémoire en même temps. Mais cela ne signifie pas que le contrôleur sera prêt à les traiter immédiatement. Pour éviter tout problème de disponibilité, le contrôleur met en attente les accès mémoire que le processeur lui envoie, pour les exécuter dès que possible. Les accès mémoire sont mis en attente dans une mémoire FIFO, histoire de les exécuter dans leur ordre d'arrivée, notamment pour renvoyer les lectures dans l'ordre demandé. Il y a aussi une mémoire FIFO pour les données à écrire et une FIFO pour les données lues. Cette dernière sert au cas où le cache ne soit pas disponible quand on lui envoie la donnée lue. [[File:Module d'interface avec la mémoire.png|centre|vignette|upright=2.5|Contrôleur mémoire avec mise en attente des requêtes processeur et autres optimisations.]] Des optimisations sont possibles dès la mémoire FIFO. Par exemple, on peut regrouper plusieurs accès à des données consécutives en un seul accès en rafale. Le contrôleur mémoire analyse les requêtes mises en attente et détecte si certaines se font à des adresses consécutives. Si c'est le cas, il fusionne ces requêtes en une lecture/écriture en rafale. Une telle optimisation est appelée la '''combinaison de lecture''' pour les lectures, et la '''combinaison d'écriture''' pour les écritures. Une autre optimisation possible est le réacheminement lecture sur écriture (''Store to load forwarding''). Il s'agit d'un équivalent de l’optimisation utilisée dans le cadre de la désambiguïsation mémoire. Quand une lecture demande à accéder à une donnée écrite récemment, il se peut que l'écriture ait été mise en attente dans le contrôleur mémoire. Dans ce cas, le contrôleur mémoire peut renvoyer la donnée directement depuis ses mémoires FIFOs, sans faire d'accès mémoire en lecture. Les deux optimisations précédentes ne paraissent pas payer de mine, mais elles deviennent plus compréhensible quand on sait que certains contrôleurs mémoire peuvent mettre en attente beaucoup de données. Par exemple, l'''Intel E8870 - Scalable Memory Controller'' peut mettre en attente 64 lignes de cache dans la mémoire FIFO, soit 8 kibioctets de RAM ! Deux pages mémoire si la mémoire virtuelle utilise des pages de 4 kibioctets ! ===Les lectures anticipées=== La mise en attente des accès mémoire est une optimisation intéressante, mais pas mirifique. Elle permet surtout de ne pas bloquer le processeur si le contrôleur mémoire a du travail sur la planche. Cependant, elle peut être optimisée quand on se rend compte d'une chose : le processeur ne voit que les lectures, pas les écritures. Pour les écritures, il les envoie au contrôleur mémoire et ce dernier fait le travail demandé, le processeur n'est pas prévenu quand une écriture se termine. Mais pour les lectures, c'est différent, car il reçoit la donnée lue. En conséquence, il est possible d'optimiser les écritures sans que le processeur ne voit quoique ce soit. Une optimisation possible est alors de changer l'ordre des accès mémoire, histoire de retarder les écritures ou au contraire de les faire en avance. La raison est que les mémoires SDRAM n'aiment pas quand on alterne lectures et écritures. Les délais mémoire, les fameux ''timings'' mémoire, sont clairs : faire une suite de lecture ou une suite d'écriture est plus performant que d'alterner entre lectures et écritures. L'idée est alors de changer l'ordre des écritures de manière à les faire en bloc, idem pour les lectures. L’optimisation est possible et le processeur n'y verra que du feu. L'optimisation est une forme d'exécution dans le désordre des accès mémoire, réalisée par le contrôleur mémoire lui-même ! Et qui dit exécution dans le désordre dit : problèmes liés aux dépendances de données. Changer l'ordre des écritures n'est pas toujours possible, notamment en raison des dépendances RAW, WAR et WAW. De plus, si on change l'ordre des accès mémoire, il faut les remettre en ordre pour que le processeur n'y voit que du feu, il ne doit pas voir des lectures dans le désordre. Heureusement, il y a des techniques très simple pour changer l'ordre des écritures sans que le processeur n'y voit quoique ce soit. Pour que le processeur n'y voit que du feu, il faut respecter plusieurs critères. Premièrement, les lectures doivent être renvoyées au processeur dans l'ordre. Et quand je dis dans l'ordre, cela veut dire : dans l'ordre d'envoi des lectures au contrôleur mémoire. Les écritures aussi doivent se faire dans l'ordre, histoire qu'une série d'écriture donne le bon résultat final. Le troisième critère est qu'une lecture doit donner le résultat de la dernière écriture ''à la même adresse''. Pour le dire autrement, il faut juste détecter les dépendances RAW. Concrètement, si on a une série de lectures et d'écritures alternées, on peut regrouper les lectures et les écritures dans deux paquets séparés, à condition qu'aucune lecture ne lise une adresse écrite par une écriture. Mais si une lecture est dépendante d'une écriture, alors la lecture doit attendre que l'écriture se termine, idem pour les lectures suivantes. Une implémentation basique remplace la mémoire FIFO vue au-dessus, par deux mémoires FIFOs : une pour les lectures, une pour les écritures. Lorsqu'un accès mémoire arrive au séquenceur, il regarde si c'est une lecture ou une écriture et place l'accès mémoire dans la file adéquate. Le fait que ce soit des FIFOs garantit que les lectures se font dans l'ordre, idem pour les écritures. En sortie des deux FIFOs, le séquenceur mémoire détecte les dépendances RAW. Prenons l'exemple des coprocesseurs IO 81341 et 81342, qui étaient en réalité des ''chipsets'' intégrant un contrôleur SDRAM. Le ''chipset'' avait 5 ports : un pour les processeurs, un pour le pont sud (''southbridge'') et trois pour des canaux DMA. Pour le port processeur, il n'y avait pas de réordonnancement et ce port utilisait une seule mémoire FIFO. Les autres ports utilisaient l'optimisation qu'on vient de voir, et avaient vraisemblablement des files séparées pour les lectures et écritures. Le séquenceur mémoire vérifiait les dépendances mémoire de type RAW et autres. [[File:Double file d'attente pour les lectures et écritures.png|centre|vignette|upright=2.5|Double file d'attente pour les lectures et écritures]] ===Le ré-ordonnancement des commandes mémoires=== Un contrôleur mémoire moderne est capable de changer l'ordre des requêtes mémoires, pour gagner en performances. En clair : il peut faire l'équivalent mémoire de l'exécution dans le désordre des processeurs haute performance. Une différence majeure est que le contrôleur mémoire ne remet pas les accès mémoire dans l'ordre avant de les envoyer au processeur. C'est le processeur qui remet en ordre les accès mémoire, dans sa ''load queue''. Pour cela, les requêtes mémoire sont "numérotées", à savoir qu'on leur attribue un identifiant binaire. Le contrôleur mémoire exécute les lectures/écritures dans le désordre, mais garde la trace de leur identifiant. Il sait dans quel ordre il exécute les accès mémoire et connait leur latence. Ce qui fait qu'il sait que la donnée lue à tel instant est associée à la requête mémoire numéro X. Pour les lectures, il renvoie la donnée lue avec l'identifiant. Le processeur sait alors à quelle lecture la donnée lue correspond et il se débrouille en interne pour gérer la situation. En clair : le processeur voit les accès mémoire dans le désordre, c'est lui qui les remet en ordre. Maintenant que ces précisions sont faites, posons cette question : quel est l'intérêt de faire les accès mémoire dans le désordre ? Le ré-ordonnancement est surtout utile si tous les accès mémoire atterrissent dans la même banque et dans la même ligne. Elle marche si plusieurs accès à une même ligne ne sont pas consécutifs, et qu'il y a des accès entre les deux. Après ré-ordonnancement, ces accès mémoire sont exécutés l'un à la suite de l'autre. Pour rendre le tout plus concret, voici un exemple. Imaginez que l'on sait les 3 accès mémoire suivants : * Une lecture ligne A ; * Une écriture ligne B ; * Une lecture ligne A. Sans réordonnancent, on doit émettre deux commandes PRECHARGE : une quand on passe de la ligne A à la ligne B, et une autre pour le passage inverse. pour éviter cela, le contrôleur mémoire peut retarder l'écriture, ou au contraire avancer la seconde lecture. le résultat est alors le suivant : * Lecture ligne A ; * Lecture ligne A ; * Une écriture ligne B. On a donc deux accès consécutifs à la même ligne, suivi par une écriture dans une autre ligne. La technique marche parce que l'on a un mix adéquat de lectures et d'écritures. Mais il existe des possibilités de réorganisation autres qui ne sont pas valides. Il faut par exemple prendre garde à éviter d'intervertir lectures et écritures à une même adresse, sous peine de problèmes. Encore une fois, il faut faire attention aux dépendances de données, encore une fois ! Ici des dépendances mémoires, mais les règles sont les mêmes. Le contrôleur mémoire teste les dépendances mémoires avant d'envoyer des commandes à la mémoire DDR/SDRAM. ===Les optimisations liées à la présence de plusieurs banques=== Les optimisations précédentes sont décuplées par la présence de plusieurs banques dans la mémoire SDRAM. Il est en effet possible de faire plusieurs accès mémoire en même temps, dans des banques différentes. Il est possible de lancer un accès mémoire dans deux banques en même temps, par exemple. La seule contrainte est que la SDRAM est limitée à 4 banques actives en même temps. {|class="wikitable" |+ Pipelining basique sur les SDRAM |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 |- ! Banque Numéro 1 | || || || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || || |- ! Banque Numéro 2 | || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || |- ! Banque Numéro 3 | || || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || |} L'implémentation n'a rien de vraiment compliqué. Il suffit d'utiliser une mémoire FIFO par banque. Ainsi, les accès dans une même banque se feront en série, dans l'ordre d'arrivée, mais des accès à des banques différentes se feront dans le désordre. Cela ne pose pas de problème car les accès se font à des adresses différentes, ce qui fait qu'il n'y a pas de conflits majeurs. [[File:Gestion parallèle des banques.png|centre|vignette|upright=2.5|Gestion parallèle des banques.]] ==L'entrelacement sur les mémoires SDRAM== L''''entrelacement''' fonctionne sur les mémoires SDRAM, mais il doit être fortement modifié. Concrètement, tout ce qui a étré dit plus est inapplicable pour les SDRAM, du fait de la présence du tampon de ligne. Des adresses consécutives atterrissent déjà dans la même ligne, ce qui fait qu'on n'a pas besoin d'entrelacement au niveau d'une ligne. Et ne parlons pas de ce la présence de rangées et de canaux mémoire ! Cependant, il peut y avoir un entrelacement lié à la présence des banques SDRAM. L'entrelacement est géré juste avant le séquenceur mémoire. Le '''circuit d'entrelacement''' intervertit certains bits de l'adresse lors des accès mémoires. On trouve aussi des mémoires FIFOs pour mettre en attente les requêtes mémoire provenant du processeur. Elles permettent de recevoir plusieurs accès mémoire consécutifs. Si vous vous demandez comment plusieurs accès mémoire consécutifs peuvent avoir lieu, sachez que c'est une bonne question. Pour le moment, nous allons dire que ces accès proviennent du processeur ou de la mémoire cache, sans expliquer comment. Le contrôleur mémoire reçoit une série d'accès mémoire assez rapprochés, qu'il doit exécuter au mieux. [[File:Controleur mémoire d'une SDRAM avec entrelacement.png|centre|vignette|upright=2.5|Contrôleur mémoire d'une SDRAM avec entrelacement.]] Pour gérer l'entrelacement, le contrôleur de SDRAM prend en entrée l'adresse envoyée par le processeur, et la découpe en plusieurs champs : un pour sélectionner la banque adéquate, un autre pour la ligne, un autre pour la colonne, un autre pour la rangée. Une solution simple désactive l'entrelacement. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de banque || Adresse de ligne || Adresse de colonne |} Si cette solution fonctionne bien si les accès mémoire sont assez aléatoires, en pratique, mieux vaut utiliser l'entrelacement. ===L'entrelacement de banques/lignes=== Une première méthode d'entrelacement est l''''entrelacement de banques'''. Elle répartit deux lignes consécutives dans deux banques différentes. Pour comprendre l'idée, prenons un exemple. Imaginons une mémoire avec deux banques et 4 lignes. Imaginons qu'on parcoure/balaye la mémoire RAM en partant des adresses basses. Sans entrelacement de ligne, les accès se feront comme suit, de gauche à droite : {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Banque numéro 1 !! colspan="4" | Banque numéro 2 |- | Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 || Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 |} Avec l'entrelacement de banques, les accès se feront comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 |- | Ligne 1 || Ligne 1 || Ligne 2 || Ligne 2 || Ligne 3 || Ligne 3 || Ligne 4 || Ligne 4 |} Avec N banques, la première ligne ira dans la première banque, la seconde ligne dans la seconde, et ainsi de suite jusqu'à la énième banque qui va dans la banque numéro N. Et la ligne N+1 repart dans la première banque. L'avantage est que passer d'une ligne à la suivante est assez rapide. Au lieu de devoir fermer la première ligne et ouvrir la suivante on ouvrira directement la ligne suivante dans une banque différente, ce qui sera plus rapide. La commande PRECHARGE pourra être envoyée à la seconde banque en avance. Pour cela, l'adresse est découpée comme suit. {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne (précise à l'octet) |} ===L'entrelacement de rangées=== Il est aussi possible de faire la même chose, mais avec les rangées. Pour simplifier fortement, une rangée est simplement un chip mémoire. En réalité, les rangées ne sont pas des chip mémoire, mais un ensemble de chips mémoire regroupés ensemble histoire d'atteindre les 64 bits du bus de données. Par exemple, une rangée peut combiner 8 chips mémoire avec un bus de données de 8 bits chacun pour obtenir les 64 bits du bus de données d'une SDRAM. Mais nous passons ce détail sous silence dans les explications qui vont suivre, par souci de simplification. Pour faire comprendre l'entrelacement de rangée, le mieux est d'utiliser un exemple, le même que précédemment. Sans entrelacement de rangée, on change de chip mémoire une fois qu'on a balayé toutes ses adresses. {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Rangée numéro 1 !! colspan="4" | Rangée numéro 2 |- | Banque 1 || Banque 2 || Banque 3 || Banque 4 || Banque 1 || Banque 2 || Banque 3 || Banque 4 |} Avec l'entrelacement de ligne, on change de rangée dès qu'on change de banque/ligne. {|class="wikitable" |+ Adresse mémoire |- ! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 |- | Banque 1 || Banque 1 || Banque 2 || Banque 2 || Banque 3 || Banque 3 || Banque 4 || Banque 4 |} En clair, quand on a épuisé toutes les banques dans une même rangée, on passe à la rangée suivante au lieu de rester dans la même rangée. Notons que dans l'exemple précédent, on a combiné l'entrelacement de rangée et de banque, mais on aurait pu utiliser l'entrelacement de rangée seul. Mais ce n'est pas le cas le plus courant en pratique. Toujours est-il qu'en combinant entrelacement de rangée et de ligne, le découpage de l'adresse est le suivant : {|class="wikitable" |+ Adresse mémoire |- | Adresse de ligne || Numéro de rangée || Adresse de banque || Adresse de colonne (précise à l'octet) |} D'autres méthodes d'entrelacement sont possibles, mais elles sont plus complexes. Chaque contrôleur mémoire fait un peu à sa sauce. Ils permettent en général de choisir entre plusieurs entrelacements. Au minimum, ils permettent de désactiver l'entrelacement, d'activer un entrelacement de ligne, et d'autres entrelacements plus complexes, dont certains sont propriétaires et tenus secrets par le fabricant. ===L'entrelacement avec le ''dual channel''=== [[File:Dual channel slots.jpg|vignette|Slots mémoires avec ''dual channel''.]] L'usage du ''dual channel'' complique encore l'entrelacement. Là encore, il y a deux grandes solutions : avec et sans entrelacement des canaux mémoire. Rappelons le principe : deux barrettes de RAM sont accédées en parallèle. Et pour cela, il faut utiliser l'entrelacement. Typiquement, chaque barrette mémoire fournit 64 bits, ce qui fait que l'on peut accéder à 128 bits d'un seul coup, par exemple avec un accès en rafale. Sans ''dual channel'', la première barrette correspond à la moitié haute de la RAM, la seconde barrette correspond à la moitié basse. Avec ''dual channel'', une forme spécifique d'entrelacement est activée. Concrétement, deux blocs de 64 bits sont placés dans des canaux mémoire séparés. Idem avec du triple ou quadruple canal, mais c'est alors trois ou quatre blocs de 64 bits qui sont dispersés dans des canaux consécutifs. Le découpage de l'adresse est alors le suivant : {|class="wikitable" |+ Adresse mémoire |- ! colspan="6" | Sans entrelacement |- | Numéro de canal/barrette || Reste de l'adresse || Position de l'octet dans un bloc de 64 bits |- | colspan="6" | |- ! colspan="6" | Avec entrelacement |- | Reste de l'adresse || Numéro de canal/barrette || Position de l'octet dans un bloc de 64 bits |} [[File:Décodage d'adresse avec dual channel.png|centre|vignette|upright=2|Décodage d'adresse avec dual channel.]] Les contrôleurs de SDRAM précédents sont assez basiques et ne représentent pas les contrôleurs les plus évolués. Ils étaient utilisés sur les anciens PC, à une époque où ils étaient encore sur la carte mère du processeur. Mais de nos jours, le contrôleur mémoire est intégré au processeur. Et il incorpore de nombreuses optimisations afin de gagner en performances. Malheureusement, ces optimisations sont elles-mêmes dépendantes des optimisations intégrées dans le processeur, ce qui fait qu'on ne peut pas en parler ici. Elles demandent que le contrôleur mémoire reçoive plusieurs accès mémoire simultanés, ce qui n'a aucun sens à ce stade du cours. Aussi, nous allons les passer sous silence pour le moment. Nous les verrons dans le chapitre sur le parallélisme mémoire. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=La désambiguïsation mémoire | prevText=La désambiguïsation mémoire | next=Les processeurs superscalaires | nextText=Les processeurs superscalaires }} </noinclude> bp3zcb87mu85boyathgin70jwj168du 764733 764732 2026-04-23T22:46:00Z Mewtow 31375 /* Les lectures anticipées */ 764733 wikitext text/x-wiki Dans ce chapitre, nous allons voir les techniques qui permettent de gérer plusieurs accès mémoire simultanés directement au niveau du cache ou de la mémoire RAM. Par plusieurs accès mémoire simultanés, vous pensez sans doute à l'usage de cache multiports, voire de mémoires RAM multiport. C'est en effet une solution, mais ce n'est clairement pas la seule. Par exemple, il est possible de pipeliner l'accès au cache, voire à la mémoire RAM. Il existe de nombreuses techniques de '''parallélisme mémoire''', et nous allons les voir dans ce chapitre. Pour donner un exemple, il est possible de pipeliner les accès mémoire. Il est possible de pipeliner l'accès au cache et/ou l'accès à la mémoire, et nous verrons comment faire dans ce chapitre. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, qu'ils aient ou non un pipeline dynamique. Par contre, pipeliner la mémoire est une technique ancienne, aujourd'hui peu utilisée. Elle était utilisée sur des très vieux ordinateurs, qui avaient un pipeline mais pas de mémoire cache. Mais de nos jours, les mémoires SDRAM et DDR ne sont pas adaptées pour ça, il est très difficile de les pipeliner correctement. Le parallélisme mémoire est utile aussi bien sur des processeurs à émission dans l'ordre que dans le désordre. Par exemple, un processeur ''in-order'' avec des lectures non-bloquantes peut e, profiter. Il peut techniquement lancer une seconde lecture, après avoir rencontré une première lecture non-bloquante. En général, le nombre de lectures consécutives est cependant limité à moins d'une dizaine. À l'opposé, les processeurs sans lectures non-bloquantes profitent pas du parallélisme mémoire. À la rigueur, ils peuvent en profiter s'ils intègrent des techniques de préchargement. Pendant que le processeur exécute une lecture/écriture, il peut précharger une autre donnée en parallèle. Inutile de dire que sans pipeline processeur, le parallélisme mémoire ne sert pas à grand-chose. ==Le ''line fill buffer'' et les techniques associées== Lors d'un défaut de cache, le processeur doit attendre que toute la ligne de cache soit chargée avant d'être utilisable. Or, la taille d'une ligne de cache est supérieure à la largeur du bus mémoire, ce qui fait qu'une ligne de cache est chargée en plusieurs fois, morceaux par morceaux, mot mémoire par mot mémoire. Le chargement peut se faire directement dans le cache, mais ce n'est pas une solution très pratique. À la place, beaucoup de processeurs ajoutent une mémoire tampon entre la RAM et le cache, appelée le '''tampon de remplissage de ligne''' (''line-fill buffer'') dans les processeurs modernes. Lors d'un défaut de cache, le processeur charge la donnée de la RAM dans le tampon de remplissage de ligne, mot mémoire par mot mémoire. Une fois plein, le tampon de remplissage de ligne est recopié dans la ligne de cache. Le tampon de remplissage de ligne contient une ligne de cache, si ce n'est que certains bits de contrôle ne sont pas présents. Le tampon de remplissage de ligne contient un bit de validité pour chaque mot mémoire de la ligne de cache, qui indiquent si le mot mémoire a été chargé. Par exemple, prenons un processeur 64 bits, qui gère donc des mots mémoire de 8 octets, avec des lignes de cache 256 octets/32 mots mémoire. Le tampon de remplissage de ligne contiendra 32 bits de validité. Si le processeur a chargé les 6 premiers mots mémoire, les 6 premiers bits de validité seront mis à 1, les autres seront encore à zéro. Il faut noter que la disponibilité de la ligne complète se détermine assez facilement en faisant un ET logique entre tous les bits de validité. Le processeur sait ainsi quand la ligne de cache est disponible entièrement et donc quand la transférer dans le cache. La même chose existe avec une hiérarchie de cache, sauf que l'on trouve une mémoire tampon entre chaque niveau de cache. Il y a un tampon de remplissage de ligne entre le cache L1 et le cache L2, entre le cache L2 et le L3, etc. Si le cache ne gère qu'un seul défaut de cache à la fois, le ''fill line buffer'' est une mémoire très simple, qui ne mémorise qu'une seule ligne de cache. Et cela vaut aussi bien pour un cache bloquant que non-bloquant. Mais sur les caches capables de gérer plusieurs défauts simultanés, le tampon de remplissage de ligne est une mémoire de type FIFO ou LIFO, capable de mémoriser plusieurs lignes de cache. Notons que le tampon de remplissage de ligne est très utile pour implémenter certaines techniques, par exemple le contournement du cache. Nous avions vu dans le chapitre sur le cache que certains accès mémoire doivent contourner le cache, pour des raisons de cohérence des caches. C'est notamment nécessaire pour accéder aux périphériques, mais c'est aussi utile pour des raisons de performances dans des cas très spécifiques. Les accès qui contournent le cache se font directement dans le tampon de remplissage de ligne : le processeur écrit ou lit les données depuis ce tampon de remplissage de ligne, sans accéder au cache. ===L'''early restart'' et le ''critical word load''=== La présence du ''line buffer'' permet une optimisation assez intéressante, qui permet de réduire la latence des défauts de cache. L'optimisation consiste à lire un mot mémoire dans le tampon de remplissage de ligne, même si la ligne de cache complète n'a pas encore été chargée. Il existe deux manières de faire cela, qui portent les noms d'''early restart'' et de ''critical word load''. La première est la version la plus simple, la seconde est plus complexe mais plus performante. Avec la technique d''''''early restart''''', la ligne de cache est chargée normalement, en partant de son premier mot mémoire. Dès que le mot mémoire lu/écrit par le processeur est copié dans le ''line fill buffer'', il est envoyé au processeur immédiatement. Illustrons le tout par un exemple, où une ligne de cache fait 16 mots mémoire. Le processeur effectue une lecture, qui lit le 5ème mot mémoire. Sans ''early restart'', le processeur doit charger les 16 mots mémoire avant de faire la lecture dans le cache. Avec ''early restart'', le processeur reçoit la donnée dès que le 5ème mot mémoire est disponible. Le processeur doit attendre que les 4 premiers mots mémoire soient chargés, puis le 5ème mot mémoire arrive et est envoyé directement au processeur, il est lu directement depuis le ''line fill buffer''. Les 11 mots mémoire suivants sont ensuite chargés dans le cache pendant que le processeur fait des calculs dans son coin. Le ''critical word load'' est une optimisation de la technique précédente où le chargement de la ligne de cache commence directement à la donnée demandée par le processeur. Pour reprendre l'exemple précédent, où le processeur demande le 5ème mot mémoire sur 16, le ''critical word load'' charge le 5ème mot mémoire en premier et l'envoie au processeur, ce qui fait qu'il est chargé très rapidement. Pas besoin d'attendre que le processeur charge les 4 mots mémoire précédents comme avec l'''early restart''. Dans le détail, le processeur charge le 5ème mot mémoire en premier, puis charge les 11 suivants, et termine par les 4 mots mémoire du début. En clair, le ''critical word load'' commence par charger le mot mémoire lu, puis les blocs suivants, avant de revenir au début du bloc pour charger les blocs restants. Ainsi, la donnée demandée par le processeur sera la première disponible. Pour cela, l'organisation du tampon de remplissage de ligne est modifiée de manière à rendre cela possible. Il a une taille égale à une ligne de cache complète, qui contient elle-même plusieurs mots mémoire. Dans le ''line-fill buffer'', chaque mot mémoire est stocké avec un tag, qui indique l'adresse du mot mémoire stocké dans le ''line-fill buffer''. Le ''line-fill buffer'' est donc un cache un peu particulier, qui fonctionne comme un cache du point de vue du processeur, comme une mémoire FIFO pour les transferts avec le cache. Ainsi, un processeur qui veut lire dans le cache après un défaut peut accéder à la donnée directement depuis le tampon de remplissage de ligne, alors que la ligne de cache n'a pas encore été totalement recopiée en mémoire. ==Les caches pipelinés== Il est possible de pipeliner l'accès au cache, ce qui demande juste de rajouter des registres au bon endroits et quelques circuits de contrôle. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, même ceux avec un pipeline dynamique. Et l'implémentation peut se faire de plusieurs manières différentes. Il faut dire que découper une mémoire cache en plusieurs étapes peut se faire de plusieurs manière. Commençons par la plus simple. Beaucoup de processeurs des années 2000 avaient une fréquence peu élevée comparée aux standards d'aujourd'hui, ce qui fait que leur cache avait bien un temps d'accès d'un cycle d'horloge. Mais l'accès au cache se faisait en deux cycles d'horloge : un cycle pour calculer une adresse et un cycle pour l'accès mémoire proprement dit. Et ces deux étapes étaient pipelinées, à savoir que deux micro-opérations mémoire peuvent s'exécuter en même temps : une dans l'étage de calcul d'adresse, une autre dans le cache. L'unité mémoire était donc pipelinée, alors que l'accès au cache ne l'était pas. Les processeurs qui implémentaient cette technique regroupent les micro-architectures K5 et K6 d'AMD, les processeurs Intel de micro-architecture P6 (Pentium 2 et 3) et quelques autres. Il est aussi possible de pipeliner l'accès au cache lui-même. Avec un cache pipeliné, l'accès au cache ne se fait pas en un seul cycle, mais en plusieurs. Cependant, on peut lancer un nouvel accès au cache à chaque cycle d'horloge, comme un processeur pipeliné lance une nouvelle instruction à chaque cycle. L'implémentation est assez simple : il suffit d'ajouter des registres dans le cache. Pour cela, on profite que le cache est composé de plusieurs composants séparés, qui échangent des données dans un ordre bien précis, d'un composant à un autre. De plus, le trajet des informations dans un cache est linéaire, ce qui les rend parfois pour l'usage d'un pipeline. ===Le ''pipelining'' des caches ''direct-mapped''=== Voici ce qui se passe avec un cache directement adressé. Pour rappel ce genre de cache est conçu en combinant une mémoire RAM, généralement une SRAM, avec quelques circuits de comparaison et un MUX. L'accès au cache se fait globalement en deux étapes : la première lit la donnée et le ''tag'' dans la SRAM, et on utilise les deux dans une seconde étape. Nous avions vu il y a quelques chapitre comment pipeliner des mémoires, dans le chapitre sur les mémoires évoluées. Et bien ces méthodes peuvent s'utiliser pour la mémoire RAM interne au cache !L'idée est d'insérer un registre entre la sortie de la RAM et la suite du cache, pour en faire un cache pipéliné, à deux étages. Le premier étage lit dans la SRAM, le second fait le reste. L'implémentation sur les caches associatifs à plusieurs voies est globalement la même à quelques détails près. [[File:Cache directement adressé pipeliné - deux étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - deux étages]] Avec le circuit précédent, il est possible d'aller plus loin, cette fois en pipelinant l'accès à la mémoire RAM interne au cache. Pour comprendre comment, rappelons qu'une mémoire SRAM est composée d'un plan mémoire et d'un décodeur. L'accès à la mémoire demande d'abord que le décodeur fasse son travail pour sélectionner la case mémoire adéquate, puis ensuite la lecture ou écriture a lieu dans cette case mémoire. L'accès se fait donc en deux étapes successives séparées, on a juste à mettre un registre entre les deux. Il suffit donc de mettre un gros registre entre le décodeur et le plan mémoire. [[File:Cache directement adressé pipeliné - trois étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - trois étages]] Et on peut aller encore plus loin en découpant le décodeur en deux circuits séparés. En effet, rappelez-vous le chapitre sur les circuits de sélection : nous avions vu qu'il est possible de créer des décodeurs en assemblant des décodeurs plus petits, contrôlés par un circuit de prédécodage. Et bien on peut encore une ajouter un registre entre ce circuit de prédécodage et les petits décodeurs. : Théoriquement, toute l'adresse est fournie d'un seul coup au cache, la quasi-totalité des processeurs présentent l'adresse complète à un cache pipeliné. Mais le Pentium 4 fait autrement. Il faut noter que les premiers étages manipulent l'indice dans la SRAM, qui est dans les bits de poids faible, alors que les étages ultérieurs manipulent le ''tag'' qui est dans les bits de poids fort. Les concepteurs du Pentium 4 ont alors décidé de présenter les bits de poids faible lors du premier cycle d'accès au cache, puis ceux de poids fort au second cycle. Pour cela, l'ALU fonctionnait à une fréquence double de celle du processeur, tout comme le cache L1. Il n'y avait pas de pipeline proprement dit, mais cela réduisait grandement la latence d'accès au cache. ===Le ''pipelining'' des caches splittés/sériels=== Il est aussi possible de pipeliner un cache dits splittés, aussi appelés à accès sériel. Pour rappel, les caches à accès sériel vérifient si il y a succès ou défaut de cache, avant d'accéder aux lignes de cache en cas de succès. Ils font donc différemment des autres caches, qui accèdent à une ligne de cache, avant de déterminer s'il y a succès ou défaut en lisant le tag de la ligne de cache. Les caches sériels disposent de deux SRAM : une pour les tags des lignes de cache et une pour les données. Ils accèdent à la SRAM pour les tags, avant d’accéder à la SRAM des données en cas de succès de cache. Vu que l'accès se fait en deux étapes, une vérification des tags suivie de la lecture/écriture des données, il est facile à pipeliner. Pipeliner le cache permet de régler le problème des accès au cache L1, et elle est tout le temps utilisé sur les processeurs modernes. Mais que faire en cas de défaut de cache ? ==Les caches non bloquants== Un '''cache bloquant''' est un cache auquel le processeur ne peut pas accéder pendant un défaut de cache. Il faut attendre que la lecture ou écriture en RAM soit terminée avant de pouvoir utiliser de nouveau le cache. Un '''cache non bloquant''' n'a pas ce problème : on peut l'utiliser pendant un défaut de cache. Les caches non bloquants permettent de démarrer une nouvelle lecture ou écriture alors qu'une autre est en cours, ce qui permet d'exécuter plusieurs lectures ou écritures en même temps. ===Les ''Miss Handling Status Registers''=== Lors d'un défaut de cache, la mémoire RAM est consultée pendant le défaut de cache, mais le cache est inutilisé. Un défaut de cache n'utilise pas le cache, ce n'est qu'un accès en mémoire RAM, sur le bus mémoire (ou un accès aux niveaux de cache inférieurs, peu importe). Le cache en lui-même est laissé libre, rien n’empêche d'y accéder, il est en réalité intrinsèquement non-bloquant. Les caches, bloquants comme non-bloquants, sont en réalité composés d'une mémoire cache proprement dite, entourée de circuits qui servent d'interface entre le processeur et le cache lui-même. Et parmi les circuits tout autour, certains gèrent l'accès au cache lors d'un défaut de cache. Ils sont regroupés sous le terme de '''''Miss Handling Architecture''''' (MHA). La différence entre un cache bloquant et un cache non-bloquant est en réalité liée à l'implémentation de la MHA. Les caches bloquants coupent volontairement l'accès au cache lors d'un défaut, car les défauts sont plus simples à gérer ainsi. Le défaut de cache rapatrie une donnée depuis la RAM, qui sera écrite dans le cache. Et il ne faut pas qu'une tentative d'accès à cette donnée ait lieu avant qu'elle ne soit chargée. Mais ce blocage est général et touche tout le cache, alors que seule une ligne de cache est concernée par le défaut de cache. L'idée derrière un cache non-bloquant est que seule la ligne de cache est bloquée, mais les autres sont accessibles. L'idée est alors de mémoriser les lignes de cache concernées par le défaut de cache, afin d'en bloquer l'accès. À chaque accès, on vérifie si la ligne de cache est déjà réservée par un défaut de cache. La lecture/écriture est alors bloquée si c'est le cas, mais elle accepte les accès sinon. Pour cela, la ''Miss Handling Architecture'' contient des registres qui mémorisent des informations sur les défauts de cache en cours. Ils portent le nom de '''''miss status handling registers''''', que j’appellerais dorénavant MSHR, qui sont aussi appelés des '''''miss buffer'''''. Le contenu des MSHR varie beaucoup suivant le processeur, mais ces derniers stockent au minimum les informations suivantes : * Le numéro de la ligne de cache dans laquelle les données sont chargées. * Un bit de validité qui indique si le MSHR est vide ou pas, qui est mis à 0 quand le défaut de cache est résolu. * Un ou plusieurs champs de lecture/écriture, qui contiennent des informations sur la lecture/écriture. ** Pour une lecture, elle contient des informations sur la destination de la donnée, à savoir qui prévenir quand le défaut de cache est terminé. C'est parfois un nom/numéro de registre (celui dans lequel charger la donnée), mais c'est souvent le numéro de l'entrée dans le ''load/store queue''. ** Pour les écritures, elle contient la donnée à écrire, ou éventuellement un numéro de ''load/store queue'' où se trouve la donnée à écrire. Il faut noter que le bus mémoire ne peut gérer qu'un seul défaut de cache à la fois. Aussi, il est intéressant de regarder ce qui se passe lorsqu'un second défaut de cache survient, pendant qu'un premier est en cours. Dans ce cas, il y a deux réponses qui correspondent à deux types de caches non-bloquants, qui portent les noms barbares de caches de type succès après défaut et défaut après défaut. Sur le premier type, il ne peut pas y avoir plusieurs défauts de cache simultanés. Dès qu'un second défaut de cache survient, le cache stoppe son activité et on ne peut plus démarrer de nouvelle lecture/écriture, tant que le premier défaut de cache n'est pas résolu. Le second type est plus souple et autorise la survenue de plusieurs défauts de cache simultanés. Du moins, jusqu'à une certaine limite, car le cache ne peut supporter qu'un nombre limité d'accès mémoires simultanés (pipelinés). ===Les accès simultanés à une même ligne de cache=== Il arrive que le processeur fasse plusieurs accès mémoire simultanés à la même ligne de cache. Si la ligne de cache en question n'a pas encore été chargée dans le cache, alors on a plusieurs défauts de cache consécutifs pour la même ligne de cache, et le cache non-bloquant doit gérer la situation. Pour la suite, il va falloir faire une petite distinction entre les défauts primaires et secondaires. Imaginons qu'un défaut de cache ait lieu et demande à charger une donnée dans la ligne de cache numéro N. Il s'agit du premier défaut impliquant cette ligne de cache précise, ce qui lui vaut le nom de '''défaut de cache primaire'''. Mais par la suite, d'autres accès mémoire à la même ligne de cache ont lieu, alors que la ligne de cache n'est pas encore disponible. Dans ce cas, il s'agit de '''défauts de cache secondaires'''. Pour l'unité d'accès mémoire, les défauts de cache primaire et secondaire sont différents (ils prennent tous une entrée dans la ''load/store queue''). Mais pour le cache, ils ne correspondent qu'à un seul accès au cache : celui qui demande de charger la ligne de cache demandée. Les défauts de cache primaire et secondaire à la même ligne de cache se voient attribuer un MSHR unique. La gestion des défauts de cache secondaires dépend du cache non-bloquant. La solution la plus simple ne permet pas les défauts de cache secondaires. Le cache ne permet pas deux défauts de cache simultanés pour la même ligne de cache. Les autres solutions le permettent, en fusionnant des accès simultanés à la même ligne de cache en un seul au niveau des MSHR. Dans tous les cas, détecter les défauts de cache secondaires sont un problème qu'il faut détecter. La ''Miss Handling Architecture'' doit détecter les défauts de cache secondaire. Pour cela, elle procède comme suit. Lors de chaque défaut de cache, la MHA récupère le numéro de la ligne de cache associé. Il vérifie alors chaque MSHR pour vérifier s'il contient le numéro en question. S'il n'y a aucune correspondance dans les MSHR, alors c'est signe que le défaut de cache est un défaut primaire. Mais s'il y en a une, alors c'est un défaut secondaire. Évidemment, cela signifie que lors d'un défaut de cache, le numéro de ligne de cache est envoyé à tous les MSHR, pour comparaison. Les MSHR sont donc regroupés dans une mémoire associative, une sorte de mini-cache, faciliter l'implémentation. Lors d'un défaut de cache primaire, l'accès à un cache non-bloquant se fait comme suit : le processeur envoie une adresse au cache, accède à celui-ci, et détecte la survenue d'un défaut de cache. Il en profite alors pour attribuer une ligne de cache dans laquelle sera chargée ce défaut. L'attribution est très simple dans le cas des caches ''direct mapped'', ou associatifs par voie, pour lesquels l'attribution se fait assez simplement. Il mémorise alors cette information dans les MSHR, après avoir vérifié que le défaut de cache n'était pas un défaut secondaire. Lors des accès ultérieurs à une adresse proche, censée être dans la même ligne de cache, le processeur va encore une fois rencontrer un défaut de cache. Il va alors déterminer le numéro de la ligne de cache associée à l'adresse, et comparer ce numéro avec les MSHRs. Si un MSHR contient ce numéro, c'est signe que le défaut de cache est un défaut secondaire. La MHA réagit alors différemment selon le processeur considéré. Une première solution n'autorise pas les défauts de cache secondaires. Si l'un d'entre eux survient, le processeur est gelé par un ''pipeline stall'', une bulle de pipeline. Une autre solution fusionne les défauts de cache secondaires avec le défaut de cache primaire : tout cela ne correspond qu'à un seul défaut de cache pour lui. ===Les MSHR simples=== Un cache non-bloquant à '''MSHR simple''' contient juste plusieurs MSHR qui mémorisent juste un numéro de ligne de cache, un bit de validité, le champ de lecture/écriture, et l'adresse exacte de lecture/écriture. Avec cette organisation, il est possible d'avoir plusieurs défauts de cache séparés, mais à la condition que chaque défaut accède à une ligne de cache différente. Deux accès simultanés à une même ligne de cache ne sont pas possibles, les défauts de cache secondaires ne sont pas autorisés. Ainsi, chaque défaut de cache se voit attribuer son propre MSHR, chacun contient un numéro de ligne de cache différent. Pour comprendre pourquoi c'est impossible de gérer les défauts secondaires, il faut regarder le champ de lecture/écriture. Si on veut effectuer plusieurs écritures consécutives à la même adresse, le MSHR n'aura pas de quoi mémoriser les deux données à écrire. Il pourra mémoriser la première donnée à écrire, pas la seconde. Même chose lors d'une lecture : le champ lecture/écriture peut mémorisr la destination de la première lecture, pas de la seconde. L'avantage est que la MHA n'a besoin que des MSHR et de quelques circuits annexes. Les autres solutions rajoutent des circuits annexes pour gérer les défauts de cache secondaires, qui utilisent beaucoup de circuits. Le cout en circuit est donc élevé, mais le gain en performance est là. Passons maintenant aux caches non-bloquants qui autorisent les défauts de cache secondaire. La solution la plus simple consiste à utiliser ===Les MSHR adressés implicitement=== Avec les '''MSHR adressés implicitement''', il est possible de fusionner plusieurs accès mémoire à une même ligne de cache, mais sous une condition très importante : ces accès lisent/écrivent des mots mémoire différents. Par exemple, imaginons qu'une ligne de cache contienne 8 mots mémoire de 64 bits. Si un premier accès mémoire lit le mot mémoire numéro 7 (dernier mot mémoire de la ligne), et le second accès le mot mémoire numéro 3, alors la fusion est possible. Mais si deux accès mémoire veulent lire/écrire le mot mémoire numéro 7, alors la fusion n'est pas possible et le processeur se bloque, un ''pipeline stall'' survient. Un MSHR adressé implicitement est un MSHR simple, qui contient naturellement un numéro de ligne de cache (tag) et un bit de validité, sauf que le champ de lecture/écriture est dupliqué. Les différents champs lecture/écriture d'un MSHR sont regroupés dans une mémoire RAM/cache qui contient autant d'entrées qu'il y a de mot mémoire dans une ligne de cache. Chaque entrée de la table est associée à un mot mémoire de la ligne de cache et stocke des informations sur celui-ci. Une entrée mémorise, au minimum : * Un champ de lecture/écriture qui contient soit la destination de la lecture, soit la donnée à écrire. * Un bit R/W permettant d'interpréter correctement le champ de lecture/écriture. * Un bit de validité pour chaque entrée de la table, qui dit si un défaut de cache antérieur accède déjà à ce mot mémoire. La fusion de deux défauts de cache est ainsi assez simple. Un défaut de cache primaire/secondaire configure l'entrée associée au mot mémoire lu/écrit. Si un défaut secondaire ultérieur lit/écrit un mot mémoire différent (dont le bit de validité de l'entrée dans la table est à zéro), pas de conflit : il configure une autre entrée, vide. Mais s'il lit/écrit un mot mémoire pour lequel l'entrée de la table est occupée, il y a conflit, le processeur est bloqué par un ''pipeline stall'', une bulle de pipeline. Avec cette organisation, le nombre de MSHR indique combien de lignes de cache peuvent être lues en même temps depuis la mémoire. Quant au nombre d'entrées par MSHR, il détermine combien d'accès mémoires qui ne se recouvrent pas peuvent avoir lieu en même temps. ===Les MSHR adressés explicitement=== Les '''MSHR adressés explicitement''' n'ont pas les contraintes des MSHR adressés implicitement. Avec eux, il est possible d'avoir plusieurs défauts de cache pointant vers la même ligne de cache, mais aussi vers le même mot mémoire. De tels défauts de cache apparaissent sur les processeurs à exécution dans le désordre, mais sont très rares, voire inexistants, sur les processeurs ''in-order''. L'idée est encore que chaque MSHR est associée à une table mémoire qui mémorise des entrées. Cette table n'est autre qu'une mémoire FIFO. Sauf que cette fois-ci, une entrée n'est pas associée à un mot mémoire. Une entrée est associée à un défaut de cache. Une entrée mémorise là encore la destination de la lecture ou la donnée de l'écriture suivi du bit R/W, ainsi que d'un bit de validité par entrée, mais aussi : la position du mot mémoire lu dans la ligne de cache. C'est cette dernière information qui n'était pas présente dans les MSHR adressés implicitement. De plus, le nombre d'entrée par MSHR n'est pas égal au nombre de mots mémoires dans une ligne de cache, mais peut être arbitrairement grand. L'avantage d'un tel cache est qu'il est capable de traité les défauts secondaires concernant un même mot mémoire, et ce, car les accès à la ligne de cache concernée se produisent dans l'ordre de génération des défauts (la table mémoire est une FIFO). ===Les MSHRs inversés=== Généralement, plus on veut supporter de défauts de cache, plus le nombre de MSHR et d'entrées augmente. Mais au-delà d'un certain nombre d'entrées et de MSHR, les MSHR adressés implicitement et explicitement ont tendance à bouffer un peu trop de circuits. Utiliser une organisation un peu moins gourmande en circuits est donc une nécessité. Cette organisation plus économe se base sur des MSHR inversés. Les MSHR inversés ne contiennent qu'une seule entrée, en quelque sorte : au lieu d’utiliser n MSHR de m entrées chacun, on va utiliser n × m MSHR inversés. La différence, c'est que plusieurs MSHR peuvent contenir un tag identique, contrairement aux MSHR adressés implicitement et explicitement. Lorsqu'un défaut de cache a lieu, chaque MSHR est vérifié. Si jamais aucun MSHR ne contient de tag identique à celui utilisé par le défaut, un MSHR vide est choisi pour stocker ce défaut, et une requête de lecture en mémoire est lancée. Dans le cas contraire, un MSHR est réservé au défaut de cache, mais la requête n'est pas lancée. Quand la donnée est disponible, les MSHR correspondant à la ligne qui vient d'être chargée vont être utilisés un par un pour résoudre les défauts de cache en attente. ===Les MSHR intégrés au cache=== Certains chercheurs ont remarqué que pendant qu'une ligne de cache est en train de subir un défaut de cache, celle-ci reste inutilisée, et son contenu est destiné à être perdu une fois le défaut de cache résolu. Ils se sont dit que, plutôt que d'utiliser des MSHR séparés, il vaudrait mieux utiliser la ligne de cache pour stocker les informations sur les défauts de cache en attente dans cette ligne de cache. Pour éviter tout problème, il faut rajouter un bit dans les bits de contrôle de la ligne de cache, qui sert à indiquer que la ligne de cache est occupée : un défaut de cache a eu lieu dans cette ligne, et elle stocke donc des informations pour résoudre les défauts de cache. ==L'entrelacement mémoire== Nous venons de voir comment une mémoire cache peut gérer plusieurs accès mémoire simultanés. Il se trouve que ce genre d'optimisation dépasse largement le cadre du cache. Les mémoires RAM ne sont pas en reste, elles aussi ! Il est possible de pipeliner l'accès à une mémoire RAM, en utilisant des techniques dites d''''entrelacement'''. Précisons cependant que cette optimisation n'est pas utilisée sur les mémoires SDRAM ou DDR modernes, du moins pas sans modifications majeures. Mais divers ordinateurs assez anciens, dont des superordinateurs, ont utilisé cette technique. La technique demande d'utiliser plusieurs mémoires séparées. Sur les anciens ordinateurs, les mémoires en question sont des chips mémoire, à savoir des circuits intégrés de DRAM. Pour faire plus général, nous allons utiliser le terme de '''banques''', ou encore de bancs mémoire. La différence est qu'il existe des mémoires multi-banque, qui regroupent plusieurs banques indépendantes dans un seul boitier, dans un seul chip mémoire. En clair, de telles mémoires regroupent plusieurs sous-mémoires dans un seul circuit intégré. Il est aussi possible d'utiliser plusieurs chips mémoire séparés, chacun ayant une banque. Reste à combiner ces banques pour former un pseudo-pipeline. ===Utiliser plusieurs banques sans entrelacement=== Sans optimisation particulière, les adresses sont réparties dans les banques comme indiqué ci-dessous. Il s'agit de ce qui s'appelle un arrangement horizontal, et nous avions vu celui-ci dans le chapiutre sur les mémoires SDRAM. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Avec cet arrangement, les bits de poids fort de l'adresse sont utilisées pour sélectionner la banque adéquate, et le reste de l'adresse est envoyé sur le bus d'adresse. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Adresse de banque !! Adresse dans la banque |- | Quelques bits de poids fort || Reste de l'adresse |} L'entrelacement change cette répartition, afin d'accélérer les accès mémoire. L'entrelacement classique vise à améliorer les accès à des adresses mémoires consécutives, mais des formes plus évoluées d'entrelacement visent à optimiser des accès mémoire arbitraires. Voyons-les dans l'ordre, du plus simple au plus complexe. ===L'entrelacement classique=== Avec l''''entrelacement classique''', des adresses consécutives sont réparties dans des banques consécutives. Précisons que cette attribution des adresses n'implique absolument pas la mémoire virtuelle ou n'importe quel mécanisme dans le processeur. La répartition décide que telle adresse mémoire va dans telle banque, à telle adresse dans la banque. Elle est donc le fait du contrôleur mémoire, donc en dehors du processeur (les contrôleurs mémoires n’étaient pas encore intégrés dans le processeur à l'époque). [[File:Répartition des adresses dans une mémoire interleaved.png|centre|vignette|upright=1.5|Répartition des adresses dans une mémoire interleaved.]] L'entrelacement simple permet d'accélérer les accès mémoire qui se font à des adresses consécutives. Avec l'entrelacement, chaque accès mémoire tombe dans une banque mémoire différente, ce qui fait qu'on peut démarrer un nouvel accès mémoire à chaque cycle d'horloge. Pendant qu'une banque est occupée par un accès mémoire, on démarre le suivant dans une autre banque, et ainsi de suite. Le tout se termine soit quand on a épuisé toutes les banques libres, soit quand l'accès en rafales se termine. Pas besoin d'attendre que la mémoire ait fini sa lecture/écriture avant de démarrer la lecture/écriture suivante. [[File:Pipemining mémoire.png|centre|vignette|upright=2.5|Pipelining mémoire]] L'animation suivante illustre bien le processus, avec quatres banques. [[File:Interleaving.gif|centre|vignette|upright=2.5|Pipelining mémoire]] L'entrelacement simple utilise les bits de poids faible pour sélectionner la banque, et les bits de poids fort pour la case mémoire. [[File:Adresse mémoire d'une mémoire entrelacée.png|centre|vignette|upright=2|Adresse mémoire d'une mémoire entrelacée]] Précisons que le temps d'accès mémoire ne change pas beaucoup avec l'entrelacement. Par contre, on peut faire plus d'accès mémoire simultanés. On peut démarrer un accès mémoire par cycle d'horloge, mais l'accès en lui-même prend plusieurs cycles. Les mémoires à entrelacement ont donc un débit supérieur aux mémoires qui ne l'utilisent pas. L'entrelacement classique pourrait être utilisé pour accélérer les transferts entre mémoire RAM et cache, qui se font en blocs d'adresses consécutives. Mais de nos jours, il n'est pas utilisé dans cette optique. Il faut dire qu'il est redondant avec le mode rafale des mémoires DRAM. Les deux font globalement la même chose et on voit mal comment utiliser les deux en même temps. Le mode rafale étant plus simple à implémenter, et plus léger niveau utilisation du bus de commande mémoire, il est préféré à l'entrelacement. Par contre, l'entrelacement a eu son heure de gloire sur d'anciens processeurs qui n'avaient pas de mémoire cache, alors qu'ils disposaient d'un pipeline. L'exemple typique est celui des nombreux ''processeurs vectoriels'', que nous n'avons pas encore abordé à ce stade du cours. De tels processeurs avaient un pipeline, une unité de calcul fortement pipelinée, et parfois même de l'exécution dans le désordre et du renommage de registres ! ===L'entrelacement par décalage=== L'entrelacement simple est très utile pour les accès en rafle ou équivalents. Par contre, il existe de rares situations où il n'est pas optimal. Une de ces situations est celle des accès en enjambée (en ''stride''), où l'on accède en série à des adresses sont séparées par N mots mémoires. [[File:Accès par enjambées.png|centre|vignette|upright=2|Accès par enjambées.]] De tels accès surviennent quand un logiciel accède à des structures de données spécifiques, à savoir des tableaux de structures, des matrices, ou d'autres structures de données dans le genre. Un cas classique est celui du parcours d'une matrice colonne par colonne. Les matrices de nombres sont mémorisées ligne par ligne, ce qui fait que parcourir une colonne demande de passer d'une ligne à la suivante et de faire des grands sauts en mémoire RAM, mais tous espacés par la même distance. Avec l'entrelacement simple, les accès en enjambée sont moins performants que les accès à des adresses consécutives, mais cela ne fonctionne pas trop mal. Le pire cas est celui où l'on a N banques et où les données sont justement placées toutes les N adresses. Des accès consécutifs vont tous tomber dans la même banque, on ne peut plus accéder à des banques différentes en parallèle. Mais en dehors de ce cas, on voit une amélioration par rapport à la situation sans entrelacement. Pour obtenir des performances maximales pour les accès en enjambées, il faut répartir les mots mémoires dans la mémoire autrement. L'organisation idéale est la suivante. : Dans les explications qui vont suivre, la variable N représente le nombre de banques, qui sont numérotées de 0 à N-1. On commence par organiser les N premières adresses comme une mémoire entrelacée simple : l'adresse 0 correspond à la banque 0, l'adresse 1 à la banque 1, etc. Pour le bloc suivant, on décale tout d'une adresse, à savoir qu'on commence à remplir les banques à partir de la seconde, non de la première. Une fois la fin du bloc atteinte, on finit de remplir le bloc en repartant du début du bloc. Le troisième bloc d'adresse subit le même traitement, sauf qu'on commence à remplir à partir de la seconde banque. Et on poursuit l’assignation des adresses en décalant d'un cran en plus à chaque bloc. Ainsi, chaque bloc verra ses adresses décalées d'un cran en plus comparé au bloc précédent. Si jamais le décalage dépasse la fin d'un bloc, alors on reprend au début. [[File:Mémoire interleaved par décalage.png|centre|vignette|upright=1.5|Mémoire entrelacée par décalage.]] En faisant cela, on remarque que les banques situées à N adresses d'intervalle sont différentes. Dans l'exemple du dessus, nous avons ajouté un décalage de 1 à chaque nouveau bloc à remplir. Mais on aurait tout aussi bien pu prendre un décalage de 2, 3, etc. Dans tous les cas, on obtient un entrelacement par décalage. Ce décalage est appelé le pas d'entrelacement, noté P. Le calcul de l'adresse à envoyer à la banque, ainsi que la banque à sélectionner se fait en utilisant les formules suivantes : * adresse à envoyer à la banque = adresse totale / N ; * numéro de la banque = (adresse + décalage) modulo N ; * décalage = (adresse totale * P) mod N. Avec cet entrelacement par décalage, on peut prouver que la bande passante maximale est atteinte si le nombre de banques est un nombre premier. Seulement, utiliser un nombre de banques premier peut créer des trous dans la mémoire, des mots mémoires inadressables. Malgré ce défaut, la technique a été utilisée sur quelques ordinateurs, avec l'exemple notable du superordinateur ''Burroughs Scientific Processor''. Pour éviter cela, il y a plusieurs solutions. Par exemple, on peut faire en sorte que N et la taille d'une banque soient premiers entre eux : ils ne doivent pas avoir de diviseur commun. Mais en pratique, elles n'ont pas vraiment été implémentées dans une vraie machine, et sont restées à l'état de recherche, aussi je les passe sous silence. ===L'entrelacement pseudo-aléatoire=== Une dernière méthode de répartition consiste à répartir les adresses dans les banques de manière "pseudo-aléatoire". L'idée exacte est de faire passer l'adresse dans une '''fonction de hachage''', plus ou moins complexe. Pour rappel, une fonction de hachage prend une entrée de grande taille et fournit en sortie un résultat de petite taille. Idéalement, elles doivent donner des résultats aussi différents que possible pour des entrées similaires, histoire de simuler une sorte de pseudo-aléatoire. Ici, une adresse est transformée en un numéro de banque. Une fonction de hachage simple ne fait que permuter des bits de l'adresse pour obtenir son résultat. Elle ne fait qu'échanger des bits de place avant de couper l'adresse en deux morceaux : un pour la sélection de la banque, et un autre pour la sélection de l'adresse dans la banque. Cette permutation est fixe, et ne change pas suivant l'adresse. Des fonctions de hachage plus complexes font des XOR bit à bit entre certains bits de l'adresse. ==Les contrôleurs mémoires SDRAM/DDR optimisés== Après avoir vu le cas des mémoires RAM, nous allons nous concentrer sur le cas particulier des mémoires SDRAM. Les mémoires SDRAM et DDR modernes sont capables de gérer plusieurs accès simultanés à la mémoire RAM, et leurs contrôleurs mémoire en font tout autant. C'est une différence majeure avec les mémoires asynchrones FPM/EDO, qui n'acceptaient qu'un seul accès mémoire à la fois. Leur contrôleur mémoire n'acceptait qu'un seul accès mémoire à la fois, c'était un contrôleur mémoire bloquant. Les contrôleurs mémoires des SDRAM sont eux non-bloquants et peuvent encaisser une dizaine d'accès mémoire à la fois. Peu de choses sont connues sur les contrôleurs de SDRAM/DDR modernes, les fabricants ne donnant que peu de détails dessus. Les rares simulateurs qui tentent de décrire leur fonctionnement, comme DRAM SIM I et II, sont particulièrement simples et ne vont pas dans le détail. Néanmoins, le peu qu'on sait est tout de même instructif. ===Rappel sur les SDRAM : tampon de ligne et commandes=== Les mémoires SDRAM sont des mémoires à tampon de ligne. Elles font un accès mémoire en deux étapes. La première étape recopie une ligne de N * 64 bits dans un tampon interne à la SDRAM. La seconde étape sélectionne une colonne, à savoir une donnée de 64 bits, qui est soit envoyée sur le bus de données pour une lecture, soit modifiée par une écriture. [[File:Mémoire à tampon de ligne.png|centre|vignette|upright=2|Mémoire à tampon de ligne]] Les accès mémoire sont traduits par un séquenceur mémoire en une série de commandes mémoires, qui sont séparées par des délais mémoire de quelques cycles d'horloge. Les délais sont très précis, et sont à respecter à la lettre. Une lecture ou une écriture se fait en maximum trois commandes : une commande PRECHARGE qui ferme la ligne précédemment utilisée, une commande ACT qui précise l'adresse de la ligne, et une commande READ ou WRITE qui précise l'adresse de la colonne et éventuellement la donnée à écrire. : Pour être plus précis, la commande PRECHARGE précharge les lignes de bits du plan mémoire à une tension particulière, ce qui les vide de leur contenu. Mais c'est un détail sans importance pour ce qui va suivre. Les SDRAM permettent de se passer de la première étape si des accès consécutifs se font dans la même ligne. Une fois activée, la ligne reste ouverte et on peut accéder plusieurs fois de suite dedans. On a alors juste à préciser l'adresse de colonne dedans. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | bgcolor="#FFA0FF" | PRECHARGE || bgcolor="#FFA0FF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || bgcolor="#A0FFFF" | READ (2) || bgcolor="#A0FFFF" | READ (3) || || || bgcolor="#A0FFFF" | READ (4) || bgcolor="#A0FFFF" | READ (5) || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | Donnée READ 1 || ||bgcolor="#A0FFFF" | Donnée READ 2 | bgcolor="#A0FFFF" | Donnée READ 3 || || || bgcolor="#A0FFFF" | Donnée READ 4 || bgcolor="#A0FFFF" | Donnée READ 5 |} Les SDRAM sont parfois capables de démarrer une commande avant que la précédente soit terminée. Mais le respect des délais mémoire est très limitant, ce qui fait qu'on ne peut pas parler de réel pipeline, comme c'est le cas sur les processeurs. Parlons plutot de '''commandes anticipées'''. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | || bgcolor="#A0FFFF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || || || bgcolor="#FFA0FF" | READ (2) || || || || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 | bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 | |} Un dernier point est que les chips mémoires de SDRAM sont composés de plusieurs banques, chacune étant une sorte de mini-mémoire miniature. Chaque banque a son propre décodeur, son propre tampon de ligne, ses propres multiplexeurs de colonne, sa logique de rafraichissement mémoire, etc. C'est comme si une SDRAM regroupait plusieurs mémoires séparées dans un même circuit intégré. [[File:Arrangement vertical.jpg|centre|vignette|upright=2.5|Mémoire multi-banques.]] ===La mise en attente des accès mémoire=== Un contrôleur de SDRAM peut accepter plusieurs accès mémoire en même temps. Mais cela ne signifie pas que le contrôleur sera prêt à les traiter immédiatement. Pour éviter tout problème de disponibilité, le contrôleur met en attente les accès mémoire que le processeur lui envoie, pour les exécuter dès que possible. Les accès mémoire sont mis en attente dans une mémoire FIFO, histoire de les exécuter dans leur ordre d'arrivée, notamment pour renvoyer les lectures dans l'ordre demandé. Il y a aussi une mémoire FIFO pour les données à écrire et une FIFO pour les données lues. Cette dernière sert au cas où le cache ne soit pas disponible quand on lui envoie la donnée lue. [[File:Module d'interface avec la mémoire.png|centre|vignette|upright=2.5|Contrôleur mémoire avec mise en attente des requêtes processeur et autres optimisations.]] Des optimisations sont possibles dès la mémoire FIFO. Par exemple, on peut regrouper plusieurs accès à des données consécutives en un seul accès en rafale. Le contrôleur mémoire analyse les requêtes mises en attente et détecte si certaines se font à des adresses consécutives. Si c'est le cas, il fusionne ces requêtes en une lecture/écriture en rafale. Une telle optimisation est appelée la '''combinaison de lecture''' pour les lectures, et la '''combinaison d'écriture''' pour les écritures. Une autre optimisation possible est le réacheminement lecture sur écriture (''Store to load forwarding''). Il s'agit d'un équivalent de l’optimisation utilisée dans le cadre de la désambiguïsation mémoire. Quand une lecture demande à accéder à une donnée écrite récemment, il se peut que l'écriture ait été mise en attente dans le contrôleur mémoire. Dans ce cas, le contrôleur mémoire peut renvoyer la donnée directement depuis ses mémoires FIFOs, sans faire d'accès mémoire en lecture. Les deux optimisations précédentes ne paraissent pas payer de mine, mais elles deviennent plus compréhensible quand on sait que certains contrôleurs mémoire peuvent mettre en attente beaucoup de données. Par exemple, l'''Intel E8870 - Scalable Memory Controller'' peut mettre en attente 64 lignes de cache dans la mémoire FIFO, soit 8 kibioctets de RAM ! Deux pages mémoire si la mémoire virtuelle utilise des pages de 4 kibioctets ! ===Les lectures anticipées=== La mise en attente des accès mémoire est une optimisation intéressante, mais pas mirifique. Elle permet surtout de ne pas bloquer le processeur si le contrôleur mémoire a du travail sur la planche. Cependant, elle peut être optimisée quand on se rend compte d'une chose : le processeur ne voit que les lectures, pas les écritures. Pour les écritures, il les envoie au contrôleur mémoire et ce dernier fait le travail demandé, le processeur n'est pas prévenu quand une écriture se termine. Mais pour les lectures, c'est différent, car il reçoit la donnée lue. En conséquence, il est possible d'optimiser les écritures sans que le processeur ne voit quoique ce soit. Une optimisation possible est alors de changer l'ordre des accès mémoire, histoire de retarder les écritures ou au contraire de les faire en avance. La raison est que les mémoires SDRAM n'aiment pas quand on alterne lectures et écritures. Les délais mémoire, les fameux ''timings'' mémoire, sont clairs : faire une suite de lecture ou une suite d'écriture est plus performant que d'alterner entre lectures et écritures. L'idée est alors de changer l'ordre des écritures de manière à les faire en bloc, idem pour les lectures. L’optimisation est possible, mais à condition que le processeur n'y verra que du feu. Or, vous l'avez deviné, il y a des cas où ces réorganisations ne sont pas possibles, et vous avez sans doute trouvé que la situation était familière. L'optimisation en question est une forme d'exécution dans le désordre des accès mémoire, réalisée par le contrôleur mémoire lui-même ! Et qui dit exécution dans le désordre dit : problèmes liés aux dépendances de données. Changer l'ordre des écritures n'est pas toujours possible, notamment en raison des dépendances RAW, WAR et WAW. Pour que le processeur n'y voit que du feu, il faut respecter plusieurs critères. Premièrement, les lectures doivent être renvoyées au processeur dans l'ordre. Et quand je dis dans l'ordre, cela veut dire : dans l'ordre d'envoi des lectures au contrôleur mémoire. Les écritures aussi doivent se faire dans l'ordre, histoire qu'une série d'écriture donne le bon résultat final. Le troisième critère est qu'une lecture doit donner le résultat de la dernière écriture ''à la même adresse''. Pour le dire autrement, il faut juste détecter les dépendances RAW. Concrètement, si on a une série de lectures et d'écritures alternées, on peut regrouper les lectures et les écritures dans deux paquets séparés, à condition qu'aucune lecture ne lise une adresse écrite par une écriture. Mais si une lecture est dépendante d'une écriture, alors la lecture doit attendre que l'écriture se termine, idem pour les lectures suivantes. Une implémentation basique remplace la mémoire FIFO vue au-dessus, par deux mémoires FIFOs : une pour les lectures, une pour les écritures. Lorsqu'un accès mémoire arrive au séquenceur, il regarde si c'est une lecture ou une écriture et place l'accès mémoire dans la file adéquate. Le fait que ce soit des FIFOs garantit que les lectures se font dans l'ordre, idem pour les écritures. En sortie des deux FIFOs, le séquenceur mémoire détecte les dépendances RAW. Prenons l'exemple des coprocesseurs IO 81341 et 81342, qui étaient en réalité des ''chipsets'' intégrant un contrôleur SDRAM. Le ''chipset'' avait 5 ports : un pour les processeurs, un pour le pont sud (''southbridge'') et trois pour des canaux DMA. Pour le port processeur, il n'y avait pas de réordonnancement et ce port utilisait une seule mémoire FIFO. Les autres ports utilisaient l'optimisation qu'on vient de voir, et avaient vraisemblablement des files séparées pour les lectures et écritures. Le séquenceur mémoire vérifiait les dépendances mémoire de type RAW et autres. [[File:Double file d'attente pour les lectures et écritures.png|centre|vignette|upright=2.5|Double file d'attente pour les lectures et écritures]] ===Le ré-ordonnancement des commandes mémoires=== Un contrôleur mémoire moderne est capable de changer l'ordre des requêtes mémoires, pour gagner en performances. En clair : il peut faire l'équivalent mémoire de l'exécution dans le désordre des processeurs haute performance. Une différence majeure est que le contrôleur mémoire ne remet pas les accès mémoire dans l'ordre avant de les envoyer au processeur. C'est le processeur qui remet en ordre les accès mémoire, dans sa ''load queue''. Pour cela, les requêtes mémoire sont "numérotées", à savoir qu'on leur attribue un identifiant binaire. Le contrôleur mémoire exécute les lectures/écritures dans le désordre, mais garde la trace de leur identifiant. Il sait dans quel ordre il exécute les accès mémoire et connait leur latence. Ce qui fait qu'il sait que la donnée lue à tel instant est associée à la requête mémoire numéro X. Pour les lectures, il renvoie la donnée lue avec l'identifiant. Le processeur sait alors à quelle lecture la donnée lue correspond et il se débrouille en interne pour gérer la situation. En clair : le processeur voit les accès mémoire dans le désordre, c'est lui qui les remet en ordre. Maintenant que ces précisions sont faites, posons cette question : quel est l'intérêt de faire les accès mémoire dans le désordre ? Le ré-ordonnancement est surtout utile si tous les accès mémoire atterrissent dans la même banque et dans la même ligne. Elle marche si plusieurs accès à une même ligne ne sont pas consécutifs, et qu'il y a des accès entre les deux. Après ré-ordonnancement, ces accès mémoire sont exécutés l'un à la suite de l'autre. Pour rendre le tout plus concret, voici un exemple. Imaginez que l'on sait les 3 accès mémoire suivants : * Une lecture ligne A ; * Une écriture ligne B ; * Une lecture ligne A. Sans réordonnancent, on doit émettre deux commandes PRECHARGE : une quand on passe de la ligne A à la ligne B, et une autre pour le passage inverse. pour éviter cela, le contrôleur mémoire peut retarder l'écriture, ou au contraire avancer la seconde lecture. le résultat est alors le suivant : * Lecture ligne A ; * Lecture ligne A ; * Une écriture ligne B. On a donc deux accès consécutifs à la même ligne, suivi par une écriture dans une autre ligne. La technique marche parce que l'on a un mix adéquat de lectures et d'écritures. Mais il existe des possibilités de réorganisation autres qui ne sont pas valides. Il faut par exemple prendre garde à éviter d'intervertir lectures et écritures à une même adresse, sous peine de problèmes. Encore une fois, il faut faire attention aux dépendances de données, encore une fois ! Ici des dépendances mémoires, mais les règles sont les mêmes. Le contrôleur mémoire teste les dépendances mémoires avant d'envoyer des commandes à la mémoire DDR/SDRAM. ===Les optimisations liées à la présence de plusieurs banques=== Les optimisations précédentes sont décuplées par la présence de plusieurs banques dans la mémoire SDRAM. Il est en effet possible de faire plusieurs accès mémoire en même temps, dans des banques différentes. Il est possible de lancer un accès mémoire dans deux banques en même temps, par exemple. La seule contrainte est que la SDRAM est limitée à 4 banques actives en même temps. {|class="wikitable" |+ Pipelining basique sur les SDRAM |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 |- ! Banque Numéro 1 | || || || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || || |- ! Banque Numéro 2 | || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || |- ! Banque Numéro 3 | || || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || |} L'implémentation n'a rien de vraiment compliqué. Il suffit d'utiliser une mémoire FIFO par banque. Ainsi, les accès dans une même banque se feront en série, dans l'ordre d'arrivée, mais des accès à des banques différentes se feront dans le désordre. Cela ne pose pas de problème car les accès se font à des adresses différentes, ce qui fait qu'il n'y a pas de conflits majeurs. [[File:Gestion parallèle des banques.png|centre|vignette|upright=2.5|Gestion parallèle des banques.]] ==L'entrelacement sur les mémoires SDRAM== L''''entrelacement''' fonctionne sur les mémoires SDRAM, mais il doit être fortement modifié. Concrètement, tout ce qui a étré dit plus est inapplicable pour les SDRAM, du fait de la présence du tampon de ligne. Des adresses consécutives atterrissent déjà dans la même ligne, ce qui fait qu'on n'a pas besoin d'entrelacement au niveau d'une ligne. Et ne parlons pas de ce la présence de rangées et de canaux mémoire ! Cependant, il peut y avoir un entrelacement lié à la présence des banques SDRAM. L'entrelacement est géré juste avant le séquenceur mémoire. Le '''circuit d'entrelacement''' intervertit certains bits de l'adresse lors des accès mémoires. On trouve aussi des mémoires FIFOs pour mettre en attente les requêtes mémoire provenant du processeur. Elles permettent de recevoir plusieurs accès mémoire consécutifs. Si vous vous demandez comment plusieurs accès mémoire consécutifs peuvent avoir lieu, sachez que c'est une bonne question. Pour le moment, nous allons dire que ces accès proviennent du processeur ou de la mémoire cache, sans expliquer comment. Le contrôleur mémoire reçoit une série d'accès mémoire assez rapprochés, qu'il doit exécuter au mieux. [[File:Controleur mémoire d'une SDRAM avec entrelacement.png|centre|vignette|upright=2.5|Contrôleur mémoire d'une SDRAM avec entrelacement.]] Pour gérer l'entrelacement, le contrôleur de SDRAM prend en entrée l'adresse envoyée par le processeur, et la découpe en plusieurs champs : un pour sélectionner la banque adéquate, un autre pour la ligne, un autre pour la colonne, un autre pour la rangée. Une solution simple désactive l'entrelacement. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de banque || Adresse de ligne || Adresse de colonne |} Si cette solution fonctionne bien si les accès mémoire sont assez aléatoires, en pratique, mieux vaut utiliser l'entrelacement. ===L'entrelacement de banques/lignes=== Une première méthode d'entrelacement est l''''entrelacement de banques'''. Elle répartit deux lignes consécutives dans deux banques différentes. Pour comprendre l'idée, prenons un exemple. Imaginons une mémoire avec deux banques et 4 lignes. Imaginons qu'on parcoure/balaye la mémoire RAM en partant des adresses basses. Sans entrelacement de ligne, les accès se feront comme suit, de gauche à droite : {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Banque numéro 1 !! colspan="4" | Banque numéro 2 |- | Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 || Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 |} Avec l'entrelacement de banques, les accès se feront comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 |- | Ligne 1 || Ligne 1 || Ligne 2 || Ligne 2 || Ligne 3 || Ligne 3 || Ligne 4 || Ligne 4 |} Avec N banques, la première ligne ira dans la première banque, la seconde ligne dans la seconde, et ainsi de suite jusqu'à la énième banque qui va dans la banque numéro N. Et la ligne N+1 repart dans la première banque. L'avantage est que passer d'une ligne à la suivante est assez rapide. Au lieu de devoir fermer la première ligne et ouvrir la suivante on ouvrira directement la ligne suivante dans une banque différente, ce qui sera plus rapide. La commande PRECHARGE pourra être envoyée à la seconde banque en avance. Pour cela, l'adresse est découpée comme suit. {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne (précise à l'octet) |} ===L'entrelacement de rangées=== Il est aussi possible de faire la même chose, mais avec les rangées. Pour simplifier fortement, une rangée est simplement un chip mémoire. En réalité, les rangées ne sont pas des chip mémoire, mais un ensemble de chips mémoire regroupés ensemble histoire d'atteindre les 64 bits du bus de données. Par exemple, une rangée peut combiner 8 chips mémoire avec un bus de données de 8 bits chacun pour obtenir les 64 bits du bus de données d'une SDRAM. Mais nous passons ce détail sous silence dans les explications qui vont suivre, par souci de simplification. Pour faire comprendre l'entrelacement de rangée, le mieux est d'utiliser un exemple, le même que précédemment. Sans entrelacement de rangée, on change de chip mémoire une fois qu'on a balayé toutes ses adresses. {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Rangée numéro 1 !! colspan="4" | Rangée numéro 2 |- | Banque 1 || Banque 2 || Banque 3 || Banque 4 || Banque 1 || Banque 2 || Banque 3 || Banque 4 |} Avec l'entrelacement de ligne, on change de rangée dès qu'on change de banque/ligne. {|class="wikitable" |+ Adresse mémoire |- ! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 |- | Banque 1 || Banque 1 || Banque 2 || Banque 2 || Banque 3 || Banque 3 || Banque 4 || Banque 4 |} En clair, quand on a épuisé toutes les banques dans une même rangée, on passe à la rangée suivante au lieu de rester dans la même rangée. Notons que dans l'exemple précédent, on a combiné l'entrelacement de rangée et de banque, mais on aurait pu utiliser l'entrelacement de rangée seul. Mais ce n'est pas le cas le plus courant en pratique. Toujours est-il qu'en combinant entrelacement de rangée et de ligne, le découpage de l'adresse est le suivant : {|class="wikitable" |+ Adresse mémoire |- | Adresse de ligne || Numéro de rangée || Adresse de banque || Adresse de colonne (précise à l'octet) |} D'autres méthodes d'entrelacement sont possibles, mais elles sont plus complexes. Chaque contrôleur mémoire fait un peu à sa sauce. Ils permettent en général de choisir entre plusieurs entrelacements. Au minimum, ils permettent de désactiver l'entrelacement, d'activer un entrelacement de ligne, et d'autres entrelacements plus complexes, dont certains sont propriétaires et tenus secrets par le fabricant. ===L'entrelacement avec le ''dual channel''=== [[File:Dual channel slots.jpg|vignette|Slots mémoires avec ''dual channel''.]] L'usage du ''dual channel'' complique encore l'entrelacement. Là encore, il y a deux grandes solutions : avec et sans entrelacement des canaux mémoire. Rappelons le principe : deux barrettes de RAM sont accédées en parallèle. Et pour cela, il faut utiliser l'entrelacement. Typiquement, chaque barrette mémoire fournit 64 bits, ce qui fait que l'on peut accéder à 128 bits d'un seul coup, par exemple avec un accès en rafale. Sans ''dual channel'', la première barrette correspond à la moitié haute de la RAM, la seconde barrette correspond à la moitié basse. Avec ''dual channel'', une forme spécifique d'entrelacement est activée. Concrétement, deux blocs de 64 bits sont placés dans des canaux mémoire séparés. Idem avec du triple ou quadruple canal, mais c'est alors trois ou quatre blocs de 64 bits qui sont dispersés dans des canaux consécutifs. Le découpage de l'adresse est alors le suivant : {|class="wikitable" |+ Adresse mémoire |- ! colspan="6" | Sans entrelacement |- | Numéro de canal/barrette || Reste de l'adresse || Position de l'octet dans un bloc de 64 bits |- | colspan="6" | |- ! colspan="6" | Avec entrelacement |- | Reste de l'adresse || Numéro de canal/barrette || Position de l'octet dans un bloc de 64 bits |} [[File:Décodage d'adresse avec dual channel.png|centre|vignette|upright=2|Décodage d'adresse avec dual channel.]] Les contrôleurs de SDRAM précédents sont assez basiques et ne représentent pas les contrôleurs les plus évolués. Ils étaient utilisés sur les anciens PC, à une époque où ils étaient encore sur la carte mère du processeur. Mais de nos jours, le contrôleur mémoire est intégré au processeur. Et il incorpore de nombreuses optimisations afin de gagner en performances. Malheureusement, ces optimisations sont elles-mêmes dépendantes des optimisations intégrées dans le processeur, ce qui fait qu'on ne peut pas en parler ici. Elles demandent que le contrôleur mémoire reçoive plusieurs accès mémoire simultanés, ce qui n'a aucun sens à ce stade du cours. Aussi, nous allons les passer sous silence pour le moment. Nous les verrons dans le chapitre sur le parallélisme mémoire. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=La désambiguïsation mémoire | prevText=La désambiguïsation mémoire | next=Les processeurs superscalaires | nextText=Les processeurs superscalaires }} </noinclude> owiqovkpwph1gk5kpr2cxvaxip8rzab 764736 764733 2026-04-23T22:51:07Z Mewtow 31375 /* Le ré-ordonnancement des commandes mémoires */ 764736 wikitext text/x-wiki Dans ce chapitre, nous allons voir les techniques qui permettent de gérer plusieurs accès mémoire simultanés directement au niveau du cache ou de la mémoire RAM. Par plusieurs accès mémoire simultanés, vous pensez sans doute à l'usage de cache multiports, voire de mémoires RAM multiport. C'est en effet une solution, mais ce n'est clairement pas la seule. Par exemple, il est possible de pipeliner l'accès au cache, voire à la mémoire RAM. Il existe de nombreuses techniques de '''parallélisme mémoire''', et nous allons les voir dans ce chapitre. Pour donner un exemple, il est possible de pipeliner les accès mémoire. Il est possible de pipeliner l'accès au cache et/ou l'accès à la mémoire, et nous verrons comment faire dans ce chapitre. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, qu'ils aient ou non un pipeline dynamique. Par contre, pipeliner la mémoire est une technique ancienne, aujourd'hui peu utilisée. Elle était utilisée sur des très vieux ordinateurs, qui avaient un pipeline mais pas de mémoire cache. Mais de nos jours, les mémoires SDRAM et DDR ne sont pas adaptées pour ça, il est très difficile de les pipeliner correctement. Le parallélisme mémoire est utile aussi bien sur des processeurs à émission dans l'ordre que dans le désordre. Par exemple, un processeur ''in-order'' avec des lectures non-bloquantes peut e, profiter. Il peut techniquement lancer une seconde lecture, après avoir rencontré une première lecture non-bloquante. En général, le nombre de lectures consécutives est cependant limité à moins d'une dizaine. À l'opposé, les processeurs sans lectures non-bloquantes profitent pas du parallélisme mémoire. À la rigueur, ils peuvent en profiter s'ils intègrent des techniques de préchargement. Pendant que le processeur exécute une lecture/écriture, il peut précharger une autre donnée en parallèle. Inutile de dire que sans pipeline processeur, le parallélisme mémoire ne sert pas à grand-chose. ==Le ''line fill buffer'' et les techniques associées== Lors d'un défaut de cache, le processeur doit attendre que toute la ligne de cache soit chargée avant d'être utilisable. Or, la taille d'une ligne de cache est supérieure à la largeur du bus mémoire, ce qui fait qu'une ligne de cache est chargée en plusieurs fois, morceaux par morceaux, mot mémoire par mot mémoire. Le chargement peut se faire directement dans le cache, mais ce n'est pas une solution très pratique. À la place, beaucoup de processeurs ajoutent une mémoire tampon entre la RAM et le cache, appelée le '''tampon de remplissage de ligne''' (''line-fill buffer'') dans les processeurs modernes. Lors d'un défaut de cache, le processeur charge la donnée de la RAM dans le tampon de remplissage de ligne, mot mémoire par mot mémoire. Une fois plein, le tampon de remplissage de ligne est recopié dans la ligne de cache. Le tampon de remplissage de ligne contient une ligne de cache, si ce n'est que certains bits de contrôle ne sont pas présents. Le tampon de remplissage de ligne contient un bit de validité pour chaque mot mémoire de la ligne de cache, qui indiquent si le mot mémoire a été chargé. Par exemple, prenons un processeur 64 bits, qui gère donc des mots mémoire de 8 octets, avec des lignes de cache 256 octets/32 mots mémoire. Le tampon de remplissage de ligne contiendra 32 bits de validité. Si le processeur a chargé les 6 premiers mots mémoire, les 6 premiers bits de validité seront mis à 1, les autres seront encore à zéro. Il faut noter que la disponibilité de la ligne complète se détermine assez facilement en faisant un ET logique entre tous les bits de validité. Le processeur sait ainsi quand la ligne de cache est disponible entièrement et donc quand la transférer dans le cache. La même chose existe avec une hiérarchie de cache, sauf que l'on trouve une mémoire tampon entre chaque niveau de cache. Il y a un tampon de remplissage de ligne entre le cache L1 et le cache L2, entre le cache L2 et le L3, etc. Si le cache ne gère qu'un seul défaut de cache à la fois, le ''fill line buffer'' est une mémoire très simple, qui ne mémorise qu'une seule ligne de cache. Et cela vaut aussi bien pour un cache bloquant que non-bloquant. Mais sur les caches capables de gérer plusieurs défauts simultanés, le tampon de remplissage de ligne est une mémoire de type FIFO ou LIFO, capable de mémoriser plusieurs lignes de cache. Notons que le tampon de remplissage de ligne est très utile pour implémenter certaines techniques, par exemple le contournement du cache. Nous avions vu dans le chapitre sur le cache que certains accès mémoire doivent contourner le cache, pour des raisons de cohérence des caches. C'est notamment nécessaire pour accéder aux périphériques, mais c'est aussi utile pour des raisons de performances dans des cas très spécifiques. Les accès qui contournent le cache se font directement dans le tampon de remplissage de ligne : le processeur écrit ou lit les données depuis ce tampon de remplissage de ligne, sans accéder au cache. ===L'''early restart'' et le ''critical word load''=== La présence du ''line buffer'' permet une optimisation assez intéressante, qui permet de réduire la latence des défauts de cache. L'optimisation consiste à lire un mot mémoire dans le tampon de remplissage de ligne, même si la ligne de cache complète n'a pas encore été chargée. Il existe deux manières de faire cela, qui portent les noms d'''early restart'' et de ''critical word load''. La première est la version la plus simple, la seconde est plus complexe mais plus performante. Avec la technique d''''''early restart''''', la ligne de cache est chargée normalement, en partant de son premier mot mémoire. Dès que le mot mémoire lu/écrit par le processeur est copié dans le ''line fill buffer'', il est envoyé au processeur immédiatement. Illustrons le tout par un exemple, où une ligne de cache fait 16 mots mémoire. Le processeur effectue une lecture, qui lit le 5ème mot mémoire. Sans ''early restart'', le processeur doit charger les 16 mots mémoire avant de faire la lecture dans le cache. Avec ''early restart'', le processeur reçoit la donnée dès que le 5ème mot mémoire est disponible. Le processeur doit attendre que les 4 premiers mots mémoire soient chargés, puis le 5ème mot mémoire arrive et est envoyé directement au processeur, il est lu directement depuis le ''line fill buffer''. Les 11 mots mémoire suivants sont ensuite chargés dans le cache pendant que le processeur fait des calculs dans son coin. Le ''critical word load'' est une optimisation de la technique précédente où le chargement de la ligne de cache commence directement à la donnée demandée par le processeur. Pour reprendre l'exemple précédent, où le processeur demande le 5ème mot mémoire sur 16, le ''critical word load'' charge le 5ème mot mémoire en premier et l'envoie au processeur, ce qui fait qu'il est chargé très rapidement. Pas besoin d'attendre que le processeur charge les 4 mots mémoire précédents comme avec l'''early restart''. Dans le détail, le processeur charge le 5ème mot mémoire en premier, puis charge les 11 suivants, et termine par les 4 mots mémoire du début. En clair, le ''critical word load'' commence par charger le mot mémoire lu, puis les blocs suivants, avant de revenir au début du bloc pour charger les blocs restants. Ainsi, la donnée demandée par le processeur sera la première disponible. Pour cela, l'organisation du tampon de remplissage de ligne est modifiée de manière à rendre cela possible. Il a une taille égale à une ligne de cache complète, qui contient elle-même plusieurs mots mémoire. Dans le ''line-fill buffer'', chaque mot mémoire est stocké avec un tag, qui indique l'adresse du mot mémoire stocké dans le ''line-fill buffer''. Le ''line-fill buffer'' est donc un cache un peu particulier, qui fonctionne comme un cache du point de vue du processeur, comme une mémoire FIFO pour les transferts avec le cache. Ainsi, un processeur qui veut lire dans le cache après un défaut peut accéder à la donnée directement depuis le tampon de remplissage de ligne, alors que la ligne de cache n'a pas encore été totalement recopiée en mémoire. ==Les caches pipelinés== Il est possible de pipeliner l'accès au cache, ce qui demande juste de rajouter des registres au bon endroits et quelques circuits de contrôle. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, même ceux avec un pipeline dynamique. Et l'implémentation peut se faire de plusieurs manières différentes. Il faut dire que découper une mémoire cache en plusieurs étapes peut se faire de plusieurs manière. Commençons par la plus simple. Beaucoup de processeurs des années 2000 avaient une fréquence peu élevée comparée aux standards d'aujourd'hui, ce qui fait que leur cache avait bien un temps d'accès d'un cycle d'horloge. Mais l'accès au cache se faisait en deux cycles d'horloge : un cycle pour calculer une adresse et un cycle pour l'accès mémoire proprement dit. Et ces deux étapes étaient pipelinées, à savoir que deux micro-opérations mémoire peuvent s'exécuter en même temps : une dans l'étage de calcul d'adresse, une autre dans le cache. L'unité mémoire était donc pipelinée, alors que l'accès au cache ne l'était pas. Les processeurs qui implémentaient cette technique regroupent les micro-architectures K5 et K6 d'AMD, les processeurs Intel de micro-architecture P6 (Pentium 2 et 3) et quelques autres. Il est aussi possible de pipeliner l'accès au cache lui-même. Avec un cache pipeliné, l'accès au cache ne se fait pas en un seul cycle, mais en plusieurs. Cependant, on peut lancer un nouvel accès au cache à chaque cycle d'horloge, comme un processeur pipeliné lance une nouvelle instruction à chaque cycle. L'implémentation est assez simple : il suffit d'ajouter des registres dans le cache. Pour cela, on profite que le cache est composé de plusieurs composants séparés, qui échangent des données dans un ordre bien précis, d'un composant à un autre. De plus, le trajet des informations dans un cache est linéaire, ce qui les rend parfois pour l'usage d'un pipeline. ===Le ''pipelining'' des caches ''direct-mapped''=== Voici ce qui se passe avec un cache directement adressé. Pour rappel ce genre de cache est conçu en combinant une mémoire RAM, généralement une SRAM, avec quelques circuits de comparaison et un MUX. L'accès au cache se fait globalement en deux étapes : la première lit la donnée et le ''tag'' dans la SRAM, et on utilise les deux dans une seconde étape. Nous avions vu il y a quelques chapitre comment pipeliner des mémoires, dans le chapitre sur les mémoires évoluées. Et bien ces méthodes peuvent s'utiliser pour la mémoire RAM interne au cache !L'idée est d'insérer un registre entre la sortie de la RAM et la suite du cache, pour en faire un cache pipéliné, à deux étages. Le premier étage lit dans la SRAM, le second fait le reste. L'implémentation sur les caches associatifs à plusieurs voies est globalement la même à quelques détails près. [[File:Cache directement adressé pipeliné - deux étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - deux étages]] Avec le circuit précédent, il est possible d'aller plus loin, cette fois en pipelinant l'accès à la mémoire RAM interne au cache. Pour comprendre comment, rappelons qu'une mémoire SRAM est composée d'un plan mémoire et d'un décodeur. L'accès à la mémoire demande d'abord que le décodeur fasse son travail pour sélectionner la case mémoire adéquate, puis ensuite la lecture ou écriture a lieu dans cette case mémoire. L'accès se fait donc en deux étapes successives séparées, on a juste à mettre un registre entre les deux. Il suffit donc de mettre un gros registre entre le décodeur et le plan mémoire. [[File:Cache directement adressé pipeliné - trois étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - trois étages]] Et on peut aller encore plus loin en découpant le décodeur en deux circuits séparés. En effet, rappelez-vous le chapitre sur les circuits de sélection : nous avions vu qu'il est possible de créer des décodeurs en assemblant des décodeurs plus petits, contrôlés par un circuit de prédécodage. Et bien on peut encore une ajouter un registre entre ce circuit de prédécodage et les petits décodeurs. : Théoriquement, toute l'adresse est fournie d'un seul coup au cache, la quasi-totalité des processeurs présentent l'adresse complète à un cache pipeliné. Mais le Pentium 4 fait autrement. Il faut noter que les premiers étages manipulent l'indice dans la SRAM, qui est dans les bits de poids faible, alors que les étages ultérieurs manipulent le ''tag'' qui est dans les bits de poids fort. Les concepteurs du Pentium 4 ont alors décidé de présenter les bits de poids faible lors du premier cycle d'accès au cache, puis ceux de poids fort au second cycle. Pour cela, l'ALU fonctionnait à une fréquence double de celle du processeur, tout comme le cache L1. Il n'y avait pas de pipeline proprement dit, mais cela réduisait grandement la latence d'accès au cache. ===Le ''pipelining'' des caches splittés/sériels=== Il est aussi possible de pipeliner un cache dits splittés, aussi appelés à accès sériel. Pour rappel, les caches à accès sériel vérifient si il y a succès ou défaut de cache, avant d'accéder aux lignes de cache en cas de succès. Ils font donc différemment des autres caches, qui accèdent à une ligne de cache, avant de déterminer s'il y a succès ou défaut en lisant le tag de la ligne de cache. Les caches sériels disposent de deux SRAM : une pour les tags des lignes de cache et une pour les données. Ils accèdent à la SRAM pour les tags, avant d’accéder à la SRAM des données en cas de succès de cache. Vu que l'accès se fait en deux étapes, une vérification des tags suivie de la lecture/écriture des données, il est facile à pipeliner. Pipeliner le cache permet de régler le problème des accès au cache L1, et elle est tout le temps utilisé sur les processeurs modernes. Mais que faire en cas de défaut de cache ? ==Les caches non bloquants== Un '''cache bloquant''' est un cache auquel le processeur ne peut pas accéder pendant un défaut de cache. Il faut attendre que la lecture ou écriture en RAM soit terminée avant de pouvoir utiliser de nouveau le cache. Un '''cache non bloquant''' n'a pas ce problème : on peut l'utiliser pendant un défaut de cache. Les caches non bloquants permettent de démarrer une nouvelle lecture ou écriture alors qu'une autre est en cours, ce qui permet d'exécuter plusieurs lectures ou écritures en même temps. ===Les ''Miss Handling Status Registers''=== Lors d'un défaut de cache, la mémoire RAM est consultée pendant le défaut de cache, mais le cache est inutilisé. Un défaut de cache n'utilise pas le cache, ce n'est qu'un accès en mémoire RAM, sur le bus mémoire (ou un accès aux niveaux de cache inférieurs, peu importe). Le cache en lui-même est laissé libre, rien n’empêche d'y accéder, il est en réalité intrinsèquement non-bloquant. Les caches, bloquants comme non-bloquants, sont en réalité composés d'une mémoire cache proprement dite, entourée de circuits qui servent d'interface entre le processeur et le cache lui-même. Et parmi les circuits tout autour, certains gèrent l'accès au cache lors d'un défaut de cache. Ils sont regroupés sous le terme de '''''Miss Handling Architecture''''' (MHA). La différence entre un cache bloquant et un cache non-bloquant est en réalité liée à l'implémentation de la MHA. Les caches bloquants coupent volontairement l'accès au cache lors d'un défaut, car les défauts sont plus simples à gérer ainsi. Le défaut de cache rapatrie une donnée depuis la RAM, qui sera écrite dans le cache. Et il ne faut pas qu'une tentative d'accès à cette donnée ait lieu avant qu'elle ne soit chargée. Mais ce blocage est général et touche tout le cache, alors que seule une ligne de cache est concernée par le défaut de cache. L'idée derrière un cache non-bloquant est que seule la ligne de cache est bloquée, mais les autres sont accessibles. L'idée est alors de mémoriser les lignes de cache concernées par le défaut de cache, afin d'en bloquer l'accès. À chaque accès, on vérifie si la ligne de cache est déjà réservée par un défaut de cache. La lecture/écriture est alors bloquée si c'est le cas, mais elle accepte les accès sinon. Pour cela, la ''Miss Handling Architecture'' contient des registres qui mémorisent des informations sur les défauts de cache en cours. Ils portent le nom de '''''miss status handling registers''''', que j’appellerais dorénavant MSHR, qui sont aussi appelés des '''''miss buffer'''''. Le contenu des MSHR varie beaucoup suivant le processeur, mais ces derniers stockent au minimum les informations suivantes : * Le numéro de la ligne de cache dans laquelle les données sont chargées. * Un bit de validité qui indique si le MSHR est vide ou pas, qui est mis à 0 quand le défaut de cache est résolu. * Un ou plusieurs champs de lecture/écriture, qui contiennent des informations sur la lecture/écriture. ** Pour une lecture, elle contient des informations sur la destination de la donnée, à savoir qui prévenir quand le défaut de cache est terminé. C'est parfois un nom/numéro de registre (celui dans lequel charger la donnée), mais c'est souvent le numéro de l'entrée dans le ''load/store queue''. ** Pour les écritures, elle contient la donnée à écrire, ou éventuellement un numéro de ''load/store queue'' où se trouve la donnée à écrire. Il faut noter que le bus mémoire ne peut gérer qu'un seul défaut de cache à la fois. Aussi, il est intéressant de regarder ce qui se passe lorsqu'un second défaut de cache survient, pendant qu'un premier est en cours. Dans ce cas, il y a deux réponses qui correspondent à deux types de caches non-bloquants, qui portent les noms barbares de caches de type succès après défaut et défaut après défaut. Sur le premier type, il ne peut pas y avoir plusieurs défauts de cache simultanés. Dès qu'un second défaut de cache survient, le cache stoppe son activité et on ne peut plus démarrer de nouvelle lecture/écriture, tant que le premier défaut de cache n'est pas résolu. Le second type est plus souple et autorise la survenue de plusieurs défauts de cache simultanés. Du moins, jusqu'à une certaine limite, car le cache ne peut supporter qu'un nombre limité d'accès mémoires simultanés (pipelinés). ===Les accès simultanés à une même ligne de cache=== Il arrive que le processeur fasse plusieurs accès mémoire simultanés à la même ligne de cache. Si la ligne de cache en question n'a pas encore été chargée dans le cache, alors on a plusieurs défauts de cache consécutifs pour la même ligne de cache, et le cache non-bloquant doit gérer la situation. Pour la suite, il va falloir faire une petite distinction entre les défauts primaires et secondaires. Imaginons qu'un défaut de cache ait lieu et demande à charger une donnée dans la ligne de cache numéro N. Il s'agit du premier défaut impliquant cette ligne de cache précise, ce qui lui vaut le nom de '''défaut de cache primaire'''. Mais par la suite, d'autres accès mémoire à la même ligne de cache ont lieu, alors que la ligne de cache n'est pas encore disponible. Dans ce cas, il s'agit de '''défauts de cache secondaires'''. Pour l'unité d'accès mémoire, les défauts de cache primaire et secondaire sont différents (ils prennent tous une entrée dans la ''load/store queue''). Mais pour le cache, ils ne correspondent qu'à un seul accès au cache : celui qui demande de charger la ligne de cache demandée. Les défauts de cache primaire et secondaire à la même ligne de cache se voient attribuer un MSHR unique. La gestion des défauts de cache secondaires dépend du cache non-bloquant. La solution la plus simple ne permet pas les défauts de cache secondaires. Le cache ne permet pas deux défauts de cache simultanés pour la même ligne de cache. Les autres solutions le permettent, en fusionnant des accès simultanés à la même ligne de cache en un seul au niveau des MSHR. Dans tous les cas, détecter les défauts de cache secondaires sont un problème qu'il faut détecter. La ''Miss Handling Architecture'' doit détecter les défauts de cache secondaire. Pour cela, elle procède comme suit. Lors de chaque défaut de cache, la MHA récupère le numéro de la ligne de cache associé. Il vérifie alors chaque MSHR pour vérifier s'il contient le numéro en question. S'il n'y a aucune correspondance dans les MSHR, alors c'est signe que le défaut de cache est un défaut primaire. Mais s'il y en a une, alors c'est un défaut secondaire. Évidemment, cela signifie que lors d'un défaut de cache, le numéro de ligne de cache est envoyé à tous les MSHR, pour comparaison. Les MSHR sont donc regroupés dans une mémoire associative, une sorte de mini-cache, faciliter l'implémentation. Lors d'un défaut de cache primaire, l'accès à un cache non-bloquant se fait comme suit : le processeur envoie une adresse au cache, accède à celui-ci, et détecte la survenue d'un défaut de cache. Il en profite alors pour attribuer une ligne de cache dans laquelle sera chargée ce défaut. L'attribution est très simple dans le cas des caches ''direct mapped'', ou associatifs par voie, pour lesquels l'attribution se fait assez simplement. Il mémorise alors cette information dans les MSHR, après avoir vérifié que le défaut de cache n'était pas un défaut secondaire. Lors des accès ultérieurs à une adresse proche, censée être dans la même ligne de cache, le processeur va encore une fois rencontrer un défaut de cache. Il va alors déterminer le numéro de la ligne de cache associée à l'adresse, et comparer ce numéro avec les MSHRs. Si un MSHR contient ce numéro, c'est signe que le défaut de cache est un défaut secondaire. La MHA réagit alors différemment selon le processeur considéré. Une première solution n'autorise pas les défauts de cache secondaires. Si l'un d'entre eux survient, le processeur est gelé par un ''pipeline stall'', une bulle de pipeline. Une autre solution fusionne les défauts de cache secondaires avec le défaut de cache primaire : tout cela ne correspond qu'à un seul défaut de cache pour lui. ===Les MSHR simples=== Un cache non-bloquant à '''MSHR simple''' contient juste plusieurs MSHR qui mémorisent juste un numéro de ligne de cache, un bit de validité, le champ de lecture/écriture, et l'adresse exacte de lecture/écriture. Avec cette organisation, il est possible d'avoir plusieurs défauts de cache séparés, mais à la condition que chaque défaut accède à une ligne de cache différente. Deux accès simultanés à une même ligne de cache ne sont pas possibles, les défauts de cache secondaires ne sont pas autorisés. Ainsi, chaque défaut de cache se voit attribuer son propre MSHR, chacun contient un numéro de ligne de cache différent. Pour comprendre pourquoi c'est impossible de gérer les défauts secondaires, il faut regarder le champ de lecture/écriture. Si on veut effectuer plusieurs écritures consécutives à la même adresse, le MSHR n'aura pas de quoi mémoriser les deux données à écrire. Il pourra mémoriser la première donnée à écrire, pas la seconde. Même chose lors d'une lecture : le champ lecture/écriture peut mémorisr la destination de la première lecture, pas de la seconde. L'avantage est que la MHA n'a besoin que des MSHR et de quelques circuits annexes. Les autres solutions rajoutent des circuits annexes pour gérer les défauts de cache secondaires, qui utilisent beaucoup de circuits. Le cout en circuit est donc élevé, mais le gain en performance est là. Passons maintenant aux caches non-bloquants qui autorisent les défauts de cache secondaire. La solution la plus simple consiste à utiliser ===Les MSHR adressés implicitement=== Avec les '''MSHR adressés implicitement''', il est possible de fusionner plusieurs accès mémoire à une même ligne de cache, mais sous une condition très importante : ces accès lisent/écrivent des mots mémoire différents. Par exemple, imaginons qu'une ligne de cache contienne 8 mots mémoire de 64 bits. Si un premier accès mémoire lit le mot mémoire numéro 7 (dernier mot mémoire de la ligne), et le second accès le mot mémoire numéro 3, alors la fusion est possible. Mais si deux accès mémoire veulent lire/écrire le mot mémoire numéro 7, alors la fusion n'est pas possible et le processeur se bloque, un ''pipeline stall'' survient. Un MSHR adressé implicitement est un MSHR simple, qui contient naturellement un numéro de ligne de cache (tag) et un bit de validité, sauf que le champ de lecture/écriture est dupliqué. Les différents champs lecture/écriture d'un MSHR sont regroupés dans une mémoire RAM/cache qui contient autant d'entrées qu'il y a de mot mémoire dans une ligne de cache. Chaque entrée de la table est associée à un mot mémoire de la ligne de cache et stocke des informations sur celui-ci. Une entrée mémorise, au minimum : * Un champ de lecture/écriture qui contient soit la destination de la lecture, soit la donnée à écrire. * Un bit R/W permettant d'interpréter correctement le champ de lecture/écriture. * Un bit de validité pour chaque entrée de la table, qui dit si un défaut de cache antérieur accède déjà à ce mot mémoire. La fusion de deux défauts de cache est ainsi assez simple. Un défaut de cache primaire/secondaire configure l'entrée associée au mot mémoire lu/écrit. Si un défaut secondaire ultérieur lit/écrit un mot mémoire différent (dont le bit de validité de l'entrée dans la table est à zéro), pas de conflit : il configure une autre entrée, vide. Mais s'il lit/écrit un mot mémoire pour lequel l'entrée de la table est occupée, il y a conflit, le processeur est bloqué par un ''pipeline stall'', une bulle de pipeline. Avec cette organisation, le nombre de MSHR indique combien de lignes de cache peuvent être lues en même temps depuis la mémoire. Quant au nombre d'entrées par MSHR, il détermine combien d'accès mémoires qui ne se recouvrent pas peuvent avoir lieu en même temps. ===Les MSHR adressés explicitement=== Les '''MSHR adressés explicitement''' n'ont pas les contraintes des MSHR adressés implicitement. Avec eux, il est possible d'avoir plusieurs défauts de cache pointant vers la même ligne de cache, mais aussi vers le même mot mémoire. De tels défauts de cache apparaissent sur les processeurs à exécution dans le désordre, mais sont très rares, voire inexistants, sur les processeurs ''in-order''. L'idée est encore que chaque MSHR est associée à une table mémoire qui mémorise des entrées. Cette table n'est autre qu'une mémoire FIFO. Sauf que cette fois-ci, une entrée n'est pas associée à un mot mémoire. Une entrée est associée à un défaut de cache. Une entrée mémorise là encore la destination de la lecture ou la donnée de l'écriture suivi du bit R/W, ainsi que d'un bit de validité par entrée, mais aussi : la position du mot mémoire lu dans la ligne de cache. C'est cette dernière information qui n'était pas présente dans les MSHR adressés implicitement. De plus, le nombre d'entrée par MSHR n'est pas égal au nombre de mots mémoires dans une ligne de cache, mais peut être arbitrairement grand. L'avantage d'un tel cache est qu'il est capable de traité les défauts secondaires concernant un même mot mémoire, et ce, car les accès à la ligne de cache concernée se produisent dans l'ordre de génération des défauts (la table mémoire est une FIFO). ===Les MSHRs inversés=== Généralement, plus on veut supporter de défauts de cache, plus le nombre de MSHR et d'entrées augmente. Mais au-delà d'un certain nombre d'entrées et de MSHR, les MSHR adressés implicitement et explicitement ont tendance à bouffer un peu trop de circuits. Utiliser une organisation un peu moins gourmande en circuits est donc une nécessité. Cette organisation plus économe se base sur des MSHR inversés. Les MSHR inversés ne contiennent qu'une seule entrée, en quelque sorte : au lieu d’utiliser n MSHR de m entrées chacun, on va utiliser n × m MSHR inversés. La différence, c'est que plusieurs MSHR peuvent contenir un tag identique, contrairement aux MSHR adressés implicitement et explicitement. Lorsqu'un défaut de cache a lieu, chaque MSHR est vérifié. Si jamais aucun MSHR ne contient de tag identique à celui utilisé par le défaut, un MSHR vide est choisi pour stocker ce défaut, et une requête de lecture en mémoire est lancée. Dans le cas contraire, un MSHR est réservé au défaut de cache, mais la requête n'est pas lancée. Quand la donnée est disponible, les MSHR correspondant à la ligne qui vient d'être chargée vont être utilisés un par un pour résoudre les défauts de cache en attente. ===Les MSHR intégrés au cache=== Certains chercheurs ont remarqué que pendant qu'une ligne de cache est en train de subir un défaut de cache, celle-ci reste inutilisée, et son contenu est destiné à être perdu une fois le défaut de cache résolu. Ils se sont dit que, plutôt que d'utiliser des MSHR séparés, il vaudrait mieux utiliser la ligne de cache pour stocker les informations sur les défauts de cache en attente dans cette ligne de cache. Pour éviter tout problème, il faut rajouter un bit dans les bits de contrôle de la ligne de cache, qui sert à indiquer que la ligne de cache est occupée : un défaut de cache a eu lieu dans cette ligne, et elle stocke donc des informations pour résoudre les défauts de cache. ==L'entrelacement mémoire== Nous venons de voir comment une mémoire cache peut gérer plusieurs accès mémoire simultanés. Il se trouve que ce genre d'optimisation dépasse largement le cadre du cache. Les mémoires RAM ne sont pas en reste, elles aussi ! Il est possible de pipeliner l'accès à une mémoire RAM, en utilisant des techniques dites d''''entrelacement'''. Précisons cependant que cette optimisation n'est pas utilisée sur les mémoires SDRAM ou DDR modernes, du moins pas sans modifications majeures. Mais divers ordinateurs assez anciens, dont des superordinateurs, ont utilisé cette technique. La technique demande d'utiliser plusieurs mémoires séparées. Sur les anciens ordinateurs, les mémoires en question sont des chips mémoire, à savoir des circuits intégrés de DRAM. Pour faire plus général, nous allons utiliser le terme de '''banques''', ou encore de bancs mémoire. La différence est qu'il existe des mémoires multi-banque, qui regroupent plusieurs banques indépendantes dans un seul boitier, dans un seul chip mémoire. En clair, de telles mémoires regroupent plusieurs sous-mémoires dans un seul circuit intégré. Il est aussi possible d'utiliser plusieurs chips mémoire séparés, chacun ayant une banque. Reste à combiner ces banques pour former un pseudo-pipeline. ===Utiliser plusieurs banques sans entrelacement=== Sans optimisation particulière, les adresses sont réparties dans les banques comme indiqué ci-dessous. Il s'agit de ce qui s'appelle un arrangement horizontal, et nous avions vu celui-ci dans le chapiutre sur les mémoires SDRAM. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Avec cet arrangement, les bits de poids fort de l'adresse sont utilisées pour sélectionner la banque adéquate, et le reste de l'adresse est envoyé sur le bus d'adresse. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Adresse de banque !! Adresse dans la banque |- | Quelques bits de poids fort || Reste de l'adresse |} L'entrelacement change cette répartition, afin d'accélérer les accès mémoire. L'entrelacement classique vise à améliorer les accès à des adresses mémoires consécutives, mais des formes plus évoluées d'entrelacement visent à optimiser des accès mémoire arbitraires. Voyons-les dans l'ordre, du plus simple au plus complexe. ===L'entrelacement classique=== Avec l''''entrelacement classique''', des adresses consécutives sont réparties dans des banques consécutives. Précisons que cette attribution des adresses n'implique absolument pas la mémoire virtuelle ou n'importe quel mécanisme dans le processeur. La répartition décide que telle adresse mémoire va dans telle banque, à telle adresse dans la banque. Elle est donc le fait du contrôleur mémoire, donc en dehors du processeur (les contrôleurs mémoires n’étaient pas encore intégrés dans le processeur à l'époque). [[File:Répartition des adresses dans une mémoire interleaved.png|centre|vignette|upright=1.5|Répartition des adresses dans une mémoire interleaved.]] L'entrelacement simple permet d'accélérer les accès mémoire qui se font à des adresses consécutives. Avec l'entrelacement, chaque accès mémoire tombe dans une banque mémoire différente, ce qui fait qu'on peut démarrer un nouvel accès mémoire à chaque cycle d'horloge. Pendant qu'une banque est occupée par un accès mémoire, on démarre le suivant dans une autre banque, et ainsi de suite. Le tout se termine soit quand on a épuisé toutes les banques libres, soit quand l'accès en rafales se termine. Pas besoin d'attendre que la mémoire ait fini sa lecture/écriture avant de démarrer la lecture/écriture suivante. [[File:Pipemining mémoire.png|centre|vignette|upright=2.5|Pipelining mémoire]] L'animation suivante illustre bien le processus, avec quatres banques. [[File:Interleaving.gif|centre|vignette|upright=2.5|Pipelining mémoire]] L'entrelacement simple utilise les bits de poids faible pour sélectionner la banque, et les bits de poids fort pour la case mémoire. [[File:Adresse mémoire d'une mémoire entrelacée.png|centre|vignette|upright=2|Adresse mémoire d'une mémoire entrelacée]] Précisons que le temps d'accès mémoire ne change pas beaucoup avec l'entrelacement. Par contre, on peut faire plus d'accès mémoire simultanés. On peut démarrer un accès mémoire par cycle d'horloge, mais l'accès en lui-même prend plusieurs cycles. Les mémoires à entrelacement ont donc un débit supérieur aux mémoires qui ne l'utilisent pas. L'entrelacement classique pourrait être utilisé pour accélérer les transferts entre mémoire RAM et cache, qui se font en blocs d'adresses consécutives. Mais de nos jours, il n'est pas utilisé dans cette optique. Il faut dire qu'il est redondant avec le mode rafale des mémoires DRAM. Les deux font globalement la même chose et on voit mal comment utiliser les deux en même temps. Le mode rafale étant plus simple à implémenter, et plus léger niveau utilisation du bus de commande mémoire, il est préféré à l'entrelacement. Par contre, l'entrelacement a eu son heure de gloire sur d'anciens processeurs qui n'avaient pas de mémoire cache, alors qu'ils disposaient d'un pipeline. L'exemple typique est celui des nombreux ''processeurs vectoriels'', que nous n'avons pas encore abordé à ce stade du cours. De tels processeurs avaient un pipeline, une unité de calcul fortement pipelinée, et parfois même de l'exécution dans le désordre et du renommage de registres ! ===L'entrelacement par décalage=== L'entrelacement simple est très utile pour les accès en rafle ou équivalents. Par contre, il existe de rares situations où il n'est pas optimal. Une de ces situations est celle des accès en enjambée (en ''stride''), où l'on accède en série à des adresses sont séparées par N mots mémoires. [[File:Accès par enjambées.png|centre|vignette|upright=2|Accès par enjambées.]] De tels accès surviennent quand un logiciel accède à des structures de données spécifiques, à savoir des tableaux de structures, des matrices, ou d'autres structures de données dans le genre. Un cas classique est celui du parcours d'une matrice colonne par colonne. Les matrices de nombres sont mémorisées ligne par ligne, ce qui fait que parcourir une colonne demande de passer d'une ligne à la suivante et de faire des grands sauts en mémoire RAM, mais tous espacés par la même distance. Avec l'entrelacement simple, les accès en enjambée sont moins performants que les accès à des adresses consécutives, mais cela ne fonctionne pas trop mal. Le pire cas est celui où l'on a N banques et où les données sont justement placées toutes les N adresses. Des accès consécutifs vont tous tomber dans la même banque, on ne peut plus accéder à des banques différentes en parallèle. Mais en dehors de ce cas, on voit une amélioration par rapport à la situation sans entrelacement. Pour obtenir des performances maximales pour les accès en enjambées, il faut répartir les mots mémoires dans la mémoire autrement. L'organisation idéale est la suivante. : Dans les explications qui vont suivre, la variable N représente le nombre de banques, qui sont numérotées de 0 à N-1. On commence par organiser les N premières adresses comme une mémoire entrelacée simple : l'adresse 0 correspond à la banque 0, l'adresse 1 à la banque 1, etc. Pour le bloc suivant, on décale tout d'une adresse, à savoir qu'on commence à remplir les banques à partir de la seconde, non de la première. Une fois la fin du bloc atteinte, on finit de remplir le bloc en repartant du début du bloc. Le troisième bloc d'adresse subit le même traitement, sauf qu'on commence à remplir à partir de la seconde banque. Et on poursuit l’assignation des adresses en décalant d'un cran en plus à chaque bloc. Ainsi, chaque bloc verra ses adresses décalées d'un cran en plus comparé au bloc précédent. Si jamais le décalage dépasse la fin d'un bloc, alors on reprend au début. [[File:Mémoire interleaved par décalage.png|centre|vignette|upright=1.5|Mémoire entrelacée par décalage.]] En faisant cela, on remarque que les banques situées à N adresses d'intervalle sont différentes. Dans l'exemple du dessus, nous avons ajouté un décalage de 1 à chaque nouveau bloc à remplir. Mais on aurait tout aussi bien pu prendre un décalage de 2, 3, etc. Dans tous les cas, on obtient un entrelacement par décalage. Ce décalage est appelé le pas d'entrelacement, noté P. Le calcul de l'adresse à envoyer à la banque, ainsi que la banque à sélectionner se fait en utilisant les formules suivantes : * adresse à envoyer à la banque = adresse totale / N ; * numéro de la banque = (adresse + décalage) modulo N ; * décalage = (adresse totale * P) mod N. Avec cet entrelacement par décalage, on peut prouver que la bande passante maximale est atteinte si le nombre de banques est un nombre premier. Seulement, utiliser un nombre de banques premier peut créer des trous dans la mémoire, des mots mémoires inadressables. Malgré ce défaut, la technique a été utilisée sur quelques ordinateurs, avec l'exemple notable du superordinateur ''Burroughs Scientific Processor''. Pour éviter cela, il y a plusieurs solutions. Par exemple, on peut faire en sorte que N et la taille d'une banque soient premiers entre eux : ils ne doivent pas avoir de diviseur commun. Mais en pratique, elles n'ont pas vraiment été implémentées dans une vraie machine, et sont restées à l'état de recherche, aussi je les passe sous silence. ===L'entrelacement pseudo-aléatoire=== Une dernière méthode de répartition consiste à répartir les adresses dans les banques de manière "pseudo-aléatoire". L'idée exacte est de faire passer l'adresse dans une '''fonction de hachage''', plus ou moins complexe. Pour rappel, une fonction de hachage prend une entrée de grande taille et fournit en sortie un résultat de petite taille. Idéalement, elles doivent donner des résultats aussi différents que possible pour des entrées similaires, histoire de simuler une sorte de pseudo-aléatoire. Ici, une adresse est transformée en un numéro de banque. Une fonction de hachage simple ne fait que permuter des bits de l'adresse pour obtenir son résultat. Elle ne fait qu'échanger des bits de place avant de couper l'adresse en deux morceaux : un pour la sélection de la banque, et un autre pour la sélection de l'adresse dans la banque. Cette permutation est fixe, et ne change pas suivant l'adresse. Des fonctions de hachage plus complexes font des XOR bit à bit entre certains bits de l'adresse. ==Les contrôleurs mémoires SDRAM/DDR optimisés== Après avoir vu le cas des mémoires RAM, nous allons nous concentrer sur le cas particulier des mémoires SDRAM. Les mémoires SDRAM et DDR modernes sont capables de gérer plusieurs accès simultanés à la mémoire RAM, et leurs contrôleurs mémoire en font tout autant. C'est une différence majeure avec les mémoires asynchrones FPM/EDO, qui n'acceptaient qu'un seul accès mémoire à la fois. Leur contrôleur mémoire n'acceptait qu'un seul accès mémoire à la fois, c'était un contrôleur mémoire bloquant. Les contrôleurs mémoires des SDRAM sont eux non-bloquants et peuvent encaisser une dizaine d'accès mémoire à la fois. Peu de choses sont connues sur les contrôleurs de SDRAM/DDR modernes, les fabricants ne donnant que peu de détails dessus. Les rares simulateurs qui tentent de décrire leur fonctionnement, comme DRAM SIM I et II, sont particulièrement simples et ne vont pas dans le détail. Néanmoins, le peu qu'on sait est tout de même instructif. ===Rappel sur les SDRAM : tampon de ligne et commandes=== Les mémoires SDRAM sont des mémoires à tampon de ligne. Elles font un accès mémoire en deux étapes. La première étape recopie une ligne de N * 64 bits dans un tampon interne à la SDRAM. La seconde étape sélectionne une colonne, à savoir une donnée de 64 bits, qui est soit envoyée sur le bus de données pour une lecture, soit modifiée par une écriture. [[File:Mémoire à tampon de ligne.png|centre|vignette|upright=2|Mémoire à tampon de ligne]] Les accès mémoire sont traduits par un séquenceur mémoire en une série de commandes mémoires, qui sont séparées par des délais mémoire de quelques cycles d'horloge. Les délais sont très précis, et sont à respecter à la lettre. Une lecture ou une écriture se fait en maximum trois commandes : une commande PRECHARGE qui ferme la ligne précédemment utilisée, une commande ACT qui précise l'adresse de la ligne, et une commande READ ou WRITE qui précise l'adresse de la colonne et éventuellement la donnée à écrire. : Pour être plus précis, la commande PRECHARGE précharge les lignes de bits du plan mémoire à une tension particulière, ce qui les vide de leur contenu. Mais c'est un détail sans importance pour ce qui va suivre. Les SDRAM permettent de se passer de la première étape si des accès consécutifs se font dans la même ligne. Une fois activée, la ligne reste ouverte et on peut accéder plusieurs fois de suite dedans. On a alors juste à préciser l'adresse de colonne dedans. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | bgcolor="#FFA0FF" | PRECHARGE || bgcolor="#FFA0FF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || bgcolor="#A0FFFF" | READ (2) || bgcolor="#A0FFFF" | READ (3) || || || bgcolor="#A0FFFF" | READ (4) || bgcolor="#A0FFFF" | READ (5) || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | Donnée READ 1 || ||bgcolor="#A0FFFF" | Donnée READ 2 | bgcolor="#A0FFFF" | Donnée READ 3 || || || bgcolor="#A0FFFF" | Donnée READ 4 || bgcolor="#A0FFFF" | Donnée READ 5 |} Les SDRAM sont parfois capables de démarrer une commande avant que la précédente soit terminée. Mais le respect des délais mémoire est très limitant, ce qui fait qu'on ne peut pas parler de réel pipeline, comme c'est le cas sur les processeurs. Parlons plutot de '''commandes anticipées'''. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | || bgcolor="#A0FFFF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || || || bgcolor="#FFA0FF" | READ (2) || || || || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 | bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 | |} Un dernier point est que les chips mémoires de SDRAM sont composés de plusieurs banques, chacune étant une sorte de mini-mémoire miniature. Chaque banque a son propre décodeur, son propre tampon de ligne, ses propres multiplexeurs de colonne, sa logique de rafraichissement mémoire, etc. C'est comme si une SDRAM regroupait plusieurs mémoires séparées dans un même circuit intégré. [[File:Arrangement vertical.jpg|centre|vignette|upright=2.5|Mémoire multi-banques.]] ===La mise en attente des accès mémoire=== Un contrôleur de SDRAM peut accepter plusieurs accès mémoire en même temps. Mais cela ne signifie pas que le contrôleur sera prêt à les traiter immédiatement. Pour éviter tout problème de disponibilité, le contrôleur met en attente les accès mémoire que le processeur lui envoie, pour les exécuter dès que possible. Les accès mémoire sont mis en attente dans une mémoire FIFO, histoire de les exécuter dans leur ordre d'arrivée, notamment pour renvoyer les lectures dans l'ordre demandé. Il y a aussi une mémoire FIFO pour les données à écrire et une FIFO pour les données lues. Cette dernière sert au cas où le cache ne soit pas disponible quand on lui envoie la donnée lue. [[File:Module d'interface avec la mémoire.png|centre|vignette|upright=2.5|Contrôleur mémoire avec mise en attente des requêtes processeur et autres optimisations.]] Des optimisations sont possibles dès la mémoire FIFO. Par exemple, on peut regrouper plusieurs accès à des données consécutives en un seul accès en rafale. Le contrôleur mémoire analyse les requêtes mises en attente et détecte si certaines se font à des adresses consécutives. Si c'est le cas, il fusionne ces requêtes en une lecture/écriture en rafale. Une telle optimisation est appelée la '''combinaison de lecture''' pour les lectures, et la '''combinaison d'écriture''' pour les écritures. Une autre optimisation possible est le réacheminement lecture sur écriture (''Store to load forwarding''). Il s'agit d'un équivalent de l’optimisation utilisée dans le cadre de la désambiguïsation mémoire. Quand une lecture demande à accéder à une donnée écrite récemment, il se peut que l'écriture ait été mise en attente dans le contrôleur mémoire. Dans ce cas, le contrôleur mémoire peut renvoyer la donnée directement depuis ses mémoires FIFOs, sans faire d'accès mémoire en lecture. Les deux optimisations précédentes ne paraissent pas payer de mine, mais elles deviennent plus compréhensible quand on sait que certains contrôleurs mémoire peuvent mettre en attente beaucoup de données. Par exemple, l'''Intel E8870 - Scalable Memory Controller'' peut mettre en attente 64 lignes de cache dans la mémoire FIFO, soit 8 kibioctets de RAM ! Deux pages mémoire si la mémoire virtuelle utilise des pages de 4 kibioctets ! ===Les lectures anticipées=== La mise en attente des accès mémoire est une optimisation intéressante, mais pas mirifique. Elle permet surtout de ne pas bloquer le processeur si le contrôleur mémoire a du travail sur la planche. Cependant, elle peut être optimisée quand on se rend compte d'une chose : le processeur ne voit que les lectures, pas les écritures. Pour les écritures, il les envoie au contrôleur mémoire et ce dernier fait le travail demandé, le processeur n'est pas prévenu quand une écriture se termine. Mais pour les lectures, c'est différent, car il reçoit la donnée lue. En conséquence, il est possible d'optimiser les écritures sans que le processeur ne voit quoique ce soit. Une optimisation possible est alors de changer l'ordre des accès mémoire, histoire de retarder les écritures ou au contraire de les faire en avance. La raison est que les mémoires SDRAM n'aiment pas quand on alterne lectures et écritures. Les délais mémoire, les fameux ''timings'' mémoire, sont clairs : faire une suite de lecture ou une suite d'écriture est plus performant que d'alterner entre lectures et écritures. L'idée est alors de changer l'ordre des écritures de manière à les faire en bloc, idem pour les lectures. L’optimisation est possible, mais à condition que le processeur n'y verra que du feu. Or, vous l'avez deviné, il y a des cas où ces réorganisations ne sont pas possibles, et vous avez sans doute trouvé que la situation était familière. L'optimisation en question est une forme d'exécution dans le désordre des accès mémoire, réalisée par le contrôleur mémoire lui-même ! Et qui dit exécution dans le désordre dit : problèmes liés aux dépendances de données. Changer l'ordre des écritures n'est pas toujours possible, notamment en raison des dépendances RAW, WAR et WAW. Pour que le processeur n'y voit que du feu, il faut respecter plusieurs critères. Premièrement, les lectures doivent être renvoyées au processeur dans l'ordre. Et quand je dis dans l'ordre, cela veut dire : dans l'ordre d'envoi des lectures au contrôleur mémoire. Les écritures aussi doivent se faire dans l'ordre, histoire qu'une série d'écriture donne le bon résultat final. Le troisième critère est qu'une lecture doit donner le résultat de la dernière écriture ''à la même adresse''. Pour le dire autrement, il faut juste détecter les dépendances RAW. Concrètement, si on a une série de lectures et d'écritures alternées, on peut regrouper les lectures et les écritures dans deux paquets séparés, à condition qu'aucune lecture ne lise une adresse écrite par une écriture. Mais si une lecture est dépendante d'une écriture, alors la lecture doit attendre que l'écriture se termine, idem pour les lectures suivantes. Une implémentation basique remplace la mémoire FIFO vue au-dessus, par deux mémoires FIFOs : une pour les lectures, une pour les écritures. Lorsqu'un accès mémoire arrive au séquenceur, il regarde si c'est une lecture ou une écriture et place l'accès mémoire dans la file adéquate. Le fait que ce soit des FIFOs garantit que les lectures se font dans l'ordre, idem pour les écritures. En sortie des deux FIFOs, le séquenceur mémoire détecte les dépendances RAW. Prenons l'exemple des coprocesseurs IO 81341 et 81342, qui étaient en réalité des ''chipsets'' intégrant un contrôleur SDRAM. Le ''chipset'' avait 5 ports : un pour les processeurs, un pour le pont sud (''southbridge'') et trois pour des canaux DMA. Pour le port processeur, il n'y avait pas de réordonnancement et ce port utilisait une seule mémoire FIFO. Les autres ports utilisaient l'optimisation qu'on vient de voir, et avaient vraisemblablement des files séparées pour les lectures et écritures. Le séquenceur mémoire vérifiait les dépendances mémoire de type RAW et autres. [[File:Double file d'attente pour les lectures et écritures.png|centre|vignette|upright=2.5|Double file d'attente pour les lectures et écritures]] ===Le ré-ordonnancement des commandes mémoires=== L'optimisation précédente est peu poussée, comparé aux formes d'exécution dans le désordre que les processeurs utilisent. Et on peut se demander si une exécution dans le désordre plus poussée est possible. La réponse est oui : un contrôleur mémoire peut faire des réorganisations bien plus poussées. Par contre, il faut faire une précision très importante : le contrôleur mémoire ne remet pas les accès mémoire dans l'ordre avant de les envoyer au processeur. C'est le processeur qui remet en ordre les accès mémoire, dans sa ''load queue''. En clair : le processeur voit les accès mémoire dans le désordre, c'est lui qui les remet en ordre. Pour cela, les requêtes mémoire sont "numérotées", à savoir qu'on leur attribue un identifiant binaire. Le contrôleur mémoire exécute les lectures/écritures dans le désordre, mais garde la trace de leur identifiant. Il sait dans quel ordre il exécute les accès mémoire et connait leur latence. Ce qui fait qu'il sait que la donnée lue à tel instant est associée à la requête mémoire numéro X. Pour les lectures, il renvoie la donnée lue avec l'identifiant. Le processeur sait alors à quelle lecture la donnée lue correspond et il se débrouille en interne pour gérer la situation. Maintenant que ces précisions sont faites, posons cette question : dans quelles situations est-il pertinent de faire des accès mémoire dans le désordre ? La réponse est : quand plusieurs accès à une même ligne ne sont pas consécutifs, qu'il y a des accès entre les deux. Après ré-ordonnancement, ces accès mémoire à une même ligne sont exécutés l'un à la suite de l'autre, ce qui est beaucoup plus rapide. Pour rendre le tout plus concret, voici un exemple. Imaginez que l'on sait les 3 accès mémoire suivants : * Une lecture ligne A ; * PRECHARGE + ACT ; * Une écriture ligne B ; * PRECHARGE + ACT ; * Une lecture ligne A. Sans réordonnancent, on doit émettre deux commandes PRECHARGE quand on passe d'une ligne à l'autre, et il faut ajouter les commandes ACT avec. pour éviter cela, le contrôleur mémoire peut retarder l'écriture, ou au contraire avancer la seconde lecture. le résultat est alors le suivant : * Lecture ligne A ; * Lecture ligne A * PRECHARGE + ACT ; * Une écriture ligne B. On a donc deux accès consécutifs à la même ligne, suivi par une écriture dans une autre ligne. La technique marche parce que l'on a un mix adéquat de lectures et d'écritures. Mais il existe des possibilités de réorganisation autres qui ne sont pas valides. Il faut par exemple prendre garde à éviter d'intervertir lectures et écritures à une même adresse, sous peine de problèmes. Encore une fois, il faut faire attention aux dépendances de données, encore une fois ! Ici des dépendances mémoires, mais les règles sont les mêmes. Le contrôleur mémoire teste les dépendances mémoires avant d'envoyer des commandes à la mémoire DDR/SDRAM. ===Les optimisations liées à la présence de plusieurs banques=== Les optimisations précédentes sont décuplées par la présence de plusieurs banques dans la mémoire SDRAM. Il est en effet possible de faire plusieurs accès mémoire en même temps, dans des banques différentes. Il est possible de lancer un accès mémoire dans deux banques en même temps, par exemple. La seule contrainte est que la SDRAM est limitée à 4 banques actives en même temps. {|class="wikitable" |+ Pipelining basique sur les SDRAM |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 |- ! Banque Numéro 1 | || || || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || || |- ! Banque Numéro 2 | || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || |- ! Banque Numéro 3 | || || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || |} L'implémentation n'a rien de vraiment compliqué. Il suffit d'utiliser une mémoire FIFO par banque. Ainsi, les accès dans une même banque se feront en série, dans l'ordre d'arrivée, mais des accès à des banques différentes se feront dans le désordre. Cela ne pose pas de problème car les accès se font à des adresses différentes, ce qui fait qu'il n'y a pas de conflits majeurs. [[File:Gestion parallèle des banques.png|centre|vignette|upright=2.5|Gestion parallèle des banques.]] ==L'entrelacement sur les mémoires SDRAM== L''''entrelacement''' fonctionne sur les mémoires SDRAM, mais il doit être fortement modifié. Concrètement, tout ce qui a étré dit plus est inapplicable pour les SDRAM, du fait de la présence du tampon de ligne. Des adresses consécutives atterrissent déjà dans la même ligne, ce qui fait qu'on n'a pas besoin d'entrelacement au niveau d'une ligne. Et ne parlons pas de ce la présence de rangées et de canaux mémoire ! Cependant, il peut y avoir un entrelacement lié à la présence des banques SDRAM. L'entrelacement est géré juste avant le séquenceur mémoire. Le '''circuit d'entrelacement''' intervertit certains bits de l'adresse lors des accès mémoires. On trouve aussi des mémoires FIFOs pour mettre en attente les requêtes mémoire provenant du processeur. Elles permettent de recevoir plusieurs accès mémoire consécutifs. Si vous vous demandez comment plusieurs accès mémoire consécutifs peuvent avoir lieu, sachez que c'est une bonne question. Pour le moment, nous allons dire que ces accès proviennent du processeur ou de la mémoire cache, sans expliquer comment. Le contrôleur mémoire reçoit une série d'accès mémoire assez rapprochés, qu'il doit exécuter au mieux. [[File:Controleur mémoire d'une SDRAM avec entrelacement.png|centre|vignette|upright=2.5|Contrôleur mémoire d'une SDRAM avec entrelacement.]] Pour gérer l'entrelacement, le contrôleur de SDRAM prend en entrée l'adresse envoyée par le processeur, et la découpe en plusieurs champs : un pour sélectionner la banque adéquate, un autre pour la ligne, un autre pour la colonne, un autre pour la rangée. Une solution simple désactive l'entrelacement. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de banque || Adresse de ligne || Adresse de colonne |} Si cette solution fonctionne bien si les accès mémoire sont assez aléatoires, en pratique, mieux vaut utiliser l'entrelacement. ===L'entrelacement de banques/lignes=== Une première méthode d'entrelacement est l''''entrelacement de banques'''. Elle répartit deux lignes consécutives dans deux banques différentes. Pour comprendre l'idée, prenons un exemple. Imaginons une mémoire avec deux banques et 4 lignes. Imaginons qu'on parcoure/balaye la mémoire RAM en partant des adresses basses. Sans entrelacement de ligne, les accès se feront comme suit, de gauche à droite : {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Banque numéro 1 !! colspan="4" | Banque numéro 2 |- | Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 || Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 |} Avec l'entrelacement de banques, les accès se feront comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 |- | Ligne 1 || Ligne 1 || Ligne 2 || Ligne 2 || Ligne 3 || Ligne 3 || Ligne 4 || Ligne 4 |} Avec N banques, la première ligne ira dans la première banque, la seconde ligne dans la seconde, et ainsi de suite jusqu'à la énième banque qui va dans la banque numéro N. Et la ligne N+1 repart dans la première banque. L'avantage est que passer d'une ligne à la suivante est assez rapide. Au lieu de devoir fermer la première ligne et ouvrir la suivante on ouvrira directement la ligne suivante dans une banque différente, ce qui sera plus rapide. La commande PRECHARGE pourra être envoyée à la seconde banque en avance. Pour cela, l'adresse est découpée comme suit. {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne (précise à l'octet) |} ===L'entrelacement de rangées=== Il est aussi possible de faire la même chose, mais avec les rangées. Pour simplifier fortement, une rangée est simplement un chip mémoire. En réalité, les rangées ne sont pas des chip mémoire, mais un ensemble de chips mémoire regroupés ensemble histoire d'atteindre les 64 bits du bus de données. Par exemple, une rangée peut combiner 8 chips mémoire avec un bus de données de 8 bits chacun pour obtenir les 64 bits du bus de données d'une SDRAM. Mais nous passons ce détail sous silence dans les explications qui vont suivre, par souci de simplification. Pour faire comprendre l'entrelacement de rangée, le mieux est d'utiliser un exemple, le même que précédemment. Sans entrelacement de rangée, on change de chip mémoire une fois qu'on a balayé toutes ses adresses. {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Rangée numéro 1 !! colspan="4" | Rangée numéro 2 |- | Banque 1 || Banque 2 || Banque 3 || Banque 4 || Banque 1 || Banque 2 || Banque 3 || Banque 4 |} Avec l'entrelacement de ligne, on change de rangée dès qu'on change de banque/ligne. {|class="wikitable" |+ Adresse mémoire |- ! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 |- | Banque 1 || Banque 1 || Banque 2 || Banque 2 || Banque 3 || Banque 3 || Banque 4 || Banque 4 |} En clair, quand on a épuisé toutes les banques dans une même rangée, on passe à la rangée suivante au lieu de rester dans la même rangée. Notons que dans l'exemple précédent, on a combiné l'entrelacement de rangée et de banque, mais on aurait pu utiliser l'entrelacement de rangée seul. Mais ce n'est pas le cas le plus courant en pratique. Toujours est-il qu'en combinant entrelacement de rangée et de ligne, le découpage de l'adresse est le suivant : {|class="wikitable" |+ Adresse mémoire |- | Adresse de ligne || Numéro de rangée || Adresse de banque || Adresse de colonne (précise à l'octet) |} D'autres méthodes d'entrelacement sont possibles, mais elles sont plus complexes. Chaque contrôleur mémoire fait un peu à sa sauce. Ils permettent en général de choisir entre plusieurs entrelacements. Au minimum, ils permettent de désactiver l'entrelacement, d'activer un entrelacement de ligne, et d'autres entrelacements plus complexes, dont certains sont propriétaires et tenus secrets par le fabricant. ===L'entrelacement avec le ''dual channel''=== [[File:Dual channel slots.jpg|vignette|Slots mémoires avec ''dual channel''.]] L'usage du ''dual channel'' complique encore l'entrelacement. Là encore, il y a deux grandes solutions : avec et sans entrelacement des canaux mémoire. Rappelons le principe : deux barrettes de RAM sont accédées en parallèle. Et pour cela, il faut utiliser l'entrelacement. Typiquement, chaque barrette mémoire fournit 64 bits, ce qui fait que l'on peut accéder à 128 bits d'un seul coup, par exemple avec un accès en rafale. Sans ''dual channel'', la première barrette correspond à la moitié haute de la RAM, la seconde barrette correspond à la moitié basse. Avec ''dual channel'', une forme spécifique d'entrelacement est activée. Concrétement, deux blocs de 64 bits sont placés dans des canaux mémoire séparés. Idem avec du triple ou quadruple canal, mais c'est alors trois ou quatre blocs de 64 bits qui sont dispersés dans des canaux consécutifs. Le découpage de l'adresse est alors le suivant : {|class="wikitable" |+ Adresse mémoire |- ! colspan="6" | Sans entrelacement |- | Numéro de canal/barrette || Reste de l'adresse || Position de l'octet dans un bloc de 64 bits |- | colspan="6" | |- ! colspan="6" | Avec entrelacement |- | Reste de l'adresse || Numéro de canal/barrette || Position de l'octet dans un bloc de 64 bits |} [[File:Décodage d'adresse avec dual channel.png|centre|vignette|upright=2|Décodage d'adresse avec dual channel.]] Les contrôleurs de SDRAM précédents sont assez basiques et ne représentent pas les contrôleurs les plus évolués. Ils étaient utilisés sur les anciens PC, à une époque où ils étaient encore sur la carte mère du processeur. Mais de nos jours, le contrôleur mémoire est intégré au processeur. Et il incorpore de nombreuses optimisations afin de gagner en performances. Malheureusement, ces optimisations sont elles-mêmes dépendantes des optimisations intégrées dans le processeur, ce qui fait qu'on ne peut pas en parler ici. Elles demandent que le contrôleur mémoire reçoive plusieurs accès mémoire simultanés, ce qui n'a aucun sens à ce stade du cours. Aussi, nous allons les passer sous silence pour le moment. Nous les verrons dans le chapitre sur le parallélisme mémoire. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=La désambiguïsation mémoire | prevText=La désambiguïsation mémoire | next=Les processeurs superscalaires | nextText=Les processeurs superscalaires }} </noinclude> oce0pf2v039bn31cdb5sbia6s8ox7q1 764742 764736 2026-04-23T22:58:17Z Mewtow 31375 /* Le ré-ordonnancement des commandes mémoires */ 764742 wikitext text/x-wiki Dans ce chapitre, nous allons voir les techniques qui permettent de gérer plusieurs accès mémoire simultanés directement au niveau du cache ou de la mémoire RAM. Par plusieurs accès mémoire simultanés, vous pensez sans doute à l'usage de cache multiports, voire de mémoires RAM multiport. C'est en effet une solution, mais ce n'est clairement pas la seule. Par exemple, il est possible de pipeliner l'accès au cache, voire à la mémoire RAM. Il existe de nombreuses techniques de '''parallélisme mémoire''', et nous allons les voir dans ce chapitre. Pour donner un exemple, il est possible de pipeliner les accès mémoire. Il est possible de pipeliner l'accès au cache et/ou l'accès à la mémoire, et nous verrons comment faire dans ce chapitre. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, qu'ils aient ou non un pipeline dynamique. Par contre, pipeliner la mémoire est une technique ancienne, aujourd'hui peu utilisée. Elle était utilisée sur des très vieux ordinateurs, qui avaient un pipeline mais pas de mémoire cache. Mais de nos jours, les mémoires SDRAM et DDR ne sont pas adaptées pour ça, il est très difficile de les pipeliner correctement. Le parallélisme mémoire est utile aussi bien sur des processeurs à émission dans l'ordre que dans le désordre. Par exemple, un processeur ''in-order'' avec des lectures non-bloquantes peut e, profiter. Il peut techniquement lancer une seconde lecture, après avoir rencontré une première lecture non-bloquante. En général, le nombre de lectures consécutives est cependant limité à moins d'une dizaine. À l'opposé, les processeurs sans lectures non-bloquantes profitent pas du parallélisme mémoire. À la rigueur, ils peuvent en profiter s'ils intègrent des techniques de préchargement. Pendant que le processeur exécute une lecture/écriture, il peut précharger une autre donnée en parallèle. Inutile de dire que sans pipeline processeur, le parallélisme mémoire ne sert pas à grand-chose. ==Le ''line fill buffer'' et les techniques associées== Lors d'un défaut de cache, le processeur doit attendre que toute la ligne de cache soit chargée avant d'être utilisable. Or, la taille d'une ligne de cache est supérieure à la largeur du bus mémoire, ce qui fait qu'une ligne de cache est chargée en plusieurs fois, morceaux par morceaux, mot mémoire par mot mémoire. Le chargement peut se faire directement dans le cache, mais ce n'est pas une solution très pratique. À la place, beaucoup de processeurs ajoutent une mémoire tampon entre la RAM et le cache, appelée le '''tampon de remplissage de ligne''' (''line-fill buffer'') dans les processeurs modernes. Lors d'un défaut de cache, le processeur charge la donnée de la RAM dans le tampon de remplissage de ligne, mot mémoire par mot mémoire. Une fois plein, le tampon de remplissage de ligne est recopié dans la ligne de cache. Le tampon de remplissage de ligne contient une ligne de cache, si ce n'est que certains bits de contrôle ne sont pas présents. Le tampon de remplissage de ligne contient un bit de validité pour chaque mot mémoire de la ligne de cache, qui indiquent si le mot mémoire a été chargé. Par exemple, prenons un processeur 64 bits, qui gère donc des mots mémoire de 8 octets, avec des lignes de cache 256 octets/32 mots mémoire. Le tampon de remplissage de ligne contiendra 32 bits de validité. Si le processeur a chargé les 6 premiers mots mémoire, les 6 premiers bits de validité seront mis à 1, les autres seront encore à zéro. Il faut noter que la disponibilité de la ligne complète se détermine assez facilement en faisant un ET logique entre tous les bits de validité. Le processeur sait ainsi quand la ligne de cache est disponible entièrement et donc quand la transférer dans le cache. La même chose existe avec une hiérarchie de cache, sauf que l'on trouve une mémoire tampon entre chaque niveau de cache. Il y a un tampon de remplissage de ligne entre le cache L1 et le cache L2, entre le cache L2 et le L3, etc. Si le cache ne gère qu'un seul défaut de cache à la fois, le ''fill line buffer'' est une mémoire très simple, qui ne mémorise qu'une seule ligne de cache. Et cela vaut aussi bien pour un cache bloquant que non-bloquant. Mais sur les caches capables de gérer plusieurs défauts simultanés, le tampon de remplissage de ligne est une mémoire de type FIFO ou LIFO, capable de mémoriser plusieurs lignes de cache. Notons que le tampon de remplissage de ligne est très utile pour implémenter certaines techniques, par exemple le contournement du cache. Nous avions vu dans le chapitre sur le cache que certains accès mémoire doivent contourner le cache, pour des raisons de cohérence des caches. C'est notamment nécessaire pour accéder aux périphériques, mais c'est aussi utile pour des raisons de performances dans des cas très spécifiques. Les accès qui contournent le cache se font directement dans le tampon de remplissage de ligne : le processeur écrit ou lit les données depuis ce tampon de remplissage de ligne, sans accéder au cache. ===L'''early restart'' et le ''critical word load''=== La présence du ''line buffer'' permet une optimisation assez intéressante, qui permet de réduire la latence des défauts de cache. L'optimisation consiste à lire un mot mémoire dans le tampon de remplissage de ligne, même si la ligne de cache complète n'a pas encore été chargée. Il existe deux manières de faire cela, qui portent les noms d'''early restart'' et de ''critical word load''. La première est la version la plus simple, la seconde est plus complexe mais plus performante. Avec la technique d''''''early restart''''', la ligne de cache est chargée normalement, en partant de son premier mot mémoire. Dès que le mot mémoire lu/écrit par le processeur est copié dans le ''line fill buffer'', il est envoyé au processeur immédiatement. Illustrons le tout par un exemple, où une ligne de cache fait 16 mots mémoire. Le processeur effectue une lecture, qui lit le 5ème mot mémoire. Sans ''early restart'', le processeur doit charger les 16 mots mémoire avant de faire la lecture dans le cache. Avec ''early restart'', le processeur reçoit la donnée dès que le 5ème mot mémoire est disponible. Le processeur doit attendre que les 4 premiers mots mémoire soient chargés, puis le 5ème mot mémoire arrive et est envoyé directement au processeur, il est lu directement depuis le ''line fill buffer''. Les 11 mots mémoire suivants sont ensuite chargés dans le cache pendant que le processeur fait des calculs dans son coin. Le ''critical word load'' est une optimisation de la technique précédente où le chargement de la ligne de cache commence directement à la donnée demandée par le processeur. Pour reprendre l'exemple précédent, où le processeur demande le 5ème mot mémoire sur 16, le ''critical word load'' charge le 5ème mot mémoire en premier et l'envoie au processeur, ce qui fait qu'il est chargé très rapidement. Pas besoin d'attendre que le processeur charge les 4 mots mémoire précédents comme avec l'''early restart''. Dans le détail, le processeur charge le 5ème mot mémoire en premier, puis charge les 11 suivants, et termine par les 4 mots mémoire du début. En clair, le ''critical word load'' commence par charger le mot mémoire lu, puis les blocs suivants, avant de revenir au début du bloc pour charger les blocs restants. Ainsi, la donnée demandée par le processeur sera la première disponible. Pour cela, l'organisation du tampon de remplissage de ligne est modifiée de manière à rendre cela possible. Il a une taille égale à une ligne de cache complète, qui contient elle-même plusieurs mots mémoire. Dans le ''line-fill buffer'', chaque mot mémoire est stocké avec un tag, qui indique l'adresse du mot mémoire stocké dans le ''line-fill buffer''. Le ''line-fill buffer'' est donc un cache un peu particulier, qui fonctionne comme un cache du point de vue du processeur, comme une mémoire FIFO pour les transferts avec le cache. Ainsi, un processeur qui veut lire dans le cache après un défaut peut accéder à la donnée directement depuis le tampon de remplissage de ligne, alors que la ligne de cache n'a pas encore été totalement recopiée en mémoire. ==Les caches pipelinés== Il est possible de pipeliner l'accès au cache, ce qui demande juste de rajouter des registres au bon endroits et quelques circuits de contrôle. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, même ceux avec un pipeline dynamique. Et l'implémentation peut se faire de plusieurs manières différentes. Il faut dire que découper une mémoire cache en plusieurs étapes peut se faire de plusieurs manière. Commençons par la plus simple. Beaucoup de processeurs des années 2000 avaient une fréquence peu élevée comparée aux standards d'aujourd'hui, ce qui fait que leur cache avait bien un temps d'accès d'un cycle d'horloge. Mais l'accès au cache se faisait en deux cycles d'horloge : un cycle pour calculer une adresse et un cycle pour l'accès mémoire proprement dit. Et ces deux étapes étaient pipelinées, à savoir que deux micro-opérations mémoire peuvent s'exécuter en même temps : une dans l'étage de calcul d'adresse, une autre dans le cache. L'unité mémoire était donc pipelinée, alors que l'accès au cache ne l'était pas. Les processeurs qui implémentaient cette technique regroupent les micro-architectures K5 et K6 d'AMD, les processeurs Intel de micro-architecture P6 (Pentium 2 et 3) et quelques autres. Il est aussi possible de pipeliner l'accès au cache lui-même. Avec un cache pipeliné, l'accès au cache ne se fait pas en un seul cycle, mais en plusieurs. Cependant, on peut lancer un nouvel accès au cache à chaque cycle d'horloge, comme un processeur pipeliné lance une nouvelle instruction à chaque cycle. L'implémentation est assez simple : il suffit d'ajouter des registres dans le cache. Pour cela, on profite que le cache est composé de plusieurs composants séparés, qui échangent des données dans un ordre bien précis, d'un composant à un autre. De plus, le trajet des informations dans un cache est linéaire, ce qui les rend parfois pour l'usage d'un pipeline. ===Le ''pipelining'' des caches ''direct-mapped''=== Voici ce qui se passe avec un cache directement adressé. Pour rappel ce genre de cache est conçu en combinant une mémoire RAM, généralement une SRAM, avec quelques circuits de comparaison et un MUX. L'accès au cache se fait globalement en deux étapes : la première lit la donnée et le ''tag'' dans la SRAM, et on utilise les deux dans une seconde étape. Nous avions vu il y a quelques chapitre comment pipeliner des mémoires, dans le chapitre sur les mémoires évoluées. Et bien ces méthodes peuvent s'utiliser pour la mémoire RAM interne au cache !L'idée est d'insérer un registre entre la sortie de la RAM et la suite du cache, pour en faire un cache pipéliné, à deux étages. Le premier étage lit dans la SRAM, le second fait le reste. L'implémentation sur les caches associatifs à plusieurs voies est globalement la même à quelques détails près. [[File:Cache directement adressé pipeliné - deux étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - deux étages]] Avec le circuit précédent, il est possible d'aller plus loin, cette fois en pipelinant l'accès à la mémoire RAM interne au cache. Pour comprendre comment, rappelons qu'une mémoire SRAM est composée d'un plan mémoire et d'un décodeur. L'accès à la mémoire demande d'abord que le décodeur fasse son travail pour sélectionner la case mémoire adéquate, puis ensuite la lecture ou écriture a lieu dans cette case mémoire. L'accès se fait donc en deux étapes successives séparées, on a juste à mettre un registre entre les deux. Il suffit donc de mettre un gros registre entre le décodeur et le plan mémoire. [[File:Cache directement adressé pipeliné - trois étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - trois étages]] Et on peut aller encore plus loin en découpant le décodeur en deux circuits séparés. En effet, rappelez-vous le chapitre sur les circuits de sélection : nous avions vu qu'il est possible de créer des décodeurs en assemblant des décodeurs plus petits, contrôlés par un circuit de prédécodage. Et bien on peut encore une ajouter un registre entre ce circuit de prédécodage et les petits décodeurs. : Théoriquement, toute l'adresse est fournie d'un seul coup au cache, la quasi-totalité des processeurs présentent l'adresse complète à un cache pipeliné. Mais le Pentium 4 fait autrement. Il faut noter que les premiers étages manipulent l'indice dans la SRAM, qui est dans les bits de poids faible, alors que les étages ultérieurs manipulent le ''tag'' qui est dans les bits de poids fort. Les concepteurs du Pentium 4 ont alors décidé de présenter les bits de poids faible lors du premier cycle d'accès au cache, puis ceux de poids fort au second cycle. Pour cela, l'ALU fonctionnait à une fréquence double de celle du processeur, tout comme le cache L1. Il n'y avait pas de pipeline proprement dit, mais cela réduisait grandement la latence d'accès au cache. ===Le ''pipelining'' des caches splittés/sériels=== Il est aussi possible de pipeliner un cache dits splittés, aussi appelés à accès sériel. Pour rappel, les caches à accès sériel vérifient si il y a succès ou défaut de cache, avant d'accéder aux lignes de cache en cas de succès. Ils font donc différemment des autres caches, qui accèdent à une ligne de cache, avant de déterminer s'il y a succès ou défaut en lisant le tag de la ligne de cache. Les caches sériels disposent de deux SRAM : une pour les tags des lignes de cache et une pour les données. Ils accèdent à la SRAM pour les tags, avant d’accéder à la SRAM des données en cas de succès de cache. Vu que l'accès se fait en deux étapes, une vérification des tags suivie de la lecture/écriture des données, il est facile à pipeliner. Pipeliner le cache permet de régler le problème des accès au cache L1, et elle est tout le temps utilisé sur les processeurs modernes. Mais que faire en cas de défaut de cache ? ==Les caches non bloquants== Un '''cache bloquant''' est un cache auquel le processeur ne peut pas accéder pendant un défaut de cache. Il faut attendre que la lecture ou écriture en RAM soit terminée avant de pouvoir utiliser de nouveau le cache. Un '''cache non bloquant''' n'a pas ce problème : on peut l'utiliser pendant un défaut de cache. Les caches non bloquants permettent de démarrer une nouvelle lecture ou écriture alors qu'une autre est en cours, ce qui permet d'exécuter plusieurs lectures ou écritures en même temps. ===Les ''Miss Handling Status Registers''=== Lors d'un défaut de cache, la mémoire RAM est consultée pendant le défaut de cache, mais le cache est inutilisé. Un défaut de cache n'utilise pas le cache, ce n'est qu'un accès en mémoire RAM, sur le bus mémoire (ou un accès aux niveaux de cache inférieurs, peu importe). Le cache en lui-même est laissé libre, rien n’empêche d'y accéder, il est en réalité intrinsèquement non-bloquant. Les caches, bloquants comme non-bloquants, sont en réalité composés d'une mémoire cache proprement dite, entourée de circuits qui servent d'interface entre le processeur et le cache lui-même. Et parmi les circuits tout autour, certains gèrent l'accès au cache lors d'un défaut de cache. Ils sont regroupés sous le terme de '''''Miss Handling Architecture''''' (MHA). La différence entre un cache bloquant et un cache non-bloquant est en réalité liée à l'implémentation de la MHA. Les caches bloquants coupent volontairement l'accès au cache lors d'un défaut, car les défauts sont plus simples à gérer ainsi. Le défaut de cache rapatrie une donnée depuis la RAM, qui sera écrite dans le cache. Et il ne faut pas qu'une tentative d'accès à cette donnée ait lieu avant qu'elle ne soit chargée. Mais ce blocage est général et touche tout le cache, alors que seule une ligne de cache est concernée par le défaut de cache. L'idée derrière un cache non-bloquant est que seule la ligne de cache est bloquée, mais les autres sont accessibles. L'idée est alors de mémoriser les lignes de cache concernées par le défaut de cache, afin d'en bloquer l'accès. À chaque accès, on vérifie si la ligne de cache est déjà réservée par un défaut de cache. La lecture/écriture est alors bloquée si c'est le cas, mais elle accepte les accès sinon. Pour cela, la ''Miss Handling Architecture'' contient des registres qui mémorisent des informations sur les défauts de cache en cours. Ils portent le nom de '''''miss status handling registers''''', que j’appellerais dorénavant MSHR, qui sont aussi appelés des '''''miss buffer'''''. Le contenu des MSHR varie beaucoup suivant le processeur, mais ces derniers stockent au minimum les informations suivantes : * Le numéro de la ligne de cache dans laquelle les données sont chargées. * Un bit de validité qui indique si le MSHR est vide ou pas, qui est mis à 0 quand le défaut de cache est résolu. * Un ou plusieurs champs de lecture/écriture, qui contiennent des informations sur la lecture/écriture. ** Pour une lecture, elle contient des informations sur la destination de la donnée, à savoir qui prévenir quand le défaut de cache est terminé. C'est parfois un nom/numéro de registre (celui dans lequel charger la donnée), mais c'est souvent le numéro de l'entrée dans le ''load/store queue''. ** Pour les écritures, elle contient la donnée à écrire, ou éventuellement un numéro de ''load/store queue'' où se trouve la donnée à écrire. Il faut noter que le bus mémoire ne peut gérer qu'un seul défaut de cache à la fois. Aussi, il est intéressant de regarder ce qui se passe lorsqu'un second défaut de cache survient, pendant qu'un premier est en cours. Dans ce cas, il y a deux réponses qui correspondent à deux types de caches non-bloquants, qui portent les noms barbares de caches de type succès après défaut et défaut après défaut. Sur le premier type, il ne peut pas y avoir plusieurs défauts de cache simultanés. Dès qu'un second défaut de cache survient, le cache stoppe son activité et on ne peut plus démarrer de nouvelle lecture/écriture, tant que le premier défaut de cache n'est pas résolu. Le second type est plus souple et autorise la survenue de plusieurs défauts de cache simultanés. Du moins, jusqu'à une certaine limite, car le cache ne peut supporter qu'un nombre limité d'accès mémoires simultanés (pipelinés). ===Les accès simultanés à une même ligne de cache=== Il arrive que le processeur fasse plusieurs accès mémoire simultanés à la même ligne de cache. Si la ligne de cache en question n'a pas encore été chargée dans le cache, alors on a plusieurs défauts de cache consécutifs pour la même ligne de cache, et le cache non-bloquant doit gérer la situation. Pour la suite, il va falloir faire une petite distinction entre les défauts primaires et secondaires. Imaginons qu'un défaut de cache ait lieu et demande à charger une donnée dans la ligne de cache numéro N. Il s'agit du premier défaut impliquant cette ligne de cache précise, ce qui lui vaut le nom de '''défaut de cache primaire'''. Mais par la suite, d'autres accès mémoire à la même ligne de cache ont lieu, alors que la ligne de cache n'est pas encore disponible. Dans ce cas, il s'agit de '''défauts de cache secondaires'''. Pour l'unité d'accès mémoire, les défauts de cache primaire et secondaire sont différents (ils prennent tous une entrée dans la ''load/store queue''). Mais pour le cache, ils ne correspondent qu'à un seul accès au cache : celui qui demande de charger la ligne de cache demandée. Les défauts de cache primaire et secondaire à la même ligne de cache se voient attribuer un MSHR unique. La gestion des défauts de cache secondaires dépend du cache non-bloquant. La solution la plus simple ne permet pas les défauts de cache secondaires. Le cache ne permet pas deux défauts de cache simultanés pour la même ligne de cache. Les autres solutions le permettent, en fusionnant des accès simultanés à la même ligne de cache en un seul au niveau des MSHR. Dans tous les cas, détecter les défauts de cache secondaires sont un problème qu'il faut détecter. La ''Miss Handling Architecture'' doit détecter les défauts de cache secondaire. Pour cela, elle procède comme suit. Lors de chaque défaut de cache, la MHA récupère le numéro de la ligne de cache associé. Il vérifie alors chaque MSHR pour vérifier s'il contient le numéro en question. S'il n'y a aucune correspondance dans les MSHR, alors c'est signe que le défaut de cache est un défaut primaire. Mais s'il y en a une, alors c'est un défaut secondaire. Évidemment, cela signifie que lors d'un défaut de cache, le numéro de ligne de cache est envoyé à tous les MSHR, pour comparaison. Les MSHR sont donc regroupés dans une mémoire associative, une sorte de mini-cache, faciliter l'implémentation. Lors d'un défaut de cache primaire, l'accès à un cache non-bloquant se fait comme suit : le processeur envoie une adresse au cache, accède à celui-ci, et détecte la survenue d'un défaut de cache. Il en profite alors pour attribuer une ligne de cache dans laquelle sera chargée ce défaut. L'attribution est très simple dans le cas des caches ''direct mapped'', ou associatifs par voie, pour lesquels l'attribution se fait assez simplement. Il mémorise alors cette information dans les MSHR, après avoir vérifié que le défaut de cache n'était pas un défaut secondaire. Lors des accès ultérieurs à une adresse proche, censée être dans la même ligne de cache, le processeur va encore une fois rencontrer un défaut de cache. Il va alors déterminer le numéro de la ligne de cache associée à l'adresse, et comparer ce numéro avec les MSHRs. Si un MSHR contient ce numéro, c'est signe que le défaut de cache est un défaut secondaire. La MHA réagit alors différemment selon le processeur considéré. Une première solution n'autorise pas les défauts de cache secondaires. Si l'un d'entre eux survient, le processeur est gelé par un ''pipeline stall'', une bulle de pipeline. Une autre solution fusionne les défauts de cache secondaires avec le défaut de cache primaire : tout cela ne correspond qu'à un seul défaut de cache pour lui. ===Les MSHR simples=== Un cache non-bloquant à '''MSHR simple''' contient juste plusieurs MSHR qui mémorisent juste un numéro de ligne de cache, un bit de validité, le champ de lecture/écriture, et l'adresse exacte de lecture/écriture. Avec cette organisation, il est possible d'avoir plusieurs défauts de cache séparés, mais à la condition que chaque défaut accède à une ligne de cache différente. Deux accès simultanés à une même ligne de cache ne sont pas possibles, les défauts de cache secondaires ne sont pas autorisés. Ainsi, chaque défaut de cache se voit attribuer son propre MSHR, chacun contient un numéro de ligne de cache différent. Pour comprendre pourquoi c'est impossible de gérer les défauts secondaires, il faut regarder le champ de lecture/écriture. Si on veut effectuer plusieurs écritures consécutives à la même adresse, le MSHR n'aura pas de quoi mémoriser les deux données à écrire. Il pourra mémoriser la première donnée à écrire, pas la seconde. Même chose lors d'une lecture : le champ lecture/écriture peut mémorisr la destination de la première lecture, pas de la seconde. L'avantage est que la MHA n'a besoin que des MSHR et de quelques circuits annexes. Les autres solutions rajoutent des circuits annexes pour gérer les défauts de cache secondaires, qui utilisent beaucoup de circuits. Le cout en circuit est donc élevé, mais le gain en performance est là. Passons maintenant aux caches non-bloquants qui autorisent les défauts de cache secondaire. La solution la plus simple consiste à utiliser ===Les MSHR adressés implicitement=== Avec les '''MSHR adressés implicitement''', il est possible de fusionner plusieurs accès mémoire à une même ligne de cache, mais sous une condition très importante : ces accès lisent/écrivent des mots mémoire différents. Par exemple, imaginons qu'une ligne de cache contienne 8 mots mémoire de 64 bits. Si un premier accès mémoire lit le mot mémoire numéro 7 (dernier mot mémoire de la ligne), et le second accès le mot mémoire numéro 3, alors la fusion est possible. Mais si deux accès mémoire veulent lire/écrire le mot mémoire numéro 7, alors la fusion n'est pas possible et le processeur se bloque, un ''pipeline stall'' survient. Un MSHR adressé implicitement est un MSHR simple, qui contient naturellement un numéro de ligne de cache (tag) et un bit de validité, sauf que le champ de lecture/écriture est dupliqué. Les différents champs lecture/écriture d'un MSHR sont regroupés dans une mémoire RAM/cache qui contient autant d'entrées qu'il y a de mot mémoire dans une ligne de cache. Chaque entrée de la table est associée à un mot mémoire de la ligne de cache et stocke des informations sur celui-ci. Une entrée mémorise, au minimum : * Un champ de lecture/écriture qui contient soit la destination de la lecture, soit la donnée à écrire. * Un bit R/W permettant d'interpréter correctement le champ de lecture/écriture. * Un bit de validité pour chaque entrée de la table, qui dit si un défaut de cache antérieur accède déjà à ce mot mémoire. La fusion de deux défauts de cache est ainsi assez simple. Un défaut de cache primaire/secondaire configure l'entrée associée au mot mémoire lu/écrit. Si un défaut secondaire ultérieur lit/écrit un mot mémoire différent (dont le bit de validité de l'entrée dans la table est à zéro), pas de conflit : il configure une autre entrée, vide. Mais s'il lit/écrit un mot mémoire pour lequel l'entrée de la table est occupée, il y a conflit, le processeur est bloqué par un ''pipeline stall'', une bulle de pipeline. Avec cette organisation, le nombre de MSHR indique combien de lignes de cache peuvent être lues en même temps depuis la mémoire. Quant au nombre d'entrées par MSHR, il détermine combien d'accès mémoires qui ne se recouvrent pas peuvent avoir lieu en même temps. ===Les MSHR adressés explicitement=== Les '''MSHR adressés explicitement''' n'ont pas les contraintes des MSHR adressés implicitement. Avec eux, il est possible d'avoir plusieurs défauts de cache pointant vers la même ligne de cache, mais aussi vers le même mot mémoire. De tels défauts de cache apparaissent sur les processeurs à exécution dans le désordre, mais sont très rares, voire inexistants, sur les processeurs ''in-order''. L'idée est encore que chaque MSHR est associée à une table mémoire qui mémorise des entrées. Cette table n'est autre qu'une mémoire FIFO. Sauf que cette fois-ci, une entrée n'est pas associée à un mot mémoire. Une entrée est associée à un défaut de cache. Une entrée mémorise là encore la destination de la lecture ou la donnée de l'écriture suivi du bit R/W, ainsi que d'un bit de validité par entrée, mais aussi : la position du mot mémoire lu dans la ligne de cache. C'est cette dernière information qui n'était pas présente dans les MSHR adressés implicitement. De plus, le nombre d'entrée par MSHR n'est pas égal au nombre de mots mémoires dans une ligne de cache, mais peut être arbitrairement grand. L'avantage d'un tel cache est qu'il est capable de traité les défauts secondaires concernant un même mot mémoire, et ce, car les accès à la ligne de cache concernée se produisent dans l'ordre de génération des défauts (la table mémoire est une FIFO). ===Les MSHRs inversés=== Généralement, plus on veut supporter de défauts de cache, plus le nombre de MSHR et d'entrées augmente. Mais au-delà d'un certain nombre d'entrées et de MSHR, les MSHR adressés implicitement et explicitement ont tendance à bouffer un peu trop de circuits. Utiliser une organisation un peu moins gourmande en circuits est donc une nécessité. Cette organisation plus économe se base sur des MSHR inversés. Les MSHR inversés ne contiennent qu'une seule entrée, en quelque sorte : au lieu d’utiliser n MSHR de m entrées chacun, on va utiliser n × m MSHR inversés. La différence, c'est que plusieurs MSHR peuvent contenir un tag identique, contrairement aux MSHR adressés implicitement et explicitement. Lorsqu'un défaut de cache a lieu, chaque MSHR est vérifié. Si jamais aucun MSHR ne contient de tag identique à celui utilisé par le défaut, un MSHR vide est choisi pour stocker ce défaut, et une requête de lecture en mémoire est lancée. Dans le cas contraire, un MSHR est réservé au défaut de cache, mais la requête n'est pas lancée. Quand la donnée est disponible, les MSHR correspondant à la ligne qui vient d'être chargée vont être utilisés un par un pour résoudre les défauts de cache en attente. ===Les MSHR intégrés au cache=== Certains chercheurs ont remarqué que pendant qu'une ligne de cache est en train de subir un défaut de cache, celle-ci reste inutilisée, et son contenu est destiné à être perdu une fois le défaut de cache résolu. Ils se sont dit que, plutôt que d'utiliser des MSHR séparés, il vaudrait mieux utiliser la ligne de cache pour stocker les informations sur les défauts de cache en attente dans cette ligne de cache. Pour éviter tout problème, il faut rajouter un bit dans les bits de contrôle de la ligne de cache, qui sert à indiquer que la ligne de cache est occupée : un défaut de cache a eu lieu dans cette ligne, et elle stocke donc des informations pour résoudre les défauts de cache. ==L'entrelacement mémoire== Nous venons de voir comment une mémoire cache peut gérer plusieurs accès mémoire simultanés. Il se trouve que ce genre d'optimisation dépasse largement le cadre du cache. Les mémoires RAM ne sont pas en reste, elles aussi ! Il est possible de pipeliner l'accès à une mémoire RAM, en utilisant des techniques dites d''''entrelacement'''. Précisons cependant que cette optimisation n'est pas utilisée sur les mémoires SDRAM ou DDR modernes, du moins pas sans modifications majeures. Mais divers ordinateurs assez anciens, dont des superordinateurs, ont utilisé cette technique. La technique demande d'utiliser plusieurs mémoires séparées. Sur les anciens ordinateurs, les mémoires en question sont des chips mémoire, à savoir des circuits intégrés de DRAM. Pour faire plus général, nous allons utiliser le terme de '''banques''', ou encore de bancs mémoire. La différence est qu'il existe des mémoires multi-banque, qui regroupent plusieurs banques indépendantes dans un seul boitier, dans un seul chip mémoire. En clair, de telles mémoires regroupent plusieurs sous-mémoires dans un seul circuit intégré. Il est aussi possible d'utiliser plusieurs chips mémoire séparés, chacun ayant une banque. Reste à combiner ces banques pour former un pseudo-pipeline. ===Utiliser plusieurs banques sans entrelacement=== Sans optimisation particulière, les adresses sont réparties dans les banques comme indiqué ci-dessous. Il s'agit de ce qui s'appelle un arrangement horizontal, et nous avions vu celui-ci dans le chapiutre sur les mémoires SDRAM. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Avec cet arrangement, les bits de poids fort de l'adresse sont utilisées pour sélectionner la banque adéquate, et le reste de l'adresse est envoyé sur le bus d'adresse. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Adresse de banque !! Adresse dans la banque |- | Quelques bits de poids fort || Reste de l'adresse |} L'entrelacement change cette répartition, afin d'accélérer les accès mémoire. L'entrelacement classique vise à améliorer les accès à des adresses mémoires consécutives, mais des formes plus évoluées d'entrelacement visent à optimiser des accès mémoire arbitraires. Voyons-les dans l'ordre, du plus simple au plus complexe. ===L'entrelacement classique=== Avec l''''entrelacement classique''', des adresses consécutives sont réparties dans des banques consécutives. Précisons que cette attribution des adresses n'implique absolument pas la mémoire virtuelle ou n'importe quel mécanisme dans le processeur. La répartition décide que telle adresse mémoire va dans telle banque, à telle adresse dans la banque. Elle est donc le fait du contrôleur mémoire, donc en dehors du processeur (les contrôleurs mémoires n’étaient pas encore intégrés dans le processeur à l'époque). [[File:Répartition des adresses dans une mémoire interleaved.png|centre|vignette|upright=1.5|Répartition des adresses dans une mémoire interleaved.]] L'entrelacement simple permet d'accélérer les accès mémoire qui se font à des adresses consécutives. Avec l'entrelacement, chaque accès mémoire tombe dans une banque mémoire différente, ce qui fait qu'on peut démarrer un nouvel accès mémoire à chaque cycle d'horloge. Pendant qu'une banque est occupée par un accès mémoire, on démarre le suivant dans une autre banque, et ainsi de suite. Le tout se termine soit quand on a épuisé toutes les banques libres, soit quand l'accès en rafales se termine. Pas besoin d'attendre que la mémoire ait fini sa lecture/écriture avant de démarrer la lecture/écriture suivante. [[File:Pipemining mémoire.png|centre|vignette|upright=2.5|Pipelining mémoire]] L'animation suivante illustre bien le processus, avec quatres banques. [[File:Interleaving.gif|centre|vignette|upright=2.5|Pipelining mémoire]] L'entrelacement simple utilise les bits de poids faible pour sélectionner la banque, et les bits de poids fort pour la case mémoire. [[File:Adresse mémoire d'une mémoire entrelacée.png|centre|vignette|upright=2|Adresse mémoire d'une mémoire entrelacée]] Précisons que le temps d'accès mémoire ne change pas beaucoup avec l'entrelacement. Par contre, on peut faire plus d'accès mémoire simultanés. On peut démarrer un accès mémoire par cycle d'horloge, mais l'accès en lui-même prend plusieurs cycles. Les mémoires à entrelacement ont donc un débit supérieur aux mémoires qui ne l'utilisent pas. L'entrelacement classique pourrait être utilisé pour accélérer les transferts entre mémoire RAM et cache, qui se font en blocs d'adresses consécutives. Mais de nos jours, il n'est pas utilisé dans cette optique. Il faut dire qu'il est redondant avec le mode rafale des mémoires DRAM. Les deux font globalement la même chose et on voit mal comment utiliser les deux en même temps. Le mode rafale étant plus simple à implémenter, et plus léger niveau utilisation du bus de commande mémoire, il est préféré à l'entrelacement. Par contre, l'entrelacement a eu son heure de gloire sur d'anciens processeurs qui n'avaient pas de mémoire cache, alors qu'ils disposaient d'un pipeline. L'exemple typique est celui des nombreux ''processeurs vectoriels'', que nous n'avons pas encore abordé à ce stade du cours. De tels processeurs avaient un pipeline, une unité de calcul fortement pipelinée, et parfois même de l'exécution dans le désordre et du renommage de registres ! ===L'entrelacement par décalage=== L'entrelacement simple est très utile pour les accès en rafle ou équivalents. Par contre, il existe de rares situations où il n'est pas optimal. Une de ces situations est celle des accès en enjambée (en ''stride''), où l'on accède en série à des adresses sont séparées par N mots mémoires. [[File:Accès par enjambées.png|centre|vignette|upright=2|Accès par enjambées.]] De tels accès surviennent quand un logiciel accède à des structures de données spécifiques, à savoir des tableaux de structures, des matrices, ou d'autres structures de données dans le genre. Un cas classique est celui du parcours d'une matrice colonne par colonne. Les matrices de nombres sont mémorisées ligne par ligne, ce qui fait que parcourir une colonne demande de passer d'une ligne à la suivante et de faire des grands sauts en mémoire RAM, mais tous espacés par la même distance. Avec l'entrelacement simple, les accès en enjambée sont moins performants que les accès à des adresses consécutives, mais cela ne fonctionne pas trop mal. Le pire cas est celui où l'on a N banques et où les données sont justement placées toutes les N adresses. Des accès consécutifs vont tous tomber dans la même banque, on ne peut plus accéder à des banques différentes en parallèle. Mais en dehors de ce cas, on voit une amélioration par rapport à la situation sans entrelacement. Pour obtenir des performances maximales pour les accès en enjambées, il faut répartir les mots mémoires dans la mémoire autrement. L'organisation idéale est la suivante. : Dans les explications qui vont suivre, la variable N représente le nombre de banques, qui sont numérotées de 0 à N-1. On commence par organiser les N premières adresses comme une mémoire entrelacée simple : l'adresse 0 correspond à la banque 0, l'adresse 1 à la banque 1, etc. Pour le bloc suivant, on décale tout d'une adresse, à savoir qu'on commence à remplir les banques à partir de la seconde, non de la première. Une fois la fin du bloc atteinte, on finit de remplir le bloc en repartant du début du bloc. Le troisième bloc d'adresse subit le même traitement, sauf qu'on commence à remplir à partir de la seconde banque. Et on poursuit l’assignation des adresses en décalant d'un cran en plus à chaque bloc. Ainsi, chaque bloc verra ses adresses décalées d'un cran en plus comparé au bloc précédent. Si jamais le décalage dépasse la fin d'un bloc, alors on reprend au début. [[File:Mémoire interleaved par décalage.png|centre|vignette|upright=1.5|Mémoire entrelacée par décalage.]] En faisant cela, on remarque que les banques situées à N adresses d'intervalle sont différentes. Dans l'exemple du dessus, nous avons ajouté un décalage de 1 à chaque nouveau bloc à remplir. Mais on aurait tout aussi bien pu prendre un décalage de 2, 3, etc. Dans tous les cas, on obtient un entrelacement par décalage. Ce décalage est appelé le pas d'entrelacement, noté P. Le calcul de l'adresse à envoyer à la banque, ainsi que la banque à sélectionner se fait en utilisant les formules suivantes : * adresse à envoyer à la banque = adresse totale / N ; * numéro de la banque = (adresse + décalage) modulo N ; * décalage = (adresse totale * P) mod N. Avec cet entrelacement par décalage, on peut prouver que la bande passante maximale est atteinte si le nombre de banques est un nombre premier. Seulement, utiliser un nombre de banques premier peut créer des trous dans la mémoire, des mots mémoires inadressables. Malgré ce défaut, la technique a été utilisée sur quelques ordinateurs, avec l'exemple notable du superordinateur ''Burroughs Scientific Processor''. Pour éviter cela, il y a plusieurs solutions. Par exemple, on peut faire en sorte que N et la taille d'une banque soient premiers entre eux : ils ne doivent pas avoir de diviseur commun. Mais en pratique, elles n'ont pas vraiment été implémentées dans une vraie machine, et sont restées à l'état de recherche, aussi je les passe sous silence. ===L'entrelacement pseudo-aléatoire=== Une dernière méthode de répartition consiste à répartir les adresses dans les banques de manière "pseudo-aléatoire". L'idée exacte est de faire passer l'adresse dans une '''fonction de hachage''', plus ou moins complexe. Pour rappel, une fonction de hachage prend une entrée de grande taille et fournit en sortie un résultat de petite taille. Idéalement, elles doivent donner des résultats aussi différents que possible pour des entrées similaires, histoire de simuler une sorte de pseudo-aléatoire. Ici, une adresse est transformée en un numéro de banque. Une fonction de hachage simple ne fait que permuter des bits de l'adresse pour obtenir son résultat. Elle ne fait qu'échanger des bits de place avant de couper l'adresse en deux morceaux : un pour la sélection de la banque, et un autre pour la sélection de l'adresse dans la banque. Cette permutation est fixe, et ne change pas suivant l'adresse. Des fonctions de hachage plus complexes font des XOR bit à bit entre certains bits de l'adresse. ==Les contrôleurs mémoires SDRAM/DDR optimisés== Après avoir vu le cas des mémoires RAM, nous allons nous concentrer sur le cas particulier des mémoires SDRAM. Les mémoires SDRAM et DDR modernes sont capables de gérer plusieurs accès simultanés à la mémoire RAM, et leurs contrôleurs mémoire en font tout autant. C'est une différence majeure avec les mémoires asynchrones FPM/EDO, qui n'acceptaient qu'un seul accès mémoire à la fois. Leur contrôleur mémoire n'acceptait qu'un seul accès mémoire à la fois, c'était un contrôleur mémoire bloquant. Les contrôleurs mémoires des SDRAM sont eux non-bloquants et peuvent encaisser une dizaine d'accès mémoire à la fois. Peu de choses sont connues sur les contrôleurs de SDRAM/DDR modernes, les fabricants ne donnant que peu de détails dessus. Les rares simulateurs qui tentent de décrire leur fonctionnement, comme DRAM SIM I et II, sont particulièrement simples et ne vont pas dans le détail. Néanmoins, le peu qu'on sait est tout de même instructif. ===Rappel sur les SDRAM : tampon de ligne et commandes=== Les mémoires SDRAM sont des mémoires à tampon de ligne. Elles font un accès mémoire en deux étapes. La première étape recopie une ligne de N * 64 bits dans un tampon interne à la SDRAM. La seconde étape sélectionne une colonne, à savoir une donnée de 64 bits, qui est soit envoyée sur le bus de données pour une lecture, soit modifiée par une écriture. [[File:Mémoire à tampon de ligne.png|centre|vignette|upright=2|Mémoire à tampon de ligne]] Les accès mémoire sont traduits par un séquenceur mémoire en une série de commandes mémoires, qui sont séparées par des délais mémoire de quelques cycles d'horloge. Les délais sont très précis, et sont à respecter à la lettre. Une lecture ou une écriture se fait en maximum trois commandes : une commande PRECHARGE qui ferme la ligne précédemment utilisée, une commande ACT qui précise l'adresse de la ligne, et une commande READ ou WRITE qui précise l'adresse de la colonne et éventuellement la donnée à écrire. : Pour être plus précis, la commande PRECHARGE précharge les lignes de bits du plan mémoire à une tension particulière, ce qui les vide de leur contenu. Mais c'est un détail sans importance pour ce qui va suivre. Les SDRAM permettent de se passer de la première étape si des accès consécutifs se font dans la même ligne. Une fois activée, la ligne reste ouverte et on peut accéder plusieurs fois de suite dedans. On a alors juste à préciser l'adresse de colonne dedans. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | bgcolor="#FFA0FF" | PRECHARGE || bgcolor="#FFA0FF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || bgcolor="#A0FFFF" | READ (2) || bgcolor="#A0FFFF" | READ (3) || || || bgcolor="#A0FFFF" | READ (4) || bgcolor="#A0FFFF" | READ (5) || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | Donnée READ 1 || ||bgcolor="#A0FFFF" | Donnée READ 2 | bgcolor="#A0FFFF" | Donnée READ 3 || || || bgcolor="#A0FFFF" | Donnée READ 4 || bgcolor="#A0FFFF" | Donnée READ 5 |} Les SDRAM sont parfois capables de démarrer une commande avant que la précédente soit terminée. Mais le respect des délais mémoire est très limitant, ce qui fait qu'on ne peut pas parler de réel pipeline, comme c'est le cas sur les processeurs. Parlons plutot de '''commandes anticipées'''. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | || bgcolor="#A0FFFF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || || || bgcolor="#FFA0FF" | READ (2) || || || || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 | bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 | |} Un dernier point est que les chips mémoires de SDRAM sont composés de plusieurs banques, chacune étant une sorte de mini-mémoire miniature. Chaque banque a son propre décodeur, son propre tampon de ligne, ses propres multiplexeurs de colonne, sa logique de rafraichissement mémoire, etc. C'est comme si une SDRAM regroupait plusieurs mémoires séparées dans un même circuit intégré. [[File:Arrangement vertical.jpg|centre|vignette|upright=2.5|Mémoire multi-banques.]] ===La mise en attente des accès mémoire=== Un contrôleur de SDRAM peut accepter plusieurs accès mémoire en même temps. Mais cela ne signifie pas que le contrôleur sera prêt à les traiter immédiatement. Pour éviter tout problème de disponibilité, le contrôleur met en attente les accès mémoire que le processeur lui envoie, pour les exécuter dès que possible. Les accès mémoire sont mis en attente dans une mémoire FIFO, histoire de les exécuter dans leur ordre d'arrivée, notamment pour renvoyer les lectures dans l'ordre demandé. Il y a aussi une mémoire FIFO pour les données à écrire et une FIFO pour les données lues. Cette dernière sert au cas où le cache ne soit pas disponible quand on lui envoie la donnée lue. [[File:Module d'interface avec la mémoire.png|centre|vignette|upright=2.5|Contrôleur mémoire avec mise en attente des requêtes processeur et autres optimisations.]] Des optimisations sont possibles dès la mémoire FIFO. Par exemple, on peut regrouper plusieurs accès à des données consécutives en un seul accès en rafale. Le contrôleur mémoire analyse les requêtes mises en attente et détecte si certaines se font à des adresses consécutives. Si c'est le cas, il fusionne ces requêtes en une lecture/écriture en rafale. Une telle optimisation est appelée la '''combinaison de lecture''' pour les lectures, et la '''combinaison d'écriture''' pour les écritures. Une autre optimisation possible est le réacheminement lecture sur écriture (''Store to load forwarding''). Il s'agit d'un équivalent de l’optimisation utilisée dans le cadre de la désambiguïsation mémoire. Quand une lecture demande à accéder à une donnée écrite récemment, il se peut que l'écriture ait été mise en attente dans le contrôleur mémoire. Dans ce cas, le contrôleur mémoire peut renvoyer la donnée directement depuis ses mémoires FIFOs, sans faire d'accès mémoire en lecture. Les deux optimisations précédentes ne paraissent pas payer de mine, mais elles deviennent plus compréhensible quand on sait que certains contrôleurs mémoire peuvent mettre en attente beaucoup de données. Par exemple, l'''Intel E8870 - Scalable Memory Controller'' peut mettre en attente 64 lignes de cache dans la mémoire FIFO, soit 8 kibioctets de RAM ! Deux pages mémoire si la mémoire virtuelle utilise des pages de 4 kibioctets ! ===Les lectures anticipées=== La mise en attente des accès mémoire est une optimisation intéressante, mais pas mirifique. Elle permet surtout de ne pas bloquer le processeur si le contrôleur mémoire a du travail sur la planche. Cependant, elle peut être optimisée quand on se rend compte d'une chose : le processeur ne voit que les lectures, pas les écritures. Pour les écritures, il les envoie au contrôleur mémoire et ce dernier fait le travail demandé, le processeur n'est pas prévenu quand une écriture se termine. Mais pour les lectures, c'est différent, car il reçoit la donnée lue. En conséquence, il est possible d'optimiser les écritures sans que le processeur ne voit quoique ce soit. Une optimisation possible est alors de changer l'ordre des accès mémoire, histoire de retarder les écritures ou au contraire de les faire en avance. La raison est que les mémoires SDRAM n'aiment pas quand on alterne lectures et écritures. Les délais mémoire, les fameux ''timings'' mémoire, sont clairs : faire une suite de lecture ou une suite d'écriture est plus performant que d'alterner entre lectures et écritures. L'idée est alors de changer l'ordre des écritures de manière à les faire en bloc, idem pour les lectures. L’optimisation est possible, mais à condition que le processeur n'y verra que du feu. Or, vous l'avez deviné, il y a des cas où ces réorganisations ne sont pas possibles, et vous avez sans doute trouvé que la situation était familière. L'optimisation en question est une forme d'exécution dans le désordre des accès mémoire, réalisée par le contrôleur mémoire lui-même ! Et qui dit exécution dans le désordre dit : problèmes liés aux dépendances de données. Changer l'ordre des écritures n'est pas toujours possible, notamment en raison des dépendances RAW, WAR et WAW. Pour que le processeur n'y voit que du feu, il faut respecter plusieurs critères. Premièrement, les lectures doivent être renvoyées au processeur dans l'ordre. Et quand je dis dans l'ordre, cela veut dire : dans l'ordre d'envoi des lectures au contrôleur mémoire. Les écritures aussi doivent se faire dans l'ordre, histoire qu'une série d'écriture donne le bon résultat final. Le troisième critère est qu'une lecture doit donner le résultat de la dernière écriture ''à la même adresse''. Pour le dire autrement, il faut juste détecter les dépendances RAW. Concrètement, si on a une série de lectures et d'écritures alternées, on peut regrouper les lectures et les écritures dans deux paquets séparés, à condition qu'aucune lecture ne lise une adresse écrite par une écriture. Mais si une lecture est dépendante d'une écriture, alors la lecture doit attendre que l'écriture se termine, idem pour les lectures suivantes. Une implémentation basique remplace la mémoire FIFO vue au-dessus, par deux mémoires FIFOs : une pour les lectures, une pour les écritures. Lorsqu'un accès mémoire arrive au séquenceur, il regarde si c'est une lecture ou une écriture et place l'accès mémoire dans la file adéquate. Le fait que ce soit des FIFOs garantit que les lectures se font dans l'ordre, idem pour les écritures. En sortie des deux FIFOs, le séquenceur mémoire détecte les dépendances RAW. Prenons l'exemple des coprocesseurs IO 81341 et 81342, qui étaient en réalité des ''chipsets'' intégrant un contrôleur SDRAM. Le ''chipset'' avait 5 ports : un pour les processeurs, un pour le pont sud (''southbridge'') et trois pour des canaux DMA. Pour le port processeur, il n'y avait pas de réordonnancement et ce port utilisait une seule mémoire FIFO. Les autres ports utilisaient l'optimisation qu'on vient de voir, et avaient vraisemblablement des files séparées pour les lectures et écritures. Le séquenceur mémoire vérifiait les dépendances mémoire de type RAW et autres. [[File:Double file d'attente pour les lectures et écritures.png|centre|vignette|upright=2.5|Double file d'attente pour les lectures et écritures]] ===Le ré-ordonnancement des commandes mémoires=== L'optimisation précédente est peu poussée, comparé aux formes d'exécution dans le désordre que les processeurs utilisent. Et on peut se demander si une exécution dans le désordre plus poussée est possible. La réponse est oui : un contrôleur mémoire peut faire des réorganisations bien plus poussées. Par contre, il faut faire une précision très importante : le contrôleur mémoire ne remet pas les accès mémoire dans l'ordre avant de les envoyer au processeur. C'est le processeur qui remet en ordre les accès mémoire, dans sa ''load queue''. En clair : le processeur voit les accès mémoire dans le désordre, c'est lui qui les remet en ordre. Pour cela, les requêtes mémoire sont "numérotées", à savoir qu'on leur attribue un identifiant binaire. Le contrôleur mémoire exécute les lectures/écritures dans le désordre, mais garde la trace de leur identifiant. Il sait dans quel ordre il exécute les accès mémoire et connait leur latence. Ce qui fait qu'il sait que la donnée lue à tel instant est associée à la requête mémoire numéro X. Pour les lectures, il renvoie la donnée lue avec l'identifiant. Le processeur sait alors à quelle lecture la donnée lue correspond et il se débrouille en interne pour gérer la situation. Maintenant que ces précisions sont faites, posons cette question : dans quelles situations est-il pertinent de faire des accès mémoire dans le désordre ? La réponse est : quand plusieurs accès à une même ligne ne sont pas consécutifs, qu'il y a des accès entre les deux. Après ré-ordonnancement, ces accès mémoire à une même ligne sont exécutés l'un à la suite de l'autre, ce qui est beaucoup plus rapide. Pour rendre le tout plus concret, voici un exemple. Imaginez que l'on sait les 3 accès mémoire suivants : * Une lecture ligne A ; * PRECHARGE + ACT ; * Une écriture ligne B ; * PRECHARGE + ACT ; * Une lecture ligne A. Sans réordonnancent, on doit émettre deux commandes PRECHARGE quand on passe d'une ligne à l'autre, et il faut ajouter les commandes ACT avec. pour éviter cela, le contrôleur mémoire peut retarder l'écriture, ou au contraire avancer la seconde lecture. le résultat est alors le suivant : * Lecture ligne A ; * Lecture ligne A * PRECHARGE + ACT ; * Une écriture ligne B. On a donc deux accès consécutifs à la même ligne, suivi par une écriture dans une autre ligne. La technique marche parce que l'on a un mix adéquat de lectures et d'écritures. Et encore une fois, il faut éviter d'intervertir lectures et écritures à une même adresse, sous peine de problèmes. Encore une fois, les dépendances RAW posent problème ! L'implémentation est assez simple : la ou les mémoires FIFOs précédentes sont remplacées par des mémoires similaires aux fenêtres d'instruction. Le contrôleur mémoire vérifie à chaque cycle les accès en attente, et vérifie à quelle ligne ils accèdent. Il priorise alors les accès qui tombent dans la ligne ouverte : ceux-là sont exécutés avant les autres. S'il n'y en a pas, il prend l'accès mémoire le plus ancien (ordre FIFO). Il teste aussi les dépendances mémoires RAW avant d'envoyer des commandes à la mémoire DDR/SDRAM. une telle solution est appelée l''''algorithme ''FR-FCFS''''' (''First Ready-First Come First Serve''). ===Les optimisations liées à la présence de plusieurs banques=== Les optimisations précédentes sont décuplées par la présence de plusieurs banques dans la mémoire SDRAM. Il est en effet possible de faire plusieurs accès mémoire en même temps, dans des banques différentes. Il est possible de lancer un accès mémoire dans deux banques en même temps, par exemple. La seule contrainte est que la SDRAM est limitée à 4 banques actives en même temps. {|class="wikitable" |+ Pipelining basique sur les SDRAM |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 |- ! Banque Numéro 1 | || || || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || || |- ! Banque Numéro 2 | || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || |- ! Banque Numéro 3 | || || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || |} L'implémentation n'a rien de vraiment compliqué. Il suffit d'utiliser une mémoire FIFO par banque. Ainsi, les accès dans une même banque se feront en série, dans l'ordre d'arrivée, mais des accès à des banques différentes se feront dans le désordre. Cela ne pose pas de problème car les accès se font à des adresses différentes, ce qui fait qu'il n'y a pas de conflits majeurs. [[File:Gestion parallèle des banques.png|centre|vignette|upright=2.5|Gestion parallèle des banques.]] ==L'entrelacement sur les mémoires SDRAM== L''''entrelacement''' fonctionne sur les mémoires SDRAM, mais il doit être fortement modifié. Concrètement, tout ce qui a étré dit plus est inapplicable pour les SDRAM, du fait de la présence du tampon de ligne. Des adresses consécutives atterrissent déjà dans la même ligne, ce qui fait qu'on n'a pas besoin d'entrelacement au niveau d'une ligne. Et ne parlons pas de ce la présence de rangées et de canaux mémoire ! Cependant, il peut y avoir un entrelacement lié à la présence des banques SDRAM. L'entrelacement est géré juste avant le séquenceur mémoire. Le '''circuit d'entrelacement''' intervertit certains bits de l'adresse lors des accès mémoires. On trouve aussi des mémoires FIFOs pour mettre en attente les requêtes mémoire provenant du processeur. Elles permettent de recevoir plusieurs accès mémoire consécutifs. Si vous vous demandez comment plusieurs accès mémoire consécutifs peuvent avoir lieu, sachez que c'est une bonne question. Pour le moment, nous allons dire que ces accès proviennent du processeur ou de la mémoire cache, sans expliquer comment. Le contrôleur mémoire reçoit une série d'accès mémoire assez rapprochés, qu'il doit exécuter au mieux. [[File:Controleur mémoire d'une SDRAM avec entrelacement.png|centre|vignette|upright=2.5|Contrôleur mémoire d'une SDRAM avec entrelacement.]] Pour gérer l'entrelacement, le contrôleur de SDRAM prend en entrée l'adresse envoyée par le processeur, et la découpe en plusieurs champs : un pour sélectionner la banque adéquate, un autre pour la ligne, un autre pour la colonne, un autre pour la rangée. Une solution simple désactive l'entrelacement. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de banque || Adresse de ligne || Adresse de colonne |} Si cette solution fonctionne bien si les accès mémoire sont assez aléatoires, en pratique, mieux vaut utiliser l'entrelacement. ===L'entrelacement de banques/lignes=== Une première méthode d'entrelacement est l''''entrelacement de banques'''. Elle répartit deux lignes consécutives dans deux banques différentes. Pour comprendre l'idée, prenons un exemple. Imaginons une mémoire avec deux banques et 4 lignes. Imaginons qu'on parcoure/balaye la mémoire RAM en partant des adresses basses. Sans entrelacement de ligne, les accès se feront comme suit, de gauche à droite : {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Banque numéro 1 !! colspan="4" | Banque numéro 2 |- | Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 || Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 |} Avec l'entrelacement de banques, les accès se feront comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 |- | Ligne 1 || Ligne 1 || Ligne 2 || Ligne 2 || Ligne 3 || Ligne 3 || Ligne 4 || Ligne 4 |} Avec N banques, la première ligne ira dans la première banque, la seconde ligne dans la seconde, et ainsi de suite jusqu'à la énième banque qui va dans la banque numéro N. Et la ligne N+1 repart dans la première banque. L'avantage est que passer d'une ligne à la suivante est assez rapide. Au lieu de devoir fermer la première ligne et ouvrir la suivante on ouvrira directement la ligne suivante dans une banque différente, ce qui sera plus rapide. La commande PRECHARGE pourra être envoyée à la seconde banque en avance. Pour cela, l'adresse est découpée comme suit. {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne (précise à l'octet) |} ===L'entrelacement de rangées=== Il est aussi possible de faire la même chose, mais avec les rangées. Pour simplifier fortement, une rangée est simplement un chip mémoire. En réalité, les rangées ne sont pas des chip mémoire, mais un ensemble de chips mémoire regroupés ensemble histoire d'atteindre les 64 bits du bus de données. Par exemple, une rangée peut combiner 8 chips mémoire avec un bus de données de 8 bits chacun pour obtenir les 64 bits du bus de données d'une SDRAM. Mais nous passons ce détail sous silence dans les explications qui vont suivre, par souci de simplification. Pour faire comprendre l'entrelacement de rangée, le mieux est d'utiliser un exemple, le même que précédemment. Sans entrelacement de rangée, on change de chip mémoire une fois qu'on a balayé toutes ses adresses. {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Rangée numéro 1 !! colspan="4" | Rangée numéro 2 |- | Banque 1 || Banque 2 || Banque 3 || Banque 4 || Banque 1 || Banque 2 || Banque 3 || Banque 4 |} Avec l'entrelacement de ligne, on change de rangée dès qu'on change de banque/ligne. {|class="wikitable" |+ Adresse mémoire |- ! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 |- | Banque 1 || Banque 1 || Banque 2 || Banque 2 || Banque 3 || Banque 3 || Banque 4 || Banque 4 |} En clair, quand on a épuisé toutes les banques dans une même rangée, on passe à la rangée suivante au lieu de rester dans la même rangée. Notons que dans l'exemple précédent, on a combiné l'entrelacement de rangée et de banque, mais on aurait pu utiliser l'entrelacement de rangée seul. Mais ce n'est pas le cas le plus courant en pratique. Toujours est-il qu'en combinant entrelacement de rangée et de ligne, le découpage de l'adresse est le suivant : {|class="wikitable" |+ Adresse mémoire |- | Adresse de ligne || Numéro de rangée || Adresse de banque || Adresse de colonne (précise à l'octet) |} D'autres méthodes d'entrelacement sont possibles, mais elles sont plus complexes. Chaque contrôleur mémoire fait un peu à sa sauce. Ils permettent en général de choisir entre plusieurs entrelacements. Au minimum, ils permettent de désactiver l'entrelacement, d'activer un entrelacement de ligne, et d'autres entrelacements plus complexes, dont certains sont propriétaires et tenus secrets par le fabricant. ===L'entrelacement avec le ''dual channel''=== [[File:Dual channel slots.jpg|vignette|Slots mémoires avec ''dual channel''.]] L'usage du ''dual channel'' complique encore l'entrelacement. Là encore, il y a deux grandes solutions : avec et sans entrelacement des canaux mémoire. Rappelons le principe : deux barrettes de RAM sont accédées en parallèle. Et pour cela, il faut utiliser l'entrelacement. Typiquement, chaque barrette mémoire fournit 64 bits, ce qui fait que l'on peut accéder à 128 bits d'un seul coup, par exemple avec un accès en rafale. Sans ''dual channel'', la première barrette correspond à la moitié haute de la RAM, la seconde barrette correspond à la moitié basse. Avec ''dual channel'', une forme spécifique d'entrelacement est activée. Concrétement, deux blocs de 64 bits sont placés dans des canaux mémoire séparés. Idem avec du triple ou quadruple canal, mais c'est alors trois ou quatre blocs de 64 bits qui sont dispersés dans des canaux consécutifs. Le découpage de l'adresse est alors le suivant : {|class="wikitable" |+ Adresse mémoire |- ! colspan="6" | Sans entrelacement |- | Numéro de canal/barrette || Reste de l'adresse || Position de l'octet dans un bloc de 64 bits |- | colspan="6" | |- ! colspan="6" | Avec entrelacement |- | Reste de l'adresse || Numéro de canal/barrette || Position de l'octet dans un bloc de 64 bits |} [[File:Décodage d'adresse avec dual channel.png|centre|vignette|upright=2|Décodage d'adresse avec dual channel.]] Les contrôleurs de SDRAM précédents sont assez basiques et ne représentent pas les contrôleurs les plus évolués. Ils étaient utilisés sur les anciens PC, à une époque où ils étaient encore sur la carte mère du processeur. Mais de nos jours, le contrôleur mémoire est intégré au processeur. Et il incorpore de nombreuses optimisations afin de gagner en performances. Malheureusement, ces optimisations sont elles-mêmes dépendantes des optimisations intégrées dans le processeur, ce qui fait qu'on ne peut pas en parler ici. Elles demandent que le contrôleur mémoire reçoive plusieurs accès mémoire simultanés, ce qui n'a aucun sens à ce stade du cours. Aussi, nous allons les passer sous silence pour le moment. Nous les verrons dans le chapitre sur le parallélisme mémoire. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=La désambiguïsation mémoire | prevText=La désambiguïsation mémoire | next=Les processeurs superscalaires | nextText=Les processeurs superscalaires }} </noinclude> b1s5eysuv0il4deir1dv9xx1b5wef50 764743 764742 2026-04-23T23:02:59Z Mewtow 31375 /* Les contrôleurs mémoires SDRAM/DDR optimisés */ 764743 wikitext text/x-wiki Dans ce chapitre, nous allons voir les techniques qui permettent de gérer plusieurs accès mémoire simultanés directement au niveau du cache ou de la mémoire RAM. Par plusieurs accès mémoire simultanés, vous pensez sans doute à l'usage de cache multiports, voire de mémoires RAM multiport. C'est en effet une solution, mais ce n'est clairement pas la seule. Par exemple, il est possible de pipeliner l'accès au cache, voire à la mémoire RAM. Il existe de nombreuses techniques de '''parallélisme mémoire''', et nous allons les voir dans ce chapitre. Pour donner un exemple, il est possible de pipeliner les accès mémoire. Il est possible de pipeliner l'accès au cache et/ou l'accès à la mémoire, et nous verrons comment faire dans ce chapitre. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, qu'ils aient ou non un pipeline dynamique. Par contre, pipeliner la mémoire est une technique ancienne, aujourd'hui peu utilisée. Elle était utilisée sur des très vieux ordinateurs, qui avaient un pipeline mais pas de mémoire cache. Mais de nos jours, les mémoires SDRAM et DDR ne sont pas adaptées pour ça, il est très difficile de les pipeliner correctement. Le parallélisme mémoire est utile aussi bien sur des processeurs à émission dans l'ordre que dans le désordre. Par exemple, un processeur ''in-order'' avec des lectures non-bloquantes peut e, profiter. Il peut techniquement lancer une seconde lecture, après avoir rencontré une première lecture non-bloquante. En général, le nombre de lectures consécutives est cependant limité à moins d'une dizaine. À l'opposé, les processeurs sans lectures non-bloquantes profitent pas du parallélisme mémoire. À la rigueur, ils peuvent en profiter s'ils intègrent des techniques de préchargement. Pendant que le processeur exécute une lecture/écriture, il peut précharger une autre donnée en parallèle. Inutile de dire que sans pipeline processeur, le parallélisme mémoire ne sert pas à grand-chose. ==Le ''line fill buffer'' et les techniques associées== Lors d'un défaut de cache, le processeur doit attendre que toute la ligne de cache soit chargée avant d'être utilisable. Or, la taille d'une ligne de cache est supérieure à la largeur du bus mémoire, ce qui fait qu'une ligne de cache est chargée en plusieurs fois, morceaux par morceaux, mot mémoire par mot mémoire. Le chargement peut se faire directement dans le cache, mais ce n'est pas une solution très pratique. À la place, beaucoup de processeurs ajoutent une mémoire tampon entre la RAM et le cache, appelée le '''tampon de remplissage de ligne''' (''line-fill buffer'') dans les processeurs modernes. Lors d'un défaut de cache, le processeur charge la donnée de la RAM dans le tampon de remplissage de ligne, mot mémoire par mot mémoire. Une fois plein, le tampon de remplissage de ligne est recopié dans la ligne de cache. Le tampon de remplissage de ligne contient une ligne de cache, si ce n'est que certains bits de contrôle ne sont pas présents. Le tampon de remplissage de ligne contient un bit de validité pour chaque mot mémoire de la ligne de cache, qui indiquent si le mot mémoire a été chargé. Par exemple, prenons un processeur 64 bits, qui gère donc des mots mémoire de 8 octets, avec des lignes de cache 256 octets/32 mots mémoire. Le tampon de remplissage de ligne contiendra 32 bits de validité. Si le processeur a chargé les 6 premiers mots mémoire, les 6 premiers bits de validité seront mis à 1, les autres seront encore à zéro. Il faut noter que la disponibilité de la ligne complète se détermine assez facilement en faisant un ET logique entre tous les bits de validité. Le processeur sait ainsi quand la ligne de cache est disponible entièrement et donc quand la transférer dans le cache. La même chose existe avec une hiérarchie de cache, sauf que l'on trouve une mémoire tampon entre chaque niveau de cache. Il y a un tampon de remplissage de ligne entre le cache L1 et le cache L2, entre le cache L2 et le L3, etc. Si le cache ne gère qu'un seul défaut de cache à la fois, le ''fill line buffer'' est une mémoire très simple, qui ne mémorise qu'une seule ligne de cache. Et cela vaut aussi bien pour un cache bloquant que non-bloquant. Mais sur les caches capables de gérer plusieurs défauts simultanés, le tampon de remplissage de ligne est une mémoire de type FIFO ou LIFO, capable de mémoriser plusieurs lignes de cache. Notons que le tampon de remplissage de ligne est très utile pour implémenter certaines techniques, par exemple le contournement du cache. Nous avions vu dans le chapitre sur le cache que certains accès mémoire doivent contourner le cache, pour des raisons de cohérence des caches. C'est notamment nécessaire pour accéder aux périphériques, mais c'est aussi utile pour des raisons de performances dans des cas très spécifiques. Les accès qui contournent le cache se font directement dans le tampon de remplissage de ligne : le processeur écrit ou lit les données depuis ce tampon de remplissage de ligne, sans accéder au cache. ===L'''early restart'' et le ''critical word load''=== La présence du ''line buffer'' permet une optimisation assez intéressante, qui permet de réduire la latence des défauts de cache. L'optimisation consiste à lire un mot mémoire dans le tampon de remplissage de ligne, même si la ligne de cache complète n'a pas encore été chargée. Il existe deux manières de faire cela, qui portent les noms d'''early restart'' et de ''critical word load''. La première est la version la plus simple, la seconde est plus complexe mais plus performante. Avec la technique d''''''early restart''''', la ligne de cache est chargée normalement, en partant de son premier mot mémoire. Dès que le mot mémoire lu/écrit par le processeur est copié dans le ''line fill buffer'', il est envoyé au processeur immédiatement. Illustrons le tout par un exemple, où une ligne de cache fait 16 mots mémoire. Le processeur effectue une lecture, qui lit le 5ème mot mémoire. Sans ''early restart'', le processeur doit charger les 16 mots mémoire avant de faire la lecture dans le cache. Avec ''early restart'', le processeur reçoit la donnée dès que le 5ème mot mémoire est disponible. Le processeur doit attendre que les 4 premiers mots mémoire soient chargés, puis le 5ème mot mémoire arrive et est envoyé directement au processeur, il est lu directement depuis le ''line fill buffer''. Les 11 mots mémoire suivants sont ensuite chargés dans le cache pendant que le processeur fait des calculs dans son coin. Le ''critical word load'' est une optimisation de la technique précédente où le chargement de la ligne de cache commence directement à la donnée demandée par le processeur. Pour reprendre l'exemple précédent, où le processeur demande le 5ème mot mémoire sur 16, le ''critical word load'' charge le 5ème mot mémoire en premier et l'envoie au processeur, ce qui fait qu'il est chargé très rapidement. Pas besoin d'attendre que le processeur charge les 4 mots mémoire précédents comme avec l'''early restart''. Dans le détail, le processeur charge le 5ème mot mémoire en premier, puis charge les 11 suivants, et termine par les 4 mots mémoire du début. En clair, le ''critical word load'' commence par charger le mot mémoire lu, puis les blocs suivants, avant de revenir au début du bloc pour charger les blocs restants. Ainsi, la donnée demandée par le processeur sera la première disponible. Pour cela, l'organisation du tampon de remplissage de ligne est modifiée de manière à rendre cela possible. Il a une taille égale à une ligne de cache complète, qui contient elle-même plusieurs mots mémoire. Dans le ''line-fill buffer'', chaque mot mémoire est stocké avec un tag, qui indique l'adresse du mot mémoire stocké dans le ''line-fill buffer''. Le ''line-fill buffer'' est donc un cache un peu particulier, qui fonctionne comme un cache du point de vue du processeur, comme une mémoire FIFO pour les transferts avec le cache. Ainsi, un processeur qui veut lire dans le cache après un défaut peut accéder à la donnée directement depuis le tampon de remplissage de ligne, alors que la ligne de cache n'a pas encore été totalement recopiée en mémoire. ==Les caches pipelinés== Il est possible de pipeliner l'accès au cache, ce qui demande juste de rajouter des registres au bon endroits et quelques circuits de contrôle. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, même ceux avec un pipeline dynamique. Et l'implémentation peut se faire de plusieurs manières différentes. Il faut dire que découper une mémoire cache en plusieurs étapes peut se faire de plusieurs manière. Commençons par la plus simple. Beaucoup de processeurs des années 2000 avaient une fréquence peu élevée comparée aux standards d'aujourd'hui, ce qui fait que leur cache avait bien un temps d'accès d'un cycle d'horloge. Mais l'accès au cache se faisait en deux cycles d'horloge : un cycle pour calculer une adresse et un cycle pour l'accès mémoire proprement dit. Et ces deux étapes étaient pipelinées, à savoir que deux micro-opérations mémoire peuvent s'exécuter en même temps : une dans l'étage de calcul d'adresse, une autre dans le cache. L'unité mémoire était donc pipelinée, alors que l'accès au cache ne l'était pas. Les processeurs qui implémentaient cette technique regroupent les micro-architectures K5 et K6 d'AMD, les processeurs Intel de micro-architecture P6 (Pentium 2 et 3) et quelques autres. Il est aussi possible de pipeliner l'accès au cache lui-même. Avec un cache pipeliné, l'accès au cache ne se fait pas en un seul cycle, mais en plusieurs. Cependant, on peut lancer un nouvel accès au cache à chaque cycle d'horloge, comme un processeur pipeliné lance une nouvelle instruction à chaque cycle. L'implémentation est assez simple : il suffit d'ajouter des registres dans le cache. Pour cela, on profite que le cache est composé de plusieurs composants séparés, qui échangent des données dans un ordre bien précis, d'un composant à un autre. De plus, le trajet des informations dans un cache est linéaire, ce qui les rend parfois pour l'usage d'un pipeline. ===Le ''pipelining'' des caches ''direct-mapped''=== Voici ce qui se passe avec un cache directement adressé. Pour rappel ce genre de cache est conçu en combinant une mémoire RAM, généralement une SRAM, avec quelques circuits de comparaison et un MUX. L'accès au cache se fait globalement en deux étapes : la première lit la donnée et le ''tag'' dans la SRAM, et on utilise les deux dans une seconde étape. Nous avions vu il y a quelques chapitre comment pipeliner des mémoires, dans le chapitre sur les mémoires évoluées. Et bien ces méthodes peuvent s'utiliser pour la mémoire RAM interne au cache !L'idée est d'insérer un registre entre la sortie de la RAM et la suite du cache, pour en faire un cache pipéliné, à deux étages. Le premier étage lit dans la SRAM, le second fait le reste. L'implémentation sur les caches associatifs à plusieurs voies est globalement la même à quelques détails près. [[File:Cache directement adressé pipeliné - deux étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - deux étages]] Avec le circuit précédent, il est possible d'aller plus loin, cette fois en pipelinant l'accès à la mémoire RAM interne au cache. Pour comprendre comment, rappelons qu'une mémoire SRAM est composée d'un plan mémoire et d'un décodeur. L'accès à la mémoire demande d'abord que le décodeur fasse son travail pour sélectionner la case mémoire adéquate, puis ensuite la lecture ou écriture a lieu dans cette case mémoire. L'accès se fait donc en deux étapes successives séparées, on a juste à mettre un registre entre les deux. Il suffit donc de mettre un gros registre entre le décodeur et le plan mémoire. [[File:Cache directement adressé pipeliné - trois étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - trois étages]] Et on peut aller encore plus loin en découpant le décodeur en deux circuits séparés. En effet, rappelez-vous le chapitre sur les circuits de sélection : nous avions vu qu'il est possible de créer des décodeurs en assemblant des décodeurs plus petits, contrôlés par un circuit de prédécodage. Et bien on peut encore une ajouter un registre entre ce circuit de prédécodage et les petits décodeurs. : Théoriquement, toute l'adresse est fournie d'un seul coup au cache, la quasi-totalité des processeurs présentent l'adresse complète à un cache pipeliné. Mais le Pentium 4 fait autrement. Il faut noter que les premiers étages manipulent l'indice dans la SRAM, qui est dans les bits de poids faible, alors que les étages ultérieurs manipulent le ''tag'' qui est dans les bits de poids fort. Les concepteurs du Pentium 4 ont alors décidé de présenter les bits de poids faible lors du premier cycle d'accès au cache, puis ceux de poids fort au second cycle. Pour cela, l'ALU fonctionnait à une fréquence double de celle du processeur, tout comme le cache L1. Il n'y avait pas de pipeline proprement dit, mais cela réduisait grandement la latence d'accès au cache. ===Le ''pipelining'' des caches splittés/sériels=== Il est aussi possible de pipeliner un cache dits splittés, aussi appelés à accès sériel. Pour rappel, les caches à accès sériel vérifient si il y a succès ou défaut de cache, avant d'accéder aux lignes de cache en cas de succès. Ils font donc différemment des autres caches, qui accèdent à une ligne de cache, avant de déterminer s'il y a succès ou défaut en lisant le tag de la ligne de cache. Les caches sériels disposent de deux SRAM : une pour les tags des lignes de cache et une pour les données. Ils accèdent à la SRAM pour les tags, avant d’accéder à la SRAM des données en cas de succès de cache. Vu que l'accès se fait en deux étapes, une vérification des tags suivie de la lecture/écriture des données, il est facile à pipeliner. Pipeliner le cache permet de régler le problème des accès au cache L1, et elle est tout le temps utilisé sur les processeurs modernes. Mais que faire en cas de défaut de cache ? ==Les caches non bloquants== Un '''cache bloquant''' est un cache auquel le processeur ne peut pas accéder pendant un défaut de cache. Il faut attendre que la lecture ou écriture en RAM soit terminée avant de pouvoir utiliser de nouveau le cache. Un '''cache non bloquant''' n'a pas ce problème : on peut l'utiliser pendant un défaut de cache. Les caches non bloquants permettent de démarrer une nouvelle lecture ou écriture alors qu'une autre est en cours, ce qui permet d'exécuter plusieurs lectures ou écritures en même temps. ===Les ''Miss Handling Status Registers''=== Lors d'un défaut de cache, la mémoire RAM est consultée pendant le défaut de cache, mais le cache est inutilisé. Un défaut de cache n'utilise pas le cache, ce n'est qu'un accès en mémoire RAM, sur le bus mémoire (ou un accès aux niveaux de cache inférieurs, peu importe). Le cache en lui-même est laissé libre, rien n’empêche d'y accéder, il est en réalité intrinsèquement non-bloquant. Les caches, bloquants comme non-bloquants, sont en réalité composés d'une mémoire cache proprement dite, entourée de circuits qui servent d'interface entre le processeur et le cache lui-même. Et parmi les circuits tout autour, certains gèrent l'accès au cache lors d'un défaut de cache. Ils sont regroupés sous le terme de '''''Miss Handling Architecture''''' (MHA). La différence entre un cache bloquant et un cache non-bloquant est en réalité liée à l'implémentation de la MHA. Les caches bloquants coupent volontairement l'accès au cache lors d'un défaut, car les défauts sont plus simples à gérer ainsi. Le défaut de cache rapatrie une donnée depuis la RAM, qui sera écrite dans le cache. Et il ne faut pas qu'une tentative d'accès à cette donnée ait lieu avant qu'elle ne soit chargée. Mais ce blocage est général et touche tout le cache, alors que seule une ligne de cache est concernée par le défaut de cache. L'idée derrière un cache non-bloquant est que seule la ligne de cache est bloquée, mais les autres sont accessibles. L'idée est alors de mémoriser les lignes de cache concernées par le défaut de cache, afin d'en bloquer l'accès. À chaque accès, on vérifie si la ligne de cache est déjà réservée par un défaut de cache. La lecture/écriture est alors bloquée si c'est le cas, mais elle accepte les accès sinon. Pour cela, la ''Miss Handling Architecture'' contient des registres qui mémorisent des informations sur les défauts de cache en cours. Ils portent le nom de '''''miss status handling registers''''', que j’appellerais dorénavant MSHR, qui sont aussi appelés des '''''miss buffer'''''. Le contenu des MSHR varie beaucoup suivant le processeur, mais ces derniers stockent au minimum les informations suivantes : * Le numéro de la ligne de cache dans laquelle les données sont chargées. * Un bit de validité qui indique si le MSHR est vide ou pas, qui est mis à 0 quand le défaut de cache est résolu. * Un ou plusieurs champs de lecture/écriture, qui contiennent des informations sur la lecture/écriture. ** Pour une lecture, elle contient des informations sur la destination de la donnée, à savoir qui prévenir quand le défaut de cache est terminé. C'est parfois un nom/numéro de registre (celui dans lequel charger la donnée), mais c'est souvent le numéro de l'entrée dans le ''load/store queue''. ** Pour les écritures, elle contient la donnée à écrire, ou éventuellement un numéro de ''load/store queue'' où se trouve la donnée à écrire. Il faut noter que le bus mémoire ne peut gérer qu'un seul défaut de cache à la fois. Aussi, il est intéressant de regarder ce qui se passe lorsqu'un second défaut de cache survient, pendant qu'un premier est en cours. Dans ce cas, il y a deux réponses qui correspondent à deux types de caches non-bloquants, qui portent les noms barbares de caches de type succès après défaut et défaut après défaut. Sur le premier type, il ne peut pas y avoir plusieurs défauts de cache simultanés. Dès qu'un second défaut de cache survient, le cache stoppe son activité et on ne peut plus démarrer de nouvelle lecture/écriture, tant que le premier défaut de cache n'est pas résolu. Le second type est plus souple et autorise la survenue de plusieurs défauts de cache simultanés. Du moins, jusqu'à une certaine limite, car le cache ne peut supporter qu'un nombre limité d'accès mémoires simultanés (pipelinés). ===Les accès simultanés à une même ligne de cache=== Il arrive que le processeur fasse plusieurs accès mémoire simultanés à la même ligne de cache. Si la ligne de cache en question n'a pas encore été chargée dans le cache, alors on a plusieurs défauts de cache consécutifs pour la même ligne de cache, et le cache non-bloquant doit gérer la situation. Pour la suite, il va falloir faire une petite distinction entre les défauts primaires et secondaires. Imaginons qu'un défaut de cache ait lieu et demande à charger une donnée dans la ligne de cache numéro N. Il s'agit du premier défaut impliquant cette ligne de cache précise, ce qui lui vaut le nom de '''défaut de cache primaire'''. Mais par la suite, d'autres accès mémoire à la même ligne de cache ont lieu, alors que la ligne de cache n'est pas encore disponible. Dans ce cas, il s'agit de '''défauts de cache secondaires'''. Pour l'unité d'accès mémoire, les défauts de cache primaire et secondaire sont différents (ils prennent tous une entrée dans la ''load/store queue''). Mais pour le cache, ils ne correspondent qu'à un seul accès au cache : celui qui demande de charger la ligne de cache demandée. Les défauts de cache primaire et secondaire à la même ligne de cache se voient attribuer un MSHR unique. La gestion des défauts de cache secondaires dépend du cache non-bloquant. La solution la plus simple ne permet pas les défauts de cache secondaires. Le cache ne permet pas deux défauts de cache simultanés pour la même ligne de cache. Les autres solutions le permettent, en fusionnant des accès simultanés à la même ligne de cache en un seul au niveau des MSHR. Dans tous les cas, détecter les défauts de cache secondaires sont un problème qu'il faut détecter. La ''Miss Handling Architecture'' doit détecter les défauts de cache secondaire. Pour cela, elle procède comme suit. Lors de chaque défaut de cache, la MHA récupère le numéro de la ligne de cache associé. Il vérifie alors chaque MSHR pour vérifier s'il contient le numéro en question. S'il n'y a aucune correspondance dans les MSHR, alors c'est signe que le défaut de cache est un défaut primaire. Mais s'il y en a une, alors c'est un défaut secondaire. Évidemment, cela signifie que lors d'un défaut de cache, le numéro de ligne de cache est envoyé à tous les MSHR, pour comparaison. Les MSHR sont donc regroupés dans une mémoire associative, une sorte de mini-cache, faciliter l'implémentation. Lors d'un défaut de cache primaire, l'accès à un cache non-bloquant se fait comme suit : le processeur envoie une adresse au cache, accède à celui-ci, et détecte la survenue d'un défaut de cache. Il en profite alors pour attribuer une ligne de cache dans laquelle sera chargée ce défaut. L'attribution est très simple dans le cas des caches ''direct mapped'', ou associatifs par voie, pour lesquels l'attribution se fait assez simplement. Il mémorise alors cette information dans les MSHR, après avoir vérifié que le défaut de cache n'était pas un défaut secondaire. Lors des accès ultérieurs à une adresse proche, censée être dans la même ligne de cache, le processeur va encore une fois rencontrer un défaut de cache. Il va alors déterminer le numéro de la ligne de cache associée à l'adresse, et comparer ce numéro avec les MSHRs. Si un MSHR contient ce numéro, c'est signe que le défaut de cache est un défaut secondaire. La MHA réagit alors différemment selon le processeur considéré. Une première solution n'autorise pas les défauts de cache secondaires. Si l'un d'entre eux survient, le processeur est gelé par un ''pipeline stall'', une bulle de pipeline. Une autre solution fusionne les défauts de cache secondaires avec le défaut de cache primaire : tout cela ne correspond qu'à un seul défaut de cache pour lui. ===Les MSHR simples=== Un cache non-bloquant à '''MSHR simple''' contient juste plusieurs MSHR qui mémorisent juste un numéro de ligne de cache, un bit de validité, le champ de lecture/écriture, et l'adresse exacte de lecture/écriture. Avec cette organisation, il est possible d'avoir plusieurs défauts de cache séparés, mais à la condition que chaque défaut accède à une ligne de cache différente. Deux accès simultanés à une même ligne de cache ne sont pas possibles, les défauts de cache secondaires ne sont pas autorisés. Ainsi, chaque défaut de cache se voit attribuer son propre MSHR, chacun contient un numéro de ligne de cache différent. Pour comprendre pourquoi c'est impossible de gérer les défauts secondaires, il faut regarder le champ de lecture/écriture. Si on veut effectuer plusieurs écritures consécutives à la même adresse, le MSHR n'aura pas de quoi mémoriser les deux données à écrire. Il pourra mémoriser la première donnée à écrire, pas la seconde. Même chose lors d'une lecture : le champ lecture/écriture peut mémorisr la destination de la première lecture, pas de la seconde. L'avantage est que la MHA n'a besoin que des MSHR et de quelques circuits annexes. Les autres solutions rajoutent des circuits annexes pour gérer les défauts de cache secondaires, qui utilisent beaucoup de circuits. Le cout en circuit est donc élevé, mais le gain en performance est là. Passons maintenant aux caches non-bloquants qui autorisent les défauts de cache secondaire. La solution la plus simple consiste à utiliser ===Les MSHR adressés implicitement=== Avec les '''MSHR adressés implicitement''', il est possible de fusionner plusieurs accès mémoire à une même ligne de cache, mais sous une condition très importante : ces accès lisent/écrivent des mots mémoire différents. Par exemple, imaginons qu'une ligne de cache contienne 8 mots mémoire de 64 bits. Si un premier accès mémoire lit le mot mémoire numéro 7 (dernier mot mémoire de la ligne), et le second accès le mot mémoire numéro 3, alors la fusion est possible. Mais si deux accès mémoire veulent lire/écrire le mot mémoire numéro 7, alors la fusion n'est pas possible et le processeur se bloque, un ''pipeline stall'' survient. Un MSHR adressé implicitement est un MSHR simple, qui contient naturellement un numéro de ligne de cache (tag) et un bit de validité, sauf que le champ de lecture/écriture est dupliqué. Les différents champs lecture/écriture d'un MSHR sont regroupés dans une mémoire RAM/cache qui contient autant d'entrées qu'il y a de mot mémoire dans une ligne de cache. Chaque entrée de la table est associée à un mot mémoire de la ligne de cache et stocke des informations sur celui-ci. Une entrée mémorise, au minimum : * Un champ de lecture/écriture qui contient soit la destination de la lecture, soit la donnée à écrire. * Un bit R/W permettant d'interpréter correctement le champ de lecture/écriture. * Un bit de validité pour chaque entrée de la table, qui dit si un défaut de cache antérieur accède déjà à ce mot mémoire. La fusion de deux défauts de cache est ainsi assez simple. Un défaut de cache primaire/secondaire configure l'entrée associée au mot mémoire lu/écrit. Si un défaut secondaire ultérieur lit/écrit un mot mémoire différent (dont le bit de validité de l'entrée dans la table est à zéro), pas de conflit : il configure une autre entrée, vide. Mais s'il lit/écrit un mot mémoire pour lequel l'entrée de la table est occupée, il y a conflit, le processeur est bloqué par un ''pipeline stall'', une bulle de pipeline. Avec cette organisation, le nombre de MSHR indique combien de lignes de cache peuvent être lues en même temps depuis la mémoire. Quant au nombre d'entrées par MSHR, il détermine combien d'accès mémoires qui ne se recouvrent pas peuvent avoir lieu en même temps. ===Les MSHR adressés explicitement=== Les '''MSHR adressés explicitement''' n'ont pas les contraintes des MSHR adressés implicitement. Avec eux, il est possible d'avoir plusieurs défauts de cache pointant vers la même ligne de cache, mais aussi vers le même mot mémoire. De tels défauts de cache apparaissent sur les processeurs à exécution dans le désordre, mais sont très rares, voire inexistants, sur les processeurs ''in-order''. L'idée est encore que chaque MSHR est associée à une table mémoire qui mémorise des entrées. Cette table n'est autre qu'une mémoire FIFO. Sauf que cette fois-ci, une entrée n'est pas associée à un mot mémoire. Une entrée est associée à un défaut de cache. Une entrée mémorise là encore la destination de la lecture ou la donnée de l'écriture suivi du bit R/W, ainsi que d'un bit de validité par entrée, mais aussi : la position du mot mémoire lu dans la ligne de cache. C'est cette dernière information qui n'était pas présente dans les MSHR adressés implicitement. De plus, le nombre d'entrée par MSHR n'est pas égal au nombre de mots mémoires dans une ligne de cache, mais peut être arbitrairement grand. L'avantage d'un tel cache est qu'il est capable de traité les défauts secondaires concernant un même mot mémoire, et ce, car les accès à la ligne de cache concernée se produisent dans l'ordre de génération des défauts (la table mémoire est une FIFO). ===Les MSHRs inversés=== Généralement, plus on veut supporter de défauts de cache, plus le nombre de MSHR et d'entrées augmente. Mais au-delà d'un certain nombre d'entrées et de MSHR, les MSHR adressés implicitement et explicitement ont tendance à bouffer un peu trop de circuits. Utiliser une organisation un peu moins gourmande en circuits est donc une nécessité. Cette organisation plus économe se base sur des MSHR inversés. Les MSHR inversés ne contiennent qu'une seule entrée, en quelque sorte : au lieu d’utiliser n MSHR de m entrées chacun, on va utiliser n × m MSHR inversés. La différence, c'est que plusieurs MSHR peuvent contenir un tag identique, contrairement aux MSHR adressés implicitement et explicitement. Lorsqu'un défaut de cache a lieu, chaque MSHR est vérifié. Si jamais aucun MSHR ne contient de tag identique à celui utilisé par le défaut, un MSHR vide est choisi pour stocker ce défaut, et une requête de lecture en mémoire est lancée. Dans le cas contraire, un MSHR est réservé au défaut de cache, mais la requête n'est pas lancée. Quand la donnée est disponible, les MSHR correspondant à la ligne qui vient d'être chargée vont être utilisés un par un pour résoudre les défauts de cache en attente. ===Les MSHR intégrés au cache=== Certains chercheurs ont remarqué que pendant qu'une ligne de cache est en train de subir un défaut de cache, celle-ci reste inutilisée, et son contenu est destiné à être perdu une fois le défaut de cache résolu. Ils se sont dit que, plutôt que d'utiliser des MSHR séparés, il vaudrait mieux utiliser la ligne de cache pour stocker les informations sur les défauts de cache en attente dans cette ligne de cache. Pour éviter tout problème, il faut rajouter un bit dans les bits de contrôle de la ligne de cache, qui sert à indiquer que la ligne de cache est occupée : un défaut de cache a eu lieu dans cette ligne, et elle stocke donc des informations pour résoudre les défauts de cache. ==L'entrelacement mémoire== Nous venons de voir comment une mémoire cache peut gérer plusieurs accès mémoire simultanés. Il se trouve que ce genre d'optimisation dépasse largement le cadre du cache. Les mémoires RAM ne sont pas en reste, elles aussi ! Il est possible de pipeliner l'accès à une mémoire RAM, en utilisant des techniques dites d''''entrelacement'''. Précisons cependant que cette optimisation n'est pas utilisée sur les mémoires SDRAM ou DDR modernes, du moins pas sans modifications majeures. Mais divers ordinateurs assez anciens, dont des superordinateurs, ont utilisé cette technique. La technique demande d'utiliser plusieurs mémoires séparées. Sur les anciens ordinateurs, les mémoires en question sont des chips mémoire, à savoir des circuits intégrés de DRAM. Pour faire plus général, nous allons utiliser le terme de '''banques''', ou encore de bancs mémoire. La différence est qu'il existe des mémoires multi-banque, qui regroupent plusieurs banques indépendantes dans un seul boitier, dans un seul chip mémoire. En clair, de telles mémoires regroupent plusieurs sous-mémoires dans un seul circuit intégré. Il est aussi possible d'utiliser plusieurs chips mémoire séparés, chacun ayant une banque. Reste à combiner ces banques pour former un pseudo-pipeline. ===Utiliser plusieurs banques sans entrelacement=== Sans optimisation particulière, les adresses sont réparties dans les banques comme indiqué ci-dessous. Il s'agit de ce qui s'appelle un arrangement horizontal, et nous avions vu celui-ci dans le chapiutre sur les mémoires SDRAM. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Avec cet arrangement, les bits de poids fort de l'adresse sont utilisées pour sélectionner la banque adéquate, et le reste de l'adresse est envoyé sur le bus d'adresse. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Adresse de banque !! Adresse dans la banque |- | Quelques bits de poids fort || Reste de l'adresse |} L'entrelacement change cette répartition, afin d'accélérer les accès mémoire. L'entrelacement classique vise à améliorer les accès à des adresses mémoires consécutives, mais des formes plus évoluées d'entrelacement visent à optimiser des accès mémoire arbitraires. Voyons-les dans l'ordre, du plus simple au plus complexe. ===L'entrelacement classique=== Avec l''''entrelacement classique''', des adresses consécutives sont réparties dans des banques consécutives. Précisons que cette attribution des adresses n'implique absolument pas la mémoire virtuelle ou n'importe quel mécanisme dans le processeur. La répartition décide que telle adresse mémoire va dans telle banque, à telle adresse dans la banque. Elle est donc le fait du contrôleur mémoire, donc en dehors du processeur (les contrôleurs mémoires n’étaient pas encore intégrés dans le processeur à l'époque). [[File:Répartition des adresses dans une mémoire interleaved.png|centre|vignette|upright=1.5|Répartition des adresses dans une mémoire interleaved.]] L'entrelacement simple permet d'accélérer les accès mémoire qui se font à des adresses consécutives. Avec l'entrelacement, chaque accès mémoire tombe dans une banque mémoire différente, ce qui fait qu'on peut démarrer un nouvel accès mémoire à chaque cycle d'horloge. Pendant qu'une banque est occupée par un accès mémoire, on démarre le suivant dans une autre banque, et ainsi de suite. Le tout se termine soit quand on a épuisé toutes les banques libres, soit quand l'accès en rafales se termine. Pas besoin d'attendre que la mémoire ait fini sa lecture/écriture avant de démarrer la lecture/écriture suivante. [[File:Pipemining mémoire.png|centre|vignette|upright=2.5|Pipelining mémoire]] L'animation suivante illustre bien le processus, avec quatres banques. [[File:Interleaving.gif|centre|vignette|upright=2.5|Pipelining mémoire]] L'entrelacement simple utilise les bits de poids faible pour sélectionner la banque, et les bits de poids fort pour la case mémoire. [[File:Adresse mémoire d'une mémoire entrelacée.png|centre|vignette|upright=2|Adresse mémoire d'une mémoire entrelacée]] Précisons que le temps d'accès mémoire ne change pas beaucoup avec l'entrelacement. Par contre, on peut faire plus d'accès mémoire simultanés. On peut démarrer un accès mémoire par cycle d'horloge, mais l'accès en lui-même prend plusieurs cycles. Les mémoires à entrelacement ont donc un débit supérieur aux mémoires qui ne l'utilisent pas. L'entrelacement classique pourrait être utilisé pour accélérer les transferts entre mémoire RAM et cache, qui se font en blocs d'adresses consécutives. Mais de nos jours, il n'est pas utilisé dans cette optique. Il faut dire qu'il est redondant avec le mode rafale des mémoires DRAM. Les deux font globalement la même chose et on voit mal comment utiliser les deux en même temps. Le mode rafale étant plus simple à implémenter, et plus léger niveau utilisation du bus de commande mémoire, il est préféré à l'entrelacement. Par contre, l'entrelacement a eu son heure de gloire sur d'anciens processeurs qui n'avaient pas de mémoire cache, alors qu'ils disposaient d'un pipeline. L'exemple typique est celui des nombreux ''processeurs vectoriels'', que nous n'avons pas encore abordé à ce stade du cours. De tels processeurs avaient un pipeline, une unité de calcul fortement pipelinée, et parfois même de l'exécution dans le désordre et du renommage de registres ! ===L'entrelacement par décalage=== L'entrelacement simple est très utile pour les accès en rafle ou équivalents. Par contre, il existe de rares situations où il n'est pas optimal. Une de ces situations est celle des accès en enjambée (en ''stride''), où l'on accède en série à des adresses sont séparées par N mots mémoires. [[File:Accès par enjambées.png|centre|vignette|upright=2|Accès par enjambées.]] De tels accès surviennent quand un logiciel accède à des structures de données spécifiques, à savoir des tableaux de structures, des matrices, ou d'autres structures de données dans le genre. Un cas classique est celui du parcours d'une matrice colonne par colonne. Les matrices de nombres sont mémorisées ligne par ligne, ce qui fait que parcourir une colonne demande de passer d'une ligne à la suivante et de faire des grands sauts en mémoire RAM, mais tous espacés par la même distance. Avec l'entrelacement simple, les accès en enjambée sont moins performants que les accès à des adresses consécutives, mais cela ne fonctionne pas trop mal. Le pire cas est celui où l'on a N banques et où les données sont justement placées toutes les N adresses. Des accès consécutifs vont tous tomber dans la même banque, on ne peut plus accéder à des banques différentes en parallèle. Mais en dehors de ce cas, on voit une amélioration par rapport à la situation sans entrelacement. Pour obtenir des performances maximales pour les accès en enjambées, il faut répartir les mots mémoires dans la mémoire autrement. L'organisation idéale est la suivante. : Dans les explications qui vont suivre, la variable N représente le nombre de banques, qui sont numérotées de 0 à N-1. On commence par organiser les N premières adresses comme une mémoire entrelacée simple : l'adresse 0 correspond à la banque 0, l'adresse 1 à la banque 1, etc. Pour le bloc suivant, on décale tout d'une adresse, à savoir qu'on commence à remplir les banques à partir de la seconde, non de la première. Une fois la fin du bloc atteinte, on finit de remplir le bloc en repartant du début du bloc. Le troisième bloc d'adresse subit le même traitement, sauf qu'on commence à remplir à partir de la seconde banque. Et on poursuit l’assignation des adresses en décalant d'un cran en plus à chaque bloc. Ainsi, chaque bloc verra ses adresses décalées d'un cran en plus comparé au bloc précédent. Si jamais le décalage dépasse la fin d'un bloc, alors on reprend au début. [[File:Mémoire interleaved par décalage.png|centre|vignette|upright=1.5|Mémoire entrelacée par décalage.]] En faisant cela, on remarque que les banques situées à N adresses d'intervalle sont différentes. Dans l'exemple du dessus, nous avons ajouté un décalage de 1 à chaque nouveau bloc à remplir. Mais on aurait tout aussi bien pu prendre un décalage de 2, 3, etc. Dans tous les cas, on obtient un entrelacement par décalage. Ce décalage est appelé le pas d'entrelacement, noté P. Le calcul de l'adresse à envoyer à la banque, ainsi que la banque à sélectionner se fait en utilisant les formules suivantes : * adresse à envoyer à la banque = adresse totale / N ; * numéro de la banque = (adresse + décalage) modulo N ; * décalage = (adresse totale * P) mod N. Avec cet entrelacement par décalage, on peut prouver que la bande passante maximale est atteinte si le nombre de banques est un nombre premier. Seulement, utiliser un nombre de banques premier peut créer des trous dans la mémoire, des mots mémoires inadressables. Malgré ce défaut, la technique a été utilisée sur quelques ordinateurs, avec l'exemple notable du superordinateur ''Burroughs Scientific Processor''. Pour éviter cela, il y a plusieurs solutions. Par exemple, on peut faire en sorte que N et la taille d'une banque soient premiers entre eux : ils ne doivent pas avoir de diviseur commun. Mais en pratique, elles n'ont pas vraiment été implémentées dans une vraie machine, et sont restées à l'état de recherche, aussi je les passe sous silence. ===L'entrelacement pseudo-aléatoire=== Une dernière méthode de répartition consiste à répartir les adresses dans les banques de manière "pseudo-aléatoire". L'idée exacte est de faire passer l'adresse dans une '''fonction de hachage''', plus ou moins complexe. Pour rappel, une fonction de hachage prend une entrée de grande taille et fournit en sortie un résultat de petite taille. Idéalement, elles doivent donner des résultats aussi différents que possible pour des entrées similaires, histoire de simuler une sorte de pseudo-aléatoire. Ici, une adresse est transformée en un numéro de banque. Une fonction de hachage simple ne fait que permuter des bits de l'adresse pour obtenir son résultat. Elle ne fait qu'échanger des bits de place avant de couper l'adresse en deux morceaux : un pour la sélection de la banque, et un autre pour la sélection de l'adresse dans la banque. Cette permutation est fixe, et ne change pas suivant l'adresse. Des fonctions de hachage plus complexes font des XOR bit à bit entre certains bits de l'adresse. ==Les contrôleurs SDRAM/DDR avec "exécution dans le désordre"== Après avoir vu le cas des mémoires RAM, nous allons nous concentrer sur le cas particulier des mémoires SDRAM. Les mémoires SDRAM et DDR modernes sont capables de gérer plusieurs accès simultanés à la mémoire RAM, et leurs contrôleurs mémoire en font tout autant. C'est une différence majeure avec les mémoires asynchrones FPM/EDO, qui n'acceptaient qu'un seul accès mémoire à la fois. Leur contrôleur mémoire n'acceptait qu'un seul accès mémoire à la fois, c'était un contrôleur mémoire bloquant. Les contrôleurs mémoires des SDRAM sont eux non-bloquants et peuvent encaisser une dizaine d'accès mémoire à la fois. Peu de choses sont connues sur les contrôleurs de SDRAM/DDR modernes, les fabricants ne donnant que peu de détails dessus. Les rares simulateurs qui tentent de décrire leur fonctionnement, comme DRAM SIM I et II, sont particulièrement simples et ne vont pas dans le détail. Néanmoins, le peu qu'on sait est tout de même instructif. ===Rappel sur les SDRAM : tampon de ligne et commandes=== Les mémoires SDRAM sont des mémoires à tampon de ligne. Elles font un accès mémoire en deux étapes. La première étape recopie une ligne de N * 64 bits dans un tampon interne à la SDRAM. La seconde étape sélectionne une colonne, à savoir une donnée de 64 bits, qui est soit envoyée sur le bus de données pour une lecture, soit modifiée par une écriture. [[File:Mémoire à tampon de ligne.png|centre|vignette|upright=2|Mémoire à tampon de ligne]] Les accès mémoire sont traduits par un séquenceur mémoire en une série de commandes mémoires, qui sont séparées par des délais mémoire de quelques cycles d'horloge. Les délais sont très précis, et sont à respecter à la lettre. Une lecture ou une écriture se fait en maximum trois commandes : une commande PRECHARGE qui ferme la ligne précédemment utilisée, une commande ACT qui précise l'adresse de la ligne, et une commande READ ou WRITE qui précise l'adresse de la colonne et éventuellement la donnée à écrire. : Pour être plus précis, la commande PRECHARGE précharge les lignes de bits du plan mémoire à une tension particulière, ce qui les vide de leur contenu. Mais c'est un détail sans importance pour ce qui va suivre. Les SDRAM permettent de se passer de la première étape si des accès consécutifs se font dans la même ligne. Une fois activée, la ligne reste ouverte et on peut accéder plusieurs fois de suite dedans. On a alors juste à préciser l'adresse de colonne dedans. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | bgcolor="#FFA0FF" | PRECHARGE || bgcolor="#FFA0FF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || bgcolor="#A0FFFF" | READ (2) || bgcolor="#A0FFFF" | READ (3) || || || bgcolor="#A0FFFF" | READ (4) || bgcolor="#A0FFFF" | READ (5) || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | Donnée READ 1 || ||bgcolor="#A0FFFF" | Donnée READ 2 | bgcolor="#A0FFFF" | Donnée READ 3 || || || bgcolor="#A0FFFF" | Donnée READ 4 || bgcolor="#A0FFFF" | Donnée READ 5 |} Les SDRAM sont parfois capables de démarrer une commande avant que la précédente soit terminée. Mais le respect des délais mémoire est très limitant, ce qui fait qu'on ne peut pas parler de réel pipeline, comme c'est le cas sur les processeurs. Parlons plutot de '''commandes anticipées'''. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | || bgcolor="#A0FFFF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || || || bgcolor="#FFA0FF" | READ (2) || || || || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 | bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 | |} Un dernier point est que les chips mémoires de SDRAM sont composés de plusieurs banques, chacune étant une sorte de mini-mémoire miniature. Chaque banque a son propre décodeur, son propre tampon de ligne, ses propres multiplexeurs de colonne, sa logique de rafraichissement mémoire, etc. C'est comme si une SDRAM regroupait plusieurs mémoires séparées dans un même circuit intégré. [[File:Arrangement vertical.jpg|centre|vignette|upright=2.5|Mémoire multi-banques.]] ===La mise en attente des accès mémoire=== Un contrôleur de SDRAM peut accepter plusieurs accès mémoire en même temps. Mais cela ne signifie pas que le contrôleur sera prêt à les traiter immédiatement. Pour éviter tout problème de disponibilité, le contrôleur met en attente les accès mémoire que le processeur lui envoie, pour les exécuter dès que possible. Les accès mémoire sont mis en attente dans une mémoire FIFO, histoire de les exécuter dans leur ordre d'arrivée, notamment pour renvoyer les lectures dans l'ordre demandé. Il y a aussi une mémoire FIFO pour les données à écrire et une FIFO pour les données lues. Cette dernière sert au cas où le cache ne soit pas disponible quand on lui envoie la donnée lue. [[File:Module d'interface avec la mémoire.png|centre|vignette|upright=2.5|Contrôleur mémoire avec mise en attente des requêtes processeur et autres optimisations.]] Des optimisations sont possibles dès la mémoire FIFO. Par exemple, on peut regrouper plusieurs accès à des données consécutives en un seul accès en rafale. Le contrôleur mémoire analyse les requêtes mises en attente et détecte si certaines se font à des adresses consécutives. Si c'est le cas, il fusionne ces requêtes en une lecture/écriture en rafale. Une telle optimisation est appelée la '''combinaison de lecture''' pour les lectures, et la '''combinaison d'écriture''' pour les écritures. Une autre optimisation possible est le réacheminement lecture sur écriture (''Store to load forwarding''). Il s'agit d'un équivalent de l’optimisation utilisée dans le cadre de la désambiguïsation mémoire. Quand une lecture demande à accéder à une donnée écrite récemment, il se peut que l'écriture ait été mise en attente dans le contrôleur mémoire. Dans ce cas, le contrôleur mémoire peut renvoyer la donnée directement depuis ses mémoires FIFOs, sans faire d'accès mémoire en lecture. Les deux optimisations précédentes ne paraissent pas payer de mine, mais elles deviennent plus compréhensible quand on sait que certains contrôleurs mémoire peuvent mettre en attente beaucoup de données. Par exemple, l'''Intel E8870 - Scalable Memory Controller'' peut mettre en attente 64 lignes de cache dans la mémoire FIFO, soit 8 kibioctets de RAM ! Deux pages mémoire si la mémoire virtuelle utilise des pages de 4 kibioctets ! ===Les lectures anticipées=== La mise en attente des accès mémoire est une optimisation intéressante, mais pas mirifique. Elle permet surtout de ne pas bloquer le processeur si le contrôleur mémoire a du travail sur la planche. Cependant, elle peut être optimisée quand on se rend compte d'une chose : le processeur ne voit que les lectures, pas les écritures. Pour les écritures, il les envoie au contrôleur mémoire et ce dernier fait le travail demandé, le processeur n'est pas prévenu quand une écriture se termine. Mais pour les lectures, c'est différent, car il reçoit la donnée lue. En conséquence, il est possible d'optimiser les écritures sans que le processeur ne voit quoique ce soit. Une optimisation possible est alors de changer l'ordre des accès mémoire, histoire de retarder les écritures ou au contraire de les faire en avance. La raison est que les mémoires SDRAM n'aiment pas quand on alterne lectures et écritures. Les délais mémoire, les fameux ''timings'' mémoire, sont clairs : faire une suite de lecture ou une suite d'écriture est plus performant que d'alterner entre lectures et écritures. L'idée est alors de changer l'ordre des écritures de manière à les faire en bloc, idem pour les lectures. L’optimisation est possible, mais à condition que le processeur n'y verra que du feu. Or, vous l'avez deviné, il y a des cas où ces réorganisations ne sont pas possibles, et vous avez sans doute trouvé que la situation était familière. L'optimisation en question est une forme d'exécution dans le désordre des accès mémoire, réalisée par le contrôleur mémoire lui-même ! Et qui dit exécution dans le désordre dit : problèmes liés aux dépendances de données. Changer l'ordre des écritures n'est pas toujours possible, notamment en raison des dépendances RAW, WAR et WAW. Pour que le processeur n'y voit que du feu, il faut respecter plusieurs critères. Premièrement, les lectures doivent être renvoyées au processeur dans l'ordre. Et quand je dis dans l'ordre, cela veut dire : dans l'ordre d'envoi des lectures au contrôleur mémoire. Les écritures aussi doivent se faire dans l'ordre, histoire qu'une série d'écriture donne le bon résultat final. Le troisième critère est qu'une lecture doit donner le résultat de la dernière écriture ''à la même adresse''. Pour le dire autrement, il faut juste détecter les dépendances RAW. Concrètement, si on a une série de lectures et d'écritures alternées, on peut regrouper les lectures et les écritures dans deux paquets séparés, à condition qu'aucune lecture ne lise une adresse écrite par une écriture. Mais si une lecture est dépendante d'une écriture, alors la lecture doit attendre que l'écriture se termine, idem pour les lectures suivantes. Une implémentation basique remplace la mémoire FIFO vue au-dessus, par deux mémoires FIFOs : une pour les lectures, une pour les écritures. Lorsqu'un accès mémoire arrive au séquenceur, il regarde si c'est une lecture ou une écriture et place l'accès mémoire dans la file adéquate. Le fait que ce soit des FIFOs garantit que les lectures se font dans l'ordre, idem pour les écritures. En sortie des deux FIFOs, le séquenceur mémoire détecte les dépendances RAW. Prenons l'exemple des coprocesseurs IO 81341 et 81342, qui étaient en réalité des ''chipsets'' intégrant un contrôleur SDRAM. Le ''chipset'' avait 5 ports : un pour les processeurs, un pour le pont sud (''southbridge'') et trois pour des canaux DMA. Pour le port processeur, il n'y avait pas de réordonnancement et ce port utilisait une seule mémoire FIFO. Les autres ports utilisaient l'optimisation qu'on vient de voir, et avaient vraisemblablement des files séparées pour les lectures et écritures. Le séquenceur mémoire vérifiait les dépendances mémoire de type RAW et autres. [[File:Double file d'attente pour les lectures et écritures.png|centre|vignette|upright=2.5|Double file d'attente pour les lectures et écritures]] ===Le ré-ordonnancement des commandes mémoires=== L'optimisation précédente est peu poussée, comparé aux formes d'exécution dans le désordre que les processeurs utilisent. Et on peut se demander si une exécution dans le désordre plus poussée est possible. La réponse est oui : un contrôleur mémoire peut faire des réorganisations bien plus poussées. Par contre, il faut faire une précision très importante : le contrôleur mémoire ne remet pas les accès mémoire dans l'ordre avant de les envoyer au processeur. C'est le processeur qui remet en ordre les accès mémoire, dans sa ''load queue''. En clair : le processeur voit les accès mémoire dans le désordre, c'est lui qui les remet en ordre. Pour cela, les requêtes mémoire sont "numérotées", à savoir qu'on leur attribue un identifiant binaire. Le contrôleur mémoire exécute les lectures/écritures dans le désordre, mais garde la trace de leur identifiant. Il sait dans quel ordre il exécute les accès mémoire et connait leur latence. Ce qui fait qu'il sait que la donnée lue à tel instant est associée à la requête mémoire numéro X. Pour les lectures, il renvoie la donnée lue avec l'identifiant. Le processeur sait alors à quelle lecture la donnée lue correspond et il se débrouille en interne pour gérer la situation. Maintenant que ces précisions sont faites, posons cette question : dans quelles situations est-il pertinent de faire des accès mémoire dans le désordre ? La réponse est : quand plusieurs accès à une même ligne ne sont pas consécutifs, qu'il y a des accès entre les deux. Après ré-ordonnancement, ces accès mémoire à une même ligne sont exécutés l'un à la suite de l'autre, ce qui est beaucoup plus rapide. Pour rendre le tout plus concret, voici un exemple. Imaginez que l'on sait les 3 accès mémoire suivants : * Une lecture ligne A ; * PRECHARGE + ACT ; * Une écriture ligne B ; * PRECHARGE + ACT ; * Une lecture ligne A. Sans réordonnancent, on doit émettre deux commandes PRECHARGE quand on passe d'une ligne à l'autre, et il faut ajouter les commandes ACT avec. pour éviter cela, le contrôleur mémoire peut retarder l'écriture, ou au contraire avancer la seconde lecture. le résultat est alors le suivant : * Lecture ligne A ; * Lecture ligne A * PRECHARGE + ACT ; * Une écriture ligne B. On a donc deux accès consécutifs à la même ligne, suivi par une écriture dans une autre ligne. La technique marche parce que l'on a un mix adéquat de lectures et d'écritures. Et encore une fois, il faut éviter d'intervertir lectures et écritures à une même adresse, sous peine de problèmes. Encore une fois, les dépendances RAW posent problème ! L'implémentation est assez simple : la ou les mémoires FIFOs précédentes sont remplacées par des mémoires similaires aux fenêtres d'instruction. Le contrôleur mémoire vérifie à chaque cycle les accès en attente, et vérifie à quelle ligne ils accèdent. Il priorise alors les accès qui tombent dans la ligne ouverte : ceux-là sont exécutés avant les autres. S'il n'y en a pas, il prend l'accès mémoire le plus ancien (ordre FIFO). Il teste aussi les dépendances mémoires RAW avant d'envoyer des commandes à la mémoire DDR/SDRAM. une telle solution est appelée l''''algorithme ''FR-FCFS''''' (''First Ready-First Come First Serve''). ===Les optimisations liées à la présence de plusieurs banques=== Les optimisations précédentes sont décuplées par la présence de plusieurs banques dans la mémoire SDRAM. Il est en effet possible de faire plusieurs accès mémoire en même temps, dans des banques différentes. Il est possible de lancer un accès mémoire dans deux banques en même temps, par exemple. La seule contrainte est que la SDRAM est limitée à 4 banques actives en même temps. {|class="wikitable" |+ Pipelining basique sur les SDRAM |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 |- ! Banque Numéro 1 | || || || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || || |- ! Banque Numéro 2 | || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || |- ! Banque Numéro 3 | || || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || |} L'implémentation n'a rien de vraiment compliqué. Il suffit d'utiliser une mémoire FIFO par banque. Ainsi, les accès dans une même banque se feront en série, dans l'ordre d'arrivée, mais des accès à des banques différentes se feront dans le désordre. Cela ne pose pas de problème car les accès se font à des adresses différentes, ce qui fait qu'il n'y a pas de conflits majeurs. [[File:Gestion parallèle des banques.png|centre|vignette|upright=2.5|Gestion parallèle des banques.]] ==L'entrelacement sur les mémoires SDRAM== L''''entrelacement''' fonctionne sur les mémoires SDRAM, mais il doit être fortement modifié. Concrètement, tout ce qui a étré dit plus est inapplicable pour les SDRAM, du fait de la présence du tampon de ligne. Des adresses consécutives atterrissent déjà dans la même ligne, ce qui fait qu'on n'a pas besoin d'entrelacement au niveau d'une ligne. Et ne parlons pas de ce la présence de rangées et de canaux mémoire ! Cependant, il peut y avoir un entrelacement lié à la présence des banques SDRAM. L'entrelacement est géré juste avant le séquenceur mémoire. Le '''circuit d'entrelacement''' intervertit certains bits de l'adresse lors des accès mémoires. On trouve aussi des mémoires FIFOs pour mettre en attente les requêtes mémoire provenant du processeur. Elles permettent de recevoir plusieurs accès mémoire consécutifs. Si vous vous demandez comment plusieurs accès mémoire consécutifs peuvent avoir lieu, sachez que c'est une bonne question. Pour le moment, nous allons dire que ces accès proviennent du processeur ou de la mémoire cache, sans expliquer comment. Le contrôleur mémoire reçoit une série d'accès mémoire assez rapprochés, qu'il doit exécuter au mieux. [[File:Controleur mémoire d'une SDRAM avec entrelacement.png|centre|vignette|upright=2.5|Contrôleur mémoire d'une SDRAM avec entrelacement.]] Pour gérer l'entrelacement, le contrôleur de SDRAM prend en entrée l'adresse envoyée par le processeur, et la découpe en plusieurs champs : un pour sélectionner la banque adéquate, un autre pour la ligne, un autre pour la colonne, un autre pour la rangée. Une solution simple désactive l'entrelacement. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de banque || Adresse de ligne || Adresse de colonne |} Si cette solution fonctionne bien si les accès mémoire sont assez aléatoires, en pratique, mieux vaut utiliser l'entrelacement. ===L'entrelacement de banques/lignes=== Une première méthode d'entrelacement est l''''entrelacement de banques'''. Elle répartit deux lignes consécutives dans deux banques différentes. Pour comprendre l'idée, prenons un exemple. Imaginons une mémoire avec deux banques et 4 lignes. Imaginons qu'on parcoure/balaye la mémoire RAM en partant des adresses basses. Sans entrelacement de ligne, les accès se feront comme suit, de gauche à droite : {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Banque numéro 1 !! colspan="4" | Banque numéro 2 |- | Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 || Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 |} Avec l'entrelacement de banques, les accès se feront comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 |- | Ligne 1 || Ligne 1 || Ligne 2 || Ligne 2 || Ligne 3 || Ligne 3 || Ligne 4 || Ligne 4 |} Avec N banques, la première ligne ira dans la première banque, la seconde ligne dans la seconde, et ainsi de suite jusqu'à la énième banque qui va dans la banque numéro N. Et la ligne N+1 repart dans la première banque. L'avantage est que passer d'une ligne à la suivante est assez rapide. Au lieu de devoir fermer la première ligne et ouvrir la suivante on ouvrira directement la ligne suivante dans une banque différente, ce qui sera plus rapide. La commande PRECHARGE pourra être envoyée à la seconde banque en avance. Pour cela, l'adresse est découpée comme suit. {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne (précise à l'octet) |} ===L'entrelacement de rangées=== Il est aussi possible de faire la même chose, mais avec les rangées. Pour simplifier fortement, une rangée est simplement un chip mémoire. En réalité, les rangées ne sont pas des chip mémoire, mais un ensemble de chips mémoire regroupés ensemble histoire d'atteindre les 64 bits du bus de données. Par exemple, une rangée peut combiner 8 chips mémoire avec un bus de données de 8 bits chacun pour obtenir les 64 bits du bus de données d'une SDRAM. Mais nous passons ce détail sous silence dans les explications qui vont suivre, par souci de simplification. Pour faire comprendre l'entrelacement de rangée, le mieux est d'utiliser un exemple, le même que précédemment. Sans entrelacement de rangée, on change de chip mémoire une fois qu'on a balayé toutes ses adresses. {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Rangée numéro 1 !! colspan="4" | Rangée numéro 2 |- | Banque 1 || Banque 2 || Banque 3 || Banque 4 || Banque 1 || Banque 2 || Banque 3 || Banque 4 |} Avec l'entrelacement de ligne, on change de rangée dès qu'on change de banque/ligne. {|class="wikitable" |+ Adresse mémoire |- ! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 |- | Banque 1 || Banque 1 || Banque 2 || Banque 2 || Banque 3 || Banque 3 || Banque 4 || Banque 4 |} En clair, quand on a épuisé toutes les banques dans une même rangée, on passe à la rangée suivante au lieu de rester dans la même rangée. Notons que dans l'exemple précédent, on a combiné l'entrelacement de rangée et de banque, mais on aurait pu utiliser l'entrelacement de rangée seul. Mais ce n'est pas le cas le plus courant en pratique. Toujours est-il qu'en combinant entrelacement de rangée et de ligne, le découpage de l'adresse est le suivant : {|class="wikitable" |+ Adresse mémoire |- | Adresse de ligne || Numéro de rangée || Adresse de banque || Adresse de colonne (précise à l'octet) |} D'autres méthodes d'entrelacement sont possibles, mais elles sont plus complexes. Chaque contrôleur mémoire fait un peu à sa sauce. Ils permettent en général de choisir entre plusieurs entrelacements. Au minimum, ils permettent de désactiver l'entrelacement, d'activer un entrelacement de ligne, et d'autres entrelacements plus complexes, dont certains sont propriétaires et tenus secrets par le fabricant. ===L'entrelacement avec le ''dual channel''=== [[File:Dual channel slots.jpg|vignette|Slots mémoires avec ''dual channel''.]] L'usage du ''dual channel'' complique encore l'entrelacement. Là encore, il y a deux grandes solutions : avec et sans entrelacement des canaux mémoire. Rappelons le principe : deux barrettes de RAM sont accédées en parallèle. Et pour cela, il faut utiliser l'entrelacement. Typiquement, chaque barrette mémoire fournit 64 bits, ce qui fait que l'on peut accéder à 128 bits d'un seul coup, par exemple avec un accès en rafale. Sans ''dual channel'', la première barrette correspond à la moitié haute de la RAM, la seconde barrette correspond à la moitié basse. Avec ''dual channel'', une forme spécifique d'entrelacement est activée. Concrétement, deux blocs de 64 bits sont placés dans des canaux mémoire séparés. Idem avec du triple ou quadruple canal, mais c'est alors trois ou quatre blocs de 64 bits qui sont dispersés dans des canaux consécutifs. Le découpage de l'adresse est alors le suivant : {|class="wikitable" |+ Adresse mémoire |- ! colspan="6" | Sans entrelacement |- | Numéro de canal/barrette || Reste de l'adresse || Position de l'octet dans un bloc de 64 bits |- | colspan="6" | |- ! colspan="6" | Avec entrelacement |- | Reste de l'adresse || Numéro de canal/barrette || Position de l'octet dans un bloc de 64 bits |} [[File:Décodage d'adresse avec dual channel.png|centre|vignette|upright=2|Décodage d'adresse avec dual channel.]] Les contrôleurs de SDRAM précédents sont assez basiques et ne représentent pas les contrôleurs les plus évolués. Ils étaient utilisés sur les anciens PC, à une époque où ils étaient encore sur la carte mère du processeur. Mais de nos jours, le contrôleur mémoire est intégré au processeur. Et il incorpore de nombreuses optimisations afin de gagner en performances. Malheureusement, ces optimisations sont elles-mêmes dépendantes des optimisations intégrées dans le processeur, ce qui fait qu'on ne peut pas en parler ici. Elles demandent que le contrôleur mémoire reçoive plusieurs accès mémoire simultanés, ce qui n'a aucun sens à ce stade du cours. Aussi, nous allons les passer sous silence pour le moment. Nous les verrons dans le chapitre sur le parallélisme mémoire. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=La désambiguïsation mémoire | prevText=La désambiguïsation mémoire | next=Les processeurs superscalaires | nextText=Les processeurs superscalaires }} </noinclude> 0jy3ntkfwdwfkelfryfm7r8gdaeq6q1 764744 764743 2026-04-23T23:05:28Z Mewtow 31375 /* Les optimisations liées à la présence de plusieurs banques */ 764744 wikitext text/x-wiki Dans ce chapitre, nous allons voir les techniques qui permettent de gérer plusieurs accès mémoire simultanés directement au niveau du cache ou de la mémoire RAM. Par plusieurs accès mémoire simultanés, vous pensez sans doute à l'usage de cache multiports, voire de mémoires RAM multiport. C'est en effet une solution, mais ce n'est clairement pas la seule. Par exemple, il est possible de pipeliner l'accès au cache, voire à la mémoire RAM. Il existe de nombreuses techniques de '''parallélisme mémoire''', et nous allons les voir dans ce chapitre. Pour donner un exemple, il est possible de pipeliner les accès mémoire. Il est possible de pipeliner l'accès au cache et/ou l'accès à la mémoire, et nous verrons comment faire dans ce chapitre. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, qu'ils aient ou non un pipeline dynamique. Par contre, pipeliner la mémoire est une technique ancienne, aujourd'hui peu utilisée. Elle était utilisée sur des très vieux ordinateurs, qui avaient un pipeline mais pas de mémoire cache. Mais de nos jours, les mémoires SDRAM et DDR ne sont pas adaptées pour ça, il est très difficile de les pipeliner correctement. Le parallélisme mémoire est utile aussi bien sur des processeurs à émission dans l'ordre que dans le désordre. Par exemple, un processeur ''in-order'' avec des lectures non-bloquantes peut e, profiter. Il peut techniquement lancer une seconde lecture, après avoir rencontré une première lecture non-bloquante. En général, le nombre de lectures consécutives est cependant limité à moins d'une dizaine. À l'opposé, les processeurs sans lectures non-bloquantes profitent pas du parallélisme mémoire. À la rigueur, ils peuvent en profiter s'ils intègrent des techniques de préchargement. Pendant que le processeur exécute une lecture/écriture, il peut précharger une autre donnée en parallèle. Inutile de dire que sans pipeline processeur, le parallélisme mémoire ne sert pas à grand-chose. ==Le ''line fill buffer'' et les techniques associées== Lors d'un défaut de cache, le processeur doit attendre que toute la ligne de cache soit chargée avant d'être utilisable. Or, la taille d'une ligne de cache est supérieure à la largeur du bus mémoire, ce qui fait qu'une ligne de cache est chargée en plusieurs fois, morceaux par morceaux, mot mémoire par mot mémoire. Le chargement peut se faire directement dans le cache, mais ce n'est pas une solution très pratique. À la place, beaucoup de processeurs ajoutent une mémoire tampon entre la RAM et le cache, appelée le '''tampon de remplissage de ligne''' (''line-fill buffer'') dans les processeurs modernes. Lors d'un défaut de cache, le processeur charge la donnée de la RAM dans le tampon de remplissage de ligne, mot mémoire par mot mémoire. Une fois plein, le tampon de remplissage de ligne est recopié dans la ligne de cache. Le tampon de remplissage de ligne contient une ligne de cache, si ce n'est que certains bits de contrôle ne sont pas présents. Le tampon de remplissage de ligne contient un bit de validité pour chaque mot mémoire de la ligne de cache, qui indiquent si le mot mémoire a été chargé. Par exemple, prenons un processeur 64 bits, qui gère donc des mots mémoire de 8 octets, avec des lignes de cache 256 octets/32 mots mémoire. Le tampon de remplissage de ligne contiendra 32 bits de validité. Si le processeur a chargé les 6 premiers mots mémoire, les 6 premiers bits de validité seront mis à 1, les autres seront encore à zéro. Il faut noter que la disponibilité de la ligne complète se détermine assez facilement en faisant un ET logique entre tous les bits de validité. Le processeur sait ainsi quand la ligne de cache est disponible entièrement et donc quand la transférer dans le cache. La même chose existe avec une hiérarchie de cache, sauf que l'on trouve une mémoire tampon entre chaque niveau de cache. Il y a un tampon de remplissage de ligne entre le cache L1 et le cache L2, entre le cache L2 et le L3, etc. Si le cache ne gère qu'un seul défaut de cache à la fois, le ''fill line buffer'' est une mémoire très simple, qui ne mémorise qu'une seule ligne de cache. Et cela vaut aussi bien pour un cache bloquant que non-bloquant. Mais sur les caches capables de gérer plusieurs défauts simultanés, le tampon de remplissage de ligne est une mémoire de type FIFO ou LIFO, capable de mémoriser plusieurs lignes de cache. Notons que le tampon de remplissage de ligne est très utile pour implémenter certaines techniques, par exemple le contournement du cache. Nous avions vu dans le chapitre sur le cache que certains accès mémoire doivent contourner le cache, pour des raisons de cohérence des caches. C'est notamment nécessaire pour accéder aux périphériques, mais c'est aussi utile pour des raisons de performances dans des cas très spécifiques. Les accès qui contournent le cache se font directement dans le tampon de remplissage de ligne : le processeur écrit ou lit les données depuis ce tampon de remplissage de ligne, sans accéder au cache. ===L'''early restart'' et le ''critical word load''=== La présence du ''line buffer'' permet une optimisation assez intéressante, qui permet de réduire la latence des défauts de cache. L'optimisation consiste à lire un mot mémoire dans le tampon de remplissage de ligne, même si la ligne de cache complète n'a pas encore été chargée. Il existe deux manières de faire cela, qui portent les noms d'''early restart'' et de ''critical word load''. La première est la version la plus simple, la seconde est plus complexe mais plus performante. Avec la technique d''''''early restart''''', la ligne de cache est chargée normalement, en partant de son premier mot mémoire. Dès que le mot mémoire lu/écrit par le processeur est copié dans le ''line fill buffer'', il est envoyé au processeur immédiatement. Illustrons le tout par un exemple, où une ligne de cache fait 16 mots mémoire. Le processeur effectue une lecture, qui lit le 5ème mot mémoire. Sans ''early restart'', le processeur doit charger les 16 mots mémoire avant de faire la lecture dans le cache. Avec ''early restart'', le processeur reçoit la donnée dès que le 5ème mot mémoire est disponible. Le processeur doit attendre que les 4 premiers mots mémoire soient chargés, puis le 5ème mot mémoire arrive et est envoyé directement au processeur, il est lu directement depuis le ''line fill buffer''. Les 11 mots mémoire suivants sont ensuite chargés dans le cache pendant que le processeur fait des calculs dans son coin. Le ''critical word load'' est une optimisation de la technique précédente où le chargement de la ligne de cache commence directement à la donnée demandée par le processeur. Pour reprendre l'exemple précédent, où le processeur demande le 5ème mot mémoire sur 16, le ''critical word load'' charge le 5ème mot mémoire en premier et l'envoie au processeur, ce qui fait qu'il est chargé très rapidement. Pas besoin d'attendre que le processeur charge les 4 mots mémoire précédents comme avec l'''early restart''. Dans le détail, le processeur charge le 5ème mot mémoire en premier, puis charge les 11 suivants, et termine par les 4 mots mémoire du début. En clair, le ''critical word load'' commence par charger le mot mémoire lu, puis les blocs suivants, avant de revenir au début du bloc pour charger les blocs restants. Ainsi, la donnée demandée par le processeur sera la première disponible. Pour cela, l'organisation du tampon de remplissage de ligne est modifiée de manière à rendre cela possible. Il a une taille égale à une ligne de cache complète, qui contient elle-même plusieurs mots mémoire. Dans le ''line-fill buffer'', chaque mot mémoire est stocké avec un tag, qui indique l'adresse du mot mémoire stocké dans le ''line-fill buffer''. Le ''line-fill buffer'' est donc un cache un peu particulier, qui fonctionne comme un cache du point de vue du processeur, comme une mémoire FIFO pour les transferts avec le cache. Ainsi, un processeur qui veut lire dans le cache après un défaut peut accéder à la donnée directement depuis le tampon de remplissage de ligne, alors que la ligne de cache n'a pas encore été totalement recopiée en mémoire. ==Les caches pipelinés== Il est possible de pipeliner l'accès au cache, ce qui demande juste de rajouter des registres au bon endroits et quelques circuits de contrôle. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, même ceux avec un pipeline dynamique. Et l'implémentation peut se faire de plusieurs manières différentes. Il faut dire que découper une mémoire cache en plusieurs étapes peut se faire de plusieurs manière. Commençons par la plus simple. Beaucoup de processeurs des années 2000 avaient une fréquence peu élevée comparée aux standards d'aujourd'hui, ce qui fait que leur cache avait bien un temps d'accès d'un cycle d'horloge. Mais l'accès au cache se faisait en deux cycles d'horloge : un cycle pour calculer une adresse et un cycle pour l'accès mémoire proprement dit. Et ces deux étapes étaient pipelinées, à savoir que deux micro-opérations mémoire peuvent s'exécuter en même temps : une dans l'étage de calcul d'adresse, une autre dans le cache. L'unité mémoire était donc pipelinée, alors que l'accès au cache ne l'était pas. Les processeurs qui implémentaient cette technique regroupent les micro-architectures K5 et K6 d'AMD, les processeurs Intel de micro-architecture P6 (Pentium 2 et 3) et quelques autres. Il est aussi possible de pipeliner l'accès au cache lui-même. Avec un cache pipeliné, l'accès au cache ne se fait pas en un seul cycle, mais en plusieurs. Cependant, on peut lancer un nouvel accès au cache à chaque cycle d'horloge, comme un processeur pipeliné lance une nouvelle instruction à chaque cycle. L'implémentation est assez simple : il suffit d'ajouter des registres dans le cache. Pour cela, on profite que le cache est composé de plusieurs composants séparés, qui échangent des données dans un ordre bien précis, d'un composant à un autre. De plus, le trajet des informations dans un cache est linéaire, ce qui les rend parfois pour l'usage d'un pipeline. ===Le ''pipelining'' des caches ''direct-mapped''=== Voici ce qui se passe avec un cache directement adressé. Pour rappel ce genre de cache est conçu en combinant une mémoire RAM, généralement une SRAM, avec quelques circuits de comparaison et un MUX. L'accès au cache se fait globalement en deux étapes : la première lit la donnée et le ''tag'' dans la SRAM, et on utilise les deux dans une seconde étape. Nous avions vu il y a quelques chapitre comment pipeliner des mémoires, dans le chapitre sur les mémoires évoluées. Et bien ces méthodes peuvent s'utiliser pour la mémoire RAM interne au cache !L'idée est d'insérer un registre entre la sortie de la RAM et la suite du cache, pour en faire un cache pipéliné, à deux étages. Le premier étage lit dans la SRAM, le second fait le reste. L'implémentation sur les caches associatifs à plusieurs voies est globalement la même à quelques détails près. [[File:Cache directement adressé pipeliné - deux étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - deux étages]] Avec le circuit précédent, il est possible d'aller plus loin, cette fois en pipelinant l'accès à la mémoire RAM interne au cache. Pour comprendre comment, rappelons qu'une mémoire SRAM est composée d'un plan mémoire et d'un décodeur. L'accès à la mémoire demande d'abord que le décodeur fasse son travail pour sélectionner la case mémoire adéquate, puis ensuite la lecture ou écriture a lieu dans cette case mémoire. L'accès se fait donc en deux étapes successives séparées, on a juste à mettre un registre entre les deux. Il suffit donc de mettre un gros registre entre le décodeur et le plan mémoire. [[File:Cache directement adressé pipeliné - trois étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - trois étages]] Et on peut aller encore plus loin en découpant le décodeur en deux circuits séparés. En effet, rappelez-vous le chapitre sur les circuits de sélection : nous avions vu qu'il est possible de créer des décodeurs en assemblant des décodeurs plus petits, contrôlés par un circuit de prédécodage. Et bien on peut encore une ajouter un registre entre ce circuit de prédécodage et les petits décodeurs. : Théoriquement, toute l'adresse est fournie d'un seul coup au cache, la quasi-totalité des processeurs présentent l'adresse complète à un cache pipeliné. Mais le Pentium 4 fait autrement. Il faut noter que les premiers étages manipulent l'indice dans la SRAM, qui est dans les bits de poids faible, alors que les étages ultérieurs manipulent le ''tag'' qui est dans les bits de poids fort. Les concepteurs du Pentium 4 ont alors décidé de présenter les bits de poids faible lors du premier cycle d'accès au cache, puis ceux de poids fort au second cycle. Pour cela, l'ALU fonctionnait à une fréquence double de celle du processeur, tout comme le cache L1. Il n'y avait pas de pipeline proprement dit, mais cela réduisait grandement la latence d'accès au cache. ===Le ''pipelining'' des caches splittés/sériels=== Il est aussi possible de pipeliner un cache dits splittés, aussi appelés à accès sériel. Pour rappel, les caches à accès sériel vérifient si il y a succès ou défaut de cache, avant d'accéder aux lignes de cache en cas de succès. Ils font donc différemment des autres caches, qui accèdent à une ligne de cache, avant de déterminer s'il y a succès ou défaut en lisant le tag de la ligne de cache. Les caches sériels disposent de deux SRAM : une pour les tags des lignes de cache et une pour les données. Ils accèdent à la SRAM pour les tags, avant d’accéder à la SRAM des données en cas de succès de cache. Vu que l'accès se fait en deux étapes, une vérification des tags suivie de la lecture/écriture des données, il est facile à pipeliner. Pipeliner le cache permet de régler le problème des accès au cache L1, et elle est tout le temps utilisé sur les processeurs modernes. Mais que faire en cas de défaut de cache ? ==Les caches non bloquants== Un '''cache bloquant''' est un cache auquel le processeur ne peut pas accéder pendant un défaut de cache. Il faut attendre que la lecture ou écriture en RAM soit terminée avant de pouvoir utiliser de nouveau le cache. Un '''cache non bloquant''' n'a pas ce problème : on peut l'utiliser pendant un défaut de cache. Les caches non bloquants permettent de démarrer une nouvelle lecture ou écriture alors qu'une autre est en cours, ce qui permet d'exécuter plusieurs lectures ou écritures en même temps. ===Les ''Miss Handling Status Registers''=== Lors d'un défaut de cache, la mémoire RAM est consultée pendant le défaut de cache, mais le cache est inutilisé. Un défaut de cache n'utilise pas le cache, ce n'est qu'un accès en mémoire RAM, sur le bus mémoire (ou un accès aux niveaux de cache inférieurs, peu importe). Le cache en lui-même est laissé libre, rien n’empêche d'y accéder, il est en réalité intrinsèquement non-bloquant. Les caches, bloquants comme non-bloquants, sont en réalité composés d'une mémoire cache proprement dite, entourée de circuits qui servent d'interface entre le processeur et le cache lui-même. Et parmi les circuits tout autour, certains gèrent l'accès au cache lors d'un défaut de cache. Ils sont regroupés sous le terme de '''''Miss Handling Architecture''''' (MHA). La différence entre un cache bloquant et un cache non-bloquant est en réalité liée à l'implémentation de la MHA. Les caches bloquants coupent volontairement l'accès au cache lors d'un défaut, car les défauts sont plus simples à gérer ainsi. Le défaut de cache rapatrie une donnée depuis la RAM, qui sera écrite dans le cache. Et il ne faut pas qu'une tentative d'accès à cette donnée ait lieu avant qu'elle ne soit chargée. Mais ce blocage est général et touche tout le cache, alors que seule une ligne de cache est concernée par le défaut de cache. L'idée derrière un cache non-bloquant est que seule la ligne de cache est bloquée, mais les autres sont accessibles. L'idée est alors de mémoriser les lignes de cache concernées par le défaut de cache, afin d'en bloquer l'accès. À chaque accès, on vérifie si la ligne de cache est déjà réservée par un défaut de cache. La lecture/écriture est alors bloquée si c'est le cas, mais elle accepte les accès sinon. Pour cela, la ''Miss Handling Architecture'' contient des registres qui mémorisent des informations sur les défauts de cache en cours. Ils portent le nom de '''''miss status handling registers''''', que j’appellerais dorénavant MSHR, qui sont aussi appelés des '''''miss buffer'''''. Le contenu des MSHR varie beaucoup suivant le processeur, mais ces derniers stockent au minimum les informations suivantes : * Le numéro de la ligne de cache dans laquelle les données sont chargées. * Un bit de validité qui indique si le MSHR est vide ou pas, qui est mis à 0 quand le défaut de cache est résolu. * Un ou plusieurs champs de lecture/écriture, qui contiennent des informations sur la lecture/écriture. ** Pour une lecture, elle contient des informations sur la destination de la donnée, à savoir qui prévenir quand le défaut de cache est terminé. C'est parfois un nom/numéro de registre (celui dans lequel charger la donnée), mais c'est souvent le numéro de l'entrée dans le ''load/store queue''. ** Pour les écritures, elle contient la donnée à écrire, ou éventuellement un numéro de ''load/store queue'' où se trouve la donnée à écrire. Il faut noter que le bus mémoire ne peut gérer qu'un seul défaut de cache à la fois. Aussi, il est intéressant de regarder ce qui se passe lorsqu'un second défaut de cache survient, pendant qu'un premier est en cours. Dans ce cas, il y a deux réponses qui correspondent à deux types de caches non-bloquants, qui portent les noms barbares de caches de type succès après défaut et défaut après défaut. Sur le premier type, il ne peut pas y avoir plusieurs défauts de cache simultanés. Dès qu'un second défaut de cache survient, le cache stoppe son activité et on ne peut plus démarrer de nouvelle lecture/écriture, tant que le premier défaut de cache n'est pas résolu. Le second type est plus souple et autorise la survenue de plusieurs défauts de cache simultanés. Du moins, jusqu'à une certaine limite, car le cache ne peut supporter qu'un nombre limité d'accès mémoires simultanés (pipelinés). ===Les accès simultanés à une même ligne de cache=== Il arrive que le processeur fasse plusieurs accès mémoire simultanés à la même ligne de cache. Si la ligne de cache en question n'a pas encore été chargée dans le cache, alors on a plusieurs défauts de cache consécutifs pour la même ligne de cache, et le cache non-bloquant doit gérer la situation. Pour la suite, il va falloir faire une petite distinction entre les défauts primaires et secondaires. Imaginons qu'un défaut de cache ait lieu et demande à charger une donnée dans la ligne de cache numéro N. Il s'agit du premier défaut impliquant cette ligne de cache précise, ce qui lui vaut le nom de '''défaut de cache primaire'''. Mais par la suite, d'autres accès mémoire à la même ligne de cache ont lieu, alors que la ligne de cache n'est pas encore disponible. Dans ce cas, il s'agit de '''défauts de cache secondaires'''. Pour l'unité d'accès mémoire, les défauts de cache primaire et secondaire sont différents (ils prennent tous une entrée dans la ''load/store queue''). Mais pour le cache, ils ne correspondent qu'à un seul accès au cache : celui qui demande de charger la ligne de cache demandée. Les défauts de cache primaire et secondaire à la même ligne de cache se voient attribuer un MSHR unique. La gestion des défauts de cache secondaires dépend du cache non-bloquant. La solution la plus simple ne permet pas les défauts de cache secondaires. Le cache ne permet pas deux défauts de cache simultanés pour la même ligne de cache. Les autres solutions le permettent, en fusionnant des accès simultanés à la même ligne de cache en un seul au niveau des MSHR. Dans tous les cas, détecter les défauts de cache secondaires sont un problème qu'il faut détecter. La ''Miss Handling Architecture'' doit détecter les défauts de cache secondaire. Pour cela, elle procède comme suit. Lors de chaque défaut de cache, la MHA récupère le numéro de la ligne de cache associé. Il vérifie alors chaque MSHR pour vérifier s'il contient le numéro en question. S'il n'y a aucune correspondance dans les MSHR, alors c'est signe que le défaut de cache est un défaut primaire. Mais s'il y en a une, alors c'est un défaut secondaire. Évidemment, cela signifie que lors d'un défaut de cache, le numéro de ligne de cache est envoyé à tous les MSHR, pour comparaison. Les MSHR sont donc regroupés dans une mémoire associative, une sorte de mini-cache, faciliter l'implémentation. Lors d'un défaut de cache primaire, l'accès à un cache non-bloquant se fait comme suit : le processeur envoie une adresse au cache, accède à celui-ci, et détecte la survenue d'un défaut de cache. Il en profite alors pour attribuer une ligne de cache dans laquelle sera chargée ce défaut. L'attribution est très simple dans le cas des caches ''direct mapped'', ou associatifs par voie, pour lesquels l'attribution se fait assez simplement. Il mémorise alors cette information dans les MSHR, après avoir vérifié que le défaut de cache n'était pas un défaut secondaire. Lors des accès ultérieurs à une adresse proche, censée être dans la même ligne de cache, le processeur va encore une fois rencontrer un défaut de cache. Il va alors déterminer le numéro de la ligne de cache associée à l'adresse, et comparer ce numéro avec les MSHRs. Si un MSHR contient ce numéro, c'est signe que le défaut de cache est un défaut secondaire. La MHA réagit alors différemment selon le processeur considéré. Une première solution n'autorise pas les défauts de cache secondaires. Si l'un d'entre eux survient, le processeur est gelé par un ''pipeline stall'', une bulle de pipeline. Une autre solution fusionne les défauts de cache secondaires avec le défaut de cache primaire : tout cela ne correspond qu'à un seul défaut de cache pour lui. ===Les MSHR simples=== Un cache non-bloquant à '''MSHR simple''' contient juste plusieurs MSHR qui mémorisent juste un numéro de ligne de cache, un bit de validité, le champ de lecture/écriture, et l'adresse exacte de lecture/écriture. Avec cette organisation, il est possible d'avoir plusieurs défauts de cache séparés, mais à la condition que chaque défaut accède à une ligne de cache différente. Deux accès simultanés à une même ligne de cache ne sont pas possibles, les défauts de cache secondaires ne sont pas autorisés. Ainsi, chaque défaut de cache se voit attribuer son propre MSHR, chacun contient un numéro de ligne de cache différent. Pour comprendre pourquoi c'est impossible de gérer les défauts secondaires, il faut regarder le champ de lecture/écriture. Si on veut effectuer plusieurs écritures consécutives à la même adresse, le MSHR n'aura pas de quoi mémoriser les deux données à écrire. Il pourra mémoriser la première donnée à écrire, pas la seconde. Même chose lors d'une lecture : le champ lecture/écriture peut mémorisr la destination de la première lecture, pas de la seconde. L'avantage est que la MHA n'a besoin que des MSHR et de quelques circuits annexes. Les autres solutions rajoutent des circuits annexes pour gérer les défauts de cache secondaires, qui utilisent beaucoup de circuits. Le cout en circuit est donc élevé, mais le gain en performance est là. Passons maintenant aux caches non-bloquants qui autorisent les défauts de cache secondaire. La solution la plus simple consiste à utiliser ===Les MSHR adressés implicitement=== Avec les '''MSHR adressés implicitement''', il est possible de fusionner plusieurs accès mémoire à une même ligne de cache, mais sous une condition très importante : ces accès lisent/écrivent des mots mémoire différents. Par exemple, imaginons qu'une ligne de cache contienne 8 mots mémoire de 64 bits. Si un premier accès mémoire lit le mot mémoire numéro 7 (dernier mot mémoire de la ligne), et le second accès le mot mémoire numéro 3, alors la fusion est possible. Mais si deux accès mémoire veulent lire/écrire le mot mémoire numéro 7, alors la fusion n'est pas possible et le processeur se bloque, un ''pipeline stall'' survient. Un MSHR adressé implicitement est un MSHR simple, qui contient naturellement un numéro de ligne de cache (tag) et un bit de validité, sauf que le champ de lecture/écriture est dupliqué. Les différents champs lecture/écriture d'un MSHR sont regroupés dans une mémoire RAM/cache qui contient autant d'entrées qu'il y a de mot mémoire dans une ligne de cache. Chaque entrée de la table est associée à un mot mémoire de la ligne de cache et stocke des informations sur celui-ci. Une entrée mémorise, au minimum : * Un champ de lecture/écriture qui contient soit la destination de la lecture, soit la donnée à écrire. * Un bit R/W permettant d'interpréter correctement le champ de lecture/écriture. * Un bit de validité pour chaque entrée de la table, qui dit si un défaut de cache antérieur accède déjà à ce mot mémoire. La fusion de deux défauts de cache est ainsi assez simple. Un défaut de cache primaire/secondaire configure l'entrée associée au mot mémoire lu/écrit. Si un défaut secondaire ultérieur lit/écrit un mot mémoire différent (dont le bit de validité de l'entrée dans la table est à zéro), pas de conflit : il configure une autre entrée, vide. Mais s'il lit/écrit un mot mémoire pour lequel l'entrée de la table est occupée, il y a conflit, le processeur est bloqué par un ''pipeline stall'', une bulle de pipeline. Avec cette organisation, le nombre de MSHR indique combien de lignes de cache peuvent être lues en même temps depuis la mémoire. Quant au nombre d'entrées par MSHR, il détermine combien d'accès mémoires qui ne se recouvrent pas peuvent avoir lieu en même temps. ===Les MSHR adressés explicitement=== Les '''MSHR adressés explicitement''' n'ont pas les contraintes des MSHR adressés implicitement. Avec eux, il est possible d'avoir plusieurs défauts de cache pointant vers la même ligne de cache, mais aussi vers le même mot mémoire. De tels défauts de cache apparaissent sur les processeurs à exécution dans le désordre, mais sont très rares, voire inexistants, sur les processeurs ''in-order''. L'idée est encore que chaque MSHR est associée à une table mémoire qui mémorise des entrées. Cette table n'est autre qu'une mémoire FIFO. Sauf que cette fois-ci, une entrée n'est pas associée à un mot mémoire. Une entrée est associée à un défaut de cache. Une entrée mémorise là encore la destination de la lecture ou la donnée de l'écriture suivi du bit R/W, ainsi que d'un bit de validité par entrée, mais aussi : la position du mot mémoire lu dans la ligne de cache. C'est cette dernière information qui n'était pas présente dans les MSHR adressés implicitement. De plus, le nombre d'entrée par MSHR n'est pas égal au nombre de mots mémoires dans une ligne de cache, mais peut être arbitrairement grand. L'avantage d'un tel cache est qu'il est capable de traité les défauts secondaires concernant un même mot mémoire, et ce, car les accès à la ligne de cache concernée se produisent dans l'ordre de génération des défauts (la table mémoire est une FIFO). ===Les MSHRs inversés=== Généralement, plus on veut supporter de défauts de cache, plus le nombre de MSHR et d'entrées augmente. Mais au-delà d'un certain nombre d'entrées et de MSHR, les MSHR adressés implicitement et explicitement ont tendance à bouffer un peu trop de circuits. Utiliser une organisation un peu moins gourmande en circuits est donc une nécessité. Cette organisation plus économe se base sur des MSHR inversés. Les MSHR inversés ne contiennent qu'une seule entrée, en quelque sorte : au lieu d’utiliser n MSHR de m entrées chacun, on va utiliser n × m MSHR inversés. La différence, c'est que plusieurs MSHR peuvent contenir un tag identique, contrairement aux MSHR adressés implicitement et explicitement. Lorsqu'un défaut de cache a lieu, chaque MSHR est vérifié. Si jamais aucun MSHR ne contient de tag identique à celui utilisé par le défaut, un MSHR vide est choisi pour stocker ce défaut, et une requête de lecture en mémoire est lancée. Dans le cas contraire, un MSHR est réservé au défaut de cache, mais la requête n'est pas lancée. Quand la donnée est disponible, les MSHR correspondant à la ligne qui vient d'être chargée vont être utilisés un par un pour résoudre les défauts de cache en attente. ===Les MSHR intégrés au cache=== Certains chercheurs ont remarqué que pendant qu'une ligne de cache est en train de subir un défaut de cache, celle-ci reste inutilisée, et son contenu est destiné à être perdu une fois le défaut de cache résolu. Ils se sont dit que, plutôt que d'utiliser des MSHR séparés, il vaudrait mieux utiliser la ligne de cache pour stocker les informations sur les défauts de cache en attente dans cette ligne de cache. Pour éviter tout problème, il faut rajouter un bit dans les bits de contrôle de la ligne de cache, qui sert à indiquer que la ligne de cache est occupée : un défaut de cache a eu lieu dans cette ligne, et elle stocke donc des informations pour résoudre les défauts de cache. ==L'entrelacement mémoire== Nous venons de voir comment une mémoire cache peut gérer plusieurs accès mémoire simultanés. Il se trouve que ce genre d'optimisation dépasse largement le cadre du cache. Les mémoires RAM ne sont pas en reste, elles aussi ! Il est possible de pipeliner l'accès à une mémoire RAM, en utilisant des techniques dites d''''entrelacement'''. Précisons cependant que cette optimisation n'est pas utilisée sur les mémoires SDRAM ou DDR modernes, du moins pas sans modifications majeures. Mais divers ordinateurs assez anciens, dont des superordinateurs, ont utilisé cette technique. La technique demande d'utiliser plusieurs mémoires séparées. Sur les anciens ordinateurs, les mémoires en question sont des chips mémoire, à savoir des circuits intégrés de DRAM. Pour faire plus général, nous allons utiliser le terme de '''banques''', ou encore de bancs mémoire. La différence est qu'il existe des mémoires multi-banque, qui regroupent plusieurs banques indépendantes dans un seul boitier, dans un seul chip mémoire. En clair, de telles mémoires regroupent plusieurs sous-mémoires dans un seul circuit intégré. Il est aussi possible d'utiliser plusieurs chips mémoire séparés, chacun ayant une banque. Reste à combiner ces banques pour former un pseudo-pipeline. ===Utiliser plusieurs banques sans entrelacement=== Sans optimisation particulière, les adresses sont réparties dans les banques comme indiqué ci-dessous. Il s'agit de ce qui s'appelle un arrangement horizontal, et nous avions vu celui-ci dans le chapiutre sur les mémoires SDRAM. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Avec cet arrangement, les bits de poids fort de l'adresse sont utilisées pour sélectionner la banque adéquate, et le reste de l'adresse est envoyé sur le bus d'adresse. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Adresse de banque !! Adresse dans la banque |- | Quelques bits de poids fort || Reste de l'adresse |} L'entrelacement change cette répartition, afin d'accélérer les accès mémoire. L'entrelacement classique vise à améliorer les accès à des adresses mémoires consécutives, mais des formes plus évoluées d'entrelacement visent à optimiser des accès mémoire arbitraires. Voyons-les dans l'ordre, du plus simple au plus complexe. ===L'entrelacement classique=== Avec l''''entrelacement classique''', des adresses consécutives sont réparties dans des banques consécutives. Précisons que cette attribution des adresses n'implique absolument pas la mémoire virtuelle ou n'importe quel mécanisme dans le processeur. La répartition décide que telle adresse mémoire va dans telle banque, à telle adresse dans la banque. Elle est donc le fait du contrôleur mémoire, donc en dehors du processeur (les contrôleurs mémoires n’étaient pas encore intégrés dans le processeur à l'époque). [[File:Répartition des adresses dans une mémoire interleaved.png|centre|vignette|upright=1.5|Répartition des adresses dans une mémoire interleaved.]] L'entrelacement simple permet d'accélérer les accès mémoire qui se font à des adresses consécutives. Avec l'entrelacement, chaque accès mémoire tombe dans une banque mémoire différente, ce qui fait qu'on peut démarrer un nouvel accès mémoire à chaque cycle d'horloge. Pendant qu'une banque est occupée par un accès mémoire, on démarre le suivant dans une autre banque, et ainsi de suite. Le tout se termine soit quand on a épuisé toutes les banques libres, soit quand l'accès en rafales se termine. Pas besoin d'attendre que la mémoire ait fini sa lecture/écriture avant de démarrer la lecture/écriture suivante. [[File:Pipemining mémoire.png|centre|vignette|upright=2.5|Pipelining mémoire]] L'animation suivante illustre bien le processus, avec quatres banques. [[File:Interleaving.gif|centre|vignette|upright=2.5|Pipelining mémoire]] L'entrelacement simple utilise les bits de poids faible pour sélectionner la banque, et les bits de poids fort pour la case mémoire. [[File:Adresse mémoire d'une mémoire entrelacée.png|centre|vignette|upright=2|Adresse mémoire d'une mémoire entrelacée]] Précisons que le temps d'accès mémoire ne change pas beaucoup avec l'entrelacement. Par contre, on peut faire plus d'accès mémoire simultanés. On peut démarrer un accès mémoire par cycle d'horloge, mais l'accès en lui-même prend plusieurs cycles. Les mémoires à entrelacement ont donc un débit supérieur aux mémoires qui ne l'utilisent pas. L'entrelacement classique pourrait être utilisé pour accélérer les transferts entre mémoire RAM et cache, qui se font en blocs d'adresses consécutives. Mais de nos jours, il n'est pas utilisé dans cette optique. Il faut dire qu'il est redondant avec le mode rafale des mémoires DRAM. Les deux font globalement la même chose et on voit mal comment utiliser les deux en même temps. Le mode rafale étant plus simple à implémenter, et plus léger niveau utilisation du bus de commande mémoire, il est préféré à l'entrelacement. Par contre, l'entrelacement a eu son heure de gloire sur d'anciens processeurs qui n'avaient pas de mémoire cache, alors qu'ils disposaient d'un pipeline. L'exemple typique est celui des nombreux ''processeurs vectoriels'', que nous n'avons pas encore abordé à ce stade du cours. De tels processeurs avaient un pipeline, une unité de calcul fortement pipelinée, et parfois même de l'exécution dans le désordre et du renommage de registres ! ===L'entrelacement par décalage=== L'entrelacement simple est très utile pour les accès en rafle ou équivalents. Par contre, il existe de rares situations où il n'est pas optimal. Une de ces situations est celle des accès en enjambée (en ''stride''), où l'on accède en série à des adresses sont séparées par N mots mémoires. [[File:Accès par enjambées.png|centre|vignette|upright=2|Accès par enjambées.]] De tels accès surviennent quand un logiciel accède à des structures de données spécifiques, à savoir des tableaux de structures, des matrices, ou d'autres structures de données dans le genre. Un cas classique est celui du parcours d'une matrice colonne par colonne. Les matrices de nombres sont mémorisées ligne par ligne, ce qui fait que parcourir une colonne demande de passer d'une ligne à la suivante et de faire des grands sauts en mémoire RAM, mais tous espacés par la même distance. Avec l'entrelacement simple, les accès en enjambée sont moins performants que les accès à des adresses consécutives, mais cela ne fonctionne pas trop mal. Le pire cas est celui où l'on a N banques et où les données sont justement placées toutes les N adresses. Des accès consécutifs vont tous tomber dans la même banque, on ne peut plus accéder à des banques différentes en parallèle. Mais en dehors de ce cas, on voit une amélioration par rapport à la situation sans entrelacement. Pour obtenir des performances maximales pour les accès en enjambées, il faut répartir les mots mémoires dans la mémoire autrement. L'organisation idéale est la suivante. : Dans les explications qui vont suivre, la variable N représente le nombre de banques, qui sont numérotées de 0 à N-1. On commence par organiser les N premières adresses comme une mémoire entrelacée simple : l'adresse 0 correspond à la banque 0, l'adresse 1 à la banque 1, etc. Pour le bloc suivant, on décale tout d'une adresse, à savoir qu'on commence à remplir les banques à partir de la seconde, non de la première. Une fois la fin du bloc atteinte, on finit de remplir le bloc en repartant du début du bloc. Le troisième bloc d'adresse subit le même traitement, sauf qu'on commence à remplir à partir de la seconde banque. Et on poursuit l’assignation des adresses en décalant d'un cran en plus à chaque bloc. Ainsi, chaque bloc verra ses adresses décalées d'un cran en plus comparé au bloc précédent. Si jamais le décalage dépasse la fin d'un bloc, alors on reprend au début. [[File:Mémoire interleaved par décalage.png|centre|vignette|upright=1.5|Mémoire entrelacée par décalage.]] En faisant cela, on remarque que les banques situées à N adresses d'intervalle sont différentes. Dans l'exemple du dessus, nous avons ajouté un décalage de 1 à chaque nouveau bloc à remplir. Mais on aurait tout aussi bien pu prendre un décalage de 2, 3, etc. Dans tous les cas, on obtient un entrelacement par décalage. Ce décalage est appelé le pas d'entrelacement, noté P. Le calcul de l'adresse à envoyer à la banque, ainsi que la banque à sélectionner se fait en utilisant les formules suivantes : * adresse à envoyer à la banque = adresse totale / N ; * numéro de la banque = (adresse + décalage) modulo N ; * décalage = (adresse totale * P) mod N. Avec cet entrelacement par décalage, on peut prouver que la bande passante maximale est atteinte si le nombre de banques est un nombre premier. Seulement, utiliser un nombre de banques premier peut créer des trous dans la mémoire, des mots mémoires inadressables. Malgré ce défaut, la technique a été utilisée sur quelques ordinateurs, avec l'exemple notable du superordinateur ''Burroughs Scientific Processor''. Pour éviter cela, il y a plusieurs solutions. Par exemple, on peut faire en sorte que N et la taille d'une banque soient premiers entre eux : ils ne doivent pas avoir de diviseur commun. Mais en pratique, elles n'ont pas vraiment été implémentées dans une vraie machine, et sont restées à l'état de recherche, aussi je les passe sous silence. ===L'entrelacement pseudo-aléatoire=== Une dernière méthode de répartition consiste à répartir les adresses dans les banques de manière "pseudo-aléatoire". L'idée exacte est de faire passer l'adresse dans une '''fonction de hachage''', plus ou moins complexe. Pour rappel, une fonction de hachage prend une entrée de grande taille et fournit en sortie un résultat de petite taille. Idéalement, elles doivent donner des résultats aussi différents que possible pour des entrées similaires, histoire de simuler une sorte de pseudo-aléatoire. Ici, une adresse est transformée en un numéro de banque. Une fonction de hachage simple ne fait que permuter des bits de l'adresse pour obtenir son résultat. Elle ne fait qu'échanger des bits de place avant de couper l'adresse en deux morceaux : un pour la sélection de la banque, et un autre pour la sélection de l'adresse dans la banque. Cette permutation est fixe, et ne change pas suivant l'adresse. Des fonctions de hachage plus complexes font des XOR bit à bit entre certains bits de l'adresse. ==Les contrôleurs SDRAM/DDR avec "exécution dans le désordre"== Après avoir vu le cas des mémoires RAM, nous allons nous concentrer sur le cas particulier des mémoires SDRAM. Les mémoires SDRAM et DDR modernes sont capables de gérer plusieurs accès simultanés à la mémoire RAM, et leurs contrôleurs mémoire en font tout autant. C'est une différence majeure avec les mémoires asynchrones FPM/EDO, qui n'acceptaient qu'un seul accès mémoire à la fois. Leur contrôleur mémoire n'acceptait qu'un seul accès mémoire à la fois, c'était un contrôleur mémoire bloquant. Les contrôleurs mémoires des SDRAM sont eux non-bloquants et peuvent encaisser une dizaine d'accès mémoire à la fois. Peu de choses sont connues sur les contrôleurs de SDRAM/DDR modernes, les fabricants ne donnant que peu de détails dessus. Les rares simulateurs qui tentent de décrire leur fonctionnement, comme DRAM SIM I et II, sont particulièrement simples et ne vont pas dans le détail. Néanmoins, le peu qu'on sait est tout de même instructif. ===Rappel sur les SDRAM : tampon de ligne et commandes=== Les mémoires SDRAM sont des mémoires à tampon de ligne. Elles font un accès mémoire en deux étapes. La première étape recopie une ligne de N * 64 bits dans un tampon interne à la SDRAM. La seconde étape sélectionne une colonne, à savoir une donnée de 64 bits, qui est soit envoyée sur le bus de données pour une lecture, soit modifiée par une écriture. [[File:Mémoire à tampon de ligne.png|centre|vignette|upright=2|Mémoire à tampon de ligne]] Les accès mémoire sont traduits par un séquenceur mémoire en une série de commandes mémoires, qui sont séparées par des délais mémoire de quelques cycles d'horloge. Les délais sont très précis, et sont à respecter à la lettre. Une lecture ou une écriture se fait en maximum trois commandes : une commande PRECHARGE qui ferme la ligne précédemment utilisée, une commande ACT qui précise l'adresse de la ligne, et une commande READ ou WRITE qui précise l'adresse de la colonne et éventuellement la donnée à écrire. : Pour être plus précis, la commande PRECHARGE précharge les lignes de bits du plan mémoire à une tension particulière, ce qui les vide de leur contenu. Mais c'est un détail sans importance pour ce qui va suivre. Les SDRAM permettent de se passer de la première étape si des accès consécutifs se font dans la même ligne. Une fois activée, la ligne reste ouverte et on peut accéder plusieurs fois de suite dedans. On a alors juste à préciser l'adresse de colonne dedans. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | bgcolor="#FFA0FF" | PRECHARGE || bgcolor="#FFA0FF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || bgcolor="#A0FFFF" | READ (2) || bgcolor="#A0FFFF" | READ (3) || || || bgcolor="#A0FFFF" | READ (4) || bgcolor="#A0FFFF" | READ (5) || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | Donnée READ 1 || ||bgcolor="#A0FFFF" | Donnée READ 2 | bgcolor="#A0FFFF" | Donnée READ 3 || || || bgcolor="#A0FFFF" | Donnée READ 4 || bgcolor="#A0FFFF" | Donnée READ 5 |} Les SDRAM sont parfois capables de démarrer une commande avant que la précédente soit terminée. Mais le respect des délais mémoire est très limitant, ce qui fait qu'on ne peut pas parler de réel pipeline, comme c'est le cas sur les processeurs. Parlons plutot de '''commandes anticipées'''. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | || bgcolor="#A0FFFF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || || || bgcolor="#FFA0FF" | READ (2) || || || || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 | bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 | |} Un dernier point est que les chips mémoires de SDRAM sont composés de plusieurs banques, chacune étant une sorte de mini-mémoire miniature. Chaque banque a son propre décodeur, son propre tampon de ligne, ses propres multiplexeurs de colonne, sa logique de rafraichissement mémoire, etc. C'est comme si une SDRAM regroupait plusieurs mémoires séparées dans un même circuit intégré. [[File:Arrangement vertical.jpg|centre|vignette|upright=2.5|Mémoire multi-banques.]] ===La mise en attente des accès mémoire=== Un contrôleur de SDRAM peut accepter plusieurs accès mémoire en même temps. Mais cela ne signifie pas que le contrôleur sera prêt à les traiter immédiatement. Pour éviter tout problème de disponibilité, le contrôleur met en attente les accès mémoire que le processeur lui envoie, pour les exécuter dès que possible. Les accès mémoire sont mis en attente dans une mémoire FIFO, histoire de les exécuter dans leur ordre d'arrivée, notamment pour renvoyer les lectures dans l'ordre demandé. Il y a aussi une mémoire FIFO pour les données à écrire et une FIFO pour les données lues. Cette dernière sert au cas où le cache ne soit pas disponible quand on lui envoie la donnée lue. [[File:Module d'interface avec la mémoire.png|centre|vignette|upright=2.5|Contrôleur mémoire avec mise en attente des requêtes processeur et autres optimisations.]] Des optimisations sont possibles dès la mémoire FIFO. Par exemple, on peut regrouper plusieurs accès à des données consécutives en un seul accès en rafale. Le contrôleur mémoire analyse les requêtes mises en attente et détecte si certaines se font à des adresses consécutives. Si c'est le cas, il fusionne ces requêtes en une lecture/écriture en rafale. Une telle optimisation est appelée la '''combinaison de lecture''' pour les lectures, et la '''combinaison d'écriture''' pour les écritures. Une autre optimisation possible est le réacheminement lecture sur écriture (''Store to load forwarding''). Il s'agit d'un équivalent de l’optimisation utilisée dans le cadre de la désambiguïsation mémoire. Quand une lecture demande à accéder à une donnée écrite récemment, il se peut que l'écriture ait été mise en attente dans le contrôleur mémoire. Dans ce cas, le contrôleur mémoire peut renvoyer la donnée directement depuis ses mémoires FIFOs, sans faire d'accès mémoire en lecture. Les deux optimisations précédentes ne paraissent pas payer de mine, mais elles deviennent plus compréhensible quand on sait que certains contrôleurs mémoire peuvent mettre en attente beaucoup de données. Par exemple, l'''Intel E8870 - Scalable Memory Controller'' peut mettre en attente 64 lignes de cache dans la mémoire FIFO, soit 8 kibioctets de RAM ! Deux pages mémoire si la mémoire virtuelle utilise des pages de 4 kibioctets ! ===Les lectures anticipées=== La mise en attente des accès mémoire est une optimisation intéressante, mais pas mirifique. Elle permet surtout de ne pas bloquer le processeur si le contrôleur mémoire a du travail sur la planche. Cependant, elle peut être optimisée quand on se rend compte d'une chose : le processeur ne voit que les lectures, pas les écritures. Pour les écritures, il les envoie au contrôleur mémoire et ce dernier fait le travail demandé, le processeur n'est pas prévenu quand une écriture se termine. Mais pour les lectures, c'est différent, car il reçoit la donnée lue. En conséquence, il est possible d'optimiser les écritures sans que le processeur ne voit quoique ce soit. Une optimisation possible est alors de changer l'ordre des accès mémoire, histoire de retarder les écritures ou au contraire de les faire en avance. La raison est que les mémoires SDRAM n'aiment pas quand on alterne lectures et écritures. Les délais mémoire, les fameux ''timings'' mémoire, sont clairs : faire une suite de lecture ou une suite d'écriture est plus performant que d'alterner entre lectures et écritures. L'idée est alors de changer l'ordre des écritures de manière à les faire en bloc, idem pour les lectures. L’optimisation est possible, mais à condition que le processeur n'y verra que du feu. Or, vous l'avez deviné, il y a des cas où ces réorganisations ne sont pas possibles, et vous avez sans doute trouvé que la situation était familière. L'optimisation en question est une forme d'exécution dans le désordre des accès mémoire, réalisée par le contrôleur mémoire lui-même ! Et qui dit exécution dans le désordre dit : problèmes liés aux dépendances de données. Changer l'ordre des écritures n'est pas toujours possible, notamment en raison des dépendances RAW, WAR et WAW. Pour que le processeur n'y voit que du feu, il faut respecter plusieurs critères. Premièrement, les lectures doivent être renvoyées au processeur dans l'ordre. Et quand je dis dans l'ordre, cela veut dire : dans l'ordre d'envoi des lectures au contrôleur mémoire. Les écritures aussi doivent se faire dans l'ordre, histoire qu'une série d'écriture donne le bon résultat final. Le troisième critère est qu'une lecture doit donner le résultat de la dernière écriture ''à la même adresse''. Pour le dire autrement, il faut juste détecter les dépendances RAW. Concrètement, si on a une série de lectures et d'écritures alternées, on peut regrouper les lectures et les écritures dans deux paquets séparés, à condition qu'aucune lecture ne lise une adresse écrite par une écriture. Mais si une lecture est dépendante d'une écriture, alors la lecture doit attendre que l'écriture se termine, idem pour les lectures suivantes. Une implémentation basique remplace la mémoire FIFO vue au-dessus, par deux mémoires FIFOs : une pour les lectures, une pour les écritures. Lorsqu'un accès mémoire arrive au séquenceur, il regarde si c'est une lecture ou une écriture et place l'accès mémoire dans la file adéquate. Le fait que ce soit des FIFOs garantit que les lectures se font dans l'ordre, idem pour les écritures. En sortie des deux FIFOs, le séquenceur mémoire détecte les dépendances RAW. Prenons l'exemple des coprocesseurs IO 81341 et 81342, qui étaient en réalité des ''chipsets'' intégrant un contrôleur SDRAM. Le ''chipset'' avait 5 ports : un pour les processeurs, un pour le pont sud (''southbridge'') et trois pour des canaux DMA. Pour le port processeur, il n'y avait pas de réordonnancement et ce port utilisait une seule mémoire FIFO. Les autres ports utilisaient l'optimisation qu'on vient de voir, et avaient vraisemblablement des files séparées pour les lectures et écritures. Le séquenceur mémoire vérifiait les dépendances mémoire de type RAW et autres. [[File:Double file d'attente pour les lectures et écritures.png|centre|vignette|upright=2.5|Double file d'attente pour les lectures et écritures]] ===Le ré-ordonnancement des commandes mémoires=== L'optimisation précédente est peu poussée, comparé aux formes d'exécution dans le désordre que les processeurs utilisent. Et on peut se demander si une exécution dans le désordre plus poussée est possible. La réponse est oui : un contrôleur mémoire peut faire des réorganisations bien plus poussées. Par contre, il faut faire une précision très importante : le contrôleur mémoire ne remet pas les accès mémoire dans l'ordre avant de les envoyer au processeur. C'est le processeur qui remet en ordre les accès mémoire, dans sa ''load queue''. En clair : le processeur voit les accès mémoire dans le désordre, c'est lui qui les remet en ordre. Pour cela, les requêtes mémoire sont "numérotées", à savoir qu'on leur attribue un identifiant binaire. Le contrôleur mémoire exécute les lectures/écritures dans le désordre, mais garde la trace de leur identifiant. Il sait dans quel ordre il exécute les accès mémoire et connait leur latence. Ce qui fait qu'il sait que la donnée lue à tel instant est associée à la requête mémoire numéro X. Pour les lectures, il renvoie la donnée lue avec l'identifiant. Le processeur sait alors à quelle lecture la donnée lue correspond et il se débrouille en interne pour gérer la situation. Maintenant que ces précisions sont faites, posons cette question : dans quelles situations est-il pertinent de faire des accès mémoire dans le désordre ? La réponse est : quand plusieurs accès à une même ligne ne sont pas consécutifs, qu'il y a des accès entre les deux. Après ré-ordonnancement, ces accès mémoire à une même ligne sont exécutés l'un à la suite de l'autre, ce qui est beaucoup plus rapide. Pour rendre le tout plus concret, voici un exemple. Imaginez que l'on sait les 3 accès mémoire suivants : * Une lecture ligne A ; * PRECHARGE + ACT ; * Une écriture ligne B ; * PRECHARGE + ACT ; * Une lecture ligne A. Sans réordonnancent, on doit émettre deux commandes PRECHARGE quand on passe d'une ligne à l'autre, et il faut ajouter les commandes ACT avec. pour éviter cela, le contrôleur mémoire peut retarder l'écriture, ou au contraire avancer la seconde lecture. le résultat est alors le suivant : * Lecture ligne A ; * Lecture ligne A * PRECHARGE + ACT ; * Une écriture ligne B. On a donc deux accès consécutifs à la même ligne, suivi par une écriture dans une autre ligne. La technique marche parce que l'on a un mix adéquat de lectures et d'écritures. Et encore une fois, il faut éviter d'intervertir lectures et écritures à une même adresse, sous peine de problèmes. Encore une fois, les dépendances RAW posent problème ! L'implémentation est assez simple : la ou les mémoires FIFOs précédentes sont remplacées par des mémoires similaires aux fenêtres d'instruction. Le contrôleur mémoire vérifie à chaque cycle les accès en attente, et vérifie à quelle ligne ils accèdent. Il priorise alors les accès qui tombent dans la ligne ouverte : ceux-là sont exécutés avant les autres. S'il n'y en a pas, il prend l'accès mémoire le plus ancien (ordre FIFO). Il teste aussi les dépendances mémoires RAW avant d'envoyer des commandes à la mémoire DDR/SDRAM. une telle solution est appelée l''''algorithme ''FR-FCFS''''' (''First Ready-First Come First Serve''). ===Les optimisations liées à la présence de plusieurs banques=== Les optimisations précédentes sont décuplées par la présence de plusieurs banques dans la mémoire SDRAM. Il est en effet possible de faire plusieurs accès mémoire en même temps, dans des banques différentes. Il est possible de lancer un accès mémoire dans deux banques en même temps, par exemple. La seule contrainte est que la SDRAM est limitée à 4 banques actives en même temps. {|class="wikitable" |+ Pipelining basique sur les SDRAM |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 |- ! Banque Numéro 1 | || || || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || || |- ! Banque Numéro 2 | || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || |- ! Banque Numéro 3 | || || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || |} Les optimisations précédentes peuvent s'appliquer par banque. Il est par exemple possible d'utiliser une mémoire FIFO par banque. Ainsi, les accès dans une même banque se feront en série, dans l'ordre d'arrivée, mais des accès à des banques différentes se feront dans le désordre. Cela ne pose pas de problème car les accès se font à des adresses différentes, ce qui fait qu'il n'y a pas de conflits majeurs. Il y a cependant un risque que les lectures se fassent dans le désordre si on n'y prend pas garde. [[File:Gestion parallèle des banques.png|centre|vignette|upright=2.5|Gestion parallèle des banques.]] il est aussi possible d'utiliser une file séparée pour les lectures et les écriture, pour chaque banque. Les performances sont alors améliorées comparé à deux files globales pour toute la SDRAM. En idem avec la réorganisation des accès mémoire, pour regrouper les accès à une même ligne. ==L'entrelacement sur les mémoires SDRAM== L''''entrelacement''' fonctionne sur les mémoires SDRAM, mais il doit être fortement modifié. Concrètement, tout ce qui a étré dit plus est inapplicable pour les SDRAM, du fait de la présence du tampon de ligne. Des adresses consécutives atterrissent déjà dans la même ligne, ce qui fait qu'on n'a pas besoin d'entrelacement au niveau d'une ligne. Et ne parlons pas de ce la présence de rangées et de canaux mémoire ! Cependant, il peut y avoir un entrelacement lié à la présence des banques SDRAM. L'entrelacement est géré juste avant le séquenceur mémoire. Le '''circuit d'entrelacement''' intervertit certains bits de l'adresse lors des accès mémoires. On trouve aussi des mémoires FIFOs pour mettre en attente les requêtes mémoire provenant du processeur. Elles permettent de recevoir plusieurs accès mémoire consécutifs. Si vous vous demandez comment plusieurs accès mémoire consécutifs peuvent avoir lieu, sachez que c'est une bonne question. Pour le moment, nous allons dire que ces accès proviennent du processeur ou de la mémoire cache, sans expliquer comment. Le contrôleur mémoire reçoit une série d'accès mémoire assez rapprochés, qu'il doit exécuter au mieux. [[File:Controleur mémoire d'une SDRAM avec entrelacement.png|centre|vignette|upright=2.5|Contrôleur mémoire d'une SDRAM avec entrelacement.]] Pour gérer l'entrelacement, le contrôleur de SDRAM prend en entrée l'adresse envoyée par le processeur, et la découpe en plusieurs champs : un pour sélectionner la banque adéquate, un autre pour la ligne, un autre pour la colonne, un autre pour la rangée. Une solution simple désactive l'entrelacement. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de banque || Adresse de ligne || Adresse de colonne |} Si cette solution fonctionne bien si les accès mémoire sont assez aléatoires, en pratique, mieux vaut utiliser l'entrelacement. ===L'entrelacement de banques/lignes=== Une première méthode d'entrelacement est l''''entrelacement de banques'''. Elle répartit deux lignes consécutives dans deux banques différentes. Pour comprendre l'idée, prenons un exemple. Imaginons une mémoire avec deux banques et 4 lignes. Imaginons qu'on parcoure/balaye la mémoire RAM en partant des adresses basses. Sans entrelacement de ligne, les accès se feront comme suit, de gauche à droite : {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Banque numéro 1 !! colspan="4" | Banque numéro 2 |- | Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 || Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 |} Avec l'entrelacement de banques, les accès se feront comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 |- | Ligne 1 || Ligne 1 || Ligne 2 || Ligne 2 || Ligne 3 || Ligne 3 || Ligne 4 || Ligne 4 |} Avec N banques, la première ligne ira dans la première banque, la seconde ligne dans la seconde, et ainsi de suite jusqu'à la énième banque qui va dans la banque numéro N. Et la ligne N+1 repart dans la première banque. L'avantage est que passer d'une ligne à la suivante est assez rapide. Au lieu de devoir fermer la première ligne et ouvrir la suivante on ouvrira directement la ligne suivante dans une banque différente, ce qui sera plus rapide. La commande PRECHARGE pourra être envoyée à la seconde banque en avance. Pour cela, l'adresse est découpée comme suit. {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne (précise à l'octet) |} ===L'entrelacement de rangées=== Il est aussi possible de faire la même chose, mais avec les rangées. Pour simplifier fortement, une rangée est simplement un chip mémoire. En réalité, les rangées ne sont pas des chip mémoire, mais un ensemble de chips mémoire regroupés ensemble histoire d'atteindre les 64 bits du bus de données. Par exemple, une rangée peut combiner 8 chips mémoire avec un bus de données de 8 bits chacun pour obtenir les 64 bits du bus de données d'une SDRAM. Mais nous passons ce détail sous silence dans les explications qui vont suivre, par souci de simplification. Pour faire comprendre l'entrelacement de rangée, le mieux est d'utiliser un exemple, le même que précédemment. Sans entrelacement de rangée, on change de chip mémoire une fois qu'on a balayé toutes ses adresses. {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Rangée numéro 1 !! colspan="4" | Rangée numéro 2 |- | Banque 1 || Banque 2 || Banque 3 || Banque 4 || Banque 1 || Banque 2 || Banque 3 || Banque 4 |} Avec l'entrelacement de ligne, on change de rangée dès qu'on change de banque/ligne. {|class="wikitable" |+ Adresse mémoire |- ! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 |- | Banque 1 || Banque 1 || Banque 2 || Banque 2 || Banque 3 || Banque 3 || Banque 4 || Banque 4 |} En clair, quand on a épuisé toutes les banques dans une même rangée, on passe à la rangée suivante au lieu de rester dans la même rangée. Notons que dans l'exemple précédent, on a combiné l'entrelacement de rangée et de banque, mais on aurait pu utiliser l'entrelacement de rangée seul. Mais ce n'est pas le cas le plus courant en pratique. Toujours est-il qu'en combinant entrelacement de rangée et de ligne, le découpage de l'adresse est le suivant : {|class="wikitable" |+ Adresse mémoire |- | Adresse de ligne || Numéro de rangée || Adresse de banque || Adresse de colonne (précise à l'octet) |} D'autres méthodes d'entrelacement sont possibles, mais elles sont plus complexes. Chaque contrôleur mémoire fait un peu à sa sauce. Ils permettent en général de choisir entre plusieurs entrelacements. Au minimum, ils permettent de désactiver l'entrelacement, d'activer un entrelacement de ligne, et d'autres entrelacements plus complexes, dont certains sont propriétaires et tenus secrets par le fabricant. ===L'entrelacement avec le ''dual channel''=== [[File:Dual channel slots.jpg|vignette|Slots mémoires avec ''dual channel''.]] L'usage du ''dual channel'' complique encore l'entrelacement. Là encore, il y a deux grandes solutions : avec et sans entrelacement des canaux mémoire. Rappelons le principe : deux barrettes de RAM sont accédées en parallèle. Et pour cela, il faut utiliser l'entrelacement. Typiquement, chaque barrette mémoire fournit 64 bits, ce qui fait que l'on peut accéder à 128 bits d'un seul coup, par exemple avec un accès en rafale. Sans ''dual channel'', la première barrette correspond à la moitié haute de la RAM, la seconde barrette correspond à la moitié basse. Avec ''dual channel'', une forme spécifique d'entrelacement est activée. Concrétement, deux blocs de 64 bits sont placés dans des canaux mémoire séparés. Idem avec du triple ou quadruple canal, mais c'est alors trois ou quatre blocs de 64 bits qui sont dispersés dans des canaux consécutifs. Le découpage de l'adresse est alors le suivant : {|class="wikitable" |+ Adresse mémoire |- ! colspan="6" | Sans entrelacement |- | Numéro de canal/barrette || Reste de l'adresse || Position de l'octet dans un bloc de 64 bits |- | colspan="6" | |- ! colspan="6" | Avec entrelacement |- | Reste de l'adresse || Numéro de canal/barrette || Position de l'octet dans un bloc de 64 bits |} [[File:Décodage d'adresse avec dual channel.png|centre|vignette|upright=2|Décodage d'adresse avec dual channel.]] Les contrôleurs de SDRAM précédents sont assez basiques et ne représentent pas les contrôleurs les plus évolués. Ils étaient utilisés sur les anciens PC, à une époque où ils étaient encore sur la carte mère du processeur. Mais de nos jours, le contrôleur mémoire est intégré au processeur. Et il incorpore de nombreuses optimisations afin de gagner en performances. Malheureusement, ces optimisations sont elles-mêmes dépendantes des optimisations intégrées dans le processeur, ce qui fait qu'on ne peut pas en parler ici. Elles demandent que le contrôleur mémoire reçoive plusieurs accès mémoire simultanés, ce qui n'a aucun sens à ce stade du cours. Aussi, nous allons les passer sous silence pour le moment. Nous les verrons dans le chapitre sur le parallélisme mémoire. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=La désambiguïsation mémoire | prevText=La désambiguïsation mémoire | next=Les processeurs superscalaires | nextText=Les processeurs superscalaires }} </noinclude> 13462j5e2che1iw8gn64rm0g1fzceqi 764745 764744 2026-04-23T23:06:57Z Mewtow 31375 /* Rappel sur les SDRAM : tampon de ligne et commandes */ 764745 wikitext text/x-wiki Dans ce chapitre, nous allons voir les techniques qui permettent de gérer plusieurs accès mémoire simultanés directement au niveau du cache ou de la mémoire RAM. Par plusieurs accès mémoire simultanés, vous pensez sans doute à l'usage de cache multiports, voire de mémoires RAM multiport. C'est en effet une solution, mais ce n'est clairement pas la seule. Par exemple, il est possible de pipeliner l'accès au cache, voire à la mémoire RAM. Il existe de nombreuses techniques de '''parallélisme mémoire''', et nous allons les voir dans ce chapitre. Pour donner un exemple, il est possible de pipeliner les accès mémoire. Il est possible de pipeliner l'accès au cache et/ou l'accès à la mémoire, et nous verrons comment faire dans ce chapitre. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, qu'ils aient ou non un pipeline dynamique. Par contre, pipeliner la mémoire est une technique ancienne, aujourd'hui peu utilisée. Elle était utilisée sur des très vieux ordinateurs, qui avaient un pipeline mais pas de mémoire cache. Mais de nos jours, les mémoires SDRAM et DDR ne sont pas adaptées pour ça, il est très difficile de les pipeliner correctement. Le parallélisme mémoire est utile aussi bien sur des processeurs à émission dans l'ordre que dans le désordre. Par exemple, un processeur ''in-order'' avec des lectures non-bloquantes peut e, profiter. Il peut techniquement lancer une seconde lecture, après avoir rencontré une première lecture non-bloquante. En général, le nombre de lectures consécutives est cependant limité à moins d'une dizaine. À l'opposé, les processeurs sans lectures non-bloquantes profitent pas du parallélisme mémoire. À la rigueur, ils peuvent en profiter s'ils intègrent des techniques de préchargement. Pendant que le processeur exécute une lecture/écriture, il peut précharger une autre donnée en parallèle. Inutile de dire que sans pipeline processeur, le parallélisme mémoire ne sert pas à grand-chose. ==Le ''line fill buffer'' et les techniques associées== Lors d'un défaut de cache, le processeur doit attendre que toute la ligne de cache soit chargée avant d'être utilisable. Or, la taille d'une ligne de cache est supérieure à la largeur du bus mémoire, ce qui fait qu'une ligne de cache est chargée en plusieurs fois, morceaux par morceaux, mot mémoire par mot mémoire. Le chargement peut se faire directement dans le cache, mais ce n'est pas une solution très pratique. À la place, beaucoup de processeurs ajoutent une mémoire tampon entre la RAM et le cache, appelée le '''tampon de remplissage de ligne''' (''line-fill buffer'') dans les processeurs modernes. Lors d'un défaut de cache, le processeur charge la donnée de la RAM dans le tampon de remplissage de ligne, mot mémoire par mot mémoire. Une fois plein, le tampon de remplissage de ligne est recopié dans la ligne de cache. Le tampon de remplissage de ligne contient une ligne de cache, si ce n'est que certains bits de contrôle ne sont pas présents. Le tampon de remplissage de ligne contient un bit de validité pour chaque mot mémoire de la ligne de cache, qui indiquent si le mot mémoire a été chargé. Par exemple, prenons un processeur 64 bits, qui gère donc des mots mémoire de 8 octets, avec des lignes de cache 256 octets/32 mots mémoire. Le tampon de remplissage de ligne contiendra 32 bits de validité. Si le processeur a chargé les 6 premiers mots mémoire, les 6 premiers bits de validité seront mis à 1, les autres seront encore à zéro. Il faut noter que la disponibilité de la ligne complète se détermine assez facilement en faisant un ET logique entre tous les bits de validité. Le processeur sait ainsi quand la ligne de cache est disponible entièrement et donc quand la transférer dans le cache. La même chose existe avec une hiérarchie de cache, sauf que l'on trouve une mémoire tampon entre chaque niveau de cache. Il y a un tampon de remplissage de ligne entre le cache L1 et le cache L2, entre le cache L2 et le L3, etc. Si le cache ne gère qu'un seul défaut de cache à la fois, le ''fill line buffer'' est une mémoire très simple, qui ne mémorise qu'une seule ligne de cache. Et cela vaut aussi bien pour un cache bloquant que non-bloquant. Mais sur les caches capables de gérer plusieurs défauts simultanés, le tampon de remplissage de ligne est une mémoire de type FIFO ou LIFO, capable de mémoriser plusieurs lignes de cache. Notons que le tampon de remplissage de ligne est très utile pour implémenter certaines techniques, par exemple le contournement du cache. Nous avions vu dans le chapitre sur le cache que certains accès mémoire doivent contourner le cache, pour des raisons de cohérence des caches. C'est notamment nécessaire pour accéder aux périphériques, mais c'est aussi utile pour des raisons de performances dans des cas très spécifiques. Les accès qui contournent le cache se font directement dans le tampon de remplissage de ligne : le processeur écrit ou lit les données depuis ce tampon de remplissage de ligne, sans accéder au cache. ===L'''early restart'' et le ''critical word load''=== La présence du ''line buffer'' permet une optimisation assez intéressante, qui permet de réduire la latence des défauts de cache. L'optimisation consiste à lire un mot mémoire dans le tampon de remplissage de ligne, même si la ligne de cache complète n'a pas encore été chargée. Il existe deux manières de faire cela, qui portent les noms d'''early restart'' et de ''critical word load''. La première est la version la plus simple, la seconde est plus complexe mais plus performante. Avec la technique d''''''early restart''''', la ligne de cache est chargée normalement, en partant de son premier mot mémoire. Dès que le mot mémoire lu/écrit par le processeur est copié dans le ''line fill buffer'', il est envoyé au processeur immédiatement. Illustrons le tout par un exemple, où une ligne de cache fait 16 mots mémoire. Le processeur effectue une lecture, qui lit le 5ème mot mémoire. Sans ''early restart'', le processeur doit charger les 16 mots mémoire avant de faire la lecture dans le cache. Avec ''early restart'', le processeur reçoit la donnée dès que le 5ème mot mémoire est disponible. Le processeur doit attendre que les 4 premiers mots mémoire soient chargés, puis le 5ème mot mémoire arrive et est envoyé directement au processeur, il est lu directement depuis le ''line fill buffer''. Les 11 mots mémoire suivants sont ensuite chargés dans le cache pendant que le processeur fait des calculs dans son coin. Le ''critical word load'' est une optimisation de la technique précédente où le chargement de la ligne de cache commence directement à la donnée demandée par le processeur. Pour reprendre l'exemple précédent, où le processeur demande le 5ème mot mémoire sur 16, le ''critical word load'' charge le 5ème mot mémoire en premier et l'envoie au processeur, ce qui fait qu'il est chargé très rapidement. Pas besoin d'attendre que le processeur charge les 4 mots mémoire précédents comme avec l'''early restart''. Dans le détail, le processeur charge le 5ème mot mémoire en premier, puis charge les 11 suivants, et termine par les 4 mots mémoire du début. En clair, le ''critical word load'' commence par charger le mot mémoire lu, puis les blocs suivants, avant de revenir au début du bloc pour charger les blocs restants. Ainsi, la donnée demandée par le processeur sera la première disponible. Pour cela, l'organisation du tampon de remplissage de ligne est modifiée de manière à rendre cela possible. Il a une taille égale à une ligne de cache complète, qui contient elle-même plusieurs mots mémoire. Dans le ''line-fill buffer'', chaque mot mémoire est stocké avec un tag, qui indique l'adresse du mot mémoire stocké dans le ''line-fill buffer''. Le ''line-fill buffer'' est donc un cache un peu particulier, qui fonctionne comme un cache du point de vue du processeur, comme une mémoire FIFO pour les transferts avec le cache. Ainsi, un processeur qui veut lire dans le cache après un défaut peut accéder à la donnée directement depuis le tampon de remplissage de ligne, alors que la ligne de cache n'a pas encore été totalement recopiée en mémoire. ==Les caches pipelinés== Il est possible de pipeliner l'accès au cache, ce qui demande juste de rajouter des registres au bon endroits et quelques circuits de contrôle. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, même ceux avec un pipeline dynamique. Et l'implémentation peut se faire de plusieurs manières différentes. Il faut dire que découper une mémoire cache en plusieurs étapes peut se faire de plusieurs manière. Commençons par la plus simple. Beaucoup de processeurs des années 2000 avaient une fréquence peu élevée comparée aux standards d'aujourd'hui, ce qui fait que leur cache avait bien un temps d'accès d'un cycle d'horloge. Mais l'accès au cache se faisait en deux cycles d'horloge : un cycle pour calculer une adresse et un cycle pour l'accès mémoire proprement dit. Et ces deux étapes étaient pipelinées, à savoir que deux micro-opérations mémoire peuvent s'exécuter en même temps : une dans l'étage de calcul d'adresse, une autre dans le cache. L'unité mémoire était donc pipelinée, alors que l'accès au cache ne l'était pas. Les processeurs qui implémentaient cette technique regroupent les micro-architectures K5 et K6 d'AMD, les processeurs Intel de micro-architecture P6 (Pentium 2 et 3) et quelques autres. Il est aussi possible de pipeliner l'accès au cache lui-même. Avec un cache pipeliné, l'accès au cache ne se fait pas en un seul cycle, mais en plusieurs. Cependant, on peut lancer un nouvel accès au cache à chaque cycle d'horloge, comme un processeur pipeliné lance une nouvelle instruction à chaque cycle. L'implémentation est assez simple : il suffit d'ajouter des registres dans le cache. Pour cela, on profite que le cache est composé de plusieurs composants séparés, qui échangent des données dans un ordre bien précis, d'un composant à un autre. De plus, le trajet des informations dans un cache est linéaire, ce qui les rend parfois pour l'usage d'un pipeline. ===Le ''pipelining'' des caches ''direct-mapped''=== Voici ce qui se passe avec un cache directement adressé. Pour rappel ce genre de cache est conçu en combinant une mémoire RAM, généralement une SRAM, avec quelques circuits de comparaison et un MUX. L'accès au cache se fait globalement en deux étapes : la première lit la donnée et le ''tag'' dans la SRAM, et on utilise les deux dans une seconde étape. Nous avions vu il y a quelques chapitre comment pipeliner des mémoires, dans le chapitre sur les mémoires évoluées. Et bien ces méthodes peuvent s'utiliser pour la mémoire RAM interne au cache !L'idée est d'insérer un registre entre la sortie de la RAM et la suite du cache, pour en faire un cache pipéliné, à deux étages. Le premier étage lit dans la SRAM, le second fait le reste. L'implémentation sur les caches associatifs à plusieurs voies est globalement la même à quelques détails près. [[File:Cache directement adressé pipeliné - deux étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - deux étages]] Avec le circuit précédent, il est possible d'aller plus loin, cette fois en pipelinant l'accès à la mémoire RAM interne au cache. Pour comprendre comment, rappelons qu'une mémoire SRAM est composée d'un plan mémoire et d'un décodeur. L'accès à la mémoire demande d'abord que le décodeur fasse son travail pour sélectionner la case mémoire adéquate, puis ensuite la lecture ou écriture a lieu dans cette case mémoire. L'accès se fait donc en deux étapes successives séparées, on a juste à mettre un registre entre les deux. Il suffit donc de mettre un gros registre entre le décodeur et le plan mémoire. [[File:Cache directement adressé pipeliné - trois étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - trois étages]] Et on peut aller encore plus loin en découpant le décodeur en deux circuits séparés. En effet, rappelez-vous le chapitre sur les circuits de sélection : nous avions vu qu'il est possible de créer des décodeurs en assemblant des décodeurs plus petits, contrôlés par un circuit de prédécodage. Et bien on peut encore une ajouter un registre entre ce circuit de prédécodage et les petits décodeurs. : Théoriquement, toute l'adresse est fournie d'un seul coup au cache, la quasi-totalité des processeurs présentent l'adresse complète à un cache pipeliné. Mais le Pentium 4 fait autrement. Il faut noter que les premiers étages manipulent l'indice dans la SRAM, qui est dans les bits de poids faible, alors que les étages ultérieurs manipulent le ''tag'' qui est dans les bits de poids fort. Les concepteurs du Pentium 4 ont alors décidé de présenter les bits de poids faible lors du premier cycle d'accès au cache, puis ceux de poids fort au second cycle. Pour cela, l'ALU fonctionnait à une fréquence double de celle du processeur, tout comme le cache L1. Il n'y avait pas de pipeline proprement dit, mais cela réduisait grandement la latence d'accès au cache. ===Le ''pipelining'' des caches splittés/sériels=== Il est aussi possible de pipeliner un cache dits splittés, aussi appelés à accès sériel. Pour rappel, les caches à accès sériel vérifient si il y a succès ou défaut de cache, avant d'accéder aux lignes de cache en cas de succès. Ils font donc différemment des autres caches, qui accèdent à une ligne de cache, avant de déterminer s'il y a succès ou défaut en lisant le tag de la ligne de cache. Les caches sériels disposent de deux SRAM : une pour les tags des lignes de cache et une pour les données. Ils accèdent à la SRAM pour les tags, avant d’accéder à la SRAM des données en cas de succès de cache. Vu que l'accès se fait en deux étapes, une vérification des tags suivie de la lecture/écriture des données, il est facile à pipeliner. Pipeliner le cache permet de régler le problème des accès au cache L1, et elle est tout le temps utilisé sur les processeurs modernes. Mais que faire en cas de défaut de cache ? ==Les caches non bloquants== Un '''cache bloquant''' est un cache auquel le processeur ne peut pas accéder pendant un défaut de cache. Il faut attendre que la lecture ou écriture en RAM soit terminée avant de pouvoir utiliser de nouveau le cache. Un '''cache non bloquant''' n'a pas ce problème : on peut l'utiliser pendant un défaut de cache. Les caches non bloquants permettent de démarrer une nouvelle lecture ou écriture alors qu'une autre est en cours, ce qui permet d'exécuter plusieurs lectures ou écritures en même temps. ===Les ''Miss Handling Status Registers''=== Lors d'un défaut de cache, la mémoire RAM est consultée pendant le défaut de cache, mais le cache est inutilisé. Un défaut de cache n'utilise pas le cache, ce n'est qu'un accès en mémoire RAM, sur le bus mémoire (ou un accès aux niveaux de cache inférieurs, peu importe). Le cache en lui-même est laissé libre, rien n’empêche d'y accéder, il est en réalité intrinsèquement non-bloquant. Les caches, bloquants comme non-bloquants, sont en réalité composés d'une mémoire cache proprement dite, entourée de circuits qui servent d'interface entre le processeur et le cache lui-même. Et parmi les circuits tout autour, certains gèrent l'accès au cache lors d'un défaut de cache. Ils sont regroupés sous le terme de '''''Miss Handling Architecture''''' (MHA). La différence entre un cache bloquant et un cache non-bloquant est en réalité liée à l'implémentation de la MHA. Les caches bloquants coupent volontairement l'accès au cache lors d'un défaut, car les défauts sont plus simples à gérer ainsi. Le défaut de cache rapatrie une donnée depuis la RAM, qui sera écrite dans le cache. Et il ne faut pas qu'une tentative d'accès à cette donnée ait lieu avant qu'elle ne soit chargée. Mais ce blocage est général et touche tout le cache, alors que seule une ligne de cache est concernée par le défaut de cache. L'idée derrière un cache non-bloquant est que seule la ligne de cache est bloquée, mais les autres sont accessibles. L'idée est alors de mémoriser les lignes de cache concernées par le défaut de cache, afin d'en bloquer l'accès. À chaque accès, on vérifie si la ligne de cache est déjà réservée par un défaut de cache. La lecture/écriture est alors bloquée si c'est le cas, mais elle accepte les accès sinon. Pour cela, la ''Miss Handling Architecture'' contient des registres qui mémorisent des informations sur les défauts de cache en cours. Ils portent le nom de '''''miss status handling registers''''', que j’appellerais dorénavant MSHR, qui sont aussi appelés des '''''miss buffer'''''. Le contenu des MSHR varie beaucoup suivant le processeur, mais ces derniers stockent au minimum les informations suivantes : * Le numéro de la ligne de cache dans laquelle les données sont chargées. * Un bit de validité qui indique si le MSHR est vide ou pas, qui est mis à 0 quand le défaut de cache est résolu. * Un ou plusieurs champs de lecture/écriture, qui contiennent des informations sur la lecture/écriture. ** Pour une lecture, elle contient des informations sur la destination de la donnée, à savoir qui prévenir quand le défaut de cache est terminé. C'est parfois un nom/numéro de registre (celui dans lequel charger la donnée), mais c'est souvent le numéro de l'entrée dans le ''load/store queue''. ** Pour les écritures, elle contient la donnée à écrire, ou éventuellement un numéro de ''load/store queue'' où se trouve la donnée à écrire. Il faut noter que le bus mémoire ne peut gérer qu'un seul défaut de cache à la fois. Aussi, il est intéressant de regarder ce qui se passe lorsqu'un second défaut de cache survient, pendant qu'un premier est en cours. Dans ce cas, il y a deux réponses qui correspondent à deux types de caches non-bloquants, qui portent les noms barbares de caches de type succès après défaut et défaut après défaut. Sur le premier type, il ne peut pas y avoir plusieurs défauts de cache simultanés. Dès qu'un second défaut de cache survient, le cache stoppe son activité et on ne peut plus démarrer de nouvelle lecture/écriture, tant que le premier défaut de cache n'est pas résolu. Le second type est plus souple et autorise la survenue de plusieurs défauts de cache simultanés. Du moins, jusqu'à une certaine limite, car le cache ne peut supporter qu'un nombre limité d'accès mémoires simultanés (pipelinés). ===Les accès simultanés à une même ligne de cache=== Il arrive que le processeur fasse plusieurs accès mémoire simultanés à la même ligne de cache. Si la ligne de cache en question n'a pas encore été chargée dans le cache, alors on a plusieurs défauts de cache consécutifs pour la même ligne de cache, et le cache non-bloquant doit gérer la situation. Pour la suite, il va falloir faire une petite distinction entre les défauts primaires et secondaires. Imaginons qu'un défaut de cache ait lieu et demande à charger une donnée dans la ligne de cache numéro N. Il s'agit du premier défaut impliquant cette ligne de cache précise, ce qui lui vaut le nom de '''défaut de cache primaire'''. Mais par la suite, d'autres accès mémoire à la même ligne de cache ont lieu, alors que la ligne de cache n'est pas encore disponible. Dans ce cas, il s'agit de '''défauts de cache secondaires'''. Pour l'unité d'accès mémoire, les défauts de cache primaire et secondaire sont différents (ils prennent tous une entrée dans la ''load/store queue''). Mais pour le cache, ils ne correspondent qu'à un seul accès au cache : celui qui demande de charger la ligne de cache demandée. Les défauts de cache primaire et secondaire à la même ligne de cache se voient attribuer un MSHR unique. La gestion des défauts de cache secondaires dépend du cache non-bloquant. La solution la plus simple ne permet pas les défauts de cache secondaires. Le cache ne permet pas deux défauts de cache simultanés pour la même ligne de cache. Les autres solutions le permettent, en fusionnant des accès simultanés à la même ligne de cache en un seul au niveau des MSHR. Dans tous les cas, détecter les défauts de cache secondaires sont un problème qu'il faut détecter. La ''Miss Handling Architecture'' doit détecter les défauts de cache secondaire. Pour cela, elle procède comme suit. Lors de chaque défaut de cache, la MHA récupère le numéro de la ligne de cache associé. Il vérifie alors chaque MSHR pour vérifier s'il contient le numéro en question. S'il n'y a aucune correspondance dans les MSHR, alors c'est signe que le défaut de cache est un défaut primaire. Mais s'il y en a une, alors c'est un défaut secondaire. Évidemment, cela signifie que lors d'un défaut de cache, le numéro de ligne de cache est envoyé à tous les MSHR, pour comparaison. Les MSHR sont donc regroupés dans une mémoire associative, une sorte de mini-cache, faciliter l'implémentation. Lors d'un défaut de cache primaire, l'accès à un cache non-bloquant se fait comme suit : le processeur envoie une adresse au cache, accède à celui-ci, et détecte la survenue d'un défaut de cache. Il en profite alors pour attribuer une ligne de cache dans laquelle sera chargée ce défaut. L'attribution est très simple dans le cas des caches ''direct mapped'', ou associatifs par voie, pour lesquels l'attribution se fait assez simplement. Il mémorise alors cette information dans les MSHR, après avoir vérifié que le défaut de cache n'était pas un défaut secondaire. Lors des accès ultérieurs à une adresse proche, censée être dans la même ligne de cache, le processeur va encore une fois rencontrer un défaut de cache. Il va alors déterminer le numéro de la ligne de cache associée à l'adresse, et comparer ce numéro avec les MSHRs. Si un MSHR contient ce numéro, c'est signe que le défaut de cache est un défaut secondaire. La MHA réagit alors différemment selon le processeur considéré. Une première solution n'autorise pas les défauts de cache secondaires. Si l'un d'entre eux survient, le processeur est gelé par un ''pipeline stall'', une bulle de pipeline. Une autre solution fusionne les défauts de cache secondaires avec le défaut de cache primaire : tout cela ne correspond qu'à un seul défaut de cache pour lui. ===Les MSHR simples=== Un cache non-bloquant à '''MSHR simple''' contient juste plusieurs MSHR qui mémorisent juste un numéro de ligne de cache, un bit de validité, le champ de lecture/écriture, et l'adresse exacte de lecture/écriture. Avec cette organisation, il est possible d'avoir plusieurs défauts de cache séparés, mais à la condition que chaque défaut accède à une ligne de cache différente. Deux accès simultanés à une même ligne de cache ne sont pas possibles, les défauts de cache secondaires ne sont pas autorisés. Ainsi, chaque défaut de cache se voit attribuer son propre MSHR, chacun contient un numéro de ligne de cache différent. Pour comprendre pourquoi c'est impossible de gérer les défauts secondaires, il faut regarder le champ de lecture/écriture. Si on veut effectuer plusieurs écritures consécutives à la même adresse, le MSHR n'aura pas de quoi mémoriser les deux données à écrire. Il pourra mémoriser la première donnée à écrire, pas la seconde. Même chose lors d'une lecture : le champ lecture/écriture peut mémorisr la destination de la première lecture, pas de la seconde. L'avantage est que la MHA n'a besoin que des MSHR et de quelques circuits annexes. Les autres solutions rajoutent des circuits annexes pour gérer les défauts de cache secondaires, qui utilisent beaucoup de circuits. Le cout en circuit est donc élevé, mais le gain en performance est là. Passons maintenant aux caches non-bloquants qui autorisent les défauts de cache secondaire. La solution la plus simple consiste à utiliser ===Les MSHR adressés implicitement=== Avec les '''MSHR adressés implicitement''', il est possible de fusionner plusieurs accès mémoire à une même ligne de cache, mais sous une condition très importante : ces accès lisent/écrivent des mots mémoire différents. Par exemple, imaginons qu'une ligne de cache contienne 8 mots mémoire de 64 bits. Si un premier accès mémoire lit le mot mémoire numéro 7 (dernier mot mémoire de la ligne), et le second accès le mot mémoire numéro 3, alors la fusion est possible. Mais si deux accès mémoire veulent lire/écrire le mot mémoire numéro 7, alors la fusion n'est pas possible et le processeur se bloque, un ''pipeline stall'' survient. Un MSHR adressé implicitement est un MSHR simple, qui contient naturellement un numéro de ligne de cache (tag) et un bit de validité, sauf que le champ de lecture/écriture est dupliqué. Les différents champs lecture/écriture d'un MSHR sont regroupés dans une mémoire RAM/cache qui contient autant d'entrées qu'il y a de mot mémoire dans une ligne de cache. Chaque entrée de la table est associée à un mot mémoire de la ligne de cache et stocke des informations sur celui-ci. Une entrée mémorise, au minimum : * Un champ de lecture/écriture qui contient soit la destination de la lecture, soit la donnée à écrire. * Un bit R/W permettant d'interpréter correctement le champ de lecture/écriture. * Un bit de validité pour chaque entrée de la table, qui dit si un défaut de cache antérieur accède déjà à ce mot mémoire. La fusion de deux défauts de cache est ainsi assez simple. Un défaut de cache primaire/secondaire configure l'entrée associée au mot mémoire lu/écrit. Si un défaut secondaire ultérieur lit/écrit un mot mémoire différent (dont le bit de validité de l'entrée dans la table est à zéro), pas de conflit : il configure une autre entrée, vide. Mais s'il lit/écrit un mot mémoire pour lequel l'entrée de la table est occupée, il y a conflit, le processeur est bloqué par un ''pipeline stall'', une bulle de pipeline. Avec cette organisation, le nombre de MSHR indique combien de lignes de cache peuvent être lues en même temps depuis la mémoire. Quant au nombre d'entrées par MSHR, il détermine combien d'accès mémoires qui ne se recouvrent pas peuvent avoir lieu en même temps. ===Les MSHR adressés explicitement=== Les '''MSHR adressés explicitement''' n'ont pas les contraintes des MSHR adressés implicitement. Avec eux, il est possible d'avoir plusieurs défauts de cache pointant vers la même ligne de cache, mais aussi vers le même mot mémoire. De tels défauts de cache apparaissent sur les processeurs à exécution dans le désordre, mais sont très rares, voire inexistants, sur les processeurs ''in-order''. L'idée est encore que chaque MSHR est associée à une table mémoire qui mémorise des entrées. Cette table n'est autre qu'une mémoire FIFO. Sauf que cette fois-ci, une entrée n'est pas associée à un mot mémoire. Une entrée est associée à un défaut de cache. Une entrée mémorise là encore la destination de la lecture ou la donnée de l'écriture suivi du bit R/W, ainsi que d'un bit de validité par entrée, mais aussi : la position du mot mémoire lu dans la ligne de cache. C'est cette dernière information qui n'était pas présente dans les MSHR adressés implicitement. De plus, le nombre d'entrée par MSHR n'est pas égal au nombre de mots mémoires dans une ligne de cache, mais peut être arbitrairement grand. L'avantage d'un tel cache est qu'il est capable de traité les défauts secondaires concernant un même mot mémoire, et ce, car les accès à la ligne de cache concernée se produisent dans l'ordre de génération des défauts (la table mémoire est une FIFO). ===Les MSHRs inversés=== Généralement, plus on veut supporter de défauts de cache, plus le nombre de MSHR et d'entrées augmente. Mais au-delà d'un certain nombre d'entrées et de MSHR, les MSHR adressés implicitement et explicitement ont tendance à bouffer un peu trop de circuits. Utiliser une organisation un peu moins gourmande en circuits est donc une nécessité. Cette organisation plus économe se base sur des MSHR inversés. Les MSHR inversés ne contiennent qu'une seule entrée, en quelque sorte : au lieu d’utiliser n MSHR de m entrées chacun, on va utiliser n × m MSHR inversés. La différence, c'est que plusieurs MSHR peuvent contenir un tag identique, contrairement aux MSHR adressés implicitement et explicitement. Lorsqu'un défaut de cache a lieu, chaque MSHR est vérifié. Si jamais aucun MSHR ne contient de tag identique à celui utilisé par le défaut, un MSHR vide est choisi pour stocker ce défaut, et une requête de lecture en mémoire est lancée. Dans le cas contraire, un MSHR est réservé au défaut de cache, mais la requête n'est pas lancée. Quand la donnée est disponible, les MSHR correspondant à la ligne qui vient d'être chargée vont être utilisés un par un pour résoudre les défauts de cache en attente. ===Les MSHR intégrés au cache=== Certains chercheurs ont remarqué que pendant qu'une ligne de cache est en train de subir un défaut de cache, celle-ci reste inutilisée, et son contenu est destiné à être perdu une fois le défaut de cache résolu. Ils se sont dit que, plutôt que d'utiliser des MSHR séparés, il vaudrait mieux utiliser la ligne de cache pour stocker les informations sur les défauts de cache en attente dans cette ligne de cache. Pour éviter tout problème, il faut rajouter un bit dans les bits de contrôle de la ligne de cache, qui sert à indiquer que la ligne de cache est occupée : un défaut de cache a eu lieu dans cette ligne, et elle stocke donc des informations pour résoudre les défauts de cache. ==L'entrelacement mémoire== Nous venons de voir comment une mémoire cache peut gérer plusieurs accès mémoire simultanés. Il se trouve que ce genre d'optimisation dépasse largement le cadre du cache. Les mémoires RAM ne sont pas en reste, elles aussi ! Il est possible de pipeliner l'accès à une mémoire RAM, en utilisant des techniques dites d''''entrelacement'''. Précisons cependant que cette optimisation n'est pas utilisée sur les mémoires SDRAM ou DDR modernes, du moins pas sans modifications majeures. Mais divers ordinateurs assez anciens, dont des superordinateurs, ont utilisé cette technique. La technique demande d'utiliser plusieurs mémoires séparées. Sur les anciens ordinateurs, les mémoires en question sont des chips mémoire, à savoir des circuits intégrés de DRAM. Pour faire plus général, nous allons utiliser le terme de '''banques''', ou encore de bancs mémoire. La différence est qu'il existe des mémoires multi-banque, qui regroupent plusieurs banques indépendantes dans un seul boitier, dans un seul chip mémoire. En clair, de telles mémoires regroupent plusieurs sous-mémoires dans un seul circuit intégré. Il est aussi possible d'utiliser plusieurs chips mémoire séparés, chacun ayant une banque. Reste à combiner ces banques pour former un pseudo-pipeline. ===Utiliser plusieurs banques sans entrelacement=== Sans optimisation particulière, les adresses sont réparties dans les banques comme indiqué ci-dessous. Il s'agit de ce qui s'appelle un arrangement horizontal, et nous avions vu celui-ci dans le chapiutre sur les mémoires SDRAM. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Avec cet arrangement, les bits de poids fort de l'adresse sont utilisées pour sélectionner la banque adéquate, et le reste de l'adresse est envoyé sur le bus d'adresse. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Adresse de banque !! Adresse dans la banque |- | Quelques bits de poids fort || Reste de l'adresse |} L'entrelacement change cette répartition, afin d'accélérer les accès mémoire. L'entrelacement classique vise à améliorer les accès à des adresses mémoires consécutives, mais des formes plus évoluées d'entrelacement visent à optimiser des accès mémoire arbitraires. Voyons-les dans l'ordre, du plus simple au plus complexe. ===L'entrelacement classique=== Avec l''''entrelacement classique''', des adresses consécutives sont réparties dans des banques consécutives. Précisons que cette attribution des adresses n'implique absolument pas la mémoire virtuelle ou n'importe quel mécanisme dans le processeur. La répartition décide que telle adresse mémoire va dans telle banque, à telle adresse dans la banque. Elle est donc le fait du contrôleur mémoire, donc en dehors du processeur (les contrôleurs mémoires n’étaient pas encore intégrés dans le processeur à l'époque). [[File:Répartition des adresses dans une mémoire interleaved.png|centre|vignette|upright=1.5|Répartition des adresses dans une mémoire interleaved.]] L'entrelacement simple permet d'accélérer les accès mémoire qui se font à des adresses consécutives. Avec l'entrelacement, chaque accès mémoire tombe dans une banque mémoire différente, ce qui fait qu'on peut démarrer un nouvel accès mémoire à chaque cycle d'horloge. Pendant qu'une banque est occupée par un accès mémoire, on démarre le suivant dans une autre banque, et ainsi de suite. Le tout se termine soit quand on a épuisé toutes les banques libres, soit quand l'accès en rafales se termine. Pas besoin d'attendre que la mémoire ait fini sa lecture/écriture avant de démarrer la lecture/écriture suivante. [[File:Pipemining mémoire.png|centre|vignette|upright=2.5|Pipelining mémoire]] L'animation suivante illustre bien le processus, avec quatres banques. [[File:Interleaving.gif|centre|vignette|upright=2.5|Pipelining mémoire]] L'entrelacement simple utilise les bits de poids faible pour sélectionner la banque, et les bits de poids fort pour la case mémoire. [[File:Adresse mémoire d'une mémoire entrelacée.png|centre|vignette|upright=2|Adresse mémoire d'une mémoire entrelacée]] Précisons que le temps d'accès mémoire ne change pas beaucoup avec l'entrelacement. Par contre, on peut faire plus d'accès mémoire simultanés. On peut démarrer un accès mémoire par cycle d'horloge, mais l'accès en lui-même prend plusieurs cycles. Les mémoires à entrelacement ont donc un débit supérieur aux mémoires qui ne l'utilisent pas. L'entrelacement classique pourrait être utilisé pour accélérer les transferts entre mémoire RAM et cache, qui se font en blocs d'adresses consécutives. Mais de nos jours, il n'est pas utilisé dans cette optique. Il faut dire qu'il est redondant avec le mode rafale des mémoires DRAM. Les deux font globalement la même chose et on voit mal comment utiliser les deux en même temps. Le mode rafale étant plus simple à implémenter, et plus léger niveau utilisation du bus de commande mémoire, il est préféré à l'entrelacement. Par contre, l'entrelacement a eu son heure de gloire sur d'anciens processeurs qui n'avaient pas de mémoire cache, alors qu'ils disposaient d'un pipeline. L'exemple typique est celui des nombreux ''processeurs vectoriels'', que nous n'avons pas encore abordé à ce stade du cours. De tels processeurs avaient un pipeline, une unité de calcul fortement pipelinée, et parfois même de l'exécution dans le désordre et du renommage de registres ! ===L'entrelacement par décalage=== L'entrelacement simple est très utile pour les accès en rafle ou équivalents. Par contre, il existe de rares situations où il n'est pas optimal. Une de ces situations est celle des accès en enjambée (en ''stride''), où l'on accède en série à des adresses sont séparées par N mots mémoires. [[File:Accès par enjambées.png|centre|vignette|upright=2|Accès par enjambées.]] De tels accès surviennent quand un logiciel accède à des structures de données spécifiques, à savoir des tableaux de structures, des matrices, ou d'autres structures de données dans le genre. Un cas classique est celui du parcours d'une matrice colonne par colonne. Les matrices de nombres sont mémorisées ligne par ligne, ce qui fait que parcourir une colonne demande de passer d'une ligne à la suivante et de faire des grands sauts en mémoire RAM, mais tous espacés par la même distance. Avec l'entrelacement simple, les accès en enjambée sont moins performants que les accès à des adresses consécutives, mais cela ne fonctionne pas trop mal. Le pire cas est celui où l'on a N banques et où les données sont justement placées toutes les N adresses. Des accès consécutifs vont tous tomber dans la même banque, on ne peut plus accéder à des banques différentes en parallèle. Mais en dehors de ce cas, on voit une amélioration par rapport à la situation sans entrelacement. Pour obtenir des performances maximales pour les accès en enjambées, il faut répartir les mots mémoires dans la mémoire autrement. L'organisation idéale est la suivante. : Dans les explications qui vont suivre, la variable N représente le nombre de banques, qui sont numérotées de 0 à N-1. On commence par organiser les N premières adresses comme une mémoire entrelacée simple : l'adresse 0 correspond à la banque 0, l'adresse 1 à la banque 1, etc. Pour le bloc suivant, on décale tout d'une adresse, à savoir qu'on commence à remplir les banques à partir de la seconde, non de la première. Une fois la fin du bloc atteinte, on finit de remplir le bloc en repartant du début du bloc. Le troisième bloc d'adresse subit le même traitement, sauf qu'on commence à remplir à partir de la seconde banque. Et on poursuit l’assignation des adresses en décalant d'un cran en plus à chaque bloc. Ainsi, chaque bloc verra ses adresses décalées d'un cran en plus comparé au bloc précédent. Si jamais le décalage dépasse la fin d'un bloc, alors on reprend au début. [[File:Mémoire interleaved par décalage.png|centre|vignette|upright=1.5|Mémoire entrelacée par décalage.]] En faisant cela, on remarque que les banques situées à N adresses d'intervalle sont différentes. Dans l'exemple du dessus, nous avons ajouté un décalage de 1 à chaque nouveau bloc à remplir. Mais on aurait tout aussi bien pu prendre un décalage de 2, 3, etc. Dans tous les cas, on obtient un entrelacement par décalage. Ce décalage est appelé le pas d'entrelacement, noté P. Le calcul de l'adresse à envoyer à la banque, ainsi que la banque à sélectionner se fait en utilisant les formules suivantes : * adresse à envoyer à la banque = adresse totale / N ; * numéro de la banque = (adresse + décalage) modulo N ; * décalage = (adresse totale * P) mod N. Avec cet entrelacement par décalage, on peut prouver que la bande passante maximale est atteinte si le nombre de banques est un nombre premier. Seulement, utiliser un nombre de banques premier peut créer des trous dans la mémoire, des mots mémoires inadressables. Malgré ce défaut, la technique a été utilisée sur quelques ordinateurs, avec l'exemple notable du superordinateur ''Burroughs Scientific Processor''. Pour éviter cela, il y a plusieurs solutions. Par exemple, on peut faire en sorte que N et la taille d'une banque soient premiers entre eux : ils ne doivent pas avoir de diviseur commun. Mais en pratique, elles n'ont pas vraiment été implémentées dans une vraie machine, et sont restées à l'état de recherche, aussi je les passe sous silence. ===L'entrelacement pseudo-aléatoire=== Une dernière méthode de répartition consiste à répartir les adresses dans les banques de manière "pseudo-aléatoire". L'idée exacte est de faire passer l'adresse dans une '''fonction de hachage''', plus ou moins complexe. Pour rappel, une fonction de hachage prend une entrée de grande taille et fournit en sortie un résultat de petite taille. Idéalement, elles doivent donner des résultats aussi différents que possible pour des entrées similaires, histoire de simuler une sorte de pseudo-aléatoire. Ici, une adresse est transformée en un numéro de banque. Une fonction de hachage simple ne fait que permuter des bits de l'adresse pour obtenir son résultat. Elle ne fait qu'échanger des bits de place avant de couper l'adresse en deux morceaux : un pour la sélection de la banque, et un autre pour la sélection de l'adresse dans la banque. Cette permutation est fixe, et ne change pas suivant l'adresse. Des fonctions de hachage plus complexes font des XOR bit à bit entre certains bits de l'adresse. ==Les contrôleurs SDRAM/DDR avec "exécution dans le désordre"== Après avoir vu le cas des mémoires RAM, nous allons nous concentrer sur le cas particulier des mémoires SDRAM. Les mémoires SDRAM et DDR modernes sont capables de gérer plusieurs accès simultanés à la mémoire RAM, et leurs contrôleurs mémoire en font tout autant. C'est une différence majeure avec les mémoires asynchrones FPM/EDO, qui n'acceptaient qu'un seul accès mémoire à la fois. Leur contrôleur mémoire n'acceptait qu'un seul accès mémoire à la fois, c'était un contrôleur mémoire bloquant. Les contrôleurs mémoires des SDRAM sont eux non-bloquants et peuvent encaisser une dizaine d'accès mémoire à la fois. Peu de choses sont connues sur les contrôleurs de SDRAM/DDR modernes, les fabricants ne donnant que peu de détails dessus. Les rares simulateurs qui tentent de décrire leur fonctionnement, comme DRAM SIM I et II, sont particulièrement simples et ne vont pas dans le détail. Néanmoins, le peu qu'on sait est tout de même instructif. ===Rappel sur les SDRAM : tampon de ligne et commandes=== Les mémoires SDRAM sont des mémoires à tampon de ligne. Elles font un accès mémoire en deux étapes. La première étape recopie une ligne de N * 64 bits dans un tampon interne à la SDRAM. La seconde étape sélectionne une colonne, à savoir une donnée de 64 bits, qui est soit envoyée sur le bus de données pour une lecture, soit modifiée par une écriture. [[File:Mémoire à tampon de ligne.png|centre|vignette|upright=2|Mémoire à tampon de ligne]] Les accès mémoire sont traduits par un séquenceur mémoire en une série de commandes mémoires, qui sont séparées par des délais mémoire de quelques cycles d'horloge. Les délais sont très précis, et sont à respecter à la lettre. Une lecture ou une écriture se fait en maximum trois commandes : une commande PRECHARGE qui ferme la ligne précédemment utilisée, une commande ACT qui précise l'adresse de la ligne, et une commande READ ou WRITE qui précise l'adresse de la colonne et éventuellement la donnée à écrire. : Pour être plus précis, la commande PRECHARGE précharge les lignes de bits du plan mémoire à une tension particulière, ce qui les vide de leur contenu. Mais c'est un détail sans importance pour ce qui va suivre. Les SDRAM permettent de se passer de la première étape si des accès consécutifs se font dans la même ligne. Une fois activée, la ligne reste ouverte et on peut accéder plusieurs fois de suite dedans. On a alors juste à préciser l'adresse de colonne dedans. Si un accès mémoire accède à une ligne déjà activée, on dit que c'est un '''succès de page'''. Si ce n'est pas le cas, on doit fermer la ligne courante, rouvrir la ligne vboulue, et préciser la colonne. C'est alors un '''défaut de page'''. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | bgcolor="#FFA0FF" | PRECHARGE || bgcolor="#FFA0FF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || bgcolor="#A0FFFF" | READ (2) || bgcolor="#A0FFFF" | READ (3) || || || bgcolor="#A0FFFF" | READ (4) || bgcolor="#A0FFFF" | READ (5) || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | Donnée READ 1 || ||bgcolor="#A0FFFF" | Donnée READ 2 | bgcolor="#A0FFFF" | Donnée READ 3 || || || bgcolor="#A0FFFF" | Donnée READ 4 || bgcolor="#A0FFFF" | Donnée READ 5 |} Les SDRAM sont parfois capables de démarrer une commande avant que la précédente soit terminée. Mais le respect des délais mémoire est très limitant, ce qui fait qu'on ne peut pas parler de réel pipeline, comme c'est le cas sur les processeurs. Parlons plutot de '''commandes anticipées'''. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | || bgcolor="#A0FFFF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || || || bgcolor="#FFA0FF" | READ (2) || || || || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 | bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 | |} Un dernier point est que les chips mémoires de SDRAM sont composés de plusieurs banques, chacune étant une sorte de mini-mémoire miniature. Chaque banque a son propre décodeur, son propre tampon de ligne, ses propres multiplexeurs de colonne, sa logique de rafraichissement mémoire, etc. C'est comme si une SDRAM regroupait plusieurs mémoires séparées dans un même circuit intégré. [[File:Arrangement vertical.jpg|centre|vignette|upright=2.5|Mémoire multi-banques.]] ===La mise en attente des accès mémoire=== Un contrôleur de SDRAM peut accepter plusieurs accès mémoire en même temps. Mais cela ne signifie pas que le contrôleur sera prêt à les traiter immédiatement. Pour éviter tout problème de disponibilité, le contrôleur met en attente les accès mémoire que le processeur lui envoie, pour les exécuter dès que possible. Les accès mémoire sont mis en attente dans une mémoire FIFO, histoire de les exécuter dans leur ordre d'arrivée, notamment pour renvoyer les lectures dans l'ordre demandé. Il y a aussi une mémoire FIFO pour les données à écrire et une FIFO pour les données lues. Cette dernière sert au cas où le cache ne soit pas disponible quand on lui envoie la donnée lue. [[File:Module d'interface avec la mémoire.png|centre|vignette|upright=2.5|Contrôleur mémoire avec mise en attente des requêtes processeur et autres optimisations.]] Des optimisations sont possibles dès la mémoire FIFO. Par exemple, on peut regrouper plusieurs accès à des données consécutives en un seul accès en rafale. Le contrôleur mémoire analyse les requêtes mises en attente et détecte si certaines se font à des adresses consécutives. Si c'est le cas, il fusionne ces requêtes en une lecture/écriture en rafale. Une telle optimisation est appelée la '''combinaison de lecture''' pour les lectures, et la '''combinaison d'écriture''' pour les écritures. Une autre optimisation possible est le réacheminement lecture sur écriture (''Store to load forwarding''). Il s'agit d'un équivalent de l’optimisation utilisée dans le cadre de la désambiguïsation mémoire. Quand une lecture demande à accéder à une donnée écrite récemment, il se peut que l'écriture ait été mise en attente dans le contrôleur mémoire. Dans ce cas, le contrôleur mémoire peut renvoyer la donnée directement depuis ses mémoires FIFOs, sans faire d'accès mémoire en lecture. Les deux optimisations précédentes ne paraissent pas payer de mine, mais elles deviennent plus compréhensible quand on sait que certains contrôleurs mémoire peuvent mettre en attente beaucoup de données. Par exemple, l'''Intel E8870 - Scalable Memory Controller'' peut mettre en attente 64 lignes de cache dans la mémoire FIFO, soit 8 kibioctets de RAM ! Deux pages mémoire si la mémoire virtuelle utilise des pages de 4 kibioctets ! ===Les lectures anticipées=== La mise en attente des accès mémoire est une optimisation intéressante, mais pas mirifique. Elle permet surtout de ne pas bloquer le processeur si le contrôleur mémoire a du travail sur la planche. Cependant, elle peut être optimisée quand on se rend compte d'une chose : le processeur ne voit que les lectures, pas les écritures. Pour les écritures, il les envoie au contrôleur mémoire et ce dernier fait le travail demandé, le processeur n'est pas prévenu quand une écriture se termine. Mais pour les lectures, c'est différent, car il reçoit la donnée lue. En conséquence, il est possible d'optimiser les écritures sans que le processeur ne voit quoique ce soit. Une optimisation possible est alors de changer l'ordre des accès mémoire, histoire de retarder les écritures ou au contraire de les faire en avance. La raison est que les mémoires SDRAM n'aiment pas quand on alterne lectures et écritures. Les délais mémoire, les fameux ''timings'' mémoire, sont clairs : faire une suite de lecture ou une suite d'écriture est plus performant que d'alterner entre lectures et écritures. L'idée est alors de changer l'ordre des écritures de manière à les faire en bloc, idem pour les lectures. L’optimisation est possible, mais à condition que le processeur n'y verra que du feu. Or, vous l'avez deviné, il y a des cas où ces réorganisations ne sont pas possibles, et vous avez sans doute trouvé que la situation était familière. L'optimisation en question est une forme d'exécution dans le désordre des accès mémoire, réalisée par le contrôleur mémoire lui-même ! Et qui dit exécution dans le désordre dit : problèmes liés aux dépendances de données. Changer l'ordre des écritures n'est pas toujours possible, notamment en raison des dépendances RAW, WAR et WAW. Pour que le processeur n'y voit que du feu, il faut respecter plusieurs critères. Premièrement, les lectures doivent être renvoyées au processeur dans l'ordre. Et quand je dis dans l'ordre, cela veut dire : dans l'ordre d'envoi des lectures au contrôleur mémoire. Les écritures aussi doivent se faire dans l'ordre, histoire qu'une série d'écriture donne le bon résultat final. Le troisième critère est qu'une lecture doit donner le résultat de la dernière écriture ''à la même adresse''. Pour le dire autrement, il faut juste détecter les dépendances RAW. Concrètement, si on a une série de lectures et d'écritures alternées, on peut regrouper les lectures et les écritures dans deux paquets séparés, à condition qu'aucune lecture ne lise une adresse écrite par une écriture. Mais si une lecture est dépendante d'une écriture, alors la lecture doit attendre que l'écriture se termine, idem pour les lectures suivantes. Une implémentation basique remplace la mémoire FIFO vue au-dessus, par deux mémoires FIFOs : une pour les lectures, une pour les écritures. Lorsqu'un accès mémoire arrive au séquenceur, il regarde si c'est une lecture ou une écriture et place l'accès mémoire dans la file adéquate. Le fait que ce soit des FIFOs garantit que les lectures se font dans l'ordre, idem pour les écritures. En sortie des deux FIFOs, le séquenceur mémoire détecte les dépendances RAW. Prenons l'exemple des coprocesseurs IO 81341 et 81342, qui étaient en réalité des ''chipsets'' intégrant un contrôleur SDRAM. Le ''chipset'' avait 5 ports : un pour les processeurs, un pour le pont sud (''southbridge'') et trois pour des canaux DMA. Pour le port processeur, il n'y avait pas de réordonnancement et ce port utilisait une seule mémoire FIFO. Les autres ports utilisaient l'optimisation qu'on vient de voir, et avaient vraisemblablement des files séparées pour les lectures et écritures. Le séquenceur mémoire vérifiait les dépendances mémoire de type RAW et autres. [[File:Double file d'attente pour les lectures et écritures.png|centre|vignette|upright=2.5|Double file d'attente pour les lectures et écritures]] ===Le ré-ordonnancement des commandes mémoires=== L'optimisation précédente est peu poussée, comparé aux formes d'exécution dans le désordre que les processeurs utilisent. Et on peut se demander si une exécution dans le désordre plus poussée est possible. La réponse est oui : un contrôleur mémoire peut faire des réorganisations bien plus poussées. Par contre, il faut faire une précision très importante : le contrôleur mémoire ne remet pas les accès mémoire dans l'ordre avant de les envoyer au processeur. C'est le processeur qui remet en ordre les accès mémoire, dans sa ''load queue''. En clair : le processeur voit les accès mémoire dans le désordre, c'est lui qui les remet en ordre. Pour cela, les requêtes mémoire sont "numérotées", à savoir qu'on leur attribue un identifiant binaire. Le contrôleur mémoire exécute les lectures/écritures dans le désordre, mais garde la trace de leur identifiant. Il sait dans quel ordre il exécute les accès mémoire et connait leur latence. Ce qui fait qu'il sait que la donnée lue à tel instant est associée à la requête mémoire numéro X. Pour les lectures, il renvoie la donnée lue avec l'identifiant. Le processeur sait alors à quelle lecture la donnée lue correspond et il se débrouille en interne pour gérer la situation. Maintenant que ces précisions sont faites, posons cette question : dans quelles situations est-il pertinent de faire des accès mémoire dans le désordre ? La réponse est : quand plusieurs accès à une même ligne ne sont pas consécutifs, qu'il y a des accès entre les deux. Après ré-ordonnancement, ces accès mémoire à une même ligne sont exécutés l'un à la suite de l'autre, ce qui est beaucoup plus rapide. Pour rendre le tout plus concret, voici un exemple. Imaginez que l'on sait les 3 accès mémoire suivants : * Une lecture ligne A ; * PRECHARGE + ACT ; * Une écriture ligne B ; * PRECHARGE + ACT ; * Une lecture ligne A. Sans réordonnancent, on doit émettre deux commandes PRECHARGE quand on passe d'une ligne à l'autre, et il faut ajouter les commandes ACT avec. pour éviter cela, le contrôleur mémoire peut retarder l'écriture, ou au contraire avancer la seconde lecture. le résultat est alors le suivant : * Lecture ligne A ; * Lecture ligne A * PRECHARGE + ACT ; * Une écriture ligne B. On a donc deux accès consécutifs à la même ligne, suivi par une écriture dans une autre ligne. La technique marche parce que l'on a un mix adéquat de lectures et d'écritures. Et encore une fois, il faut éviter d'intervertir lectures et écritures à une même adresse, sous peine de problèmes. Encore une fois, les dépendances RAW posent problème ! L'implémentation est assez simple : la ou les mémoires FIFOs précédentes sont remplacées par des mémoires similaires aux fenêtres d'instruction. Le contrôleur mémoire vérifie à chaque cycle les accès en attente, et vérifie à quelle ligne ils accèdent. Il priorise alors les accès qui tombent dans la ligne ouverte : ceux-là sont exécutés avant les autres. S'il n'y en a pas, il prend l'accès mémoire le plus ancien (ordre FIFO). Il teste aussi les dépendances mémoires RAW avant d'envoyer des commandes à la mémoire DDR/SDRAM. une telle solution est appelée l''''algorithme ''FR-FCFS''''' (''First Ready-First Come First Serve''). ===Les optimisations liées à la présence de plusieurs banques=== Les optimisations précédentes sont décuplées par la présence de plusieurs banques dans la mémoire SDRAM. Il est en effet possible de faire plusieurs accès mémoire en même temps, dans des banques différentes. Il est possible de lancer un accès mémoire dans deux banques en même temps, par exemple. La seule contrainte est que la SDRAM est limitée à 4 banques actives en même temps. {|class="wikitable" |+ Pipelining basique sur les SDRAM |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 |- ! Banque Numéro 1 | || || || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || || |- ! Banque Numéro 2 | || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || |- ! Banque Numéro 3 | || || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || |} Les optimisations précédentes peuvent s'appliquer par banque. Il est par exemple possible d'utiliser une mémoire FIFO par banque. Ainsi, les accès dans une même banque se feront en série, dans l'ordre d'arrivée, mais des accès à des banques différentes se feront dans le désordre. Cela ne pose pas de problème car les accès se font à des adresses différentes, ce qui fait qu'il n'y a pas de conflits majeurs. Il y a cependant un risque que les lectures se fassent dans le désordre si on n'y prend pas garde. [[File:Gestion parallèle des banques.png|centre|vignette|upright=2.5|Gestion parallèle des banques.]] il est aussi possible d'utiliser une file séparée pour les lectures et les écriture, pour chaque banque. Les performances sont alors améliorées comparé à deux files globales pour toute la SDRAM. En idem avec la réorganisation des accès mémoire, pour regrouper les accès à une même ligne. ==L'entrelacement sur les mémoires SDRAM== L''''entrelacement''' fonctionne sur les mémoires SDRAM, mais il doit être fortement modifié. Concrètement, tout ce qui a étré dit plus est inapplicable pour les SDRAM, du fait de la présence du tampon de ligne. Des adresses consécutives atterrissent déjà dans la même ligne, ce qui fait qu'on n'a pas besoin d'entrelacement au niveau d'une ligne. Et ne parlons pas de ce la présence de rangées et de canaux mémoire ! Cependant, il peut y avoir un entrelacement lié à la présence des banques SDRAM. L'entrelacement est géré juste avant le séquenceur mémoire. Le '''circuit d'entrelacement''' intervertit certains bits de l'adresse lors des accès mémoires. On trouve aussi des mémoires FIFOs pour mettre en attente les requêtes mémoire provenant du processeur. Elles permettent de recevoir plusieurs accès mémoire consécutifs. Si vous vous demandez comment plusieurs accès mémoire consécutifs peuvent avoir lieu, sachez que c'est une bonne question. Pour le moment, nous allons dire que ces accès proviennent du processeur ou de la mémoire cache, sans expliquer comment. Le contrôleur mémoire reçoit une série d'accès mémoire assez rapprochés, qu'il doit exécuter au mieux. [[File:Controleur mémoire d'une SDRAM avec entrelacement.png|centre|vignette|upright=2.5|Contrôleur mémoire d'une SDRAM avec entrelacement.]] Pour gérer l'entrelacement, le contrôleur de SDRAM prend en entrée l'adresse envoyée par le processeur, et la découpe en plusieurs champs : un pour sélectionner la banque adéquate, un autre pour la ligne, un autre pour la colonne, un autre pour la rangée. Une solution simple désactive l'entrelacement. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de banque || Adresse de ligne || Adresse de colonne |} Si cette solution fonctionne bien si les accès mémoire sont assez aléatoires, en pratique, mieux vaut utiliser l'entrelacement. ===L'entrelacement de banques/lignes=== Une première méthode d'entrelacement est l''''entrelacement de banques'''. Elle répartit deux lignes consécutives dans deux banques différentes. Pour comprendre l'idée, prenons un exemple. Imaginons une mémoire avec deux banques et 4 lignes. Imaginons qu'on parcoure/balaye la mémoire RAM en partant des adresses basses. Sans entrelacement de ligne, les accès se feront comme suit, de gauche à droite : {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Banque numéro 1 !! colspan="4" | Banque numéro 2 |- | Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 || Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 |} Avec l'entrelacement de banques, les accès se feront comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 |- | Ligne 1 || Ligne 1 || Ligne 2 || Ligne 2 || Ligne 3 || Ligne 3 || Ligne 4 || Ligne 4 |} Avec N banques, la première ligne ira dans la première banque, la seconde ligne dans la seconde, et ainsi de suite jusqu'à la énième banque qui va dans la banque numéro N. Et la ligne N+1 repart dans la première banque. L'avantage est que passer d'une ligne à la suivante est assez rapide. Au lieu de devoir fermer la première ligne et ouvrir la suivante on ouvrira directement la ligne suivante dans une banque différente, ce qui sera plus rapide. La commande PRECHARGE pourra être envoyée à la seconde banque en avance. Pour cela, l'adresse est découpée comme suit. {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne (précise à l'octet) |} ===L'entrelacement de rangées=== Il est aussi possible de faire la même chose, mais avec les rangées. Pour simplifier fortement, une rangée est simplement un chip mémoire. En réalité, les rangées ne sont pas des chip mémoire, mais un ensemble de chips mémoire regroupés ensemble histoire d'atteindre les 64 bits du bus de données. Par exemple, une rangée peut combiner 8 chips mémoire avec un bus de données de 8 bits chacun pour obtenir les 64 bits du bus de données d'une SDRAM. Mais nous passons ce détail sous silence dans les explications qui vont suivre, par souci de simplification. Pour faire comprendre l'entrelacement de rangée, le mieux est d'utiliser un exemple, le même que précédemment. Sans entrelacement de rangée, on change de chip mémoire une fois qu'on a balayé toutes ses adresses. {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Rangée numéro 1 !! colspan="4" | Rangée numéro 2 |- | Banque 1 || Banque 2 || Banque 3 || Banque 4 || Banque 1 || Banque 2 || Banque 3 || Banque 4 |} Avec l'entrelacement de ligne, on change de rangée dès qu'on change de banque/ligne. {|class="wikitable" |+ Adresse mémoire |- ! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 |- | Banque 1 || Banque 1 || Banque 2 || Banque 2 || Banque 3 || Banque 3 || Banque 4 || Banque 4 |} En clair, quand on a épuisé toutes les banques dans une même rangée, on passe à la rangée suivante au lieu de rester dans la même rangée. Notons que dans l'exemple précédent, on a combiné l'entrelacement de rangée et de banque, mais on aurait pu utiliser l'entrelacement de rangée seul. Mais ce n'est pas le cas le plus courant en pratique. Toujours est-il qu'en combinant entrelacement de rangée et de ligne, le découpage de l'adresse est le suivant : {|class="wikitable" |+ Adresse mémoire |- | Adresse de ligne || Numéro de rangée || Adresse de banque || Adresse de colonne (précise à l'octet) |} D'autres méthodes d'entrelacement sont possibles, mais elles sont plus complexes. Chaque contrôleur mémoire fait un peu à sa sauce. Ils permettent en général de choisir entre plusieurs entrelacements. Au minimum, ils permettent de désactiver l'entrelacement, d'activer un entrelacement de ligne, et d'autres entrelacements plus complexes, dont certains sont propriétaires et tenus secrets par le fabricant. ===L'entrelacement avec le ''dual channel''=== [[File:Dual channel slots.jpg|vignette|Slots mémoires avec ''dual channel''.]] L'usage du ''dual channel'' complique encore l'entrelacement. Là encore, il y a deux grandes solutions : avec et sans entrelacement des canaux mémoire. Rappelons le principe : deux barrettes de RAM sont accédées en parallèle. Et pour cela, il faut utiliser l'entrelacement. Typiquement, chaque barrette mémoire fournit 64 bits, ce qui fait que l'on peut accéder à 128 bits d'un seul coup, par exemple avec un accès en rafale. Sans ''dual channel'', la première barrette correspond à la moitié haute de la RAM, la seconde barrette correspond à la moitié basse. Avec ''dual channel'', une forme spécifique d'entrelacement est activée. Concrétement, deux blocs de 64 bits sont placés dans des canaux mémoire séparés. Idem avec du triple ou quadruple canal, mais c'est alors trois ou quatre blocs de 64 bits qui sont dispersés dans des canaux consécutifs. Le découpage de l'adresse est alors le suivant : {|class="wikitable" |+ Adresse mémoire |- ! colspan="6" | Sans entrelacement |- | Numéro de canal/barrette || Reste de l'adresse || Position de l'octet dans un bloc de 64 bits |- | colspan="6" | |- ! colspan="6" | Avec entrelacement |- | Reste de l'adresse || Numéro de canal/barrette || Position de l'octet dans un bloc de 64 bits |} [[File:Décodage d'adresse avec dual channel.png|centre|vignette|upright=2|Décodage d'adresse avec dual channel.]] Les contrôleurs de SDRAM précédents sont assez basiques et ne représentent pas les contrôleurs les plus évolués. Ils étaient utilisés sur les anciens PC, à une époque où ils étaient encore sur la carte mère du processeur. Mais de nos jours, le contrôleur mémoire est intégré au processeur. Et il incorpore de nombreuses optimisations afin de gagner en performances. Malheureusement, ces optimisations sont elles-mêmes dépendantes des optimisations intégrées dans le processeur, ce qui fait qu'on ne peut pas en parler ici. Elles demandent que le contrôleur mémoire reçoive plusieurs accès mémoire simultanés, ce qui n'a aucun sens à ce stade du cours. Aussi, nous allons les passer sous silence pour le moment. Nous les verrons dans le chapitre sur le parallélisme mémoire. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=La désambiguïsation mémoire | prevText=La désambiguïsation mémoire | next=Les processeurs superscalaires | nextText=Les processeurs superscalaires }} </noinclude> fys8lh9cf0llbd5go49iw6a4387e36g 764746 764745 2026-04-23T23:09:11Z Mewtow 31375 /* Les lectures anticipées */ 764746 wikitext text/x-wiki Dans ce chapitre, nous allons voir les techniques qui permettent de gérer plusieurs accès mémoire simultanés directement au niveau du cache ou de la mémoire RAM. Par plusieurs accès mémoire simultanés, vous pensez sans doute à l'usage de cache multiports, voire de mémoires RAM multiport. C'est en effet une solution, mais ce n'est clairement pas la seule. Par exemple, il est possible de pipeliner l'accès au cache, voire à la mémoire RAM. Il existe de nombreuses techniques de '''parallélisme mémoire''', et nous allons les voir dans ce chapitre. Pour donner un exemple, il est possible de pipeliner les accès mémoire. Il est possible de pipeliner l'accès au cache et/ou l'accès à la mémoire, et nous verrons comment faire dans ce chapitre. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, qu'ils aient ou non un pipeline dynamique. Par contre, pipeliner la mémoire est une technique ancienne, aujourd'hui peu utilisée. Elle était utilisée sur des très vieux ordinateurs, qui avaient un pipeline mais pas de mémoire cache. Mais de nos jours, les mémoires SDRAM et DDR ne sont pas adaptées pour ça, il est très difficile de les pipeliner correctement. Le parallélisme mémoire est utile aussi bien sur des processeurs à émission dans l'ordre que dans le désordre. Par exemple, un processeur ''in-order'' avec des lectures non-bloquantes peut e, profiter. Il peut techniquement lancer une seconde lecture, après avoir rencontré une première lecture non-bloquante. En général, le nombre de lectures consécutives est cependant limité à moins d'une dizaine. À l'opposé, les processeurs sans lectures non-bloquantes profitent pas du parallélisme mémoire. À la rigueur, ils peuvent en profiter s'ils intègrent des techniques de préchargement. Pendant que le processeur exécute une lecture/écriture, il peut précharger une autre donnée en parallèle. Inutile de dire que sans pipeline processeur, le parallélisme mémoire ne sert pas à grand-chose. ==Le ''line fill buffer'' et les techniques associées== Lors d'un défaut de cache, le processeur doit attendre que toute la ligne de cache soit chargée avant d'être utilisable. Or, la taille d'une ligne de cache est supérieure à la largeur du bus mémoire, ce qui fait qu'une ligne de cache est chargée en plusieurs fois, morceaux par morceaux, mot mémoire par mot mémoire. Le chargement peut se faire directement dans le cache, mais ce n'est pas une solution très pratique. À la place, beaucoup de processeurs ajoutent une mémoire tampon entre la RAM et le cache, appelée le '''tampon de remplissage de ligne''' (''line-fill buffer'') dans les processeurs modernes. Lors d'un défaut de cache, le processeur charge la donnée de la RAM dans le tampon de remplissage de ligne, mot mémoire par mot mémoire. Une fois plein, le tampon de remplissage de ligne est recopié dans la ligne de cache. Le tampon de remplissage de ligne contient une ligne de cache, si ce n'est que certains bits de contrôle ne sont pas présents. Le tampon de remplissage de ligne contient un bit de validité pour chaque mot mémoire de la ligne de cache, qui indiquent si le mot mémoire a été chargé. Par exemple, prenons un processeur 64 bits, qui gère donc des mots mémoire de 8 octets, avec des lignes de cache 256 octets/32 mots mémoire. Le tampon de remplissage de ligne contiendra 32 bits de validité. Si le processeur a chargé les 6 premiers mots mémoire, les 6 premiers bits de validité seront mis à 1, les autres seront encore à zéro. Il faut noter que la disponibilité de la ligne complète se détermine assez facilement en faisant un ET logique entre tous les bits de validité. Le processeur sait ainsi quand la ligne de cache est disponible entièrement et donc quand la transférer dans le cache. La même chose existe avec une hiérarchie de cache, sauf que l'on trouve une mémoire tampon entre chaque niveau de cache. Il y a un tampon de remplissage de ligne entre le cache L1 et le cache L2, entre le cache L2 et le L3, etc. Si le cache ne gère qu'un seul défaut de cache à la fois, le ''fill line buffer'' est une mémoire très simple, qui ne mémorise qu'une seule ligne de cache. Et cela vaut aussi bien pour un cache bloquant que non-bloquant. Mais sur les caches capables de gérer plusieurs défauts simultanés, le tampon de remplissage de ligne est une mémoire de type FIFO ou LIFO, capable de mémoriser plusieurs lignes de cache. Notons que le tampon de remplissage de ligne est très utile pour implémenter certaines techniques, par exemple le contournement du cache. Nous avions vu dans le chapitre sur le cache que certains accès mémoire doivent contourner le cache, pour des raisons de cohérence des caches. C'est notamment nécessaire pour accéder aux périphériques, mais c'est aussi utile pour des raisons de performances dans des cas très spécifiques. Les accès qui contournent le cache se font directement dans le tampon de remplissage de ligne : le processeur écrit ou lit les données depuis ce tampon de remplissage de ligne, sans accéder au cache. ===L'''early restart'' et le ''critical word load''=== La présence du ''line buffer'' permet une optimisation assez intéressante, qui permet de réduire la latence des défauts de cache. L'optimisation consiste à lire un mot mémoire dans le tampon de remplissage de ligne, même si la ligne de cache complète n'a pas encore été chargée. Il existe deux manières de faire cela, qui portent les noms d'''early restart'' et de ''critical word load''. La première est la version la plus simple, la seconde est plus complexe mais plus performante. Avec la technique d''''''early restart''''', la ligne de cache est chargée normalement, en partant de son premier mot mémoire. Dès que le mot mémoire lu/écrit par le processeur est copié dans le ''line fill buffer'', il est envoyé au processeur immédiatement. Illustrons le tout par un exemple, où une ligne de cache fait 16 mots mémoire. Le processeur effectue une lecture, qui lit le 5ème mot mémoire. Sans ''early restart'', le processeur doit charger les 16 mots mémoire avant de faire la lecture dans le cache. Avec ''early restart'', le processeur reçoit la donnée dès que le 5ème mot mémoire est disponible. Le processeur doit attendre que les 4 premiers mots mémoire soient chargés, puis le 5ème mot mémoire arrive et est envoyé directement au processeur, il est lu directement depuis le ''line fill buffer''. Les 11 mots mémoire suivants sont ensuite chargés dans le cache pendant que le processeur fait des calculs dans son coin. Le ''critical word load'' est une optimisation de la technique précédente où le chargement de la ligne de cache commence directement à la donnée demandée par le processeur. Pour reprendre l'exemple précédent, où le processeur demande le 5ème mot mémoire sur 16, le ''critical word load'' charge le 5ème mot mémoire en premier et l'envoie au processeur, ce qui fait qu'il est chargé très rapidement. Pas besoin d'attendre que le processeur charge les 4 mots mémoire précédents comme avec l'''early restart''. Dans le détail, le processeur charge le 5ème mot mémoire en premier, puis charge les 11 suivants, et termine par les 4 mots mémoire du début. En clair, le ''critical word load'' commence par charger le mot mémoire lu, puis les blocs suivants, avant de revenir au début du bloc pour charger les blocs restants. Ainsi, la donnée demandée par le processeur sera la première disponible. Pour cela, l'organisation du tampon de remplissage de ligne est modifiée de manière à rendre cela possible. Il a une taille égale à une ligne de cache complète, qui contient elle-même plusieurs mots mémoire. Dans le ''line-fill buffer'', chaque mot mémoire est stocké avec un tag, qui indique l'adresse du mot mémoire stocké dans le ''line-fill buffer''. Le ''line-fill buffer'' est donc un cache un peu particulier, qui fonctionne comme un cache du point de vue du processeur, comme une mémoire FIFO pour les transferts avec le cache. Ainsi, un processeur qui veut lire dans le cache après un défaut peut accéder à la donnée directement depuis le tampon de remplissage de ligne, alors que la ligne de cache n'a pas encore été totalement recopiée en mémoire. ==Les caches pipelinés== Il est possible de pipeliner l'accès au cache, ce qui demande juste de rajouter des registres au bon endroits et quelques circuits de contrôle. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, même ceux avec un pipeline dynamique. Et l'implémentation peut se faire de plusieurs manières différentes. Il faut dire que découper une mémoire cache en plusieurs étapes peut se faire de plusieurs manière. Commençons par la plus simple. Beaucoup de processeurs des années 2000 avaient une fréquence peu élevée comparée aux standards d'aujourd'hui, ce qui fait que leur cache avait bien un temps d'accès d'un cycle d'horloge. Mais l'accès au cache se faisait en deux cycles d'horloge : un cycle pour calculer une adresse et un cycle pour l'accès mémoire proprement dit. Et ces deux étapes étaient pipelinées, à savoir que deux micro-opérations mémoire peuvent s'exécuter en même temps : une dans l'étage de calcul d'adresse, une autre dans le cache. L'unité mémoire était donc pipelinée, alors que l'accès au cache ne l'était pas. Les processeurs qui implémentaient cette technique regroupent les micro-architectures K5 et K6 d'AMD, les processeurs Intel de micro-architecture P6 (Pentium 2 et 3) et quelques autres. Il est aussi possible de pipeliner l'accès au cache lui-même. Avec un cache pipeliné, l'accès au cache ne se fait pas en un seul cycle, mais en plusieurs. Cependant, on peut lancer un nouvel accès au cache à chaque cycle d'horloge, comme un processeur pipeliné lance une nouvelle instruction à chaque cycle. L'implémentation est assez simple : il suffit d'ajouter des registres dans le cache. Pour cela, on profite que le cache est composé de plusieurs composants séparés, qui échangent des données dans un ordre bien précis, d'un composant à un autre. De plus, le trajet des informations dans un cache est linéaire, ce qui les rend parfois pour l'usage d'un pipeline. ===Le ''pipelining'' des caches ''direct-mapped''=== Voici ce qui se passe avec un cache directement adressé. Pour rappel ce genre de cache est conçu en combinant une mémoire RAM, généralement une SRAM, avec quelques circuits de comparaison et un MUX. L'accès au cache se fait globalement en deux étapes : la première lit la donnée et le ''tag'' dans la SRAM, et on utilise les deux dans une seconde étape. Nous avions vu il y a quelques chapitre comment pipeliner des mémoires, dans le chapitre sur les mémoires évoluées. Et bien ces méthodes peuvent s'utiliser pour la mémoire RAM interne au cache !L'idée est d'insérer un registre entre la sortie de la RAM et la suite du cache, pour en faire un cache pipéliné, à deux étages. Le premier étage lit dans la SRAM, le second fait le reste. L'implémentation sur les caches associatifs à plusieurs voies est globalement la même à quelques détails près. [[File:Cache directement adressé pipeliné - deux étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - deux étages]] Avec le circuit précédent, il est possible d'aller plus loin, cette fois en pipelinant l'accès à la mémoire RAM interne au cache. Pour comprendre comment, rappelons qu'une mémoire SRAM est composée d'un plan mémoire et d'un décodeur. L'accès à la mémoire demande d'abord que le décodeur fasse son travail pour sélectionner la case mémoire adéquate, puis ensuite la lecture ou écriture a lieu dans cette case mémoire. L'accès se fait donc en deux étapes successives séparées, on a juste à mettre un registre entre les deux. Il suffit donc de mettre un gros registre entre le décodeur et le plan mémoire. [[File:Cache directement adressé pipeliné - trois étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - trois étages]] Et on peut aller encore plus loin en découpant le décodeur en deux circuits séparés. En effet, rappelez-vous le chapitre sur les circuits de sélection : nous avions vu qu'il est possible de créer des décodeurs en assemblant des décodeurs plus petits, contrôlés par un circuit de prédécodage. Et bien on peut encore une ajouter un registre entre ce circuit de prédécodage et les petits décodeurs. : Théoriquement, toute l'adresse est fournie d'un seul coup au cache, la quasi-totalité des processeurs présentent l'adresse complète à un cache pipeliné. Mais le Pentium 4 fait autrement. Il faut noter que les premiers étages manipulent l'indice dans la SRAM, qui est dans les bits de poids faible, alors que les étages ultérieurs manipulent le ''tag'' qui est dans les bits de poids fort. Les concepteurs du Pentium 4 ont alors décidé de présenter les bits de poids faible lors du premier cycle d'accès au cache, puis ceux de poids fort au second cycle. Pour cela, l'ALU fonctionnait à une fréquence double de celle du processeur, tout comme le cache L1. Il n'y avait pas de pipeline proprement dit, mais cela réduisait grandement la latence d'accès au cache. ===Le ''pipelining'' des caches splittés/sériels=== Il est aussi possible de pipeliner un cache dits splittés, aussi appelés à accès sériel. Pour rappel, les caches à accès sériel vérifient si il y a succès ou défaut de cache, avant d'accéder aux lignes de cache en cas de succès. Ils font donc différemment des autres caches, qui accèdent à une ligne de cache, avant de déterminer s'il y a succès ou défaut en lisant le tag de la ligne de cache. Les caches sériels disposent de deux SRAM : une pour les tags des lignes de cache et une pour les données. Ils accèdent à la SRAM pour les tags, avant d’accéder à la SRAM des données en cas de succès de cache. Vu que l'accès se fait en deux étapes, une vérification des tags suivie de la lecture/écriture des données, il est facile à pipeliner. Pipeliner le cache permet de régler le problème des accès au cache L1, et elle est tout le temps utilisé sur les processeurs modernes. Mais que faire en cas de défaut de cache ? ==Les caches non bloquants== Un '''cache bloquant''' est un cache auquel le processeur ne peut pas accéder pendant un défaut de cache. Il faut attendre que la lecture ou écriture en RAM soit terminée avant de pouvoir utiliser de nouveau le cache. Un '''cache non bloquant''' n'a pas ce problème : on peut l'utiliser pendant un défaut de cache. Les caches non bloquants permettent de démarrer une nouvelle lecture ou écriture alors qu'une autre est en cours, ce qui permet d'exécuter plusieurs lectures ou écritures en même temps. ===Les ''Miss Handling Status Registers''=== Lors d'un défaut de cache, la mémoire RAM est consultée pendant le défaut de cache, mais le cache est inutilisé. Un défaut de cache n'utilise pas le cache, ce n'est qu'un accès en mémoire RAM, sur le bus mémoire (ou un accès aux niveaux de cache inférieurs, peu importe). Le cache en lui-même est laissé libre, rien n’empêche d'y accéder, il est en réalité intrinsèquement non-bloquant. Les caches, bloquants comme non-bloquants, sont en réalité composés d'une mémoire cache proprement dite, entourée de circuits qui servent d'interface entre le processeur et le cache lui-même. Et parmi les circuits tout autour, certains gèrent l'accès au cache lors d'un défaut de cache. Ils sont regroupés sous le terme de '''''Miss Handling Architecture''''' (MHA). La différence entre un cache bloquant et un cache non-bloquant est en réalité liée à l'implémentation de la MHA. Les caches bloquants coupent volontairement l'accès au cache lors d'un défaut, car les défauts sont plus simples à gérer ainsi. Le défaut de cache rapatrie une donnée depuis la RAM, qui sera écrite dans le cache. Et il ne faut pas qu'une tentative d'accès à cette donnée ait lieu avant qu'elle ne soit chargée. Mais ce blocage est général et touche tout le cache, alors que seule une ligne de cache est concernée par le défaut de cache. L'idée derrière un cache non-bloquant est que seule la ligne de cache est bloquée, mais les autres sont accessibles. L'idée est alors de mémoriser les lignes de cache concernées par le défaut de cache, afin d'en bloquer l'accès. À chaque accès, on vérifie si la ligne de cache est déjà réservée par un défaut de cache. La lecture/écriture est alors bloquée si c'est le cas, mais elle accepte les accès sinon. Pour cela, la ''Miss Handling Architecture'' contient des registres qui mémorisent des informations sur les défauts de cache en cours. Ils portent le nom de '''''miss status handling registers''''', que j’appellerais dorénavant MSHR, qui sont aussi appelés des '''''miss buffer'''''. Le contenu des MSHR varie beaucoup suivant le processeur, mais ces derniers stockent au minimum les informations suivantes : * Le numéro de la ligne de cache dans laquelle les données sont chargées. * Un bit de validité qui indique si le MSHR est vide ou pas, qui est mis à 0 quand le défaut de cache est résolu. * Un ou plusieurs champs de lecture/écriture, qui contiennent des informations sur la lecture/écriture. ** Pour une lecture, elle contient des informations sur la destination de la donnée, à savoir qui prévenir quand le défaut de cache est terminé. C'est parfois un nom/numéro de registre (celui dans lequel charger la donnée), mais c'est souvent le numéro de l'entrée dans le ''load/store queue''. ** Pour les écritures, elle contient la donnée à écrire, ou éventuellement un numéro de ''load/store queue'' où se trouve la donnée à écrire. Il faut noter que le bus mémoire ne peut gérer qu'un seul défaut de cache à la fois. Aussi, il est intéressant de regarder ce qui se passe lorsqu'un second défaut de cache survient, pendant qu'un premier est en cours. Dans ce cas, il y a deux réponses qui correspondent à deux types de caches non-bloquants, qui portent les noms barbares de caches de type succès après défaut et défaut après défaut. Sur le premier type, il ne peut pas y avoir plusieurs défauts de cache simultanés. Dès qu'un second défaut de cache survient, le cache stoppe son activité et on ne peut plus démarrer de nouvelle lecture/écriture, tant que le premier défaut de cache n'est pas résolu. Le second type est plus souple et autorise la survenue de plusieurs défauts de cache simultanés. Du moins, jusqu'à une certaine limite, car le cache ne peut supporter qu'un nombre limité d'accès mémoires simultanés (pipelinés). ===Les accès simultanés à une même ligne de cache=== Il arrive que le processeur fasse plusieurs accès mémoire simultanés à la même ligne de cache. Si la ligne de cache en question n'a pas encore été chargée dans le cache, alors on a plusieurs défauts de cache consécutifs pour la même ligne de cache, et le cache non-bloquant doit gérer la situation. Pour la suite, il va falloir faire une petite distinction entre les défauts primaires et secondaires. Imaginons qu'un défaut de cache ait lieu et demande à charger une donnée dans la ligne de cache numéro N. Il s'agit du premier défaut impliquant cette ligne de cache précise, ce qui lui vaut le nom de '''défaut de cache primaire'''. Mais par la suite, d'autres accès mémoire à la même ligne de cache ont lieu, alors que la ligne de cache n'est pas encore disponible. Dans ce cas, il s'agit de '''défauts de cache secondaires'''. Pour l'unité d'accès mémoire, les défauts de cache primaire et secondaire sont différents (ils prennent tous une entrée dans la ''load/store queue''). Mais pour le cache, ils ne correspondent qu'à un seul accès au cache : celui qui demande de charger la ligne de cache demandée. Les défauts de cache primaire et secondaire à la même ligne de cache se voient attribuer un MSHR unique. La gestion des défauts de cache secondaires dépend du cache non-bloquant. La solution la plus simple ne permet pas les défauts de cache secondaires. Le cache ne permet pas deux défauts de cache simultanés pour la même ligne de cache. Les autres solutions le permettent, en fusionnant des accès simultanés à la même ligne de cache en un seul au niveau des MSHR. Dans tous les cas, détecter les défauts de cache secondaires sont un problème qu'il faut détecter. La ''Miss Handling Architecture'' doit détecter les défauts de cache secondaire. Pour cela, elle procède comme suit. Lors de chaque défaut de cache, la MHA récupère le numéro de la ligne de cache associé. Il vérifie alors chaque MSHR pour vérifier s'il contient le numéro en question. S'il n'y a aucune correspondance dans les MSHR, alors c'est signe que le défaut de cache est un défaut primaire. Mais s'il y en a une, alors c'est un défaut secondaire. Évidemment, cela signifie que lors d'un défaut de cache, le numéro de ligne de cache est envoyé à tous les MSHR, pour comparaison. Les MSHR sont donc regroupés dans une mémoire associative, une sorte de mini-cache, faciliter l'implémentation. Lors d'un défaut de cache primaire, l'accès à un cache non-bloquant se fait comme suit : le processeur envoie une adresse au cache, accède à celui-ci, et détecte la survenue d'un défaut de cache. Il en profite alors pour attribuer une ligne de cache dans laquelle sera chargée ce défaut. L'attribution est très simple dans le cas des caches ''direct mapped'', ou associatifs par voie, pour lesquels l'attribution se fait assez simplement. Il mémorise alors cette information dans les MSHR, après avoir vérifié que le défaut de cache n'était pas un défaut secondaire. Lors des accès ultérieurs à une adresse proche, censée être dans la même ligne de cache, le processeur va encore une fois rencontrer un défaut de cache. Il va alors déterminer le numéro de la ligne de cache associée à l'adresse, et comparer ce numéro avec les MSHRs. Si un MSHR contient ce numéro, c'est signe que le défaut de cache est un défaut secondaire. La MHA réagit alors différemment selon le processeur considéré. Une première solution n'autorise pas les défauts de cache secondaires. Si l'un d'entre eux survient, le processeur est gelé par un ''pipeline stall'', une bulle de pipeline. Une autre solution fusionne les défauts de cache secondaires avec le défaut de cache primaire : tout cela ne correspond qu'à un seul défaut de cache pour lui. ===Les MSHR simples=== Un cache non-bloquant à '''MSHR simple''' contient juste plusieurs MSHR qui mémorisent juste un numéro de ligne de cache, un bit de validité, le champ de lecture/écriture, et l'adresse exacte de lecture/écriture. Avec cette organisation, il est possible d'avoir plusieurs défauts de cache séparés, mais à la condition que chaque défaut accède à une ligne de cache différente. Deux accès simultanés à une même ligne de cache ne sont pas possibles, les défauts de cache secondaires ne sont pas autorisés. Ainsi, chaque défaut de cache se voit attribuer son propre MSHR, chacun contient un numéro de ligne de cache différent. Pour comprendre pourquoi c'est impossible de gérer les défauts secondaires, il faut regarder le champ de lecture/écriture. Si on veut effectuer plusieurs écritures consécutives à la même adresse, le MSHR n'aura pas de quoi mémoriser les deux données à écrire. Il pourra mémoriser la première donnée à écrire, pas la seconde. Même chose lors d'une lecture : le champ lecture/écriture peut mémorisr la destination de la première lecture, pas de la seconde. L'avantage est que la MHA n'a besoin que des MSHR et de quelques circuits annexes. Les autres solutions rajoutent des circuits annexes pour gérer les défauts de cache secondaires, qui utilisent beaucoup de circuits. Le cout en circuit est donc élevé, mais le gain en performance est là. Passons maintenant aux caches non-bloquants qui autorisent les défauts de cache secondaire. La solution la plus simple consiste à utiliser ===Les MSHR adressés implicitement=== Avec les '''MSHR adressés implicitement''', il est possible de fusionner plusieurs accès mémoire à une même ligne de cache, mais sous une condition très importante : ces accès lisent/écrivent des mots mémoire différents. Par exemple, imaginons qu'une ligne de cache contienne 8 mots mémoire de 64 bits. Si un premier accès mémoire lit le mot mémoire numéro 7 (dernier mot mémoire de la ligne), et le second accès le mot mémoire numéro 3, alors la fusion est possible. Mais si deux accès mémoire veulent lire/écrire le mot mémoire numéro 7, alors la fusion n'est pas possible et le processeur se bloque, un ''pipeline stall'' survient. Un MSHR adressé implicitement est un MSHR simple, qui contient naturellement un numéro de ligne de cache (tag) et un bit de validité, sauf que le champ de lecture/écriture est dupliqué. Les différents champs lecture/écriture d'un MSHR sont regroupés dans une mémoire RAM/cache qui contient autant d'entrées qu'il y a de mot mémoire dans une ligne de cache. Chaque entrée de la table est associée à un mot mémoire de la ligne de cache et stocke des informations sur celui-ci. Une entrée mémorise, au minimum : * Un champ de lecture/écriture qui contient soit la destination de la lecture, soit la donnée à écrire. * Un bit R/W permettant d'interpréter correctement le champ de lecture/écriture. * Un bit de validité pour chaque entrée de la table, qui dit si un défaut de cache antérieur accède déjà à ce mot mémoire. La fusion de deux défauts de cache est ainsi assez simple. Un défaut de cache primaire/secondaire configure l'entrée associée au mot mémoire lu/écrit. Si un défaut secondaire ultérieur lit/écrit un mot mémoire différent (dont le bit de validité de l'entrée dans la table est à zéro), pas de conflit : il configure une autre entrée, vide. Mais s'il lit/écrit un mot mémoire pour lequel l'entrée de la table est occupée, il y a conflit, le processeur est bloqué par un ''pipeline stall'', une bulle de pipeline. Avec cette organisation, le nombre de MSHR indique combien de lignes de cache peuvent être lues en même temps depuis la mémoire. Quant au nombre d'entrées par MSHR, il détermine combien d'accès mémoires qui ne se recouvrent pas peuvent avoir lieu en même temps. ===Les MSHR adressés explicitement=== Les '''MSHR adressés explicitement''' n'ont pas les contraintes des MSHR adressés implicitement. Avec eux, il est possible d'avoir plusieurs défauts de cache pointant vers la même ligne de cache, mais aussi vers le même mot mémoire. De tels défauts de cache apparaissent sur les processeurs à exécution dans le désordre, mais sont très rares, voire inexistants, sur les processeurs ''in-order''. L'idée est encore que chaque MSHR est associée à une table mémoire qui mémorise des entrées. Cette table n'est autre qu'une mémoire FIFO. Sauf que cette fois-ci, une entrée n'est pas associée à un mot mémoire. Une entrée est associée à un défaut de cache. Une entrée mémorise là encore la destination de la lecture ou la donnée de l'écriture suivi du bit R/W, ainsi que d'un bit de validité par entrée, mais aussi : la position du mot mémoire lu dans la ligne de cache. C'est cette dernière information qui n'était pas présente dans les MSHR adressés implicitement. De plus, le nombre d'entrée par MSHR n'est pas égal au nombre de mots mémoires dans une ligne de cache, mais peut être arbitrairement grand. L'avantage d'un tel cache est qu'il est capable de traité les défauts secondaires concernant un même mot mémoire, et ce, car les accès à la ligne de cache concernée se produisent dans l'ordre de génération des défauts (la table mémoire est une FIFO). ===Les MSHRs inversés=== Généralement, plus on veut supporter de défauts de cache, plus le nombre de MSHR et d'entrées augmente. Mais au-delà d'un certain nombre d'entrées et de MSHR, les MSHR adressés implicitement et explicitement ont tendance à bouffer un peu trop de circuits. Utiliser une organisation un peu moins gourmande en circuits est donc une nécessité. Cette organisation plus économe se base sur des MSHR inversés. Les MSHR inversés ne contiennent qu'une seule entrée, en quelque sorte : au lieu d’utiliser n MSHR de m entrées chacun, on va utiliser n × m MSHR inversés. La différence, c'est que plusieurs MSHR peuvent contenir un tag identique, contrairement aux MSHR adressés implicitement et explicitement. Lorsqu'un défaut de cache a lieu, chaque MSHR est vérifié. Si jamais aucun MSHR ne contient de tag identique à celui utilisé par le défaut, un MSHR vide est choisi pour stocker ce défaut, et une requête de lecture en mémoire est lancée. Dans le cas contraire, un MSHR est réservé au défaut de cache, mais la requête n'est pas lancée. Quand la donnée est disponible, les MSHR correspondant à la ligne qui vient d'être chargée vont être utilisés un par un pour résoudre les défauts de cache en attente. ===Les MSHR intégrés au cache=== Certains chercheurs ont remarqué que pendant qu'une ligne de cache est en train de subir un défaut de cache, celle-ci reste inutilisée, et son contenu est destiné à être perdu une fois le défaut de cache résolu. Ils se sont dit que, plutôt que d'utiliser des MSHR séparés, il vaudrait mieux utiliser la ligne de cache pour stocker les informations sur les défauts de cache en attente dans cette ligne de cache. Pour éviter tout problème, il faut rajouter un bit dans les bits de contrôle de la ligne de cache, qui sert à indiquer que la ligne de cache est occupée : un défaut de cache a eu lieu dans cette ligne, et elle stocke donc des informations pour résoudre les défauts de cache. ==L'entrelacement mémoire== Nous venons de voir comment une mémoire cache peut gérer plusieurs accès mémoire simultanés. Il se trouve que ce genre d'optimisation dépasse largement le cadre du cache. Les mémoires RAM ne sont pas en reste, elles aussi ! Il est possible de pipeliner l'accès à une mémoire RAM, en utilisant des techniques dites d''''entrelacement'''. Précisons cependant que cette optimisation n'est pas utilisée sur les mémoires SDRAM ou DDR modernes, du moins pas sans modifications majeures. Mais divers ordinateurs assez anciens, dont des superordinateurs, ont utilisé cette technique. La technique demande d'utiliser plusieurs mémoires séparées. Sur les anciens ordinateurs, les mémoires en question sont des chips mémoire, à savoir des circuits intégrés de DRAM. Pour faire plus général, nous allons utiliser le terme de '''banques''', ou encore de bancs mémoire. La différence est qu'il existe des mémoires multi-banque, qui regroupent plusieurs banques indépendantes dans un seul boitier, dans un seul chip mémoire. En clair, de telles mémoires regroupent plusieurs sous-mémoires dans un seul circuit intégré. Il est aussi possible d'utiliser plusieurs chips mémoire séparés, chacun ayant une banque. Reste à combiner ces banques pour former un pseudo-pipeline. ===Utiliser plusieurs banques sans entrelacement=== Sans optimisation particulière, les adresses sont réparties dans les banques comme indiqué ci-dessous. Il s'agit de ce qui s'appelle un arrangement horizontal, et nous avions vu celui-ci dans le chapiutre sur les mémoires SDRAM. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Avec cet arrangement, les bits de poids fort de l'adresse sont utilisées pour sélectionner la banque adéquate, et le reste de l'adresse est envoyé sur le bus d'adresse. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Adresse de banque !! Adresse dans la banque |- | Quelques bits de poids fort || Reste de l'adresse |} L'entrelacement change cette répartition, afin d'accélérer les accès mémoire. L'entrelacement classique vise à améliorer les accès à des adresses mémoires consécutives, mais des formes plus évoluées d'entrelacement visent à optimiser des accès mémoire arbitraires. Voyons-les dans l'ordre, du plus simple au plus complexe. ===L'entrelacement classique=== Avec l''''entrelacement classique''', des adresses consécutives sont réparties dans des banques consécutives. Précisons que cette attribution des adresses n'implique absolument pas la mémoire virtuelle ou n'importe quel mécanisme dans le processeur. La répartition décide que telle adresse mémoire va dans telle banque, à telle adresse dans la banque. Elle est donc le fait du contrôleur mémoire, donc en dehors du processeur (les contrôleurs mémoires n’étaient pas encore intégrés dans le processeur à l'époque). [[File:Répartition des adresses dans une mémoire interleaved.png|centre|vignette|upright=1.5|Répartition des adresses dans une mémoire interleaved.]] L'entrelacement simple permet d'accélérer les accès mémoire qui se font à des adresses consécutives. Avec l'entrelacement, chaque accès mémoire tombe dans une banque mémoire différente, ce qui fait qu'on peut démarrer un nouvel accès mémoire à chaque cycle d'horloge. Pendant qu'une banque est occupée par un accès mémoire, on démarre le suivant dans une autre banque, et ainsi de suite. Le tout se termine soit quand on a épuisé toutes les banques libres, soit quand l'accès en rafales se termine. Pas besoin d'attendre que la mémoire ait fini sa lecture/écriture avant de démarrer la lecture/écriture suivante. [[File:Pipemining mémoire.png|centre|vignette|upright=2.5|Pipelining mémoire]] L'animation suivante illustre bien le processus, avec quatres banques. [[File:Interleaving.gif|centre|vignette|upright=2.5|Pipelining mémoire]] L'entrelacement simple utilise les bits de poids faible pour sélectionner la banque, et les bits de poids fort pour la case mémoire. [[File:Adresse mémoire d'une mémoire entrelacée.png|centre|vignette|upright=2|Adresse mémoire d'une mémoire entrelacée]] Précisons que le temps d'accès mémoire ne change pas beaucoup avec l'entrelacement. Par contre, on peut faire plus d'accès mémoire simultanés. On peut démarrer un accès mémoire par cycle d'horloge, mais l'accès en lui-même prend plusieurs cycles. Les mémoires à entrelacement ont donc un débit supérieur aux mémoires qui ne l'utilisent pas. L'entrelacement classique pourrait être utilisé pour accélérer les transferts entre mémoire RAM et cache, qui se font en blocs d'adresses consécutives. Mais de nos jours, il n'est pas utilisé dans cette optique. Il faut dire qu'il est redondant avec le mode rafale des mémoires DRAM. Les deux font globalement la même chose et on voit mal comment utiliser les deux en même temps. Le mode rafale étant plus simple à implémenter, et plus léger niveau utilisation du bus de commande mémoire, il est préféré à l'entrelacement. Par contre, l'entrelacement a eu son heure de gloire sur d'anciens processeurs qui n'avaient pas de mémoire cache, alors qu'ils disposaient d'un pipeline. L'exemple typique est celui des nombreux ''processeurs vectoriels'', que nous n'avons pas encore abordé à ce stade du cours. De tels processeurs avaient un pipeline, une unité de calcul fortement pipelinée, et parfois même de l'exécution dans le désordre et du renommage de registres ! ===L'entrelacement par décalage=== L'entrelacement simple est très utile pour les accès en rafle ou équivalents. Par contre, il existe de rares situations où il n'est pas optimal. Une de ces situations est celle des accès en enjambée (en ''stride''), où l'on accède en série à des adresses sont séparées par N mots mémoires. [[File:Accès par enjambées.png|centre|vignette|upright=2|Accès par enjambées.]] De tels accès surviennent quand un logiciel accède à des structures de données spécifiques, à savoir des tableaux de structures, des matrices, ou d'autres structures de données dans le genre. Un cas classique est celui du parcours d'une matrice colonne par colonne. Les matrices de nombres sont mémorisées ligne par ligne, ce qui fait que parcourir une colonne demande de passer d'une ligne à la suivante et de faire des grands sauts en mémoire RAM, mais tous espacés par la même distance. Avec l'entrelacement simple, les accès en enjambée sont moins performants que les accès à des adresses consécutives, mais cela ne fonctionne pas trop mal. Le pire cas est celui où l'on a N banques et où les données sont justement placées toutes les N adresses. Des accès consécutifs vont tous tomber dans la même banque, on ne peut plus accéder à des banques différentes en parallèle. Mais en dehors de ce cas, on voit une amélioration par rapport à la situation sans entrelacement. Pour obtenir des performances maximales pour les accès en enjambées, il faut répartir les mots mémoires dans la mémoire autrement. L'organisation idéale est la suivante. : Dans les explications qui vont suivre, la variable N représente le nombre de banques, qui sont numérotées de 0 à N-1. On commence par organiser les N premières adresses comme une mémoire entrelacée simple : l'adresse 0 correspond à la banque 0, l'adresse 1 à la banque 1, etc. Pour le bloc suivant, on décale tout d'une adresse, à savoir qu'on commence à remplir les banques à partir de la seconde, non de la première. Une fois la fin du bloc atteinte, on finit de remplir le bloc en repartant du début du bloc. Le troisième bloc d'adresse subit le même traitement, sauf qu'on commence à remplir à partir de la seconde banque. Et on poursuit l’assignation des adresses en décalant d'un cran en plus à chaque bloc. Ainsi, chaque bloc verra ses adresses décalées d'un cran en plus comparé au bloc précédent. Si jamais le décalage dépasse la fin d'un bloc, alors on reprend au début. [[File:Mémoire interleaved par décalage.png|centre|vignette|upright=1.5|Mémoire entrelacée par décalage.]] En faisant cela, on remarque que les banques situées à N adresses d'intervalle sont différentes. Dans l'exemple du dessus, nous avons ajouté un décalage de 1 à chaque nouveau bloc à remplir. Mais on aurait tout aussi bien pu prendre un décalage de 2, 3, etc. Dans tous les cas, on obtient un entrelacement par décalage. Ce décalage est appelé le pas d'entrelacement, noté P. Le calcul de l'adresse à envoyer à la banque, ainsi que la banque à sélectionner se fait en utilisant les formules suivantes : * adresse à envoyer à la banque = adresse totale / N ; * numéro de la banque = (adresse + décalage) modulo N ; * décalage = (adresse totale * P) mod N. Avec cet entrelacement par décalage, on peut prouver que la bande passante maximale est atteinte si le nombre de banques est un nombre premier. Seulement, utiliser un nombre de banques premier peut créer des trous dans la mémoire, des mots mémoires inadressables. Malgré ce défaut, la technique a été utilisée sur quelques ordinateurs, avec l'exemple notable du superordinateur ''Burroughs Scientific Processor''. Pour éviter cela, il y a plusieurs solutions. Par exemple, on peut faire en sorte que N et la taille d'une banque soient premiers entre eux : ils ne doivent pas avoir de diviseur commun. Mais en pratique, elles n'ont pas vraiment été implémentées dans une vraie machine, et sont restées à l'état de recherche, aussi je les passe sous silence. ===L'entrelacement pseudo-aléatoire=== Une dernière méthode de répartition consiste à répartir les adresses dans les banques de manière "pseudo-aléatoire". L'idée exacte est de faire passer l'adresse dans une '''fonction de hachage''', plus ou moins complexe. Pour rappel, une fonction de hachage prend une entrée de grande taille et fournit en sortie un résultat de petite taille. Idéalement, elles doivent donner des résultats aussi différents que possible pour des entrées similaires, histoire de simuler une sorte de pseudo-aléatoire. Ici, une adresse est transformée en un numéro de banque. Une fonction de hachage simple ne fait que permuter des bits de l'adresse pour obtenir son résultat. Elle ne fait qu'échanger des bits de place avant de couper l'adresse en deux morceaux : un pour la sélection de la banque, et un autre pour la sélection de l'adresse dans la banque. Cette permutation est fixe, et ne change pas suivant l'adresse. Des fonctions de hachage plus complexes font des XOR bit à bit entre certains bits de l'adresse. ==Les contrôleurs SDRAM/DDR avec "exécution dans le désordre"== Après avoir vu le cas des mémoires RAM, nous allons nous concentrer sur le cas particulier des mémoires SDRAM. Les mémoires SDRAM et DDR modernes sont capables de gérer plusieurs accès simultanés à la mémoire RAM, et leurs contrôleurs mémoire en font tout autant. C'est une différence majeure avec les mémoires asynchrones FPM/EDO, qui n'acceptaient qu'un seul accès mémoire à la fois. Leur contrôleur mémoire n'acceptait qu'un seul accès mémoire à la fois, c'était un contrôleur mémoire bloquant. Les contrôleurs mémoires des SDRAM sont eux non-bloquants et peuvent encaisser une dizaine d'accès mémoire à la fois. Peu de choses sont connues sur les contrôleurs de SDRAM/DDR modernes, les fabricants ne donnant que peu de détails dessus. Les rares simulateurs qui tentent de décrire leur fonctionnement, comme DRAM SIM I et II, sont particulièrement simples et ne vont pas dans le détail. Néanmoins, le peu qu'on sait est tout de même instructif. ===Rappel sur les SDRAM : tampon de ligne et commandes=== Les mémoires SDRAM sont des mémoires à tampon de ligne. Elles font un accès mémoire en deux étapes. La première étape recopie une ligne de N * 64 bits dans un tampon interne à la SDRAM. La seconde étape sélectionne une colonne, à savoir une donnée de 64 bits, qui est soit envoyée sur le bus de données pour une lecture, soit modifiée par une écriture. [[File:Mémoire à tampon de ligne.png|centre|vignette|upright=2|Mémoire à tampon de ligne]] Les accès mémoire sont traduits par un séquenceur mémoire en une série de commandes mémoires, qui sont séparées par des délais mémoire de quelques cycles d'horloge. Les délais sont très précis, et sont à respecter à la lettre. Une lecture ou une écriture se fait en maximum trois commandes : une commande PRECHARGE qui ferme la ligne précédemment utilisée, une commande ACT qui précise l'adresse de la ligne, et une commande READ ou WRITE qui précise l'adresse de la colonne et éventuellement la donnée à écrire. : Pour être plus précis, la commande PRECHARGE précharge les lignes de bits du plan mémoire à une tension particulière, ce qui les vide de leur contenu. Mais c'est un détail sans importance pour ce qui va suivre. Les SDRAM permettent de se passer de la première étape si des accès consécutifs se font dans la même ligne. Une fois activée, la ligne reste ouverte et on peut accéder plusieurs fois de suite dedans. On a alors juste à préciser l'adresse de colonne dedans. Si un accès mémoire accède à une ligne déjà activée, on dit que c'est un '''succès de page'''. Si ce n'est pas le cas, on doit fermer la ligne courante, rouvrir la ligne vboulue, et préciser la colonne. C'est alors un '''défaut de page'''. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | bgcolor="#FFA0FF" | PRECHARGE || bgcolor="#FFA0FF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || bgcolor="#A0FFFF" | READ (2) || bgcolor="#A0FFFF" | READ (3) || || || bgcolor="#A0FFFF" | READ (4) || bgcolor="#A0FFFF" | READ (5) || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | Donnée READ 1 || ||bgcolor="#A0FFFF" | Donnée READ 2 | bgcolor="#A0FFFF" | Donnée READ 3 || || || bgcolor="#A0FFFF" | Donnée READ 4 || bgcolor="#A0FFFF" | Donnée READ 5 |} Les SDRAM sont parfois capables de démarrer une commande avant que la précédente soit terminée. Mais le respect des délais mémoire est très limitant, ce qui fait qu'on ne peut pas parler de réel pipeline, comme c'est le cas sur les processeurs. Parlons plutot de '''commandes anticipées'''. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | || bgcolor="#A0FFFF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || || || bgcolor="#FFA0FF" | READ (2) || || || || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 | bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 | |} Un dernier point est que les chips mémoires de SDRAM sont composés de plusieurs banques, chacune étant une sorte de mini-mémoire miniature. Chaque banque a son propre décodeur, son propre tampon de ligne, ses propres multiplexeurs de colonne, sa logique de rafraichissement mémoire, etc. C'est comme si une SDRAM regroupait plusieurs mémoires séparées dans un même circuit intégré. [[File:Arrangement vertical.jpg|centre|vignette|upright=2.5|Mémoire multi-banques.]] ===La mise en attente des accès mémoire=== Un contrôleur de SDRAM peut accepter plusieurs accès mémoire en même temps. Mais cela ne signifie pas que le contrôleur sera prêt à les traiter immédiatement. Pour éviter tout problème de disponibilité, le contrôleur met en attente les accès mémoire que le processeur lui envoie, pour les exécuter dès que possible. Les accès mémoire sont mis en attente dans une mémoire FIFO, histoire de les exécuter dans leur ordre d'arrivée, notamment pour renvoyer les lectures dans l'ordre demandé. Il y a aussi une mémoire FIFO pour les données à écrire et une FIFO pour les données lues. Cette dernière sert au cas où le cache ne soit pas disponible quand on lui envoie la donnée lue. [[File:Module d'interface avec la mémoire.png|centre|vignette|upright=2.5|Contrôleur mémoire avec mise en attente des requêtes processeur et autres optimisations.]] Des optimisations sont possibles dès la mémoire FIFO. Par exemple, on peut regrouper plusieurs accès à des données consécutives en un seul accès en rafale. Le contrôleur mémoire analyse les requêtes mises en attente et détecte si certaines se font à des adresses consécutives. Si c'est le cas, il fusionne ces requêtes en une lecture/écriture en rafale. Une telle optimisation est appelée la '''combinaison de lecture''' pour les lectures, et la '''combinaison d'écriture''' pour les écritures. Une autre optimisation possible est le réacheminement lecture sur écriture (''Store to load forwarding''). Il s'agit d'un équivalent de l’optimisation utilisée dans le cadre de la désambiguïsation mémoire. Quand une lecture demande à accéder à une donnée écrite récemment, il se peut que l'écriture ait été mise en attente dans le contrôleur mémoire. Dans ce cas, le contrôleur mémoire peut renvoyer la donnée directement depuis ses mémoires FIFOs, sans faire d'accès mémoire en lecture. Les deux optimisations précédentes ne paraissent pas payer de mine, mais elles deviennent plus compréhensible quand on sait que certains contrôleurs mémoire peuvent mettre en attente beaucoup de données. Par exemple, l'''Intel E8870 - Scalable Memory Controller'' peut mettre en attente 64 lignes de cache dans la mémoire FIFO, soit 8 kibioctets de RAM ! Deux pages mémoire si la mémoire virtuelle utilise des pages de 4 kibioctets ! ===Les lectures anticipées=== La mise en attente des accès mémoire est une optimisation intéressante, mais pas mirifique. Elle permet surtout de ne pas bloquer le processeur si le contrôleur mémoire a du travail sur la planche. Cependant, elle peut être optimisée quand on se rend compte d'une chose : le processeur ne voit que les lectures, pas les écritures. Pour les écritures, il les envoie au contrôleur mémoire et ce dernier fait le travail demandé, le processeur n'est pas prévenu quand une écriture se termine. Mais pour les lectures, c'est différent, car il reçoit la donnée lue. En conséquence, il est possible d'optimiser les écritures sans que le processeur ne voit quoique ce soit. Une optimisation possible est alors de changer l'ordre des accès mémoire, histoire de retarder les écritures ou au contraire de les faire en avance. La raison est que les mémoires SDRAM n'aiment pas quand on alterne lectures et écritures. Les délais mémoire, les fameux ''timings'' mémoire, sont clairs : faire une suite de lecture ou une suite d'écriture est plus performant que d'alterner entre lectures et écritures. L'idée est alors de changer l'ordre des écritures de manière à les faire en bloc, idem pour les lectures. L’optimisation est possible, mais à condition que le processeur n'y verra que du feu. Or, vous l'avez deviné, il y a des cas où ces réorganisations ne sont pas possibles, et vous avez sans doute trouvé que la situation était familière. L'optimisation en question est une forme d'exécution dans le désordre des accès mémoire, réalisée par le contrôleur mémoire lui-même ! Et qui dit exécution dans le désordre dit : problèmes liés aux dépendances de données. Changer l'ordre des écritures n'est pas toujours possible, notamment en raison des dépendances RAW, WAR et WAW. Pour que le processeur n'y voit que du feu, il faut respecter plusieurs critères. Premièrement, les lectures doivent être renvoyées au processeur dans l'ordre. Et quand je dis dans l'ordre, cela veut dire : dans l'ordre d'envoi des lectures au contrôleur mémoire. Les écritures aussi doivent se faire dans l'ordre, histoire qu'une série d'écriture donne le bon résultat final. Le troisième critère est qu'une lecture doit donner le résultat de la dernière écriture ''à la même adresse''. Pour le dire autrement, il faut juste détecter les dépendances RAW. Concrètement, si on a une série de lectures et d'écritures alternées, on peut regrouper les lectures et les écritures dans deux paquets séparés, à condition qu'aucune lecture ne lise une adresse écrite par une écriture. Mais si une lecture est dépendante d'une écriture, alors la lecture doit attendre que l'écriture se termine, idem pour les lectures suivantes. Une implémentation basique remplace la mémoire FIFO vue au-dessus, par deux mémoires FIFOs : une pour les lectures, une pour les écritures. Lorsqu'un accès mémoire arrive au séquenceur, il regarde si c'est une lecture ou une écriture et place l'accès mémoire dans la file adéquate. Le fait que ce soit des FIFOs garantit que les lectures se font dans l'ordre, idem pour les écritures. En sortie des deux FIFOs, le séquenceur mémoire détecte les dépendances RAW. Le contrôleur mémoire a tendance à privilégier les lectures, car celles-ci sont cruciales pour l'exécution dans le désordre. Plus elles se font tôt, plus vite le processeur recevra les données et pourra démarrer les instructions dépendantes de cette donnée. Le séquenceur a donc tendance à piocher en priorité dans la file de lecture, plutôt que dans la file d'écriture. A vrai dire, dans le cas idéal, les écritures ne sont faites que quand la file d'écriture est pleine ou quasi-pleine... Prenons l'exemple des coprocesseurs IO 81341 et 81342, qui étaient en réalité des ''chipsets'' intégrant un contrôleur SDRAM. Le ''chipset'' avait 5 ports : un pour les processeurs, un pour le pont sud (''southbridge'') et trois pour des canaux DMA. Pour le port processeur, il n'y avait pas de réordonnancement et ce port utilisait une seule mémoire FIFO. Les autres ports utilisaient l'optimisation qu'on vient de voir, et avaient vraisemblablement des files séparées pour les lectures et écritures. Le séquenceur mémoire vérifiait les dépendances mémoire de type RAW et autres. [[File:Double file d'attente pour les lectures et écritures.png|centre|vignette|upright=2.5|Double file d'attente pour les lectures et écritures]] ===Le ré-ordonnancement des commandes mémoires=== L'optimisation précédente est peu poussée, comparé aux formes d'exécution dans le désordre que les processeurs utilisent. Et on peut se demander si une exécution dans le désordre plus poussée est possible. La réponse est oui : un contrôleur mémoire peut faire des réorganisations bien plus poussées. Par contre, il faut faire une précision très importante : le contrôleur mémoire ne remet pas les accès mémoire dans l'ordre avant de les envoyer au processeur. C'est le processeur qui remet en ordre les accès mémoire, dans sa ''load queue''. En clair : le processeur voit les accès mémoire dans le désordre, c'est lui qui les remet en ordre. Pour cela, les requêtes mémoire sont "numérotées", à savoir qu'on leur attribue un identifiant binaire. Le contrôleur mémoire exécute les lectures/écritures dans le désordre, mais garde la trace de leur identifiant. Il sait dans quel ordre il exécute les accès mémoire et connait leur latence. Ce qui fait qu'il sait que la donnée lue à tel instant est associée à la requête mémoire numéro X. Pour les lectures, il renvoie la donnée lue avec l'identifiant. Le processeur sait alors à quelle lecture la donnée lue correspond et il se débrouille en interne pour gérer la situation. Maintenant que ces précisions sont faites, posons cette question : dans quelles situations est-il pertinent de faire des accès mémoire dans le désordre ? La réponse est : quand plusieurs accès à une même ligne ne sont pas consécutifs, qu'il y a des accès entre les deux. Après ré-ordonnancement, ces accès mémoire à une même ligne sont exécutés l'un à la suite de l'autre, ce qui est beaucoup plus rapide. Pour rendre le tout plus concret, voici un exemple. Imaginez que l'on sait les 3 accès mémoire suivants : * Une lecture ligne A ; * PRECHARGE + ACT ; * Une écriture ligne B ; * PRECHARGE + ACT ; * Une lecture ligne A. Sans réordonnancent, on doit émettre deux commandes PRECHARGE quand on passe d'une ligne à l'autre, et il faut ajouter les commandes ACT avec. pour éviter cela, le contrôleur mémoire peut retarder l'écriture, ou au contraire avancer la seconde lecture. le résultat est alors le suivant : * Lecture ligne A ; * Lecture ligne A * PRECHARGE + ACT ; * Une écriture ligne B. On a donc deux accès consécutifs à la même ligne, suivi par une écriture dans une autre ligne. La technique marche parce que l'on a un mix adéquat de lectures et d'écritures. Et encore une fois, il faut éviter d'intervertir lectures et écritures à une même adresse, sous peine de problèmes. Encore une fois, les dépendances RAW posent problème ! L'implémentation est assez simple : la ou les mémoires FIFOs précédentes sont remplacées par des mémoires similaires aux fenêtres d'instruction. Le contrôleur mémoire vérifie à chaque cycle les accès en attente, et vérifie à quelle ligne ils accèdent. Il priorise alors les accès qui tombent dans la ligne ouverte : ceux-là sont exécutés avant les autres. S'il n'y en a pas, il prend l'accès mémoire le plus ancien (ordre FIFO). Il teste aussi les dépendances mémoires RAW avant d'envoyer des commandes à la mémoire DDR/SDRAM. une telle solution est appelée l''''algorithme ''FR-FCFS''''' (''First Ready-First Come First Serve''). ===Les optimisations liées à la présence de plusieurs banques=== Les optimisations précédentes sont décuplées par la présence de plusieurs banques dans la mémoire SDRAM. Il est en effet possible de faire plusieurs accès mémoire en même temps, dans des banques différentes. Il est possible de lancer un accès mémoire dans deux banques en même temps, par exemple. La seule contrainte est que la SDRAM est limitée à 4 banques actives en même temps. {|class="wikitable" |+ Pipelining basique sur les SDRAM |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 |- ! Banque Numéro 1 | || || || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || || |- ! Banque Numéro 2 | || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || |- ! Banque Numéro 3 | || || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || |} Les optimisations précédentes peuvent s'appliquer par banque. Il est par exemple possible d'utiliser une mémoire FIFO par banque. Ainsi, les accès dans une même banque se feront en série, dans l'ordre d'arrivée, mais des accès à des banques différentes se feront dans le désordre. Cela ne pose pas de problème car les accès se font à des adresses différentes, ce qui fait qu'il n'y a pas de conflits majeurs. Il y a cependant un risque que les lectures se fassent dans le désordre si on n'y prend pas garde. [[File:Gestion parallèle des banques.png|centre|vignette|upright=2.5|Gestion parallèle des banques.]] il est aussi possible d'utiliser une file séparée pour les lectures et les écriture, pour chaque banque. Les performances sont alors améliorées comparé à deux files globales pour toute la SDRAM. En idem avec la réorganisation des accès mémoire, pour regrouper les accès à une même ligne. ==L'entrelacement sur les mémoires SDRAM== L''''entrelacement''' fonctionne sur les mémoires SDRAM, mais il doit être fortement modifié. Concrètement, tout ce qui a étré dit plus est inapplicable pour les SDRAM, du fait de la présence du tampon de ligne. Des adresses consécutives atterrissent déjà dans la même ligne, ce qui fait qu'on n'a pas besoin d'entrelacement au niveau d'une ligne. Et ne parlons pas de ce la présence de rangées et de canaux mémoire ! Cependant, il peut y avoir un entrelacement lié à la présence des banques SDRAM. L'entrelacement est géré juste avant le séquenceur mémoire. Le '''circuit d'entrelacement''' intervertit certains bits de l'adresse lors des accès mémoires. On trouve aussi des mémoires FIFOs pour mettre en attente les requêtes mémoire provenant du processeur. Elles permettent de recevoir plusieurs accès mémoire consécutifs. Si vous vous demandez comment plusieurs accès mémoire consécutifs peuvent avoir lieu, sachez que c'est une bonne question. Pour le moment, nous allons dire que ces accès proviennent du processeur ou de la mémoire cache, sans expliquer comment. Le contrôleur mémoire reçoit une série d'accès mémoire assez rapprochés, qu'il doit exécuter au mieux. [[File:Controleur mémoire d'une SDRAM avec entrelacement.png|centre|vignette|upright=2.5|Contrôleur mémoire d'une SDRAM avec entrelacement.]] Pour gérer l'entrelacement, le contrôleur de SDRAM prend en entrée l'adresse envoyée par le processeur, et la découpe en plusieurs champs : un pour sélectionner la banque adéquate, un autre pour la ligne, un autre pour la colonne, un autre pour la rangée. Une solution simple désactive l'entrelacement. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de banque || Adresse de ligne || Adresse de colonne |} Si cette solution fonctionne bien si les accès mémoire sont assez aléatoires, en pratique, mieux vaut utiliser l'entrelacement. ===L'entrelacement de banques/lignes=== Une première méthode d'entrelacement est l''''entrelacement de banques'''. Elle répartit deux lignes consécutives dans deux banques différentes. Pour comprendre l'idée, prenons un exemple. Imaginons une mémoire avec deux banques et 4 lignes. Imaginons qu'on parcoure/balaye la mémoire RAM en partant des adresses basses. Sans entrelacement de ligne, les accès se feront comme suit, de gauche à droite : {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Banque numéro 1 !! colspan="4" | Banque numéro 2 |- | Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 || Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 |} Avec l'entrelacement de banques, les accès se feront comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 |- | Ligne 1 || Ligne 1 || Ligne 2 || Ligne 2 || Ligne 3 || Ligne 3 || Ligne 4 || Ligne 4 |} Avec N banques, la première ligne ira dans la première banque, la seconde ligne dans la seconde, et ainsi de suite jusqu'à la énième banque qui va dans la banque numéro N. Et la ligne N+1 repart dans la première banque. L'avantage est que passer d'une ligne à la suivante est assez rapide. Au lieu de devoir fermer la première ligne et ouvrir la suivante on ouvrira directement la ligne suivante dans une banque différente, ce qui sera plus rapide. La commande PRECHARGE pourra être envoyée à la seconde banque en avance. Pour cela, l'adresse est découpée comme suit. {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne (précise à l'octet) |} ===L'entrelacement de rangées=== Il est aussi possible de faire la même chose, mais avec les rangées. Pour simplifier fortement, une rangée est simplement un chip mémoire. En réalité, les rangées ne sont pas des chip mémoire, mais un ensemble de chips mémoire regroupés ensemble histoire d'atteindre les 64 bits du bus de données. Par exemple, une rangée peut combiner 8 chips mémoire avec un bus de données de 8 bits chacun pour obtenir les 64 bits du bus de données d'une SDRAM. Mais nous passons ce détail sous silence dans les explications qui vont suivre, par souci de simplification. Pour faire comprendre l'entrelacement de rangée, le mieux est d'utiliser un exemple, le même que précédemment. Sans entrelacement de rangée, on change de chip mémoire une fois qu'on a balayé toutes ses adresses. {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Rangée numéro 1 !! colspan="4" | Rangée numéro 2 |- | Banque 1 || Banque 2 || Banque 3 || Banque 4 || Banque 1 || Banque 2 || Banque 3 || Banque 4 |} Avec l'entrelacement de ligne, on change de rangée dès qu'on change de banque/ligne. {|class="wikitable" |+ Adresse mémoire |- ! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 |- | Banque 1 || Banque 1 || Banque 2 || Banque 2 || Banque 3 || Banque 3 || Banque 4 || Banque 4 |} En clair, quand on a épuisé toutes les banques dans une même rangée, on passe à la rangée suivante au lieu de rester dans la même rangée. Notons que dans l'exemple précédent, on a combiné l'entrelacement de rangée et de banque, mais on aurait pu utiliser l'entrelacement de rangée seul. Mais ce n'est pas le cas le plus courant en pratique. Toujours est-il qu'en combinant entrelacement de rangée et de ligne, le découpage de l'adresse est le suivant : {|class="wikitable" |+ Adresse mémoire |- | Adresse de ligne || Numéro de rangée || Adresse de banque || Adresse de colonne (précise à l'octet) |} D'autres méthodes d'entrelacement sont possibles, mais elles sont plus complexes. Chaque contrôleur mémoire fait un peu à sa sauce. Ils permettent en général de choisir entre plusieurs entrelacements. Au minimum, ils permettent de désactiver l'entrelacement, d'activer un entrelacement de ligne, et d'autres entrelacements plus complexes, dont certains sont propriétaires et tenus secrets par le fabricant. ===L'entrelacement avec le ''dual channel''=== [[File:Dual channel slots.jpg|vignette|Slots mémoires avec ''dual channel''.]] L'usage du ''dual channel'' complique encore l'entrelacement. Là encore, il y a deux grandes solutions : avec et sans entrelacement des canaux mémoire. Rappelons le principe : deux barrettes de RAM sont accédées en parallèle. Et pour cela, il faut utiliser l'entrelacement. Typiquement, chaque barrette mémoire fournit 64 bits, ce qui fait que l'on peut accéder à 128 bits d'un seul coup, par exemple avec un accès en rafale. Sans ''dual channel'', la première barrette correspond à la moitié haute de la RAM, la seconde barrette correspond à la moitié basse. Avec ''dual channel'', une forme spécifique d'entrelacement est activée. Concrétement, deux blocs de 64 bits sont placés dans des canaux mémoire séparés. Idem avec du triple ou quadruple canal, mais c'est alors trois ou quatre blocs de 64 bits qui sont dispersés dans des canaux consécutifs. Le découpage de l'adresse est alors le suivant : {|class="wikitable" |+ Adresse mémoire |- ! colspan="6" | Sans entrelacement |- | Numéro de canal/barrette || Reste de l'adresse || Position de l'octet dans un bloc de 64 bits |- | colspan="6" | |- ! colspan="6" | Avec entrelacement |- | Reste de l'adresse || Numéro de canal/barrette || Position de l'octet dans un bloc de 64 bits |} [[File:Décodage d'adresse avec dual channel.png|centre|vignette|upright=2|Décodage d'adresse avec dual channel.]] Les contrôleurs de SDRAM précédents sont assez basiques et ne représentent pas les contrôleurs les plus évolués. Ils étaient utilisés sur les anciens PC, à une époque où ils étaient encore sur la carte mère du processeur. Mais de nos jours, le contrôleur mémoire est intégré au processeur. Et il incorpore de nombreuses optimisations afin de gagner en performances. Malheureusement, ces optimisations sont elles-mêmes dépendantes des optimisations intégrées dans le processeur, ce qui fait qu'on ne peut pas en parler ici. Elles demandent que le contrôleur mémoire reçoive plusieurs accès mémoire simultanés, ce qui n'a aucun sens à ce stade du cours. Aussi, nous allons les passer sous silence pour le moment. Nous les verrons dans le chapitre sur le parallélisme mémoire. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=La désambiguïsation mémoire | prevText=La désambiguïsation mémoire | next=Les processeurs superscalaires | nextText=Les processeurs superscalaires }} </noinclude> p5u08je645jv9zduwovx1a2jyku9j5q 764747 764746 2026-04-23T23:11:00Z Mewtow 31375 /* Les contrôleurs SDRAM/DDR avec "exécution dans le désordre" */ 764747 wikitext text/x-wiki Dans ce chapitre, nous allons voir les techniques qui permettent de gérer plusieurs accès mémoire simultanés directement au niveau du cache ou de la mémoire RAM. Par plusieurs accès mémoire simultanés, vous pensez sans doute à l'usage de cache multiports, voire de mémoires RAM multiport. C'est en effet une solution, mais ce n'est clairement pas la seule. Par exemple, il est possible de pipeliner l'accès au cache, voire à la mémoire RAM. Il existe de nombreuses techniques de '''parallélisme mémoire''', et nous allons les voir dans ce chapitre. Pour donner un exemple, il est possible de pipeliner les accès mémoire. Il est possible de pipeliner l'accès au cache et/ou l'accès à la mémoire, et nous verrons comment faire dans ce chapitre. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, qu'ils aient ou non un pipeline dynamique. Par contre, pipeliner la mémoire est une technique ancienne, aujourd'hui peu utilisée. Elle était utilisée sur des très vieux ordinateurs, qui avaient un pipeline mais pas de mémoire cache. Mais de nos jours, les mémoires SDRAM et DDR ne sont pas adaptées pour ça, il est très difficile de les pipeliner correctement. Le parallélisme mémoire est utile aussi bien sur des processeurs à émission dans l'ordre que dans le désordre. Par exemple, un processeur ''in-order'' avec des lectures non-bloquantes peut e, profiter. Il peut techniquement lancer une seconde lecture, après avoir rencontré une première lecture non-bloquante. En général, le nombre de lectures consécutives est cependant limité à moins d'une dizaine. À l'opposé, les processeurs sans lectures non-bloquantes profitent pas du parallélisme mémoire. À la rigueur, ils peuvent en profiter s'ils intègrent des techniques de préchargement. Pendant que le processeur exécute une lecture/écriture, il peut précharger une autre donnée en parallèle. Inutile de dire que sans pipeline processeur, le parallélisme mémoire ne sert pas à grand-chose. ==Le ''line fill buffer'' et les techniques associées== Lors d'un défaut de cache, le processeur doit attendre que toute la ligne de cache soit chargée avant d'être utilisable. Or, la taille d'une ligne de cache est supérieure à la largeur du bus mémoire, ce qui fait qu'une ligne de cache est chargée en plusieurs fois, morceaux par morceaux, mot mémoire par mot mémoire. Le chargement peut se faire directement dans le cache, mais ce n'est pas une solution très pratique. À la place, beaucoup de processeurs ajoutent une mémoire tampon entre la RAM et le cache, appelée le '''tampon de remplissage de ligne''' (''line-fill buffer'') dans les processeurs modernes. Lors d'un défaut de cache, le processeur charge la donnée de la RAM dans le tampon de remplissage de ligne, mot mémoire par mot mémoire. Une fois plein, le tampon de remplissage de ligne est recopié dans la ligne de cache. Le tampon de remplissage de ligne contient une ligne de cache, si ce n'est que certains bits de contrôle ne sont pas présents. Le tampon de remplissage de ligne contient un bit de validité pour chaque mot mémoire de la ligne de cache, qui indiquent si le mot mémoire a été chargé. Par exemple, prenons un processeur 64 bits, qui gère donc des mots mémoire de 8 octets, avec des lignes de cache 256 octets/32 mots mémoire. Le tampon de remplissage de ligne contiendra 32 bits de validité. Si le processeur a chargé les 6 premiers mots mémoire, les 6 premiers bits de validité seront mis à 1, les autres seront encore à zéro. Il faut noter que la disponibilité de la ligne complète se détermine assez facilement en faisant un ET logique entre tous les bits de validité. Le processeur sait ainsi quand la ligne de cache est disponible entièrement et donc quand la transférer dans le cache. La même chose existe avec une hiérarchie de cache, sauf que l'on trouve une mémoire tampon entre chaque niveau de cache. Il y a un tampon de remplissage de ligne entre le cache L1 et le cache L2, entre le cache L2 et le L3, etc. Si le cache ne gère qu'un seul défaut de cache à la fois, le ''fill line buffer'' est une mémoire très simple, qui ne mémorise qu'une seule ligne de cache. Et cela vaut aussi bien pour un cache bloquant que non-bloquant. Mais sur les caches capables de gérer plusieurs défauts simultanés, le tampon de remplissage de ligne est une mémoire de type FIFO ou LIFO, capable de mémoriser plusieurs lignes de cache. Notons que le tampon de remplissage de ligne est très utile pour implémenter certaines techniques, par exemple le contournement du cache. Nous avions vu dans le chapitre sur le cache que certains accès mémoire doivent contourner le cache, pour des raisons de cohérence des caches. C'est notamment nécessaire pour accéder aux périphériques, mais c'est aussi utile pour des raisons de performances dans des cas très spécifiques. Les accès qui contournent le cache se font directement dans le tampon de remplissage de ligne : le processeur écrit ou lit les données depuis ce tampon de remplissage de ligne, sans accéder au cache. ===L'''early restart'' et le ''critical word load''=== La présence du ''line buffer'' permet une optimisation assez intéressante, qui permet de réduire la latence des défauts de cache. L'optimisation consiste à lire un mot mémoire dans le tampon de remplissage de ligne, même si la ligne de cache complète n'a pas encore été chargée. Il existe deux manières de faire cela, qui portent les noms d'''early restart'' et de ''critical word load''. La première est la version la plus simple, la seconde est plus complexe mais plus performante. Avec la technique d''''''early restart''''', la ligne de cache est chargée normalement, en partant de son premier mot mémoire. Dès que le mot mémoire lu/écrit par le processeur est copié dans le ''line fill buffer'', il est envoyé au processeur immédiatement. Illustrons le tout par un exemple, où une ligne de cache fait 16 mots mémoire. Le processeur effectue une lecture, qui lit le 5ème mot mémoire. Sans ''early restart'', le processeur doit charger les 16 mots mémoire avant de faire la lecture dans le cache. Avec ''early restart'', le processeur reçoit la donnée dès que le 5ème mot mémoire est disponible. Le processeur doit attendre que les 4 premiers mots mémoire soient chargés, puis le 5ème mot mémoire arrive et est envoyé directement au processeur, il est lu directement depuis le ''line fill buffer''. Les 11 mots mémoire suivants sont ensuite chargés dans le cache pendant que le processeur fait des calculs dans son coin. Le ''critical word load'' est une optimisation de la technique précédente où le chargement de la ligne de cache commence directement à la donnée demandée par le processeur. Pour reprendre l'exemple précédent, où le processeur demande le 5ème mot mémoire sur 16, le ''critical word load'' charge le 5ème mot mémoire en premier et l'envoie au processeur, ce qui fait qu'il est chargé très rapidement. Pas besoin d'attendre que le processeur charge les 4 mots mémoire précédents comme avec l'''early restart''. Dans le détail, le processeur charge le 5ème mot mémoire en premier, puis charge les 11 suivants, et termine par les 4 mots mémoire du début. En clair, le ''critical word load'' commence par charger le mot mémoire lu, puis les blocs suivants, avant de revenir au début du bloc pour charger les blocs restants. Ainsi, la donnée demandée par le processeur sera la première disponible. Pour cela, l'organisation du tampon de remplissage de ligne est modifiée de manière à rendre cela possible. Il a une taille égale à une ligne de cache complète, qui contient elle-même plusieurs mots mémoire. Dans le ''line-fill buffer'', chaque mot mémoire est stocké avec un tag, qui indique l'adresse du mot mémoire stocké dans le ''line-fill buffer''. Le ''line-fill buffer'' est donc un cache un peu particulier, qui fonctionne comme un cache du point de vue du processeur, comme une mémoire FIFO pour les transferts avec le cache. Ainsi, un processeur qui veut lire dans le cache après un défaut peut accéder à la donnée directement depuis le tampon de remplissage de ligne, alors que la ligne de cache n'a pas encore été totalement recopiée en mémoire. ==Les caches pipelinés== Il est possible de pipeliner l'accès au cache, ce qui demande juste de rajouter des registres au bon endroits et quelques circuits de contrôle. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, même ceux avec un pipeline dynamique. Et l'implémentation peut se faire de plusieurs manières différentes. Il faut dire que découper une mémoire cache en plusieurs étapes peut se faire de plusieurs manière. Commençons par la plus simple. Beaucoup de processeurs des années 2000 avaient une fréquence peu élevée comparée aux standards d'aujourd'hui, ce qui fait que leur cache avait bien un temps d'accès d'un cycle d'horloge. Mais l'accès au cache se faisait en deux cycles d'horloge : un cycle pour calculer une adresse et un cycle pour l'accès mémoire proprement dit. Et ces deux étapes étaient pipelinées, à savoir que deux micro-opérations mémoire peuvent s'exécuter en même temps : une dans l'étage de calcul d'adresse, une autre dans le cache. L'unité mémoire était donc pipelinée, alors que l'accès au cache ne l'était pas. Les processeurs qui implémentaient cette technique regroupent les micro-architectures K5 et K6 d'AMD, les processeurs Intel de micro-architecture P6 (Pentium 2 et 3) et quelques autres. Il est aussi possible de pipeliner l'accès au cache lui-même. Avec un cache pipeliné, l'accès au cache ne se fait pas en un seul cycle, mais en plusieurs. Cependant, on peut lancer un nouvel accès au cache à chaque cycle d'horloge, comme un processeur pipeliné lance une nouvelle instruction à chaque cycle. L'implémentation est assez simple : il suffit d'ajouter des registres dans le cache. Pour cela, on profite que le cache est composé de plusieurs composants séparés, qui échangent des données dans un ordre bien précis, d'un composant à un autre. De plus, le trajet des informations dans un cache est linéaire, ce qui les rend parfois pour l'usage d'un pipeline. ===Le ''pipelining'' des caches ''direct-mapped''=== Voici ce qui se passe avec un cache directement adressé. Pour rappel ce genre de cache est conçu en combinant une mémoire RAM, généralement une SRAM, avec quelques circuits de comparaison et un MUX. L'accès au cache se fait globalement en deux étapes : la première lit la donnée et le ''tag'' dans la SRAM, et on utilise les deux dans une seconde étape. Nous avions vu il y a quelques chapitre comment pipeliner des mémoires, dans le chapitre sur les mémoires évoluées. Et bien ces méthodes peuvent s'utiliser pour la mémoire RAM interne au cache !L'idée est d'insérer un registre entre la sortie de la RAM et la suite du cache, pour en faire un cache pipéliné, à deux étages. Le premier étage lit dans la SRAM, le second fait le reste. L'implémentation sur les caches associatifs à plusieurs voies est globalement la même à quelques détails près. [[File:Cache directement adressé pipeliné - deux étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - deux étages]] Avec le circuit précédent, il est possible d'aller plus loin, cette fois en pipelinant l'accès à la mémoire RAM interne au cache. Pour comprendre comment, rappelons qu'une mémoire SRAM est composée d'un plan mémoire et d'un décodeur. L'accès à la mémoire demande d'abord que le décodeur fasse son travail pour sélectionner la case mémoire adéquate, puis ensuite la lecture ou écriture a lieu dans cette case mémoire. L'accès se fait donc en deux étapes successives séparées, on a juste à mettre un registre entre les deux. Il suffit donc de mettre un gros registre entre le décodeur et le plan mémoire. [[File:Cache directement adressé pipeliné - trois étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - trois étages]] Et on peut aller encore plus loin en découpant le décodeur en deux circuits séparés. En effet, rappelez-vous le chapitre sur les circuits de sélection : nous avions vu qu'il est possible de créer des décodeurs en assemblant des décodeurs plus petits, contrôlés par un circuit de prédécodage. Et bien on peut encore une ajouter un registre entre ce circuit de prédécodage et les petits décodeurs. : Théoriquement, toute l'adresse est fournie d'un seul coup au cache, la quasi-totalité des processeurs présentent l'adresse complète à un cache pipeliné. Mais le Pentium 4 fait autrement. Il faut noter que les premiers étages manipulent l'indice dans la SRAM, qui est dans les bits de poids faible, alors que les étages ultérieurs manipulent le ''tag'' qui est dans les bits de poids fort. Les concepteurs du Pentium 4 ont alors décidé de présenter les bits de poids faible lors du premier cycle d'accès au cache, puis ceux de poids fort au second cycle. Pour cela, l'ALU fonctionnait à une fréquence double de celle du processeur, tout comme le cache L1. Il n'y avait pas de pipeline proprement dit, mais cela réduisait grandement la latence d'accès au cache. ===Le ''pipelining'' des caches splittés/sériels=== Il est aussi possible de pipeliner un cache dits splittés, aussi appelés à accès sériel. Pour rappel, les caches à accès sériel vérifient si il y a succès ou défaut de cache, avant d'accéder aux lignes de cache en cas de succès. Ils font donc différemment des autres caches, qui accèdent à une ligne de cache, avant de déterminer s'il y a succès ou défaut en lisant le tag de la ligne de cache. Les caches sériels disposent de deux SRAM : une pour les tags des lignes de cache et une pour les données. Ils accèdent à la SRAM pour les tags, avant d’accéder à la SRAM des données en cas de succès de cache. Vu que l'accès se fait en deux étapes, une vérification des tags suivie de la lecture/écriture des données, il est facile à pipeliner. Pipeliner le cache permet de régler le problème des accès au cache L1, et elle est tout le temps utilisé sur les processeurs modernes. Mais que faire en cas de défaut de cache ? ==Les caches non bloquants== Un '''cache bloquant''' est un cache auquel le processeur ne peut pas accéder pendant un défaut de cache. Il faut attendre que la lecture ou écriture en RAM soit terminée avant de pouvoir utiliser de nouveau le cache. Un '''cache non bloquant''' n'a pas ce problème : on peut l'utiliser pendant un défaut de cache. Les caches non bloquants permettent de démarrer une nouvelle lecture ou écriture alors qu'une autre est en cours, ce qui permet d'exécuter plusieurs lectures ou écritures en même temps. ===Les ''Miss Handling Status Registers''=== Lors d'un défaut de cache, la mémoire RAM est consultée pendant le défaut de cache, mais le cache est inutilisé. Un défaut de cache n'utilise pas le cache, ce n'est qu'un accès en mémoire RAM, sur le bus mémoire (ou un accès aux niveaux de cache inférieurs, peu importe). Le cache en lui-même est laissé libre, rien n’empêche d'y accéder, il est en réalité intrinsèquement non-bloquant. Les caches, bloquants comme non-bloquants, sont en réalité composés d'une mémoire cache proprement dite, entourée de circuits qui servent d'interface entre le processeur et le cache lui-même. Et parmi les circuits tout autour, certains gèrent l'accès au cache lors d'un défaut de cache. Ils sont regroupés sous le terme de '''''Miss Handling Architecture''''' (MHA). La différence entre un cache bloquant et un cache non-bloquant est en réalité liée à l'implémentation de la MHA. Les caches bloquants coupent volontairement l'accès au cache lors d'un défaut, car les défauts sont plus simples à gérer ainsi. Le défaut de cache rapatrie une donnée depuis la RAM, qui sera écrite dans le cache. Et il ne faut pas qu'une tentative d'accès à cette donnée ait lieu avant qu'elle ne soit chargée. Mais ce blocage est général et touche tout le cache, alors que seule une ligne de cache est concernée par le défaut de cache. L'idée derrière un cache non-bloquant est que seule la ligne de cache est bloquée, mais les autres sont accessibles. L'idée est alors de mémoriser les lignes de cache concernées par le défaut de cache, afin d'en bloquer l'accès. À chaque accès, on vérifie si la ligne de cache est déjà réservée par un défaut de cache. La lecture/écriture est alors bloquée si c'est le cas, mais elle accepte les accès sinon. Pour cela, la ''Miss Handling Architecture'' contient des registres qui mémorisent des informations sur les défauts de cache en cours. Ils portent le nom de '''''miss status handling registers''''', que j’appellerais dorénavant MSHR, qui sont aussi appelés des '''''miss buffer'''''. Le contenu des MSHR varie beaucoup suivant le processeur, mais ces derniers stockent au minimum les informations suivantes : * Le numéro de la ligne de cache dans laquelle les données sont chargées. * Un bit de validité qui indique si le MSHR est vide ou pas, qui est mis à 0 quand le défaut de cache est résolu. * Un ou plusieurs champs de lecture/écriture, qui contiennent des informations sur la lecture/écriture. ** Pour une lecture, elle contient des informations sur la destination de la donnée, à savoir qui prévenir quand le défaut de cache est terminé. C'est parfois un nom/numéro de registre (celui dans lequel charger la donnée), mais c'est souvent le numéro de l'entrée dans le ''load/store queue''. ** Pour les écritures, elle contient la donnée à écrire, ou éventuellement un numéro de ''load/store queue'' où se trouve la donnée à écrire. Il faut noter que le bus mémoire ne peut gérer qu'un seul défaut de cache à la fois. Aussi, il est intéressant de regarder ce qui se passe lorsqu'un second défaut de cache survient, pendant qu'un premier est en cours. Dans ce cas, il y a deux réponses qui correspondent à deux types de caches non-bloquants, qui portent les noms barbares de caches de type succès après défaut et défaut après défaut. Sur le premier type, il ne peut pas y avoir plusieurs défauts de cache simultanés. Dès qu'un second défaut de cache survient, le cache stoppe son activité et on ne peut plus démarrer de nouvelle lecture/écriture, tant que le premier défaut de cache n'est pas résolu. Le second type est plus souple et autorise la survenue de plusieurs défauts de cache simultanés. Du moins, jusqu'à une certaine limite, car le cache ne peut supporter qu'un nombre limité d'accès mémoires simultanés (pipelinés). ===Les accès simultanés à une même ligne de cache=== Il arrive que le processeur fasse plusieurs accès mémoire simultanés à la même ligne de cache. Si la ligne de cache en question n'a pas encore été chargée dans le cache, alors on a plusieurs défauts de cache consécutifs pour la même ligne de cache, et le cache non-bloquant doit gérer la situation. Pour la suite, il va falloir faire une petite distinction entre les défauts primaires et secondaires. Imaginons qu'un défaut de cache ait lieu et demande à charger une donnée dans la ligne de cache numéro N. Il s'agit du premier défaut impliquant cette ligne de cache précise, ce qui lui vaut le nom de '''défaut de cache primaire'''. Mais par la suite, d'autres accès mémoire à la même ligne de cache ont lieu, alors que la ligne de cache n'est pas encore disponible. Dans ce cas, il s'agit de '''défauts de cache secondaires'''. Pour l'unité d'accès mémoire, les défauts de cache primaire et secondaire sont différents (ils prennent tous une entrée dans la ''load/store queue''). Mais pour le cache, ils ne correspondent qu'à un seul accès au cache : celui qui demande de charger la ligne de cache demandée. Les défauts de cache primaire et secondaire à la même ligne de cache se voient attribuer un MSHR unique. La gestion des défauts de cache secondaires dépend du cache non-bloquant. La solution la plus simple ne permet pas les défauts de cache secondaires. Le cache ne permet pas deux défauts de cache simultanés pour la même ligne de cache. Les autres solutions le permettent, en fusionnant des accès simultanés à la même ligne de cache en un seul au niveau des MSHR. Dans tous les cas, détecter les défauts de cache secondaires sont un problème qu'il faut détecter. La ''Miss Handling Architecture'' doit détecter les défauts de cache secondaire. Pour cela, elle procède comme suit. Lors de chaque défaut de cache, la MHA récupère le numéro de la ligne de cache associé. Il vérifie alors chaque MSHR pour vérifier s'il contient le numéro en question. S'il n'y a aucune correspondance dans les MSHR, alors c'est signe que le défaut de cache est un défaut primaire. Mais s'il y en a une, alors c'est un défaut secondaire. Évidemment, cela signifie que lors d'un défaut de cache, le numéro de ligne de cache est envoyé à tous les MSHR, pour comparaison. Les MSHR sont donc regroupés dans une mémoire associative, une sorte de mini-cache, faciliter l'implémentation. Lors d'un défaut de cache primaire, l'accès à un cache non-bloquant se fait comme suit : le processeur envoie une adresse au cache, accède à celui-ci, et détecte la survenue d'un défaut de cache. Il en profite alors pour attribuer une ligne de cache dans laquelle sera chargée ce défaut. L'attribution est très simple dans le cas des caches ''direct mapped'', ou associatifs par voie, pour lesquels l'attribution se fait assez simplement. Il mémorise alors cette information dans les MSHR, après avoir vérifié que le défaut de cache n'était pas un défaut secondaire. Lors des accès ultérieurs à une adresse proche, censée être dans la même ligne de cache, le processeur va encore une fois rencontrer un défaut de cache. Il va alors déterminer le numéro de la ligne de cache associée à l'adresse, et comparer ce numéro avec les MSHRs. Si un MSHR contient ce numéro, c'est signe que le défaut de cache est un défaut secondaire. La MHA réagit alors différemment selon le processeur considéré. Une première solution n'autorise pas les défauts de cache secondaires. Si l'un d'entre eux survient, le processeur est gelé par un ''pipeline stall'', une bulle de pipeline. Une autre solution fusionne les défauts de cache secondaires avec le défaut de cache primaire : tout cela ne correspond qu'à un seul défaut de cache pour lui. ===Les MSHR simples=== Un cache non-bloquant à '''MSHR simple''' contient juste plusieurs MSHR qui mémorisent juste un numéro de ligne de cache, un bit de validité, le champ de lecture/écriture, et l'adresse exacte de lecture/écriture. Avec cette organisation, il est possible d'avoir plusieurs défauts de cache séparés, mais à la condition que chaque défaut accède à une ligne de cache différente. Deux accès simultanés à une même ligne de cache ne sont pas possibles, les défauts de cache secondaires ne sont pas autorisés. Ainsi, chaque défaut de cache se voit attribuer son propre MSHR, chacun contient un numéro de ligne de cache différent. Pour comprendre pourquoi c'est impossible de gérer les défauts secondaires, il faut regarder le champ de lecture/écriture. Si on veut effectuer plusieurs écritures consécutives à la même adresse, le MSHR n'aura pas de quoi mémoriser les deux données à écrire. Il pourra mémoriser la première donnée à écrire, pas la seconde. Même chose lors d'une lecture : le champ lecture/écriture peut mémorisr la destination de la première lecture, pas de la seconde. L'avantage est que la MHA n'a besoin que des MSHR et de quelques circuits annexes. Les autres solutions rajoutent des circuits annexes pour gérer les défauts de cache secondaires, qui utilisent beaucoup de circuits. Le cout en circuit est donc élevé, mais le gain en performance est là. Passons maintenant aux caches non-bloquants qui autorisent les défauts de cache secondaire. La solution la plus simple consiste à utiliser ===Les MSHR adressés implicitement=== Avec les '''MSHR adressés implicitement''', il est possible de fusionner plusieurs accès mémoire à une même ligne de cache, mais sous une condition très importante : ces accès lisent/écrivent des mots mémoire différents. Par exemple, imaginons qu'une ligne de cache contienne 8 mots mémoire de 64 bits. Si un premier accès mémoire lit le mot mémoire numéro 7 (dernier mot mémoire de la ligne), et le second accès le mot mémoire numéro 3, alors la fusion est possible. Mais si deux accès mémoire veulent lire/écrire le mot mémoire numéro 7, alors la fusion n'est pas possible et le processeur se bloque, un ''pipeline stall'' survient. Un MSHR adressé implicitement est un MSHR simple, qui contient naturellement un numéro de ligne de cache (tag) et un bit de validité, sauf que le champ de lecture/écriture est dupliqué. Les différents champs lecture/écriture d'un MSHR sont regroupés dans une mémoire RAM/cache qui contient autant d'entrées qu'il y a de mot mémoire dans une ligne de cache. Chaque entrée de la table est associée à un mot mémoire de la ligne de cache et stocke des informations sur celui-ci. Une entrée mémorise, au minimum : * Un champ de lecture/écriture qui contient soit la destination de la lecture, soit la donnée à écrire. * Un bit R/W permettant d'interpréter correctement le champ de lecture/écriture. * Un bit de validité pour chaque entrée de la table, qui dit si un défaut de cache antérieur accède déjà à ce mot mémoire. La fusion de deux défauts de cache est ainsi assez simple. Un défaut de cache primaire/secondaire configure l'entrée associée au mot mémoire lu/écrit. Si un défaut secondaire ultérieur lit/écrit un mot mémoire différent (dont le bit de validité de l'entrée dans la table est à zéro), pas de conflit : il configure une autre entrée, vide. Mais s'il lit/écrit un mot mémoire pour lequel l'entrée de la table est occupée, il y a conflit, le processeur est bloqué par un ''pipeline stall'', une bulle de pipeline. Avec cette organisation, le nombre de MSHR indique combien de lignes de cache peuvent être lues en même temps depuis la mémoire. Quant au nombre d'entrées par MSHR, il détermine combien d'accès mémoires qui ne se recouvrent pas peuvent avoir lieu en même temps. ===Les MSHR adressés explicitement=== Les '''MSHR adressés explicitement''' n'ont pas les contraintes des MSHR adressés implicitement. Avec eux, il est possible d'avoir plusieurs défauts de cache pointant vers la même ligne de cache, mais aussi vers le même mot mémoire. De tels défauts de cache apparaissent sur les processeurs à exécution dans le désordre, mais sont très rares, voire inexistants, sur les processeurs ''in-order''. L'idée est encore que chaque MSHR est associée à une table mémoire qui mémorise des entrées. Cette table n'est autre qu'une mémoire FIFO. Sauf que cette fois-ci, une entrée n'est pas associée à un mot mémoire. Une entrée est associée à un défaut de cache. Une entrée mémorise là encore la destination de la lecture ou la donnée de l'écriture suivi du bit R/W, ainsi que d'un bit de validité par entrée, mais aussi : la position du mot mémoire lu dans la ligne de cache. C'est cette dernière information qui n'était pas présente dans les MSHR adressés implicitement. De plus, le nombre d'entrée par MSHR n'est pas égal au nombre de mots mémoires dans une ligne de cache, mais peut être arbitrairement grand. L'avantage d'un tel cache est qu'il est capable de traité les défauts secondaires concernant un même mot mémoire, et ce, car les accès à la ligne de cache concernée se produisent dans l'ordre de génération des défauts (la table mémoire est une FIFO). ===Les MSHRs inversés=== Généralement, plus on veut supporter de défauts de cache, plus le nombre de MSHR et d'entrées augmente. Mais au-delà d'un certain nombre d'entrées et de MSHR, les MSHR adressés implicitement et explicitement ont tendance à bouffer un peu trop de circuits. Utiliser une organisation un peu moins gourmande en circuits est donc une nécessité. Cette organisation plus économe se base sur des MSHR inversés. Les MSHR inversés ne contiennent qu'une seule entrée, en quelque sorte : au lieu d’utiliser n MSHR de m entrées chacun, on va utiliser n × m MSHR inversés. La différence, c'est que plusieurs MSHR peuvent contenir un tag identique, contrairement aux MSHR adressés implicitement et explicitement. Lorsqu'un défaut de cache a lieu, chaque MSHR est vérifié. Si jamais aucun MSHR ne contient de tag identique à celui utilisé par le défaut, un MSHR vide est choisi pour stocker ce défaut, et une requête de lecture en mémoire est lancée. Dans le cas contraire, un MSHR est réservé au défaut de cache, mais la requête n'est pas lancée. Quand la donnée est disponible, les MSHR correspondant à la ligne qui vient d'être chargée vont être utilisés un par un pour résoudre les défauts de cache en attente. ===Les MSHR intégrés au cache=== Certains chercheurs ont remarqué que pendant qu'une ligne de cache est en train de subir un défaut de cache, celle-ci reste inutilisée, et son contenu est destiné à être perdu une fois le défaut de cache résolu. Ils se sont dit que, plutôt que d'utiliser des MSHR séparés, il vaudrait mieux utiliser la ligne de cache pour stocker les informations sur les défauts de cache en attente dans cette ligne de cache. Pour éviter tout problème, il faut rajouter un bit dans les bits de contrôle de la ligne de cache, qui sert à indiquer que la ligne de cache est occupée : un défaut de cache a eu lieu dans cette ligne, et elle stocke donc des informations pour résoudre les défauts de cache. ==L'entrelacement mémoire== Nous venons de voir comment une mémoire cache peut gérer plusieurs accès mémoire simultanés. Il se trouve que ce genre d'optimisation dépasse largement le cadre du cache. Les mémoires RAM ne sont pas en reste, elles aussi ! Il est possible de pipeliner l'accès à une mémoire RAM, en utilisant des techniques dites d''''entrelacement'''. Précisons cependant que cette optimisation n'est pas utilisée sur les mémoires SDRAM ou DDR modernes, du moins pas sans modifications majeures. Mais divers ordinateurs assez anciens, dont des superordinateurs, ont utilisé cette technique. La technique demande d'utiliser plusieurs mémoires séparées. Sur les anciens ordinateurs, les mémoires en question sont des chips mémoire, à savoir des circuits intégrés de DRAM. Pour faire plus général, nous allons utiliser le terme de '''banques''', ou encore de bancs mémoire. La différence est qu'il existe des mémoires multi-banque, qui regroupent plusieurs banques indépendantes dans un seul boitier, dans un seul chip mémoire. En clair, de telles mémoires regroupent plusieurs sous-mémoires dans un seul circuit intégré. Il est aussi possible d'utiliser plusieurs chips mémoire séparés, chacun ayant une banque. Reste à combiner ces banques pour former un pseudo-pipeline. ===Utiliser plusieurs banques sans entrelacement=== Sans optimisation particulière, les adresses sont réparties dans les banques comme indiqué ci-dessous. Il s'agit de ce qui s'appelle un arrangement horizontal, et nous avions vu celui-ci dans le chapiutre sur les mémoires SDRAM. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Avec cet arrangement, les bits de poids fort de l'adresse sont utilisées pour sélectionner la banque adéquate, et le reste de l'adresse est envoyé sur le bus d'adresse. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Adresse de banque !! Adresse dans la banque |- | Quelques bits de poids fort || Reste de l'adresse |} L'entrelacement change cette répartition, afin d'accélérer les accès mémoire. L'entrelacement classique vise à améliorer les accès à des adresses mémoires consécutives, mais des formes plus évoluées d'entrelacement visent à optimiser des accès mémoire arbitraires. Voyons-les dans l'ordre, du plus simple au plus complexe. ===L'entrelacement classique=== Avec l''''entrelacement classique''', des adresses consécutives sont réparties dans des banques consécutives. Précisons que cette attribution des adresses n'implique absolument pas la mémoire virtuelle ou n'importe quel mécanisme dans le processeur. La répartition décide que telle adresse mémoire va dans telle banque, à telle adresse dans la banque. Elle est donc le fait du contrôleur mémoire, donc en dehors du processeur (les contrôleurs mémoires n’étaient pas encore intégrés dans le processeur à l'époque). [[File:Répartition des adresses dans une mémoire interleaved.png|centre|vignette|upright=1.5|Répartition des adresses dans une mémoire interleaved.]] L'entrelacement simple permet d'accélérer les accès mémoire qui se font à des adresses consécutives. Avec l'entrelacement, chaque accès mémoire tombe dans une banque mémoire différente, ce qui fait qu'on peut démarrer un nouvel accès mémoire à chaque cycle d'horloge. Pendant qu'une banque est occupée par un accès mémoire, on démarre le suivant dans une autre banque, et ainsi de suite. Le tout se termine soit quand on a épuisé toutes les banques libres, soit quand l'accès en rafales se termine. Pas besoin d'attendre que la mémoire ait fini sa lecture/écriture avant de démarrer la lecture/écriture suivante. [[File:Pipemining mémoire.png|centre|vignette|upright=2.5|Pipelining mémoire]] L'animation suivante illustre bien le processus, avec quatres banques. [[File:Interleaving.gif|centre|vignette|upright=2.5|Pipelining mémoire]] L'entrelacement simple utilise les bits de poids faible pour sélectionner la banque, et les bits de poids fort pour la case mémoire. [[File:Adresse mémoire d'une mémoire entrelacée.png|centre|vignette|upright=2|Adresse mémoire d'une mémoire entrelacée]] Précisons que le temps d'accès mémoire ne change pas beaucoup avec l'entrelacement. Par contre, on peut faire plus d'accès mémoire simultanés. On peut démarrer un accès mémoire par cycle d'horloge, mais l'accès en lui-même prend plusieurs cycles. Les mémoires à entrelacement ont donc un débit supérieur aux mémoires qui ne l'utilisent pas. L'entrelacement classique pourrait être utilisé pour accélérer les transferts entre mémoire RAM et cache, qui se font en blocs d'adresses consécutives. Mais de nos jours, il n'est pas utilisé dans cette optique. Il faut dire qu'il est redondant avec le mode rafale des mémoires DRAM. Les deux font globalement la même chose et on voit mal comment utiliser les deux en même temps. Le mode rafale étant plus simple à implémenter, et plus léger niveau utilisation du bus de commande mémoire, il est préféré à l'entrelacement. Par contre, l'entrelacement a eu son heure de gloire sur d'anciens processeurs qui n'avaient pas de mémoire cache, alors qu'ils disposaient d'un pipeline. L'exemple typique est celui des nombreux ''processeurs vectoriels'', que nous n'avons pas encore abordé à ce stade du cours. De tels processeurs avaient un pipeline, une unité de calcul fortement pipelinée, et parfois même de l'exécution dans le désordre et du renommage de registres ! ===L'entrelacement par décalage=== L'entrelacement simple est très utile pour les accès en rafle ou équivalents. Par contre, il existe de rares situations où il n'est pas optimal. Une de ces situations est celle des accès en enjambée (en ''stride''), où l'on accède en série à des adresses sont séparées par N mots mémoires. [[File:Accès par enjambées.png|centre|vignette|upright=2|Accès par enjambées.]] De tels accès surviennent quand un logiciel accède à des structures de données spécifiques, à savoir des tableaux de structures, des matrices, ou d'autres structures de données dans le genre. Un cas classique est celui du parcours d'une matrice colonne par colonne. Les matrices de nombres sont mémorisées ligne par ligne, ce qui fait que parcourir une colonne demande de passer d'une ligne à la suivante et de faire des grands sauts en mémoire RAM, mais tous espacés par la même distance. Avec l'entrelacement simple, les accès en enjambée sont moins performants que les accès à des adresses consécutives, mais cela ne fonctionne pas trop mal. Le pire cas est celui où l'on a N banques et où les données sont justement placées toutes les N adresses. Des accès consécutifs vont tous tomber dans la même banque, on ne peut plus accéder à des banques différentes en parallèle. Mais en dehors de ce cas, on voit une amélioration par rapport à la situation sans entrelacement. Pour obtenir des performances maximales pour les accès en enjambées, il faut répartir les mots mémoires dans la mémoire autrement. L'organisation idéale est la suivante. : Dans les explications qui vont suivre, la variable N représente le nombre de banques, qui sont numérotées de 0 à N-1. On commence par organiser les N premières adresses comme une mémoire entrelacée simple : l'adresse 0 correspond à la banque 0, l'adresse 1 à la banque 1, etc. Pour le bloc suivant, on décale tout d'une adresse, à savoir qu'on commence à remplir les banques à partir de la seconde, non de la première. Une fois la fin du bloc atteinte, on finit de remplir le bloc en repartant du début du bloc. Le troisième bloc d'adresse subit le même traitement, sauf qu'on commence à remplir à partir de la seconde banque. Et on poursuit l’assignation des adresses en décalant d'un cran en plus à chaque bloc. Ainsi, chaque bloc verra ses adresses décalées d'un cran en plus comparé au bloc précédent. Si jamais le décalage dépasse la fin d'un bloc, alors on reprend au début. [[File:Mémoire interleaved par décalage.png|centre|vignette|upright=1.5|Mémoire entrelacée par décalage.]] En faisant cela, on remarque que les banques situées à N adresses d'intervalle sont différentes. Dans l'exemple du dessus, nous avons ajouté un décalage de 1 à chaque nouveau bloc à remplir. Mais on aurait tout aussi bien pu prendre un décalage de 2, 3, etc. Dans tous les cas, on obtient un entrelacement par décalage. Ce décalage est appelé le pas d'entrelacement, noté P. Le calcul de l'adresse à envoyer à la banque, ainsi que la banque à sélectionner se fait en utilisant les formules suivantes : * adresse à envoyer à la banque = adresse totale / N ; * numéro de la banque = (adresse + décalage) modulo N ; * décalage = (adresse totale * P) mod N. Avec cet entrelacement par décalage, on peut prouver que la bande passante maximale est atteinte si le nombre de banques est un nombre premier. Seulement, utiliser un nombre de banques premier peut créer des trous dans la mémoire, des mots mémoires inadressables. Malgré ce défaut, la technique a été utilisée sur quelques ordinateurs, avec l'exemple notable du superordinateur ''Burroughs Scientific Processor''. Pour éviter cela, il y a plusieurs solutions. Par exemple, on peut faire en sorte que N et la taille d'une banque soient premiers entre eux : ils ne doivent pas avoir de diviseur commun. Mais en pratique, elles n'ont pas vraiment été implémentées dans une vraie machine, et sont restées à l'état de recherche, aussi je les passe sous silence. ===L'entrelacement pseudo-aléatoire=== Une dernière méthode de répartition consiste à répartir les adresses dans les banques de manière "pseudo-aléatoire". L'idée exacte est de faire passer l'adresse dans une '''fonction de hachage''', plus ou moins complexe. Pour rappel, une fonction de hachage prend une entrée de grande taille et fournit en sortie un résultat de petite taille. Idéalement, elles doivent donner des résultats aussi différents que possible pour des entrées similaires, histoire de simuler une sorte de pseudo-aléatoire. Ici, une adresse est transformée en un numéro de banque. Une fonction de hachage simple ne fait que permuter des bits de l'adresse pour obtenir son résultat. Elle ne fait qu'échanger des bits de place avant de couper l'adresse en deux morceaux : un pour la sélection de la banque, et un autre pour la sélection de l'adresse dans la banque. Cette permutation est fixe, et ne change pas suivant l'adresse. Des fonctions de hachage plus complexes font des XOR bit à bit entre certains bits de l'adresse. ==Les contrôleurs SDRAM/DDR avec "exécution dans le désordre"== Après avoir vu le cas des mémoires RAM, nous allons nous concentrer sur le cas particulier des mémoires SDRAM. Les mémoires SDRAM et DDR modernes sont capables de gérer plusieurs accès simultanés à la mémoire RAM, et leurs contrôleurs mémoire en font tout autant. C'est une différence majeure avec les mémoires asynchrones FPM/EDO, qui n'acceptaient qu'un seul accès mémoire à la fois. Leur contrôleur mémoire n'acceptait qu'un seul accès mémoire à la fois, c'était un contrôleur mémoire bloquant. Les contrôleurs mémoires des SDRAM sont eux non-bloquants et peuvent encaisser une dizaine d'accès mémoire à la fois. Peu de choses sont connues sur les contrôleurs de SDRAM/DDR modernes, les fabricants ne donnant que peu de détails dessus. Les rares simulateurs qui tentent de décrire leur fonctionnement, comme DRAM SIM I et II, sont particulièrement simples et ne vont pas dans le détail. Néanmoins, le peu qu'on sait est tout de même instructif. ===Rappel sur les SDRAM : tampon de ligne et commandes=== Les mémoires SDRAM sont des mémoires à tampon de ligne. Elles font un accès mémoire en deux étapes. La première étape recopie une ligne de N * 64 bits dans un tampon interne à la SDRAM. La seconde étape sélectionne une colonne, à savoir une donnée de 64 bits, qui est soit envoyée sur le bus de données pour une lecture, soit modifiée par une écriture. [[File:Mémoire à tampon de ligne.png|centre|vignette|upright=2|Mémoire à tampon de ligne]] Les accès mémoire sont traduits par un séquenceur mémoire en une série de commandes mémoires, qui sont séparées par des délais mémoire de quelques cycles d'horloge. Les délais sont très précis, et sont à respecter à la lettre. Une lecture ou une écriture se fait en maximum trois commandes : une commande PRECHARGE qui ferme la ligne précédemment utilisée, une commande ACT qui précise l'adresse de la ligne, et une commande READ ou WRITE qui précise l'adresse de la colonne et éventuellement la donnée à écrire. : Pour être plus précis, la commande PRECHARGE précharge les lignes de bits du plan mémoire à une tension particulière, ce qui les vide de leur contenu. Mais c'est un détail sans importance pour ce qui va suivre. Les SDRAM permettent de se passer de la première étape si des accès consécutifs se font dans la même ligne. Une fois activée, la ligne reste ouverte et on peut accéder plusieurs fois de suite dedans. On a alors juste à préciser l'adresse de colonne dedans. Si un accès mémoire accède à une ligne déjà activée, on dit que c'est un '''succès de page'''. Si ce n'est pas le cas, on doit fermer la ligne courante, rouvrir la ligne vboulue, et préciser la colonne. C'est alors un '''défaut de page'''. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | bgcolor="#FFA0FF" | PRECHARGE || bgcolor="#FFA0FF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || bgcolor="#A0FFFF" | READ (2) || bgcolor="#A0FFFF" | READ (3) || || || bgcolor="#A0FFFF" | READ (4) || bgcolor="#A0FFFF" | READ (5) || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | Donnée READ 1 || ||bgcolor="#A0FFFF" | Donnée READ 2 | bgcolor="#A0FFFF" | Donnée READ 3 || || || bgcolor="#A0FFFF" | Donnée READ 4 || bgcolor="#A0FFFF" | Donnée READ 5 |} Les SDRAM sont parfois capables de démarrer une commande avant que la précédente soit terminée. Mais le respect des délais mémoire est très limitant, ce qui fait qu'on ne peut pas parler de réel pipeline, comme c'est le cas sur les processeurs. Parlons plutot de '''commandes anticipées'''. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | || bgcolor="#A0FFFF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || || || bgcolor="#FFA0FF" | READ (2) || || || || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 | bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 | |} Un dernier point est que les chips mémoires de SDRAM sont composés de plusieurs banques, chacune étant une sorte de mini-mémoire miniature. Chaque banque a son propre décodeur, son propre tampon de ligne, ses propres multiplexeurs de colonne, sa logique de rafraichissement mémoire, etc. C'est comme si une SDRAM regroupait plusieurs mémoires séparées dans un même circuit intégré. [[File:Arrangement vertical.jpg|centre|vignette|upright=2.5|Mémoire multi-banques.]] ===La mise en attente des accès mémoire=== Un contrôleur de SDRAM peut accepter plusieurs accès mémoire en même temps. Mais cela ne signifie pas que le contrôleur sera prêt à les traiter immédiatement. Pour éviter tout problème de disponibilité, le contrôleur met en attente les accès mémoire que le processeur lui envoie, pour les exécuter dès que possible. Les accès mémoire sont mis en attente dans une mémoire FIFO, histoire de les exécuter dans leur ordre d'arrivée, notamment pour renvoyer les lectures dans l'ordre demandé. Il y a aussi une mémoire FIFO pour les données à écrire et une FIFO pour les données lues. Cette dernière sert au cas où le cache ne soit pas disponible quand on lui envoie la donnée lue. [[File:Module d'interface avec la mémoire.png|centre|vignette|upright=2.5|Contrôleur mémoire avec mise en attente des requêtes processeur et autres optimisations.]] Des optimisations sont possibles dès la mémoire FIFO. Par exemple, on peut regrouper plusieurs accès à des données consécutives en un seul accès en rafale. Le contrôleur mémoire analyse les requêtes mises en attente et détecte si certaines se font à des adresses consécutives. Si c'est le cas, il fusionne ces requêtes en une lecture/écriture en rafale. Une telle optimisation est appelée la '''combinaison de lecture''' pour les lectures, et la '''combinaison d'écriture''' pour les écritures. L'optimisation précédente ne paraissent pas payer de mine, mais elles deviennent plus compréhensible quand on sait que certains contrôleurs mémoire peuvent mettre en attente beaucoup de données. Par exemple, l'''Intel E8870 - Scalable Memory Controller'' peut mettre en attente 64 lignes de cache dans la mémoire FIFO, soit 8 kibioctets de RAM ! Deux pages mémoire si la mémoire virtuelle utilise des pages de 4 kibioctets ! ===Les lectures anticipées=== La mise en attente des accès mémoire est une optimisation intéressante, mais pas mirifique. Elle permet surtout de ne pas bloquer le processeur si le contrôleur mémoire a du travail sur la planche. Cependant, elle peut être optimisée quand on se rend compte d'une chose : le processeur ne voit que les lectures, pas les écritures. Pour les écritures, il les envoie au contrôleur mémoire et ce dernier fait le travail demandé, le processeur n'est pas prévenu quand une écriture se termine. Mais pour les lectures, c'est différent, car il reçoit la donnée lue. En conséquence, il est possible d'optimiser les écritures sans que le processeur ne voit quoique ce soit. Une optimisation possible est alors de changer l'ordre des accès mémoire, histoire de retarder les écritures ou au contraire de les faire en avance. La raison est que les mémoires SDRAM n'aiment pas quand on alterne lectures et écritures. Les délais mémoire, les fameux ''timings'' mémoire, sont clairs : faire une suite de lecture ou une suite d'écriture est plus performant que d'alterner entre lectures et écritures. L'idée est alors de changer l'ordre des écritures de manière à les faire en bloc, idem pour les lectures. L’optimisation est possible, mais à condition que le processeur n'y verra que du feu. Or, vous l'avez deviné, il y a des cas où ces réorganisations ne sont pas possibles, et vous avez sans doute trouvé que la situation était familière. L'optimisation en question est une forme d'exécution dans le désordre des accès mémoire, réalisée par le contrôleur mémoire lui-même ! Et qui dit exécution dans le désordre dit : problèmes liés aux dépendances de données. Changer l'ordre des écritures n'est pas toujours possible, notamment en raison des dépendances RAW, WAR et WAW. Pour que le processeur n'y voit que du feu, il faut respecter plusieurs critères. Premièrement, les lectures doivent être renvoyées au processeur dans l'ordre. Et quand je dis dans l'ordre, cela veut dire : dans l'ordre d'envoi des lectures au contrôleur mémoire. Les écritures aussi doivent se faire dans l'ordre, histoire qu'une série d'écriture donne le bon résultat final. Le troisième critère est qu'une lecture doit donner le résultat de la dernière écriture ''à la même adresse''. Pour le dire autrement, il faut juste détecter les dépendances RAW. Concrètement, si on a une série de lectures et d'écritures alternées, on peut regrouper les lectures et les écritures dans deux paquets séparés, à condition qu'aucune lecture ne lise une adresse écrite par une écriture. Mais si une lecture est dépendante d'une écriture, alors la lecture doit attendre que l'écriture se termine, idem pour les lectures suivantes. Une implémentation basique remplace la mémoire FIFO vue au-dessus, par deux mémoires FIFOs : une pour les lectures, une pour les écritures. Lorsqu'un accès mémoire arrive au séquenceur, il regarde si c'est une lecture ou une écriture et place l'accès mémoire dans la file adéquate. Le fait que ce soit des FIFOs garantit que les lectures se font dans l'ordre, idem pour les écritures. En sortie des deux FIFOs, le séquenceur mémoire détecte les dépendances RAW. Séparer lectures et écriture est une source d'optimisation. Il est par exemple possible d'utiliser le réacheminement lecture sur écriture (''Store to load forwarding''). Il s'agit d'un équivalent de l’optimisation du même nom, utilisée dans le cadre de la désambiguïsation mémoire. Imaginez qu'une lecture accède à une donnée pas encore écrite, en attente dans le contrôleur mémoire. C'est une dépendance RAW assez claire. Dans ce cas, le contrôleur mémoire peut renvoyer la donnée directement depuis ses mémoires FIFOs, sans faire d'accès mémoire en lecture. Le contrôleur mémoire a tendance à privilégier les lectures, car celles-ci sont cruciales pour l'exécution dans le désordre. Plus elles se font tôt, plus vite le processeur recevra les données et pourra démarrer les instructions dépendantes de cette donnée. Le séquenceur a donc tendance à piocher en priorité dans la file de lecture, plutôt que dans la file d'écriture. A vrai dire, dans le cas idéal, les écritures ne sont faites que quand la file d'écriture est pleine ou quasi-pleine... Prenons l'exemple des coprocesseurs IO 81341 et 81342, qui étaient en réalité des ''chipsets'' intégrant un contrôleur SDRAM. Le ''chipset'' avait 5 ports : un pour les processeurs, un pour le pont sud (''southbridge'') et trois pour des canaux DMA. Pour le port processeur, il n'y avait pas de réordonnancement et ce port utilisait une seule mémoire FIFO. Les autres ports utilisaient l'optimisation qu'on vient de voir, et avaient vraisemblablement des files séparées pour les lectures et écritures. Le séquenceur mémoire vérifiait les dépendances mémoire de type RAW et autres. [[File:Double file d'attente pour les lectures et écritures.png|centre|vignette|upright=2.5|Double file d'attente pour les lectures et écritures]] ===Le ré-ordonnancement des commandes mémoires=== L'optimisation précédente est peu poussée, comparé aux formes d'exécution dans le désordre que les processeurs utilisent. Et on peut se demander si une exécution dans le désordre plus poussée est possible. La réponse est oui : un contrôleur mémoire peut faire des réorganisations bien plus poussées. Par contre, il faut faire une précision très importante : le contrôleur mémoire ne remet pas les accès mémoire dans l'ordre avant de les envoyer au processeur. C'est le processeur qui remet en ordre les accès mémoire, dans sa ''load queue''. En clair : le processeur voit les accès mémoire dans le désordre, c'est lui qui les remet en ordre. Pour cela, les requêtes mémoire sont "numérotées", à savoir qu'on leur attribue un identifiant binaire. Le contrôleur mémoire exécute les lectures/écritures dans le désordre, mais garde la trace de leur identifiant. Il sait dans quel ordre il exécute les accès mémoire et connait leur latence. Ce qui fait qu'il sait que la donnée lue à tel instant est associée à la requête mémoire numéro X. Pour les lectures, il renvoie la donnée lue avec l'identifiant. Le processeur sait alors à quelle lecture la donnée lue correspond et il se débrouille en interne pour gérer la situation. Maintenant que ces précisions sont faites, posons cette question : dans quelles situations est-il pertinent de faire des accès mémoire dans le désordre ? La réponse est : quand plusieurs accès à une même ligne ne sont pas consécutifs, qu'il y a des accès entre les deux. Après ré-ordonnancement, ces accès mémoire à une même ligne sont exécutés l'un à la suite de l'autre, ce qui est beaucoup plus rapide. Pour rendre le tout plus concret, voici un exemple. Imaginez que l'on sait les 3 accès mémoire suivants : * Une lecture ligne A ; * PRECHARGE + ACT ; * Une écriture ligne B ; * PRECHARGE + ACT ; * Une lecture ligne A. Sans réordonnancent, on doit émettre deux commandes PRECHARGE quand on passe d'une ligne à l'autre, et il faut ajouter les commandes ACT avec. pour éviter cela, le contrôleur mémoire peut retarder l'écriture, ou au contraire avancer la seconde lecture. le résultat est alors le suivant : * Lecture ligne A ; * Lecture ligne A * PRECHARGE + ACT ; * Une écriture ligne B. On a donc deux accès consécutifs à la même ligne, suivi par une écriture dans une autre ligne. La technique marche parce que l'on a un mix adéquat de lectures et d'écritures. Et encore une fois, il faut éviter d'intervertir lectures et écritures à une même adresse, sous peine de problèmes. Encore une fois, les dépendances RAW posent problème ! L'implémentation est assez simple : la ou les mémoires FIFOs précédentes sont remplacées par des mémoires similaires aux fenêtres d'instruction. Le contrôleur mémoire vérifie à chaque cycle les accès en attente, et vérifie à quelle ligne ils accèdent. Il priorise alors les accès qui tombent dans la ligne ouverte : ceux-là sont exécutés avant les autres. S'il n'y en a pas, il prend l'accès mémoire le plus ancien (ordre FIFO). Il teste aussi les dépendances mémoires RAW avant d'envoyer des commandes à la mémoire DDR/SDRAM. une telle solution est appelée l''''algorithme ''FR-FCFS''''' (''First Ready-First Come First Serve''). ===Les optimisations liées à la présence de plusieurs banques=== Les optimisations précédentes sont décuplées par la présence de plusieurs banques dans la mémoire SDRAM. Il est en effet possible de faire plusieurs accès mémoire en même temps, dans des banques différentes. Il est possible de lancer un accès mémoire dans deux banques en même temps, par exemple. La seule contrainte est que la SDRAM est limitée à 4 banques actives en même temps. {|class="wikitable" |+ Pipelining basique sur les SDRAM |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 |- ! Banque Numéro 1 | || || || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || || |- ! Banque Numéro 2 | || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || |- ! Banque Numéro 3 | || || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || |} Les optimisations précédentes peuvent s'appliquer par banque. Il est par exemple possible d'utiliser une mémoire FIFO par banque. Ainsi, les accès dans une même banque se feront en série, dans l'ordre d'arrivée, mais des accès à des banques différentes se feront dans le désordre. Cela ne pose pas de problème car les accès se font à des adresses différentes, ce qui fait qu'il n'y a pas de conflits majeurs. Il y a cependant un risque que les lectures se fassent dans le désordre si on n'y prend pas garde. [[File:Gestion parallèle des banques.png|centre|vignette|upright=2.5|Gestion parallèle des banques.]] il est aussi possible d'utiliser une file séparée pour les lectures et les écriture, pour chaque banque. Les performances sont alors améliorées comparé à deux files globales pour toute la SDRAM. En idem avec la réorganisation des accès mémoire, pour regrouper les accès à une même ligne. ==L'entrelacement sur les mémoires SDRAM== L''''entrelacement''' fonctionne sur les mémoires SDRAM, mais il doit être fortement modifié. Concrètement, tout ce qui a étré dit plus est inapplicable pour les SDRAM, du fait de la présence du tampon de ligne. Des adresses consécutives atterrissent déjà dans la même ligne, ce qui fait qu'on n'a pas besoin d'entrelacement au niveau d'une ligne. Et ne parlons pas de ce la présence de rangées et de canaux mémoire ! Cependant, il peut y avoir un entrelacement lié à la présence des banques SDRAM. L'entrelacement est géré juste avant le séquenceur mémoire. Le '''circuit d'entrelacement''' intervertit certains bits de l'adresse lors des accès mémoires. On trouve aussi des mémoires FIFOs pour mettre en attente les requêtes mémoire provenant du processeur. Elles permettent de recevoir plusieurs accès mémoire consécutifs. Si vous vous demandez comment plusieurs accès mémoire consécutifs peuvent avoir lieu, sachez que c'est une bonne question. Pour le moment, nous allons dire que ces accès proviennent du processeur ou de la mémoire cache, sans expliquer comment. Le contrôleur mémoire reçoit une série d'accès mémoire assez rapprochés, qu'il doit exécuter au mieux. [[File:Controleur mémoire d'une SDRAM avec entrelacement.png|centre|vignette|upright=2.5|Contrôleur mémoire d'une SDRAM avec entrelacement.]] Pour gérer l'entrelacement, le contrôleur de SDRAM prend en entrée l'adresse envoyée par le processeur, et la découpe en plusieurs champs : un pour sélectionner la banque adéquate, un autre pour la ligne, un autre pour la colonne, un autre pour la rangée. Une solution simple désactive l'entrelacement. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de banque || Adresse de ligne || Adresse de colonne |} Si cette solution fonctionne bien si les accès mémoire sont assez aléatoires, en pratique, mieux vaut utiliser l'entrelacement. ===L'entrelacement de banques/lignes=== Une première méthode d'entrelacement est l''''entrelacement de banques'''. Elle répartit deux lignes consécutives dans deux banques différentes. Pour comprendre l'idée, prenons un exemple. Imaginons une mémoire avec deux banques et 4 lignes. Imaginons qu'on parcoure/balaye la mémoire RAM en partant des adresses basses. Sans entrelacement de ligne, les accès se feront comme suit, de gauche à droite : {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Banque numéro 1 !! colspan="4" | Banque numéro 2 |- | Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 || Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 |} Avec l'entrelacement de banques, les accès se feront comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 |- | Ligne 1 || Ligne 1 || Ligne 2 || Ligne 2 || Ligne 3 || Ligne 3 || Ligne 4 || Ligne 4 |} Avec N banques, la première ligne ira dans la première banque, la seconde ligne dans la seconde, et ainsi de suite jusqu'à la énième banque qui va dans la banque numéro N. Et la ligne N+1 repart dans la première banque. L'avantage est que passer d'une ligne à la suivante est assez rapide. Au lieu de devoir fermer la première ligne et ouvrir la suivante on ouvrira directement la ligne suivante dans une banque différente, ce qui sera plus rapide. La commande PRECHARGE pourra être envoyée à la seconde banque en avance. Pour cela, l'adresse est découpée comme suit. {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne (précise à l'octet) |} ===L'entrelacement de rangées=== Il est aussi possible de faire la même chose, mais avec les rangées. Pour simplifier fortement, une rangée est simplement un chip mémoire. En réalité, les rangées ne sont pas des chip mémoire, mais un ensemble de chips mémoire regroupés ensemble histoire d'atteindre les 64 bits du bus de données. Par exemple, une rangée peut combiner 8 chips mémoire avec un bus de données de 8 bits chacun pour obtenir les 64 bits du bus de données d'une SDRAM. Mais nous passons ce détail sous silence dans les explications qui vont suivre, par souci de simplification. Pour faire comprendre l'entrelacement de rangée, le mieux est d'utiliser un exemple, le même que précédemment. Sans entrelacement de rangée, on change de chip mémoire une fois qu'on a balayé toutes ses adresses. {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Rangée numéro 1 !! colspan="4" | Rangée numéro 2 |- | Banque 1 || Banque 2 || Banque 3 || Banque 4 || Banque 1 || Banque 2 || Banque 3 || Banque 4 |} Avec l'entrelacement de ligne, on change de rangée dès qu'on change de banque/ligne. {|class="wikitable" |+ Adresse mémoire |- ! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 |- | Banque 1 || Banque 1 || Banque 2 || Banque 2 || Banque 3 || Banque 3 || Banque 4 || Banque 4 |} En clair, quand on a épuisé toutes les banques dans une même rangée, on passe à la rangée suivante au lieu de rester dans la même rangée. Notons que dans l'exemple précédent, on a combiné l'entrelacement de rangée et de banque, mais on aurait pu utiliser l'entrelacement de rangée seul. Mais ce n'est pas le cas le plus courant en pratique. Toujours est-il qu'en combinant entrelacement de rangée et de ligne, le découpage de l'adresse est le suivant : {|class="wikitable" |+ Adresse mémoire |- | Adresse de ligne || Numéro de rangée || Adresse de banque || Adresse de colonne (précise à l'octet) |} D'autres méthodes d'entrelacement sont possibles, mais elles sont plus complexes. Chaque contrôleur mémoire fait un peu à sa sauce. Ils permettent en général de choisir entre plusieurs entrelacements. Au minimum, ils permettent de désactiver l'entrelacement, d'activer un entrelacement de ligne, et d'autres entrelacements plus complexes, dont certains sont propriétaires et tenus secrets par le fabricant. ===L'entrelacement avec le ''dual channel''=== [[File:Dual channel slots.jpg|vignette|Slots mémoires avec ''dual channel''.]] L'usage du ''dual channel'' complique encore l'entrelacement. Là encore, il y a deux grandes solutions : avec et sans entrelacement des canaux mémoire. Rappelons le principe : deux barrettes de RAM sont accédées en parallèle. Et pour cela, il faut utiliser l'entrelacement. Typiquement, chaque barrette mémoire fournit 64 bits, ce qui fait que l'on peut accéder à 128 bits d'un seul coup, par exemple avec un accès en rafale. Sans ''dual channel'', la première barrette correspond à la moitié haute de la RAM, la seconde barrette correspond à la moitié basse. Avec ''dual channel'', une forme spécifique d'entrelacement est activée. Concrétement, deux blocs de 64 bits sont placés dans des canaux mémoire séparés. Idem avec du triple ou quadruple canal, mais c'est alors trois ou quatre blocs de 64 bits qui sont dispersés dans des canaux consécutifs. Le découpage de l'adresse est alors le suivant : {|class="wikitable" |+ Adresse mémoire |- ! colspan="6" | Sans entrelacement |- | Numéro de canal/barrette || Reste de l'adresse || Position de l'octet dans un bloc de 64 bits |- | colspan="6" | |- ! colspan="6" | Avec entrelacement |- | Reste de l'adresse || Numéro de canal/barrette || Position de l'octet dans un bloc de 64 bits |} [[File:Décodage d'adresse avec dual channel.png|centre|vignette|upright=2|Décodage d'adresse avec dual channel.]] Les contrôleurs de SDRAM précédents sont assez basiques et ne représentent pas les contrôleurs les plus évolués. Ils étaient utilisés sur les anciens PC, à une époque où ils étaient encore sur la carte mère du processeur. Mais de nos jours, le contrôleur mémoire est intégré au processeur. Et il incorpore de nombreuses optimisations afin de gagner en performances. Malheureusement, ces optimisations sont elles-mêmes dépendantes des optimisations intégrées dans le processeur, ce qui fait qu'on ne peut pas en parler ici. Elles demandent que le contrôleur mémoire reçoive plusieurs accès mémoire simultanés, ce qui n'a aucun sens à ce stade du cours. Aussi, nous allons les passer sous silence pour le moment. Nous les verrons dans le chapitre sur le parallélisme mémoire. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=La désambiguïsation mémoire | prevText=La désambiguïsation mémoire | next=Les processeurs superscalaires | nextText=Les processeurs superscalaires }} </noinclude> sljjg3wylj7y73mynxo5xhi37zpq9dn 764748 764747 2026-04-23T23:12:10Z Mewtow 31375 /* Les lectures anticipées */ 764748 wikitext text/x-wiki Dans ce chapitre, nous allons voir les techniques qui permettent de gérer plusieurs accès mémoire simultanés directement au niveau du cache ou de la mémoire RAM. Par plusieurs accès mémoire simultanés, vous pensez sans doute à l'usage de cache multiports, voire de mémoires RAM multiport. C'est en effet une solution, mais ce n'est clairement pas la seule. Par exemple, il est possible de pipeliner l'accès au cache, voire à la mémoire RAM. Il existe de nombreuses techniques de '''parallélisme mémoire''', et nous allons les voir dans ce chapitre. Pour donner un exemple, il est possible de pipeliner les accès mémoire. Il est possible de pipeliner l'accès au cache et/ou l'accès à la mémoire, et nous verrons comment faire dans ce chapitre. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, qu'ils aient ou non un pipeline dynamique. Par contre, pipeliner la mémoire est une technique ancienne, aujourd'hui peu utilisée. Elle était utilisée sur des très vieux ordinateurs, qui avaient un pipeline mais pas de mémoire cache. Mais de nos jours, les mémoires SDRAM et DDR ne sont pas adaptées pour ça, il est très difficile de les pipeliner correctement. Le parallélisme mémoire est utile aussi bien sur des processeurs à émission dans l'ordre que dans le désordre. Par exemple, un processeur ''in-order'' avec des lectures non-bloquantes peut e, profiter. Il peut techniquement lancer une seconde lecture, après avoir rencontré une première lecture non-bloquante. En général, le nombre de lectures consécutives est cependant limité à moins d'une dizaine. À l'opposé, les processeurs sans lectures non-bloquantes profitent pas du parallélisme mémoire. À la rigueur, ils peuvent en profiter s'ils intègrent des techniques de préchargement. Pendant que le processeur exécute une lecture/écriture, il peut précharger une autre donnée en parallèle. Inutile de dire que sans pipeline processeur, le parallélisme mémoire ne sert pas à grand-chose. ==Le ''line fill buffer'' et les techniques associées== Lors d'un défaut de cache, le processeur doit attendre que toute la ligne de cache soit chargée avant d'être utilisable. Or, la taille d'une ligne de cache est supérieure à la largeur du bus mémoire, ce qui fait qu'une ligne de cache est chargée en plusieurs fois, morceaux par morceaux, mot mémoire par mot mémoire. Le chargement peut se faire directement dans le cache, mais ce n'est pas une solution très pratique. À la place, beaucoup de processeurs ajoutent une mémoire tampon entre la RAM et le cache, appelée le '''tampon de remplissage de ligne''' (''line-fill buffer'') dans les processeurs modernes. Lors d'un défaut de cache, le processeur charge la donnée de la RAM dans le tampon de remplissage de ligne, mot mémoire par mot mémoire. Une fois plein, le tampon de remplissage de ligne est recopié dans la ligne de cache. Le tampon de remplissage de ligne contient une ligne de cache, si ce n'est que certains bits de contrôle ne sont pas présents. Le tampon de remplissage de ligne contient un bit de validité pour chaque mot mémoire de la ligne de cache, qui indiquent si le mot mémoire a été chargé. Par exemple, prenons un processeur 64 bits, qui gère donc des mots mémoire de 8 octets, avec des lignes de cache 256 octets/32 mots mémoire. Le tampon de remplissage de ligne contiendra 32 bits de validité. Si le processeur a chargé les 6 premiers mots mémoire, les 6 premiers bits de validité seront mis à 1, les autres seront encore à zéro. Il faut noter que la disponibilité de la ligne complète se détermine assez facilement en faisant un ET logique entre tous les bits de validité. Le processeur sait ainsi quand la ligne de cache est disponible entièrement et donc quand la transférer dans le cache. La même chose existe avec une hiérarchie de cache, sauf que l'on trouve une mémoire tampon entre chaque niveau de cache. Il y a un tampon de remplissage de ligne entre le cache L1 et le cache L2, entre le cache L2 et le L3, etc. Si le cache ne gère qu'un seul défaut de cache à la fois, le ''fill line buffer'' est une mémoire très simple, qui ne mémorise qu'une seule ligne de cache. Et cela vaut aussi bien pour un cache bloquant que non-bloquant. Mais sur les caches capables de gérer plusieurs défauts simultanés, le tampon de remplissage de ligne est une mémoire de type FIFO ou LIFO, capable de mémoriser plusieurs lignes de cache. Notons que le tampon de remplissage de ligne est très utile pour implémenter certaines techniques, par exemple le contournement du cache. Nous avions vu dans le chapitre sur le cache que certains accès mémoire doivent contourner le cache, pour des raisons de cohérence des caches. C'est notamment nécessaire pour accéder aux périphériques, mais c'est aussi utile pour des raisons de performances dans des cas très spécifiques. Les accès qui contournent le cache se font directement dans le tampon de remplissage de ligne : le processeur écrit ou lit les données depuis ce tampon de remplissage de ligne, sans accéder au cache. ===L'''early restart'' et le ''critical word load''=== La présence du ''line buffer'' permet une optimisation assez intéressante, qui permet de réduire la latence des défauts de cache. L'optimisation consiste à lire un mot mémoire dans le tampon de remplissage de ligne, même si la ligne de cache complète n'a pas encore été chargée. Il existe deux manières de faire cela, qui portent les noms d'''early restart'' et de ''critical word load''. La première est la version la plus simple, la seconde est plus complexe mais plus performante. Avec la technique d''''''early restart''''', la ligne de cache est chargée normalement, en partant de son premier mot mémoire. Dès que le mot mémoire lu/écrit par le processeur est copié dans le ''line fill buffer'', il est envoyé au processeur immédiatement. Illustrons le tout par un exemple, où une ligne de cache fait 16 mots mémoire. Le processeur effectue une lecture, qui lit le 5ème mot mémoire. Sans ''early restart'', le processeur doit charger les 16 mots mémoire avant de faire la lecture dans le cache. Avec ''early restart'', le processeur reçoit la donnée dès que le 5ème mot mémoire est disponible. Le processeur doit attendre que les 4 premiers mots mémoire soient chargés, puis le 5ème mot mémoire arrive et est envoyé directement au processeur, il est lu directement depuis le ''line fill buffer''. Les 11 mots mémoire suivants sont ensuite chargés dans le cache pendant que le processeur fait des calculs dans son coin. Le ''critical word load'' est une optimisation de la technique précédente où le chargement de la ligne de cache commence directement à la donnée demandée par le processeur. Pour reprendre l'exemple précédent, où le processeur demande le 5ème mot mémoire sur 16, le ''critical word load'' charge le 5ème mot mémoire en premier et l'envoie au processeur, ce qui fait qu'il est chargé très rapidement. Pas besoin d'attendre que le processeur charge les 4 mots mémoire précédents comme avec l'''early restart''. Dans le détail, le processeur charge le 5ème mot mémoire en premier, puis charge les 11 suivants, et termine par les 4 mots mémoire du début. En clair, le ''critical word load'' commence par charger le mot mémoire lu, puis les blocs suivants, avant de revenir au début du bloc pour charger les blocs restants. Ainsi, la donnée demandée par le processeur sera la première disponible. Pour cela, l'organisation du tampon de remplissage de ligne est modifiée de manière à rendre cela possible. Il a une taille égale à une ligne de cache complète, qui contient elle-même plusieurs mots mémoire. Dans le ''line-fill buffer'', chaque mot mémoire est stocké avec un tag, qui indique l'adresse du mot mémoire stocké dans le ''line-fill buffer''. Le ''line-fill buffer'' est donc un cache un peu particulier, qui fonctionne comme un cache du point de vue du processeur, comme une mémoire FIFO pour les transferts avec le cache. Ainsi, un processeur qui veut lire dans le cache après un défaut peut accéder à la donnée directement depuis le tampon de remplissage de ligne, alors que la ligne de cache n'a pas encore été totalement recopiée en mémoire. ==Les caches pipelinés== Il est possible de pipeliner l'accès au cache, ce qui demande juste de rajouter des registres au bon endroits et quelques circuits de contrôle. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, même ceux avec un pipeline dynamique. Et l'implémentation peut se faire de plusieurs manières différentes. Il faut dire que découper une mémoire cache en plusieurs étapes peut se faire de plusieurs manière. Commençons par la plus simple. Beaucoup de processeurs des années 2000 avaient une fréquence peu élevée comparée aux standards d'aujourd'hui, ce qui fait que leur cache avait bien un temps d'accès d'un cycle d'horloge. Mais l'accès au cache se faisait en deux cycles d'horloge : un cycle pour calculer une adresse et un cycle pour l'accès mémoire proprement dit. Et ces deux étapes étaient pipelinées, à savoir que deux micro-opérations mémoire peuvent s'exécuter en même temps : une dans l'étage de calcul d'adresse, une autre dans le cache. L'unité mémoire était donc pipelinée, alors que l'accès au cache ne l'était pas. Les processeurs qui implémentaient cette technique regroupent les micro-architectures K5 et K6 d'AMD, les processeurs Intel de micro-architecture P6 (Pentium 2 et 3) et quelques autres. Il est aussi possible de pipeliner l'accès au cache lui-même. Avec un cache pipeliné, l'accès au cache ne se fait pas en un seul cycle, mais en plusieurs. Cependant, on peut lancer un nouvel accès au cache à chaque cycle d'horloge, comme un processeur pipeliné lance une nouvelle instruction à chaque cycle. L'implémentation est assez simple : il suffit d'ajouter des registres dans le cache. Pour cela, on profite que le cache est composé de plusieurs composants séparés, qui échangent des données dans un ordre bien précis, d'un composant à un autre. De plus, le trajet des informations dans un cache est linéaire, ce qui les rend parfois pour l'usage d'un pipeline. ===Le ''pipelining'' des caches ''direct-mapped''=== Voici ce qui se passe avec un cache directement adressé. Pour rappel ce genre de cache est conçu en combinant une mémoire RAM, généralement une SRAM, avec quelques circuits de comparaison et un MUX. L'accès au cache se fait globalement en deux étapes : la première lit la donnée et le ''tag'' dans la SRAM, et on utilise les deux dans une seconde étape. Nous avions vu il y a quelques chapitre comment pipeliner des mémoires, dans le chapitre sur les mémoires évoluées. Et bien ces méthodes peuvent s'utiliser pour la mémoire RAM interne au cache !L'idée est d'insérer un registre entre la sortie de la RAM et la suite du cache, pour en faire un cache pipéliné, à deux étages. Le premier étage lit dans la SRAM, le second fait le reste. L'implémentation sur les caches associatifs à plusieurs voies est globalement la même à quelques détails près. [[File:Cache directement adressé pipeliné - deux étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - deux étages]] Avec le circuit précédent, il est possible d'aller plus loin, cette fois en pipelinant l'accès à la mémoire RAM interne au cache. Pour comprendre comment, rappelons qu'une mémoire SRAM est composée d'un plan mémoire et d'un décodeur. L'accès à la mémoire demande d'abord que le décodeur fasse son travail pour sélectionner la case mémoire adéquate, puis ensuite la lecture ou écriture a lieu dans cette case mémoire. L'accès se fait donc en deux étapes successives séparées, on a juste à mettre un registre entre les deux. Il suffit donc de mettre un gros registre entre le décodeur et le plan mémoire. [[File:Cache directement adressé pipeliné - trois étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - trois étages]] Et on peut aller encore plus loin en découpant le décodeur en deux circuits séparés. En effet, rappelez-vous le chapitre sur les circuits de sélection : nous avions vu qu'il est possible de créer des décodeurs en assemblant des décodeurs plus petits, contrôlés par un circuit de prédécodage. Et bien on peut encore une ajouter un registre entre ce circuit de prédécodage et les petits décodeurs. : Théoriquement, toute l'adresse est fournie d'un seul coup au cache, la quasi-totalité des processeurs présentent l'adresse complète à un cache pipeliné. Mais le Pentium 4 fait autrement. Il faut noter que les premiers étages manipulent l'indice dans la SRAM, qui est dans les bits de poids faible, alors que les étages ultérieurs manipulent le ''tag'' qui est dans les bits de poids fort. Les concepteurs du Pentium 4 ont alors décidé de présenter les bits de poids faible lors du premier cycle d'accès au cache, puis ceux de poids fort au second cycle. Pour cela, l'ALU fonctionnait à une fréquence double de celle du processeur, tout comme le cache L1. Il n'y avait pas de pipeline proprement dit, mais cela réduisait grandement la latence d'accès au cache. ===Le ''pipelining'' des caches splittés/sériels=== Il est aussi possible de pipeliner un cache dits splittés, aussi appelés à accès sériel. Pour rappel, les caches à accès sériel vérifient si il y a succès ou défaut de cache, avant d'accéder aux lignes de cache en cas de succès. Ils font donc différemment des autres caches, qui accèdent à une ligne de cache, avant de déterminer s'il y a succès ou défaut en lisant le tag de la ligne de cache. Les caches sériels disposent de deux SRAM : une pour les tags des lignes de cache et une pour les données. Ils accèdent à la SRAM pour les tags, avant d’accéder à la SRAM des données en cas de succès de cache. Vu que l'accès se fait en deux étapes, une vérification des tags suivie de la lecture/écriture des données, il est facile à pipeliner. Pipeliner le cache permet de régler le problème des accès au cache L1, et elle est tout le temps utilisé sur les processeurs modernes. Mais que faire en cas de défaut de cache ? ==Les caches non bloquants== Un '''cache bloquant''' est un cache auquel le processeur ne peut pas accéder pendant un défaut de cache. Il faut attendre que la lecture ou écriture en RAM soit terminée avant de pouvoir utiliser de nouveau le cache. Un '''cache non bloquant''' n'a pas ce problème : on peut l'utiliser pendant un défaut de cache. Les caches non bloquants permettent de démarrer une nouvelle lecture ou écriture alors qu'une autre est en cours, ce qui permet d'exécuter plusieurs lectures ou écritures en même temps. ===Les ''Miss Handling Status Registers''=== Lors d'un défaut de cache, la mémoire RAM est consultée pendant le défaut de cache, mais le cache est inutilisé. Un défaut de cache n'utilise pas le cache, ce n'est qu'un accès en mémoire RAM, sur le bus mémoire (ou un accès aux niveaux de cache inférieurs, peu importe). Le cache en lui-même est laissé libre, rien n’empêche d'y accéder, il est en réalité intrinsèquement non-bloquant. Les caches, bloquants comme non-bloquants, sont en réalité composés d'une mémoire cache proprement dite, entourée de circuits qui servent d'interface entre le processeur et le cache lui-même. Et parmi les circuits tout autour, certains gèrent l'accès au cache lors d'un défaut de cache. Ils sont regroupés sous le terme de '''''Miss Handling Architecture''''' (MHA). La différence entre un cache bloquant et un cache non-bloquant est en réalité liée à l'implémentation de la MHA. Les caches bloquants coupent volontairement l'accès au cache lors d'un défaut, car les défauts sont plus simples à gérer ainsi. Le défaut de cache rapatrie une donnée depuis la RAM, qui sera écrite dans le cache. Et il ne faut pas qu'une tentative d'accès à cette donnée ait lieu avant qu'elle ne soit chargée. Mais ce blocage est général et touche tout le cache, alors que seule une ligne de cache est concernée par le défaut de cache. L'idée derrière un cache non-bloquant est que seule la ligne de cache est bloquée, mais les autres sont accessibles. L'idée est alors de mémoriser les lignes de cache concernées par le défaut de cache, afin d'en bloquer l'accès. À chaque accès, on vérifie si la ligne de cache est déjà réservée par un défaut de cache. La lecture/écriture est alors bloquée si c'est le cas, mais elle accepte les accès sinon. Pour cela, la ''Miss Handling Architecture'' contient des registres qui mémorisent des informations sur les défauts de cache en cours. Ils portent le nom de '''''miss status handling registers''''', que j’appellerais dorénavant MSHR, qui sont aussi appelés des '''''miss buffer'''''. Le contenu des MSHR varie beaucoup suivant le processeur, mais ces derniers stockent au minimum les informations suivantes : * Le numéro de la ligne de cache dans laquelle les données sont chargées. * Un bit de validité qui indique si le MSHR est vide ou pas, qui est mis à 0 quand le défaut de cache est résolu. * Un ou plusieurs champs de lecture/écriture, qui contiennent des informations sur la lecture/écriture. ** Pour une lecture, elle contient des informations sur la destination de la donnée, à savoir qui prévenir quand le défaut de cache est terminé. C'est parfois un nom/numéro de registre (celui dans lequel charger la donnée), mais c'est souvent le numéro de l'entrée dans le ''load/store queue''. ** Pour les écritures, elle contient la donnée à écrire, ou éventuellement un numéro de ''load/store queue'' où se trouve la donnée à écrire. Il faut noter que le bus mémoire ne peut gérer qu'un seul défaut de cache à la fois. Aussi, il est intéressant de regarder ce qui se passe lorsqu'un second défaut de cache survient, pendant qu'un premier est en cours. Dans ce cas, il y a deux réponses qui correspondent à deux types de caches non-bloquants, qui portent les noms barbares de caches de type succès après défaut et défaut après défaut. Sur le premier type, il ne peut pas y avoir plusieurs défauts de cache simultanés. Dès qu'un second défaut de cache survient, le cache stoppe son activité et on ne peut plus démarrer de nouvelle lecture/écriture, tant que le premier défaut de cache n'est pas résolu. Le second type est plus souple et autorise la survenue de plusieurs défauts de cache simultanés. Du moins, jusqu'à une certaine limite, car le cache ne peut supporter qu'un nombre limité d'accès mémoires simultanés (pipelinés). ===Les accès simultanés à une même ligne de cache=== Il arrive que le processeur fasse plusieurs accès mémoire simultanés à la même ligne de cache. Si la ligne de cache en question n'a pas encore été chargée dans le cache, alors on a plusieurs défauts de cache consécutifs pour la même ligne de cache, et le cache non-bloquant doit gérer la situation. Pour la suite, il va falloir faire une petite distinction entre les défauts primaires et secondaires. Imaginons qu'un défaut de cache ait lieu et demande à charger une donnée dans la ligne de cache numéro N. Il s'agit du premier défaut impliquant cette ligne de cache précise, ce qui lui vaut le nom de '''défaut de cache primaire'''. Mais par la suite, d'autres accès mémoire à la même ligne de cache ont lieu, alors que la ligne de cache n'est pas encore disponible. Dans ce cas, il s'agit de '''défauts de cache secondaires'''. Pour l'unité d'accès mémoire, les défauts de cache primaire et secondaire sont différents (ils prennent tous une entrée dans la ''load/store queue''). Mais pour le cache, ils ne correspondent qu'à un seul accès au cache : celui qui demande de charger la ligne de cache demandée. Les défauts de cache primaire et secondaire à la même ligne de cache se voient attribuer un MSHR unique. La gestion des défauts de cache secondaires dépend du cache non-bloquant. La solution la plus simple ne permet pas les défauts de cache secondaires. Le cache ne permet pas deux défauts de cache simultanés pour la même ligne de cache. Les autres solutions le permettent, en fusionnant des accès simultanés à la même ligne de cache en un seul au niveau des MSHR. Dans tous les cas, détecter les défauts de cache secondaires sont un problème qu'il faut détecter. La ''Miss Handling Architecture'' doit détecter les défauts de cache secondaire. Pour cela, elle procède comme suit. Lors de chaque défaut de cache, la MHA récupère le numéro de la ligne de cache associé. Il vérifie alors chaque MSHR pour vérifier s'il contient le numéro en question. S'il n'y a aucune correspondance dans les MSHR, alors c'est signe que le défaut de cache est un défaut primaire. Mais s'il y en a une, alors c'est un défaut secondaire. Évidemment, cela signifie que lors d'un défaut de cache, le numéro de ligne de cache est envoyé à tous les MSHR, pour comparaison. Les MSHR sont donc regroupés dans une mémoire associative, une sorte de mini-cache, faciliter l'implémentation. Lors d'un défaut de cache primaire, l'accès à un cache non-bloquant se fait comme suit : le processeur envoie une adresse au cache, accède à celui-ci, et détecte la survenue d'un défaut de cache. Il en profite alors pour attribuer une ligne de cache dans laquelle sera chargée ce défaut. L'attribution est très simple dans le cas des caches ''direct mapped'', ou associatifs par voie, pour lesquels l'attribution se fait assez simplement. Il mémorise alors cette information dans les MSHR, après avoir vérifié que le défaut de cache n'était pas un défaut secondaire. Lors des accès ultérieurs à une adresse proche, censée être dans la même ligne de cache, le processeur va encore une fois rencontrer un défaut de cache. Il va alors déterminer le numéro de la ligne de cache associée à l'adresse, et comparer ce numéro avec les MSHRs. Si un MSHR contient ce numéro, c'est signe que le défaut de cache est un défaut secondaire. La MHA réagit alors différemment selon le processeur considéré. Une première solution n'autorise pas les défauts de cache secondaires. Si l'un d'entre eux survient, le processeur est gelé par un ''pipeline stall'', une bulle de pipeline. Une autre solution fusionne les défauts de cache secondaires avec le défaut de cache primaire : tout cela ne correspond qu'à un seul défaut de cache pour lui. ===Les MSHR simples=== Un cache non-bloquant à '''MSHR simple''' contient juste plusieurs MSHR qui mémorisent juste un numéro de ligne de cache, un bit de validité, le champ de lecture/écriture, et l'adresse exacte de lecture/écriture. Avec cette organisation, il est possible d'avoir plusieurs défauts de cache séparés, mais à la condition que chaque défaut accède à une ligne de cache différente. Deux accès simultanés à une même ligne de cache ne sont pas possibles, les défauts de cache secondaires ne sont pas autorisés. Ainsi, chaque défaut de cache se voit attribuer son propre MSHR, chacun contient un numéro de ligne de cache différent. Pour comprendre pourquoi c'est impossible de gérer les défauts secondaires, il faut regarder le champ de lecture/écriture. Si on veut effectuer plusieurs écritures consécutives à la même adresse, le MSHR n'aura pas de quoi mémoriser les deux données à écrire. Il pourra mémoriser la première donnée à écrire, pas la seconde. Même chose lors d'une lecture : le champ lecture/écriture peut mémorisr la destination de la première lecture, pas de la seconde. L'avantage est que la MHA n'a besoin que des MSHR et de quelques circuits annexes. Les autres solutions rajoutent des circuits annexes pour gérer les défauts de cache secondaires, qui utilisent beaucoup de circuits. Le cout en circuit est donc élevé, mais le gain en performance est là. Passons maintenant aux caches non-bloquants qui autorisent les défauts de cache secondaire. La solution la plus simple consiste à utiliser ===Les MSHR adressés implicitement=== Avec les '''MSHR adressés implicitement''', il est possible de fusionner plusieurs accès mémoire à une même ligne de cache, mais sous une condition très importante : ces accès lisent/écrivent des mots mémoire différents. Par exemple, imaginons qu'une ligne de cache contienne 8 mots mémoire de 64 bits. Si un premier accès mémoire lit le mot mémoire numéro 7 (dernier mot mémoire de la ligne), et le second accès le mot mémoire numéro 3, alors la fusion est possible. Mais si deux accès mémoire veulent lire/écrire le mot mémoire numéro 7, alors la fusion n'est pas possible et le processeur se bloque, un ''pipeline stall'' survient. Un MSHR adressé implicitement est un MSHR simple, qui contient naturellement un numéro de ligne de cache (tag) et un bit de validité, sauf que le champ de lecture/écriture est dupliqué. Les différents champs lecture/écriture d'un MSHR sont regroupés dans une mémoire RAM/cache qui contient autant d'entrées qu'il y a de mot mémoire dans une ligne de cache. Chaque entrée de la table est associée à un mot mémoire de la ligne de cache et stocke des informations sur celui-ci. Une entrée mémorise, au minimum : * Un champ de lecture/écriture qui contient soit la destination de la lecture, soit la donnée à écrire. * Un bit R/W permettant d'interpréter correctement le champ de lecture/écriture. * Un bit de validité pour chaque entrée de la table, qui dit si un défaut de cache antérieur accède déjà à ce mot mémoire. La fusion de deux défauts de cache est ainsi assez simple. Un défaut de cache primaire/secondaire configure l'entrée associée au mot mémoire lu/écrit. Si un défaut secondaire ultérieur lit/écrit un mot mémoire différent (dont le bit de validité de l'entrée dans la table est à zéro), pas de conflit : il configure une autre entrée, vide. Mais s'il lit/écrit un mot mémoire pour lequel l'entrée de la table est occupée, il y a conflit, le processeur est bloqué par un ''pipeline stall'', une bulle de pipeline. Avec cette organisation, le nombre de MSHR indique combien de lignes de cache peuvent être lues en même temps depuis la mémoire. Quant au nombre d'entrées par MSHR, il détermine combien d'accès mémoires qui ne se recouvrent pas peuvent avoir lieu en même temps. ===Les MSHR adressés explicitement=== Les '''MSHR adressés explicitement''' n'ont pas les contraintes des MSHR adressés implicitement. Avec eux, il est possible d'avoir plusieurs défauts de cache pointant vers la même ligne de cache, mais aussi vers le même mot mémoire. De tels défauts de cache apparaissent sur les processeurs à exécution dans le désordre, mais sont très rares, voire inexistants, sur les processeurs ''in-order''. L'idée est encore que chaque MSHR est associée à une table mémoire qui mémorise des entrées. Cette table n'est autre qu'une mémoire FIFO. Sauf que cette fois-ci, une entrée n'est pas associée à un mot mémoire. Une entrée est associée à un défaut de cache. Une entrée mémorise là encore la destination de la lecture ou la donnée de l'écriture suivi du bit R/W, ainsi que d'un bit de validité par entrée, mais aussi : la position du mot mémoire lu dans la ligne de cache. C'est cette dernière information qui n'était pas présente dans les MSHR adressés implicitement. De plus, le nombre d'entrée par MSHR n'est pas égal au nombre de mots mémoires dans une ligne de cache, mais peut être arbitrairement grand. L'avantage d'un tel cache est qu'il est capable de traité les défauts secondaires concernant un même mot mémoire, et ce, car les accès à la ligne de cache concernée se produisent dans l'ordre de génération des défauts (la table mémoire est une FIFO). ===Les MSHRs inversés=== Généralement, plus on veut supporter de défauts de cache, plus le nombre de MSHR et d'entrées augmente. Mais au-delà d'un certain nombre d'entrées et de MSHR, les MSHR adressés implicitement et explicitement ont tendance à bouffer un peu trop de circuits. Utiliser une organisation un peu moins gourmande en circuits est donc une nécessité. Cette organisation plus économe se base sur des MSHR inversés. Les MSHR inversés ne contiennent qu'une seule entrée, en quelque sorte : au lieu d’utiliser n MSHR de m entrées chacun, on va utiliser n × m MSHR inversés. La différence, c'est que plusieurs MSHR peuvent contenir un tag identique, contrairement aux MSHR adressés implicitement et explicitement. Lorsqu'un défaut de cache a lieu, chaque MSHR est vérifié. Si jamais aucun MSHR ne contient de tag identique à celui utilisé par le défaut, un MSHR vide est choisi pour stocker ce défaut, et une requête de lecture en mémoire est lancée. Dans le cas contraire, un MSHR est réservé au défaut de cache, mais la requête n'est pas lancée. Quand la donnée est disponible, les MSHR correspondant à la ligne qui vient d'être chargée vont être utilisés un par un pour résoudre les défauts de cache en attente. ===Les MSHR intégrés au cache=== Certains chercheurs ont remarqué que pendant qu'une ligne de cache est en train de subir un défaut de cache, celle-ci reste inutilisée, et son contenu est destiné à être perdu une fois le défaut de cache résolu. Ils se sont dit que, plutôt que d'utiliser des MSHR séparés, il vaudrait mieux utiliser la ligne de cache pour stocker les informations sur les défauts de cache en attente dans cette ligne de cache. Pour éviter tout problème, il faut rajouter un bit dans les bits de contrôle de la ligne de cache, qui sert à indiquer que la ligne de cache est occupée : un défaut de cache a eu lieu dans cette ligne, et elle stocke donc des informations pour résoudre les défauts de cache. ==L'entrelacement mémoire== Nous venons de voir comment une mémoire cache peut gérer plusieurs accès mémoire simultanés. Il se trouve que ce genre d'optimisation dépasse largement le cadre du cache. Les mémoires RAM ne sont pas en reste, elles aussi ! Il est possible de pipeliner l'accès à une mémoire RAM, en utilisant des techniques dites d''''entrelacement'''. Précisons cependant que cette optimisation n'est pas utilisée sur les mémoires SDRAM ou DDR modernes, du moins pas sans modifications majeures. Mais divers ordinateurs assez anciens, dont des superordinateurs, ont utilisé cette technique. La technique demande d'utiliser plusieurs mémoires séparées. Sur les anciens ordinateurs, les mémoires en question sont des chips mémoire, à savoir des circuits intégrés de DRAM. Pour faire plus général, nous allons utiliser le terme de '''banques''', ou encore de bancs mémoire. La différence est qu'il existe des mémoires multi-banque, qui regroupent plusieurs banques indépendantes dans un seul boitier, dans un seul chip mémoire. En clair, de telles mémoires regroupent plusieurs sous-mémoires dans un seul circuit intégré. Il est aussi possible d'utiliser plusieurs chips mémoire séparés, chacun ayant une banque. Reste à combiner ces banques pour former un pseudo-pipeline. ===Utiliser plusieurs banques sans entrelacement=== Sans optimisation particulière, les adresses sont réparties dans les banques comme indiqué ci-dessous. Il s'agit de ce qui s'appelle un arrangement horizontal, et nous avions vu celui-ci dans le chapiutre sur les mémoires SDRAM. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Avec cet arrangement, les bits de poids fort de l'adresse sont utilisées pour sélectionner la banque adéquate, et le reste de l'adresse est envoyé sur le bus d'adresse. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Adresse de banque !! Adresse dans la banque |- | Quelques bits de poids fort || Reste de l'adresse |} L'entrelacement change cette répartition, afin d'accélérer les accès mémoire. L'entrelacement classique vise à améliorer les accès à des adresses mémoires consécutives, mais des formes plus évoluées d'entrelacement visent à optimiser des accès mémoire arbitraires. Voyons-les dans l'ordre, du plus simple au plus complexe. ===L'entrelacement classique=== Avec l''''entrelacement classique''', des adresses consécutives sont réparties dans des banques consécutives. Précisons que cette attribution des adresses n'implique absolument pas la mémoire virtuelle ou n'importe quel mécanisme dans le processeur. La répartition décide que telle adresse mémoire va dans telle banque, à telle adresse dans la banque. Elle est donc le fait du contrôleur mémoire, donc en dehors du processeur (les contrôleurs mémoires n’étaient pas encore intégrés dans le processeur à l'époque). [[File:Répartition des adresses dans une mémoire interleaved.png|centre|vignette|upright=1.5|Répartition des adresses dans une mémoire interleaved.]] L'entrelacement simple permet d'accélérer les accès mémoire qui se font à des adresses consécutives. Avec l'entrelacement, chaque accès mémoire tombe dans une banque mémoire différente, ce qui fait qu'on peut démarrer un nouvel accès mémoire à chaque cycle d'horloge. Pendant qu'une banque est occupée par un accès mémoire, on démarre le suivant dans une autre banque, et ainsi de suite. Le tout se termine soit quand on a épuisé toutes les banques libres, soit quand l'accès en rafales se termine. Pas besoin d'attendre que la mémoire ait fini sa lecture/écriture avant de démarrer la lecture/écriture suivante. [[File:Pipemining mémoire.png|centre|vignette|upright=2.5|Pipelining mémoire]] L'animation suivante illustre bien le processus, avec quatres banques. [[File:Interleaving.gif|centre|vignette|upright=2.5|Pipelining mémoire]] L'entrelacement simple utilise les bits de poids faible pour sélectionner la banque, et les bits de poids fort pour la case mémoire. [[File:Adresse mémoire d'une mémoire entrelacée.png|centre|vignette|upright=2|Adresse mémoire d'une mémoire entrelacée]] Précisons que le temps d'accès mémoire ne change pas beaucoup avec l'entrelacement. Par contre, on peut faire plus d'accès mémoire simultanés. On peut démarrer un accès mémoire par cycle d'horloge, mais l'accès en lui-même prend plusieurs cycles. Les mémoires à entrelacement ont donc un débit supérieur aux mémoires qui ne l'utilisent pas. L'entrelacement classique pourrait être utilisé pour accélérer les transferts entre mémoire RAM et cache, qui se font en blocs d'adresses consécutives. Mais de nos jours, il n'est pas utilisé dans cette optique. Il faut dire qu'il est redondant avec le mode rafale des mémoires DRAM. Les deux font globalement la même chose et on voit mal comment utiliser les deux en même temps. Le mode rafale étant plus simple à implémenter, et plus léger niveau utilisation du bus de commande mémoire, il est préféré à l'entrelacement. Par contre, l'entrelacement a eu son heure de gloire sur d'anciens processeurs qui n'avaient pas de mémoire cache, alors qu'ils disposaient d'un pipeline. L'exemple typique est celui des nombreux ''processeurs vectoriels'', que nous n'avons pas encore abordé à ce stade du cours. De tels processeurs avaient un pipeline, une unité de calcul fortement pipelinée, et parfois même de l'exécution dans le désordre et du renommage de registres ! ===L'entrelacement par décalage=== L'entrelacement simple est très utile pour les accès en rafle ou équivalents. Par contre, il existe de rares situations où il n'est pas optimal. Une de ces situations est celle des accès en enjambée (en ''stride''), où l'on accède en série à des adresses sont séparées par N mots mémoires. [[File:Accès par enjambées.png|centre|vignette|upright=2|Accès par enjambées.]] De tels accès surviennent quand un logiciel accède à des structures de données spécifiques, à savoir des tableaux de structures, des matrices, ou d'autres structures de données dans le genre. Un cas classique est celui du parcours d'une matrice colonne par colonne. Les matrices de nombres sont mémorisées ligne par ligne, ce qui fait que parcourir une colonne demande de passer d'une ligne à la suivante et de faire des grands sauts en mémoire RAM, mais tous espacés par la même distance. Avec l'entrelacement simple, les accès en enjambée sont moins performants que les accès à des adresses consécutives, mais cela ne fonctionne pas trop mal. Le pire cas est celui où l'on a N banques et où les données sont justement placées toutes les N adresses. Des accès consécutifs vont tous tomber dans la même banque, on ne peut plus accéder à des banques différentes en parallèle. Mais en dehors de ce cas, on voit une amélioration par rapport à la situation sans entrelacement. Pour obtenir des performances maximales pour les accès en enjambées, il faut répartir les mots mémoires dans la mémoire autrement. L'organisation idéale est la suivante. : Dans les explications qui vont suivre, la variable N représente le nombre de banques, qui sont numérotées de 0 à N-1. On commence par organiser les N premières adresses comme une mémoire entrelacée simple : l'adresse 0 correspond à la banque 0, l'adresse 1 à la banque 1, etc. Pour le bloc suivant, on décale tout d'une adresse, à savoir qu'on commence à remplir les banques à partir de la seconde, non de la première. Une fois la fin du bloc atteinte, on finit de remplir le bloc en repartant du début du bloc. Le troisième bloc d'adresse subit le même traitement, sauf qu'on commence à remplir à partir de la seconde banque. Et on poursuit l’assignation des adresses en décalant d'un cran en plus à chaque bloc. Ainsi, chaque bloc verra ses adresses décalées d'un cran en plus comparé au bloc précédent. Si jamais le décalage dépasse la fin d'un bloc, alors on reprend au début. [[File:Mémoire interleaved par décalage.png|centre|vignette|upright=1.5|Mémoire entrelacée par décalage.]] En faisant cela, on remarque que les banques situées à N adresses d'intervalle sont différentes. Dans l'exemple du dessus, nous avons ajouté un décalage de 1 à chaque nouveau bloc à remplir. Mais on aurait tout aussi bien pu prendre un décalage de 2, 3, etc. Dans tous les cas, on obtient un entrelacement par décalage. Ce décalage est appelé le pas d'entrelacement, noté P. Le calcul de l'adresse à envoyer à la banque, ainsi que la banque à sélectionner se fait en utilisant les formules suivantes : * adresse à envoyer à la banque = adresse totale / N ; * numéro de la banque = (adresse + décalage) modulo N ; * décalage = (adresse totale * P) mod N. Avec cet entrelacement par décalage, on peut prouver que la bande passante maximale est atteinte si le nombre de banques est un nombre premier. Seulement, utiliser un nombre de banques premier peut créer des trous dans la mémoire, des mots mémoires inadressables. Malgré ce défaut, la technique a été utilisée sur quelques ordinateurs, avec l'exemple notable du superordinateur ''Burroughs Scientific Processor''. Pour éviter cela, il y a plusieurs solutions. Par exemple, on peut faire en sorte que N et la taille d'une banque soient premiers entre eux : ils ne doivent pas avoir de diviseur commun. Mais en pratique, elles n'ont pas vraiment été implémentées dans une vraie machine, et sont restées à l'état de recherche, aussi je les passe sous silence. ===L'entrelacement pseudo-aléatoire=== Une dernière méthode de répartition consiste à répartir les adresses dans les banques de manière "pseudo-aléatoire". L'idée exacte est de faire passer l'adresse dans une '''fonction de hachage''', plus ou moins complexe. Pour rappel, une fonction de hachage prend une entrée de grande taille et fournit en sortie un résultat de petite taille. Idéalement, elles doivent donner des résultats aussi différents que possible pour des entrées similaires, histoire de simuler une sorte de pseudo-aléatoire. Ici, une adresse est transformée en un numéro de banque. Une fonction de hachage simple ne fait que permuter des bits de l'adresse pour obtenir son résultat. Elle ne fait qu'échanger des bits de place avant de couper l'adresse en deux morceaux : un pour la sélection de la banque, et un autre pour la sélection de l'adresse dans la banque. Cette permutation est fixe, et ne change pas suivant l'adresse. Des fonctions de hachage plus complexes font des XOR bit à bit entre certains bits de l'adresse. ==Les contrôleurs SDRAM/DDR avec "exécution dans le désordre"== Après avoir vu le cas des mémoires RAM, nous allons nous concentrer sur le cas particulier des mémoires SDRAM. Les mémoires SDRAM et DDR modernes sont capables de gérer plusieurs accès simultanés à la mémoire RAM, et leurs contrôleurs mémoire en font tout autant. C'est une différence majeure avec les mémoires asynchrones FPM/EDO, qui n'acceptaient qu'un seul accès mémoire à la fois. Leur contrôleur mémoire n'acceptait qu'un seul accès mémoire à la fois, c'était un contrôleur mémoire bloquant. Les contrôleurs mémoires des SDRAM sont eux non-bloquants et peuvent encaisser une dizaine d'accès mémoire à la fois. Peu de choses sont connues sur les contrôleurs de SDRAM/DDR modernes, les fabricants ne donnant que peu de détails dessus. Les rares simulateurs qui tentent de décrire leur fonctionnement, comme DRAM SIM I et II, sont particulièrement simples et ne vont pas dans le détail. Néanmoins, le peu qu'on sait est tout de même instructif. ===Rappel sur les SDRAM : tampon de ligne et commandes=== Les mémoires SDRAM sont des mémoires à tampon de ligne. Elles font un accès mémoire en deux étapes. La première étape recopie une ligne de N * 64 bits dans un tampon interne à la SDRAM. La seconde étape sélectionne une colonne, à savoir une donnée de 64 bits, qui est soit envoyée sur le bus de données pour une lecture, soit modifiée par une écriture. [[File:Mémoire à tampon de ligne.png|centre|vignette|upright=2|Mémoire à tampon de ligne]] Les accès mémoire sont traduits par un séquenceur mémoire en une série de commandes mémoires, qui sont séparées par des délais mémoire de quelques cycles d'horloge. Les délais sont très précis, et sont à respecter à la lettre. Une lecture ou une écriture se fait en maximum trois commandes : une commande PRECHARGE qui ferme la ligne précédemment utilisée, une commande ACT qui précise l'adresse de la ligne, et une commande READ ou WRITE qui précise l'adresse de la colonne et éventuellement la donnée à écrire. : Pour être plus précis, la commande PRECHARGE précharge les lignes de bits du plan mémoire à une tension particulière, ce qui les vide de leur contenu. Mais c'est un détail sans importance pour ce qui va suivre. Les SDRAM permettent de se passer de la première étape si des accès consécutifs se font dans la même ligne. Une fois activée, la ligne reste ouverte et on peut accéder plusieurs fois de suite dedans. On a alors juste à préciser l'adresse de colonne dedans. Si un accès mémoire accède à une ligne déjà activée, on dit que c'est un '''succès de page'''. Si ce n'est pas le cas, on doit fermer la ligne courante, rouvrir la ligne vboulue, et préciser la colonne. C'est alors un '''défaut de page'''. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | bgcolor="#FFA0FF" | PRECHARGE || bgcolor="#FFA0FF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || bgcolor="#A0FFFF" | READ (2) || bgcolor="#A0FFFF" | READ (3) || || || bgcolor="#A0FFFF" | READ (4) || bgcolor="#A0FFFF" | READ (5) || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | Donnée READ 1 || ||bgcolor="#A0FFFF" | Donnée READ 2 | bgcolor="#A0FFFF" | Donnée READ 3 || || || bgcolor="#A0FFFF" | Donnée READ 4 || bgcolor="#A0FFFF" | Donnée READ 5 |} Les SDRAM sont parfois capables de démarrer une commande avant que la précédente soit terminée. Mais le respect des délais mémoire est très limitant, ce qui fait qu'on ne peut pas parler de réel pipeline, comme c'est le cas sur les processeurs. Parlons plutot de '''commandes anticipées'''. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | || bgcolor="#A0FFFF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || || || bgcolor="#FFA0FF" | READ (2) || || || || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 | bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 | |} Un dernier point est que les chips mémoires de SDRAM sont composés de plusieurs banques, chacune étant une sorte de mini-mémoire miniature. Chaque banque a son propre décodeur, son propre tampon de ligne, ses propres multiplexeurs de colonne, sa logique de rafraichissement mémoire, etc. C'est comme si une SDRAM regroupait plusieurs mémoires séparées dans un même circuit intégré. [[File:Arrangement vertical.jpg|centre|vignette|upright=2.5|Mémoire multi-banques.]] ===La mise en attente des accès mémoire=== Un contrôleur de SDRAM peut accepter plusieurs accès mémoire en même temps. Mais cela ne signifie pas que le contrôleur sera prêt à les traiter immédiatement. Pour éviter tout problème de disponibilité, le contrôleur met en attente les accès mémoire que le processeur lui envoie, pour les exécuter dès que possible. Les accès mémoire sont mis en attente dans une mémoire FIFO, histoire de les exécuter dans leur ordre d'arrivée, notamment pour renvoyer les lectures dans l'ordre demandé. Il y a aussi une mémoire FIFO pour les données à écrire et une FIFO pour les données lues. Cette dernière sert au cas où le cache ne soit pas disponible quand on lui envoie la donnée lue. [[File:Module d'interface avec la mémoire.png|centre|vignette|upright=2.5|Contrôleur mémoire avec mise en attente des requêtes processeur et autres optimisations.]] Des optimisations sont possibles dès la mémoire FIFO. Par exemple, on peut regrouper plusieurs accès à des données consécutives en un seul accès en rafale. Le contrôleur mémoire analyse les requêtes mises en attente et détecte si certaines se font à des adresses consécutives. Si c'est le cas, il fusionne ces requêtes en une lecture/écriture en rafale. Une telle optimisation est appelée la '''combinaison de lecture''' pour les lectures, et la '''combinaison d'écriture''' pour les écritures. L'optimisation précédente ne paraissent pas payer de mine, mais elles deviennent plus compréhensible quand on sait que certains contrôleurs mémoire peuvent mettre en attente beaucoup de données. Par exemple, l'''Intel E8870 - Scalable Memory Controller'' peut mettre en attente 64 lignes de cache dans la mémoire FIFO, soit 8 kibioctets de RAM ! Deux pages mémoire si la mémoire virtuelle utilise des pages de 4 kibioctets ! ===Les lectures anticipées : une forme d'OOO mémoire=== La mise en attente des accès mémoire est une optimisation intéressante, mais pas mirifique. Elle permet surtout de ne pas bloquer le processeur si le contrôleur mémoire a du travail sur la planche. Cependant, elle peut être optimisée quand on se rend compte d'une chose : le processeur ne voit que les lectures, pas les écritures. Pour les écritures, il les envoie au contrôleur mémoire et ce dernier fait le travail demandé, le processeur n'est pas prévenu quand une écriture se termine. Mais pour les lectures, c'est différent, car il reçoit la donnée lue. En conséquence, il est possible d'optimiser les écritures sans que le processeur ne voit quoique ce soit. Une optimisation possible est alors de changer l'ordre des accès mémoire, histoire de retarder les écritures ou au contraire de les faire en avance. La raison est que les mémoires SDRAM n'aiment pas quand on alterne lectures et écritures. Les délais mémoire, les fameux ''timings'' mémoire, sont clairs : faire une suite de lecture ou une suite d'écriture est plus performant que d'alterner entre lectures et écritures. L'idée est alors de changer l'ordre des écritures de manière à les faire en bloc, idem pour les lectures. L’optimisation est possible, mais à condition que le processeur n'y verra que du feu. Or, vous l'avez deviné, il y a des cas où ces réorganisations ne sont pas possibles, et vous avez sans doute trouvé que la situation était familière. L'optimisation en question est une forme d'exécution dans le désordre des accès mémoire, réalisée par le contrôleur mémoire lui-même ! Et qui dit exécution dans le désordre dit : problèmes liés aux dépendances de données. Changer l'ordre des écritures n'est pas toujours possible, notamment en raison des dépendances RAW, WAR et WAW. Pour que le processeur n'y voit que du feu, il faut respecter plusieurs critères. Premièrement, les lectures doivent être renvoyées au processeur dans l'ordre. Et quand je dis dans l'ordre, cela veut dire : dans l'ordre d'envoi des lectures au contrôleur mémoire. Les écritures aussi doivent se faire dans l'ordre, histoire qu'une série d'écriture donne le bon résultat final. Le troisième critère est qu'une lecture doit donner le résultat de la dernière écriture ''à la même adresse''. Pour le dire autrement, il faut juste détecter les dépendances RAW. Concrètement, si on a une série de lectures et d'écritures alternées, on peut regrouper les lectures et les écritures dans deux paquets séparés, à condition qu'aucune lecture ne lise une adresse écrite par une écriture. Mais si une lecture est dépendante d'une écriture, alors la lecture doit attendre que l'écriture se termine, idem pour les lectures suivantes. Une implémentation basique remplace la mémoire FIFO vue au-dessus, par deux mémoires FIFOs : une pour les lectures, une pour les écritures. Lorsqu'un accès mémoire arrive au séquenceur, il regarde si c'est une lecture ou une écriture et place l'accès mémoire dans la file adéquate. Le fait que ce soit des FIFOs garantit que les lectures se font dans l'ordre, idem pour les écritures. En sortie des deux FIFOs, le séquenceur mémoire détecte les dépendances RAW. Précisément, quand une lecture sort de la mémoire FIFO, il consulte la file d'attente des écriture pour voir si la lecture est dépendante d'une écriture en attente. La lecture attend si c'est le cas, et les écritures sont exécutées à la place. Séparer lectures et écriture est une source d'optimisation. Il est par exemple possible d'utiliser le réacheminement lecture sur écriture (''Store to load forwarding''). Il s'agit d'un équivalent de l’optimisation du même nom, utilisée dans le cadre de la désambiguïsation mémoire. Imaginez qu'une lecture accède à une donnée pas encore écrite, en attente dans le contrôleur mémoire. C'est une dépendance RAW assez claire. Dans ce cas, le contrôleur mémoire peut renvoyer la donnée directement depuis ses mémoires FIFOs, sans faire d'accès mémoire en lecture. Le contrôleur mémoire a tendance à privilégier les lectures, car celles-ci sont cruciales pour l'exécution dans le désordre. Plus elles se font tôt, plus vite le processeur recevra les données et pourra démarrer les instructions dépendantes de cette donnée. Le séquenceur a donc tendance à piocher en priorité dans la file de lecture, plutôt que dans la file d'écriture. A vrai dire, dans le cas idéal, les écritures ne sont faites que quand la file d'écriture est pleine ou quasi-pleine... Prenons l'exemple des coprocesseurs IO 81341 et 81342, qui étaient en réalité des ''chipsets'' intégrant un contrôleur SDRAM. Le ''chipset'' avait 5 ports : un pour les processeurs, un pour le pont sud (''southbridge'') et trois pour des canaux DMA. Pour le port processeur, il n'y avait pas de réordonnancement et ce port utilisait une seule mémoire FIFO. Les autres ports utilisaient l'optimisation qu'on vient de voir, et avaient vraisemblablement des files séparées pour les lectures et écritures. Le séquenceur mémoire vérifiait les dépendances mémoire de type RAW et autres. [[File:Double file d'attente pour les lectures et écritures.png|centre|vignette|upright=2.5|Double file d'attente pour les lectures et écritures]] ===Le ré-ordonnancement des commandes mémoires=== L'optimisation précédente est peu poussée, comparé aux formes d'exécution dans le désordre que les processeurs utilisent. Et on peut se demander si une exécution dans le désordre plus poussée est possible. La réponse est oui : un contrôleur mémoire peut faire des réorganisations bien plus poussées. Par contre, il faut faire une précision très importante : le contrôleur mémoire ne remet pas les accès mémoire dans l'ordre avant de les envoyer au processeur. C'est le processeur qui remet en ordre les accès mémoire, dans sa ''load queue''. En clair : le processeur voit les accès mémoire dans le désordre, c'est lui qui les remet en ordre. Pour cela, les requêtes mémoire sont "numérotées", à savoir qu'on leur attribue un identifiant binaire. Le contrôleur mémoire exécute les lectures/écritures dans le désordre, mais garde la trace de leur identifiant. Il sait dans quel ordre il exécute les accès mémoire et connait leur latence. Ce qui fait qu'il sait que la donnée lue à tel instant est associée à la requête mémoire numéro X. Pour les lectures, il renvoie la donnée lue avec l'identifiant. Le processeur sait alors à quelle lecture la donnée lue correspond et il se débrouille en interne pour gérer la situation. Maintenant que ces précisions sont faites, posons cette question : dans quelles situations est-il pertinent de faire des accès mémoire dans le désordre ? La réponse est : quand plusieurs accès à une même ligne ne sont pas consécutifs, qu'il y a des accès entre les deux. Après ré-ordonnancement, ces accès mémoire à une même ligne sont exécutés l'un à la suite de l'autre, ce qui est beaucoup plus rapide. Pour rendre le tout plus concret, voici un exemple. Imaginez que l'on sait les 3 accès mémoire suivants : * Une lecture ligne A ; * PRECHARGE + ACT ; * Une écriture ligne B ; * PRECHARGE + ACT ; * Une lecture ligne A. Sans réordonnancent, on doit émettre deux commandes PRECHARGE quand on passe d'une ligne à l'autre, et il faut ajouter les commandes ACT avec. pour éviter cela, le contrôleur mémoire peut retarder l'écriture, ou au contraire avancer la seconde lecture. le résultat est alors le suivant : * Lecture ligne A ; * Lecture ligne A * PRECHARGE + ACT ; * Une écriture ligne B. On a donc deux accès consécutifs à la même ligne, suivi par une écriture dans une autre ligne. La technique marche parce que l'on a un mix adéquat de lectures et d'écritures. Et encore une fois, il faut éviter d'intervertir lectures et écritures à une même adresse, sous peine de problèmes. Encore une fois, les dépendances RAW posent problème ! L'implémentation est assez simple : la ou les mémoires FIFOs précédentes sont remplacées par des mémoires similaires aux fenêtres d'instruction. Le contrôleur mémoire vérifie à chaque cycle les accès en attente, et vérifie à quelle ligne ils accèdent. Il priorise alors les accès qui tombent dans la ligne ouverte : ceux-là sont exécutés avant les autres. S'il n'y en a pas, il prend l'accès mémoire le plus ancien (ordre FIFO). Il teste aussi les dépendances mémoires RAW avant d'envoyer des commandes à la mémoire DDR/SDRAM. une telle solution est appelée l''''algorithme ''FR-FCFS''''' (''First Ready-First Come First Serve''). ===Les optimisations liées à la présence de plusieurs banques=== Les optimisations précédentes sont décuplées par la présence de plusieurs banques dans la mémoire SDRAM. Il est en effet possible de faire plusieurs accès mémoire en même temps, dans des banques différentes. Il est possible de lancer un accès mémoire dans deux banques en même temps, par exemple. La seule contrainte est que la SDRAM est limitée à 4 banques actives en même temps. {|class="wikitable" |+ Pipelining basique sur les SDRAM |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 |- ! Banque Numéro 1 | || || || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || || |- ! Banque Numéro 2 | || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || |- ! Banque Numéro 3 | || || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || |} Les optimisations précédentes peuvent s'appliquer par banque. Il est par exemple possible d'utiliser une mémoire FIFO par banque. Ainsi, les accès dans une même banque se feront en série, dans l'ordre d'arrivée, mais des accès à des banques différentes se feront dans le désordre. Cela ne pose pas de problème car les accès se font à des adresses différentes, ce qui fait qu'il n'y a pas de conflits majeurs. Il y a cependant un risque que les lectures se fassent dans le désordre si on n'y prend pas garde. [[File:Gestion parallèle des banques.png|centre|vignette|upright=2.5|Gestion parallèle des banques.]] il est aussi possible d'utiliser une file séparée pour les lectures et les écriture, pour chaque banque. Les performances sont alors améliorées comparé à deux files globales pour toute la SDRAM. En idem avec la réorganisation des accès mémoire, pour regrouper les accès à une même ligne. ==L'entrelacement sur les mémoires SDRAM== L''''entrelacement''' fonctionne sur les mémoires SDRAM, mais il doit être fortement modifié. Concrètement, tout ce qui a étré dit plus est inapplicable pour les SDRAM, du fait de la présence du tampon de ligne. Des adresses consécutives atterrissent déjà dans la même ligne, ce qui fait qu'on n'a pas besoin d'entrelacement au niveau d'une ligne. Et ne parlons pas de ce la présence de rangées et de canaux mémoire ! Cependant, il peut y avoir un entrelacement lié à la présence des banques SDRAM. L'entrelacement est géré juste avant le séquenceur mémoire. Le '''circuit d'entrelacement''' intervertit certains bits de l'adresse lors des accès mémoires. On trouve aussi des mémoires FIFOs pour mettre en attente les requêtes mémoire provenant du processeur. Elles permettent de recevoir plusieurs accès mémoire consécutifs. Si vous vous demandez comment plusieurs accès mémoire consécutifs peuvent avoir lieu, sachez que c'est une bonne question. Pour le moment, nous allons dire que ces accès proviennent du processeur ou de la mémoire cache, sans expliquer comment. Le contrôleur mémoire reçoit une série d'accès mémoire assez rapprochés, qu'il doit exécuter au mieux. [[File:Controleur mémoire d'une SDRAM avec entrelacement.png|centre|vignette|upright=2.5|Contrôleur mémoire d'une SDRAM avec entrelacement.]] Pour gérer l'entrelacement, le contrôleur de SDRAM prend en entrée l'adresse envoyée par le processeur, et la découpe en plusieurs champs : un pour sélectionner la banque adéquate, un autre pour la ligne, un autre pour la colonne, un autre pour la rangée. Une solution simple désactive l'entrelacement. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de banque || Adresse de ligne || Adresse de colonne |} Si cette solution fonctionne bien si les accès mémoire sont assez aléatoires, en pratique, mieux vaut utiliser l'entrelacement. ===L'entrelacement de banques/lignes=== Une première méthode d'entrelacement est l''''entrelacement de banques'''. Elle répartit deux lignes consécutives dans deux banques différentes. Pour comprendre l'idée, prenons un exemple. Imaginons une mémoire avec deux banques et 4 lignes. Imaginons qu'on parcoure/balaye la mémoire RAM en partant des adresses basses. Sans entrelacement de ligne, les accès se feront comme suit, de gauche à droite : {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Banque numéro 1 !! colspan="4" | Banque numéro 2 |- | Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 || Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 |} Avec l'entrelacement de banques, les accès se feront comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 |- | Ligne 1 || Ligne 1 || Ligne 2 || Ligne 2 || Ligne 3 || Ligne 3 || Ligne 4 || Ligne 4 |} Avec N banques, la première ligne ira dans la première banque, la seconde ligne dans la seconde, et ainsi de suite jusqu'à la énième banque qui va dans la banque numéro N. Et la ligne N+1 repart dans la première banque. L'avantage est que passer d'une ligne à la suivante est assez rapide. Au lieu de devoir fermer la première ligne et ouvrir la suivante on ouvrira directement la ligne suivante dans une banque différente, ce qui sera plus rapide. La commande PRECHARGE pourra être envoyée à la seconde banque en avance. Pour cela, l'adresse est découpée comme suit. {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne (précise à l'octet) |} ===L'entrelacement de rangées=== Il est aussi possible de faire la même chose, mais avec les rangées. Pour simplifier fortement, une rangée est simplement un chip mémoire. En réalité, les rangées ne sont pas des chip mémoire, mais un ensemble de chips mémoire regroupés ensemble histoire d'atteindre les 64 bits du bus de données. Par exemple, une rangée peut combiner 8 chips mémoire avec un bus de données de 8 bits chacun pour obtenir les 64 bits du bus de données d'une SDRAM. Mais nous passons ce détail sous silence dans les explications qui vont suivre, par souci de simplification. Pour faire comprendre l'entrelacement de rangée, le mieux est d'utiliser un exemple, le même que précédemment. Sans entrelacement de rangée, on change de chip mémoire une fois qu'on a balayé toutes ses adresses. {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Rangée numéro 1 !! colspan="4" | Rangée numéro 2 |- | Banque 1 || Banque 2 || Banque 3 || Banque 4 || Banque 1 || Banque 2 || Banque 3 || Banque 4 |} Avec l'entrelacement de ligne, on change de rangée dès qu'on change de banque/ligne. {|class="wikitable" |+ Adresse mémoire |- ! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 |- | Banque 1 || Banque 1 || Banque 2 || Banque 2 || Banque 3 || Banque 3 || Banque 4 || Banque 4 |} En clair, quand on a épuisé toutes les banques dans une même rangée, on passe à la rangée suivante au lieu de rester dans la même rangée. Notons que dans l'exemple précédent, on a combiné l'entrelacement de rangée et de banque, mais on aurait pu utiliser l'entrelacement de rangée seul. Mais ce n'est pas le cas le plus courant en pratique. Toujours est-il qu'en combinant entrelacement de rangée et de ligne, le découpage de l'adresse est le suivant : {|class="wikitable" |+ Adresse mémoire |- | Adresse de ligne || Numéro de rangée || Adresse de banque || Adresse de colonne (précise à l'octet) |} D'autres méthodes d'entrelacement sont possibles, mais elles sont plus complexes. Chaque contrôleur mémoire fait un peu à sa sauce. Ils permettent en général de choisir entre plusieurs entrelacements. Au minimum, ils permettent de désactiver l'entrelacement, d'activer un entrelacement de ligne, et d'autres entrelacements plus complexes, dont certains sont propriétaires et tenus secrets par le fabricant. ===L'entrelacement avec le ''dual channel''=== [[File:Dual channel slots.jpg|vignette|Slots mémoires avec ''dual channel''.]] L'usage du ''dual channel'' complique encore l'entrelacement. Là encore, il y a deux grandes solutions : avec et sans entrelacement des canaux mémoire. Rappelons le principe : deux barrettes de RAM sont accédées en parallèle. Et pour cela, il faut utiliser l'entrelacement. Typiquement, chaque barrette mémoire fournit 64 bits, ce qui fait que l'on peut accéder à 128 bits d'un seul coup, par exemple avec un accès en rafale. Sans ''dual channel'', la première barrette correspond à la moitié haute de la RAM, la seconde barrette correspond à la moitié basse. Avec ''dual channel'', une forme spécifique d'entrelacement est activée. Concrétement, deux blocs de 64 bits sont placés dans des canaux mémoire séparés. Idem avec du triple ou quadruple canal, mais c'est alors trois ou quatre blocs de 64 bits qui sont dispersés dans des canaux consécutifs. Le découpage de l'adresse est alors le suivant : {|class="wikitable" |+ Adresse mémoire |- ! colspan="6" | Sans entrelacement |- | Numéro de canal/barrette || Reste de l'adresse || Position de l'octet dans un bloc de 64 bits |- | colspan="6" | |- ! colspan="6" | Avec entrelacement |- | Reste de l'adresse || Numéro de canal/barrette || Position de l'octet dans un bloc de 64 bits |} [[File:Décodage d'adresse avec dual channel.png|centre|vignette|upright=2|Décodage d'adresse avec dual channel.]] Les contrôleurs de SDRAM précédents sont assez basiques et ne représentent pas les contrôleurs les plus évolués. Ils étaient utilisés sur les anciens PC, à une époque où ils étaient encore sur la carte mère du processeur. Mais de nos jours, le contrôleur mémoire est intégré au processeur. Et il incorpore de nombreuses optimisations afin de gagner en performances. Malheureusement, ces optimisations sont elles-mêmes dépendantes des optimisations intégrées dans le processeur, ce qui fait qu'on ne peut pas en parler ici. Elles demandent que le contrôleur mémoire reçoive plusieurs accès mémoire simultanés, ce qui n'a aucun sens à ce stade du cours. Aussi, nous allons les passer sous silence pour le moment. Nous les verrons dans le chapitre sur le parallélisme mémoire. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=La désambiguïsation mémoire | prevText=La désambiguïsation mémoire | next=Les processeurs superscalaires | nextText=Les processeurs superscalaires }} </noinclude> pjpvua3q86udm40fuypqxl2vlnos5zm 764749 764748 2026-04-23T23:12:31Z Mewtow 31375 /* L'entrelacement sur les mémoires SDRAM */ 764749 wikitext text/x-wiki Dans ce chapitre, nous allons voir les techniques qui permettent de gérer plusieurs accès mémoire simultanés directement au niveau du cache ou de la mémoire RAM. Par plusieurs accès mémoire simultanés, vous pensez sans doute à l'usage de cache multiports, voire de mémoires RAM multiport. C'est en effet une solution, mais ce n'est clairement pas la seule. Par exemple, il est possible de pipeliner l'accès au cache, voire à la mémoire RAM. Il existe de nombreuses techniques de '''parallélisme mémoire''', et nous allons les voir dans ce chapitre. Pour donner un exemple, il est possible de pipeliner les accès mémoire. Il est possible de pipeliner l'accès au cache et/ou l'accès à la mémoire, et nous verrons comment faire dans ce chapitre. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, qu'ils aient ou non un pipeline dynamique. Par contre, pipeliner la mémoire est une technique ancienne, aujourd'hui peu utilisée. Elle était utilisée sur des très vieux ordinateurs, qui avaient un pipeline mais pas de mémoire cache. Mais de nos jours, les mémoires SDRAM et DDR ne sont pas adaptées pour ça, il est très difficile de les pipeliner correctement. Le parallélisme mémoire est utile aussi bien sur des processeurs à émission dans l'ordre que dans le désordre. Par exemple, un processeur ''in-order'' avec des lectures non-bloquantes peut e, profiter. Il peut techniquement lancer une seconde lecture, après avoir rencontré une première lecture non-bloquante. En général, le nombre de lectures consécutives est cependant limité à moins d'une dizaine. À l'opposé, les processeurs sans lectures non-bloquantes profitent pas du parallélisme mémoire. À la rigueur, ils peuvent en profiter s'ils intègrent des techniques de préchargement. Pendant que le processeur exécute une lecture/écriture, il peut précharger une autre donnée en parallèle. Inutile de dire que sans pipeline processeur, le parallélisme mémoire ne sert pas à grand-chose. ==Le ''line fill buffer'' et les techniques associées== Lors d'un défaut de cache, le processeur doit attendre que toute la ligne de cache soit chargée avant d'être utilisable. Or, la taille d'une ligne de cache est supérieure à la largeur du bus mémoire, ce qui fait qu'une ligne de cache est chargée en plusieurs fois, morceaux par morceaux, mot mémoire par mot mémoire. Le chargement peut se faire directement dans le cache, mais ce n'est pas une solution très pratique. À la place, beaucoup de processeurs ajoutent une mémoire tampon entre la RAM et le cache, appelée le '''tampon de remplissage de ligne''' (''line-fill buffer'') dans les processeurs modernes. Lors d'un défaut de cache, le processeur charge la donnée de la RAM dans le tampon de remplissage de ligne, mot mémoire par mot mémoire. Une fois plein, le tampon de remplissage de ligne est recopié dans la ligne de cache. Le tampon de remplissage de ligne contient une ligne de cache, si ce n'est que certains bits de contrôle ne sont pas présents. Le tampon de remplissage de ligne contient un bit de validité pour chaque mot mémoire de la ligne de cache, qui indiquent si le mot mémoire a été chargé. Par exemple, prenons un processeur 64 bits, qui gère donc des mots mémoire de 8 octets, avec des lignes de cache 256 octets/32 mots mémoire. Le tampon de remplissage de ligne contiendra 32 bits de validité. Si le processeur a chargé les 6 premiers mots mémoire, les 6 premiers bits de validité seront mis à 1, les autres seront encore à zéro. Il faut noter que la disponibilité de la ligne complète se détermine assez facilement en faisant un ET logique entre tous les bits de validité. Le processeur sait ainsi quand la ligne de cache est disponible entièrement et donc quand la transférer dans le cache. La même chose existe avec une hiérarchie de cache, sauf que l'on trouve une mémoire tampon entre chaque niveau de cache. Il y a un tampon de remplissage de ligne entre le cache L1 et le cache L2, entre le cache L2 et le L3, etc. Si le cache ne gère qu'un seul défaut de cache à la fois, le ''fill line buffer'' est une mémoire très simple, qui ne mémorise qu'une seule ligne de cache. Et cela vaut aussi bien pour un cache bloquant que non-bloquant. Mais sur les caches capables de gérer plusieurs défauts simultanés, le tampon de remplissage de ligne est une mémoire de type FIFO ou LIFO, capable de mémoriser plusieurs lignes de cache. Notons que le tampon de remplissage de ligne est très utile pour implémenter certaines techniques, par exemple le contournement du cache. Nous avions vu dans le chapitre sur le cache que certains accès mémoire doivent contourner le cache, pour des raisons de cohérence des caches. C'est notamment nécessaire pour accéder aux périphériques, mais c'est aussi utile pour des raisons de performances dans des cas très spécifiques. Les accès qui contournent le cache se font directement dans le tampon de remplissage de ligne : le processeur écrit ou lit les données depuis ce tampon de remplissage de ligne, sans accéder au cache. ===L'''early restart'' et le ''critical word load''=== La présence du ''line buffer'' permet une optimisation assez intéressante, qui permet de réduire la latence des défauts de cache. L'optimisation consiste à lire un mot mémoire dans le tampon de remplissage de ligne, même si la ligne de cache complète n'a pas encore été chargée. Il existe deux manières de faire cela, qui portent les noms d'''early restart'' et de ''critical word load''. La première est la version la plus simple, la seconde est plus complexe mais plus performante. Avec la technique d''''''early restart''''', la ligne de cache est chargée normalement, en partant de son premier mot mémoire. Dès que le mot mémoire lu/écrit par le processeur est copié dans le ''line fill buffer'', il est envoyé au processeur immédiatement. Illustrons le tout par un exemple, où une ligne de cache fait 16 mots mémoire. Le processeur effectue une lecture, qui lit le 5ème mot mémoire. Sans ''early restart'', le processeur doit charger les 16 mots mémoire avant de faire la lecture dans le cache. Avec ''early restart'', le processeur reçoit la donnée dès que le 5ème mot mémoire est disponible. Le processeur doit attendre que les 4 premiers mots mémoire soient chargés, puis le 5ème mot mémoire arrive et est envoyé directement au processeur, il est lu directement depuis le ''line fill buffer''. Les 11 mots mémoire suivants sont ensuite chargés dans le cache pendant que le processeur fait des calculs dans son coin. Le ''critical word load'' est une optimisation de la technique précédente où le chargement de la ligne de cache commence directement à la donnée demandée par le processeur. Pour reprendre l'exemple précédent, où le processeur demande le 5ème mot mémoire sur 16, le ''critical word load'' charge le 5ème mot mémoire en premier et l'envoie au processeur, ce qui fait qu'il est chargé très rapidement. Pas besoin d'attendre que le processeur charge les 4 mots mémoire précédents comme avec l'''early restart''. Dans le détail, le processeur charge le 5ème mot mémoire en premier, puis charge les 11 suivants, et termine par les 4 mots mémoire du début. En clair, le ''critical word load'' commence par charger le mot mémoire lu, puis les blocs suivants, avant de revenir au début du bloc pour charger les blocs restants. Ainsi, la donnée demandée par le processeur sera la première disponible. Pour cela, l'organisation du tampon de remplissage de ligne est modifiée de manière à rendre cela possible. Il a une taille égale à une ligne de cache complète, qui contient elle-même plusieurs mots mémoire. Dans le ''line-fill buffer'', chaque mot mémoire est stocké avec un tag, qui indique l'adresse du mot mémoire stocké dans le ''line-fill buffer''. Le ''line-fill buffer'' est donc un cache un peu particulier, qui fonctionne comme un cache du point de vue du processeur, comme une mémoire FIFO pour les transferts avec le cache. Ainsi, un processeur qui veut lire dans le cache après un défaut peut accéder à la donnée directement depuis le tampon de remplissage de ligne, alors que la ligne de cache n'a pas encore été totalement recopiée en mémoire. ==Les caches pipelinés== Il est possible de pipeliner l'accès au cache, ce qui demande juste de rajouter des registres au bon endroits et quelques circuits de contrôle. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, même ceux avec un pipeline dynamique. Et l'implémentation peut se faire de plusieurs manières différentes. Il faut dire que découper une mémoire cache en plusieurs étapes peut se faire de plusieurs manière. Commençons par la plus simple. Beaucoup de processeurs des années 2000 avaient une fréquence peu élevée comparée aux standards d'aujourd'hui, ce qui fait que leur cache avait bien un temps d'accès d'un cycle d'horloge. Mais l'accès au cache se faisait en deux cycles d'horloge : un cycle pour calculer une adresse et un cycle pour l'accès mémoire proprement dit. Et ces deux étapes étaient pipelinées, à savoir que deux micro-opérations mémoire peuvent s'exécuter en même temps : une dans l'étage de calcul d'adresse, une autre dans le cache. L'unité mémoire était donc pipelinée, alors que l'accès au cache ne l'était pas. Les processeurs qui implémentaient cette technique regroupent les micro-architectures K5 et K6 d'AMD, les processeurs Intel de micro-architecture P6 (Pentium 2 et 3) et quelques autres. Il est aussi possible de pipeliner l'accès au cache lui-même. Avec un cache pipeliné, l'accès au cache ne se fait pas en un seul cycle, mais en plusieurs. Cependant, on peut lancer un nouvel accès au cache à chaque cycle d'horloge, comme un processeur pipeliné lance une nouvelle instruction à chaque cycle. L'implémentation est assez simple : il suffit d'ajouter des registres dans le cache. Pour cela, on profite que le cache est composé de plusieurs composants séparés, qui échangent des données dans un ordre bien précis, d'un composant à un autre. De plus, le trajet des informations dans un cache est linéaire, ce qui les rend parfois pour l'usage d'un pipeline. ===Le ''pipelining'' des caches ''direct-mapped''=== Voici ce qui se passe avec un cache directement adressé. Pour rappel ce genre de cache est conçu en combinant une mémoire RAM, généralement une SRAM, avec quelques circuits de comparaison et un MUX. L'accès au cache se fait globalement en deux étapes : la première lit la donnée et le ''tag'' dans la SRAM, et on utilise les deux dans une seconde étape. Nous avions vu il y a quelques chapitre comment pipeliner des mémoires, dans le chapitre sur les mémoires évoluées. Et bien ces méthodes peuvent s'utiliser pour la mémoire RAM interne au cache !L'idée est d'insérer un registre entre la sortie de la RAM et la suite du cache, pour en faire un cache pipéliné, à deux étages. Le premier étage lit dans la SRAM, le second fait le reste. L'implémentation sur les caches associatifs à plusieurs voies est globalement la même à quelques détails près. [[File:Cache directement adressé pipeliné - deux étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - deux étages]] Avec le circuit précédent, il est possible d'aller plus loin, cette fois en pipelinant l'accès à la mémoire RAM interne au cache. Pour comprendre comment, rappelons qu'une mémoire SRAM est composée d'un plan mémoire et d'un décodeur. L'accès à la mémoire demande d'abord que le décodeur fasse son travail pour sélectionner la case mémoire adéquate, puis ensuite la lecture ou écriture a lieu dans cette case mémoire. L'accès se fait donc en deux étapes successives séparées, on a juste à mettre un registre entre les deux. Il suffit donc de mettre un gros registre entre le décodeur et le plan mémoire. [[File:Cache directement adressé pipeliné - trois étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - trois étages]] Et on peut aller encore plus loin en découpant le décodeur en deux circuits séparés. En effet, rappelez-vous le chapitre sur les circuits de sélection : nous avions vu qu'il est possible de créer des décodeurs en assemblant des décodeurs plus petits, contrôlés par un circuit de prédécodage. Et bien on peut encore une ajouter un registre entre ce circuit de prédécodage et les petits décodeurs. : Théoriquement, toute l'adresse est fournie d'un seul coup au cache, la quasi-totalité des processeurs présentent l'adresse complète à un cache pipeliné. Mais le Pentium 4 fait autrement. Il faut noter que les premiers étages manipulent l'indice dans la SRAM, qui est dans les bits de poids faible, alors que les étages ultérieurs manipulent le ''tag'' qui est dans les bits de poids fort. Les concepteurs du Pentium 4 ont alors décidé de présenter les bits de poids faible lors du premier cycle d'accès au cache, puis ceux de poids fort au second cycle. Pour cela, l'ALU fonctionnait à une fréquence double de celle du processeur, tout comme le cache L1. Il n'y avait pas de pipeline proprement dit, mais cela réduisait grandement la latence d'accès au cache. ===Le ''pipelining'' des caches splittés/sériels=== Il est aussi possible de pipeliner un cache dits splittés, aussi appelés à accès sériel. Pour rappel, les caches à accès sériel vérifient si il y a succès ou défaut de cache, avant d'accéder aux lignes de cache en cas de succès. Ils font donc différemment des autres caches, qui accèdent à une ligne de cache, avant de déterminer s'il y a succès ou défaut en lisant le tag de la ligne de cache. Les caches sériels disposent de deux SRAM : une pour les tags des lignes de cache et une pour les données. Ils accèdent à la SRAM pour les tags, avant d’accéder à la SRAM des données en cas de succès de cache. Vu que l'accès se fait en deux étapes, une vérification des tags suivie de la lecture/écriture des données, il est facile à pipeliner. Pipeliner le cache permet de régler le problème des accès au cache L1, et elle est tout le temps utilisé sur les processeurs modernes. Mais que faire en cas de défaut de cache ? ==Les caches non bloquants== Un '''cache bloquant''' est un cache auquel le processeur ne peut pas accéder pendant un défaut de cache. Il faut attendre que la lecture ou écriture en RAM soit terminée avant de pouvoir utiliser de nouveau le cache. Un '''cache non bloquant''' n'a pas ce problème : on peut l'utiliser pendant un défaut de cache. Les caches non bloquants permettent de démarrer une nouvelle lecture ou écriture alors qu'une autre est en cours, ce qui permet d'exécuter plusieurs lectures ou écritures en même temps. ===Les ''Miss Handling Status Registers''=== Lors d'un défaut de cache, la mémoire RAM est consultée pendant le défaut de cache, mais le cache est inutilisé. Un défaut de cache n'utilise pas le cache, ce n'est qu'un accès en mémoire RAM, sur le bus mémoire (ou un accès aux niveaux de cache inférieurs, peu importe). Le cache en lui-même est laissé libre, rien n’empêche d'y accéder, il est en réalité intrinsèquement non-bloquant. Les caches, bloquants comme non-bloquants, sont en réalité composés d'une mémoire cache proprement dite, entourée de circuits qui servent d'interface entre le processeur et le cache lui-même. Et parmi les circuits tout autour, certains gèrent l'accès au cache lors d'un défaut de cache. Ils sont regroupés sous le terme de '''''Miss Handling Architecture''''' (MHA). La différence entre un cache bloquant et un cache non-bloquant est en réalité liée à l'implémentation de la MHA. Les caches bloquants coupent volontairement l'accès au cache lors d'un défaut, car les défauts sont plus simples à gérer ainsi. Le défaut de cache rapatrie une donnée depuis la RAM, qui sera écrite dans le cache. Et il ne faut pas qu'une tentative d'accès à cette donnée ait lieu avant qu'elle ne soit chargée. Mais ce blocage est général et touche tout le cache, alors que seule une ligne de cache est concernée par le défaut de cache. L'idée derrière un cache non-bloquant est que seule la ligne de cache est bloquée, mais les autres sont accessibles. L'idée est alors de mémoriser les lignes de cache concernées par le défaut de cache, afin d'en bloquer l'accès. À chaque accès, on vérifie si la ligne de cache est déjà réservée par un défaut de cache. La lecture/écriture est alors bloquée si c'est le cas, mais elle accepte les accès sinon. Pour cela, la ''Miss Handling Architecture'' contient des registres qui mémorisent des informations sur les défauts de cache en cours. Ils portent le nom de '''''miss status handling registers''''', que j’appellerais dorénavant MSHR, qui sont aussi appelés des '''''miss buffer'''''. Le contenu des MSHR varie beaucoup suivant le processeur, mais ces derniers stockent au minimum les informations suivantes : * Le numéro de la ligne de cache dans laquelle les données sont chargées. * Un bit de validité qui indique si le MSHR est vide ou pas, qui est mis à 0 quand le défaut de cache est résolu. * Un ou plusieurs champs de lecture/écriture, qui contiennent des informations sur la lecture/écriture. ** Pour une lecture, elle contient des informations sur la destination de la donnée, à savoir qui prévenir quand le défaut de cache est terminé. C'est parfois un nom/numéro de registre (celui dans lequel charger la donnée), mais c'est souvent le numéro de l'entrée dans le ''load/store queue''. ** Pour les écritures, elle contient la donnée à écrire, ou éventuellement un numéro de ''load/store queue'' où se trouve la donnée à écrire. Il faut noter que le bus mémoire ne peut gérer qu'un seul défaut de cache à la fois. Aussi, il est intéressant de regarder ce qui se passe lorsqu'un second défaut de cache survient, pendant qu'un premier est en cours. Dans ce cas, il y a deux réponses qui correspondent à deux types de caches non-bloquants, qui portent les noms barbares de caches de type succès après défaut et défaut après défaut. Sur le premier type, il ne peut pas y avoir plusieurs défauts de cache simultanés. Dès qu'un second défaut de cache survient, le cache stoppe son activité et on ne peut plus démarrer de nouvelle lecture/écriture, tant que le premier défaut de cache n'est pas résolu. Le second type est plus souple et autorise la survenue de plusieurs défauts de cache simultanés. Du moins, jusqu'à une certaine limite, car le cache ne peut supporter qu'un nombre limité d'accès mémoires simultanés (pipelinés). ===Les accès simultanés à une même ligne de cache=== Il arrive que le processeur fasse plusieurs accès mémoire simultanés à la même ligne de cache. Si la ligne de cache en question n'a pas encore été chargée dans le cache, alors on a plusieurs défauts de cache consécutifs pour la même ligne de cache, et le cache non-bloquant doit gérer la situation. Pour la suite, il va falloir faire une petite distinction entre les défauts primaires et secondaires. Imaginons qu'un défaut de cache ait lieu et demande à charger une donnée dans la ligne de cache numéro N. Il s'agit du premier défaut impliquant cette ligne de cache précise, ce qui lui vaut le nom de '''défaut de cache primaire'''. Mais par la suite, d'autres accès mémoire à la même ligne de cache ont lieu, alors que la ligne de cache n'est pas encore disponible. Dans ce cas, il s'agit de '''défauts de cache secondaires'''. Pour l'unité d'accès mémoire, les défauts de cache primaire et secondaire sont différents (ils prennent tous une entrée dans la ''load/store queue''). Mais pour le cache, ils ne correspondent qu'à un seul accès au cache : celui qui demande de charger la ligne de cache demandée. Les défauts de cache primaire et secondaire à la même ligne de cache se voient attribuer un MSHR unique. La gestion des défauts de cache secondaires dépend du cache non-bloquant. La solution la plus simple ne permet pas les défauts de cache secondaires. Le cache ne permet pas deux défauts de cache simultanés pour la même ligne de cache. Les autres solutions le permettent, en fusionnant des accès simultanés à la même ligne de cache en un seul au niveau des MSHR. Dans tous les cas, détecter les défauts de cache secondaires sont un problème qu'il faut détecter. La ''Miss Handling Architecture'' doit détecter les défauts de cache secondaire. Pour cela, elle procède comme suit. Lors de chaque défaut de cache, la MHA récupère le numéro de la ligne de cache associé. Il vérifie alors chaque MSHR pour vérifier s'il contient le numéro en question. S'il n'y a aucune correspondance dans les MSHR, alors c'est signe que le défaut de cache est un défaut primaire. Mais s'il y en a une, alors c'est un défaut secondaire. Évidemment, cela signifie que lors d'un défaut de cache, le numéro de ligne de cache est envoyé à tous les MSHR, pour comparaison. Les MSHR sont donc regroupés dans une mémoire associative, une sorte de mini-cache, faciliter l'implémentation. Lors d'un défaut de cache primaire, l'accès à un cache non-bloquant se fait comme suit : le processeur envoie une adresse au cache, accède à celui-ci, et détecte la survenue d'un défaut de cache. Il en profite alors pour attribuer une ligne de cache dans laquelle sera chargée ce défaut. L'attribution est très simple dans le cas des caches ''direct mapped'', ou associatifs par voie, pour lesquels l'attribution se fait assez simplement. Il mémorise alors cette information dans les MSHR, après avoir vérifié que le défaut de cache n'était pas un défaut secondaire. Lors des accès ultérieurs à une adresse proche, censée être dans la même ligne de cache, le processeur va encore une fois rencontrer un défaut de cache. Il va alors déterminer le numéro de la ligne de cache associée à l'adresse, et comparer ce numéro avec les MSHRs. Si un MSHR contient ce numéro, c'est signe que le défaut de cache est un défaut secondaire. La MHA réagit alors différemment selon le processeur considéré. Une première solution n'autorise pas les défauts de cache secondaires. Si l'un d'entre eux survient, le processeur est gelé par un ''pipeline stall'', une bulle de pipeline. Une autre solution fusionne les défauts de cache secondaires avec le défaut de cache primaire : tout cela ne correspond qu'à un seul défaut de cache pour lui. ===Les MSHR simples=== Un cache non-bloquant à '''MSHR simple''' contient juste plusieurs MSHR qui mémorisent juste un numéro de ligne de cache, un bit de validité, le champ de lecture/écriture, et l'adresse exacte de lecture/écriture. Avec cette organisation, il est possible d'avoir plusieurs défauts de cache séparés, mais à la condition que chaque défaut accède à une ligne de cache différente. Deux accès simultanés à une même ligne de cache ne sont pas possibles, les défauts de cache secondaires ne sont pas autorisés. Ainsi, chaque défaut de cache se voit attribuer son propre MSHR, chacun contient un numéro de ligne de cache différent. Pour comprendre pourquoi c'est impossible de gérer les défauts secondaires, il faut regarder le champ de lecture/écriture. Si on veut effectuer plusieurs écritures consécutives à la même adresse, le MSHR n'aura pas de quoi mémoriser les deux données à écrire. Il pourra mémoriser la première donnée à écrire, pas la seconde. Même chose lors d'une lecture : le champ lecture/écriture peut mémorisr la destination de la première lecture, pas de la seconde. L'avantage est que la MHA n'a besoin que des MSHR et de quelques circuits annexes. Les autres solutions rajoutent des circuits annexes pour gérer les défauts de cache secondaires, qui utilisent beaucoup de circuits. Le cout en circuit est donc élevé, mais le gain en performance est là. Passons maintenant aux caches non-bloquants qui autorisent les défauts de cache secondaire. La solution la plus simple consiste à utiliser ===Les MSHR adressés implicitement=== Avec les '''MSHR adressés implicitement''', il est possible de fusionner plusieurs accès mémoire à une même ligne de cache, mais sous une condition très importante : ces accès lisent/écrivent des mots mémoire différents. Par exemple, imaginons qu'une ligne de cache contienne 8 mots mémoire de 64 bits. Si un premier accès mémoire lit le mot mémoire numéro 7 (dernier mot mémoire de la ligne), et le second accès le mot mémoire numéro 3, alors la fusion est possible. Mais si deux accès mémoire veulent lire/écrire le mot mémoire numéro 7, alors la fusion n'est pas possible et le processeur se bloque, un ''pipeline stall'' survient. Un MSHR adressé implicitement est un MSHR simple, qui contient naturellement un numéro de ligne de cache (tag) et un bit de validité, sauf que le champ de lecture/écriture est dupliqué. Les différents champs lecture/écriture d'un MSHR sont regroupés dans une mémoire RAM/cache qui contient autant d'entrées qu'il y a de mot mémoire dans une ligne de cache. Chaque entrée de la table est associée à un mot mémoire de la ligne de cache et stocke des informations sur celui-ci. Une entrée mémorise, au minimum : * Un champ de lecture/écriture qui contient soit la destination de la lecture, soit la donnée à écrire. * Un bit R/W permettant d'interpréter correctement le champ de lecture/écriture. * Un bit de validité pour chaque entrée de la table, qui dit si un défaut de cache antérieur accède déjà à ce mot mémoire. La fusion de deux défauts de cache est ainsi assez simple. Un défaut de cache primaire/secondaire configure l'entrée associée au mot mémoire lu/écrit. Si un défaut secondaire ultérieur lit/écrit un mot mémoire différent (dont le bit de validité de l'entrée dans la table est à zéro), pas de conflit : il configure une autre entrée, vide. Mais s'il lit/écrit un mot mémoire pour lequel l'entrée de la table est occupée, il y a conflit, le processeur est bloqué par un ''pipeline stall'', une bulle de pipeline. Avec cette organisation, le nombre de MSHR indique combien de lignes de cache peuvent être lues en même temps depuis la mémoire. Quant au nombre d'entrées par MSHR, il détermine combien d'accès mémoires qui ne se recouvrent pas peuvent avoir lieu en même temps. ===Les MSHR adressés explicitement=== Les '''MSHR adressés explicitement''' n'ont pas les contraintes des MSHR adressés implicitement. Avec eux, il est possible d'avoir plusieurs défauts de cache pointant vers la même ligne de cache, mais aussi vers le même mot mémoire. De tels défauts de cache apparaissent sur les processeurs à exécution dans le désordre, mais sont très rares, voire inexistants, sur les processeurs ''in-order''. L'idée est encore que chaque MSHR est associée à une table mémoire qui mémorise des entrées. Cette table n'est autre qu'une mémoire FIFO. Sauf que cette fois-ci, une entrée n'est pas associée à un mot mémoire. Une entrée est associée à un défaut de cache. Une entrée mémorise là encore la destination de la lecture ou la donnée de l'écriture suivi du bit R/W, ainsi que d'un bit de validité par entrée, mais aussi : la position du mot mémoire lu dans la ligne de cache. C'est cette dernière information qui n'était pas présente dans les MSHR adressés implicitement. De plus, le nombre d'entrée par MSHR n'est pas égal au nombre de mots mémoires dans une ligne de cache, mais peut être arbitrairement grand. L'avantage d'un tel cache est qu'il est capable de traité les défauts secondaires concernant un même mot mémoire, et ce, car les accès à la ligne de cache concernée se produisent dans l'ordre de génération des défauts (la table mémoire est une FIFO). ===Les MSHRs inversés=== Généralement, plus on veut supporter de défauts de cache, plus le nombre de MSHR et d'entrées augmente. Mais au-delà d'un certain nombre d'entrées et de MSHR, les MSHR adressés implicitement et explicitement ont tendance à bouffer un peu trop de circuits. Utiliser une organisation un peu moins gourmande en circuits est donc une nécessité. Cette organisation plus économe se base sur des MSHR inversés. Les MSHR inversés ne contiennent qu'une seule entrée, en quelque sorte : au lieu d’utiliser n MSHR de m entrées chacun, on va utiliser n × m MSHR inversés. La différence, c'est que plusieurs MSHR peuvent contenir un tag identique, contrairement aux MSHR adressés implicitement et explicitement. Lorsqu'un défaut de cache a lieu, chaque MSHR est vérifié. Si jamais aucun MSHR ne contient de tag identique à celui utilisé par le défaut, un MSHR vide est choisi pour stocker ce défaut, et une requête de lecture en mémoire est lancée. Dans le cas contraire, un MSHR est réservé au défaut de cache, mais la requête n'est pas lancée. Quand la donnée est disponible, les MSHR correspondant à la ligne qui vient d'être chargée vont être utilisés un par un pour résoudre les défauts de cache en attente. ===Les MSHR intégrés au cache=== Certains chercheurs ont remarqué que pendant qu'une ligne de cache est en train de subir un défaut de cache, celle-ci reste inutilisée, et son contenu est destiné à être perdu une fois le défaut de cache résolu. Ils se sont dit que, plutôt que d'utiliser des MSHR séparés, il vaudrait mieux utiliser la ligne de cache pour stocker les informations sur les défauts de cache en attente dans cette ligne de cache. Pour éviter tout problème, il faut rajouter un bit dans les bits de contrôle de la ligne de cache, qui sert à indiquer que la ligne de cache est occupée : un défaut de cache a eu lieu dans cette ligne, et elle stocke donc des informations pour résoudre les défauts de cache. ==L'entrelacement mémoire== Nous venons de voir comment une mémoire cache peut gérer plusieurs accès mémoire simultanés. Il se trouve que ce genre d'optimisation dépasse largement le cadre du cache. Les mémoires RAM ne sont pas en reste, elles aussi ! Il est possible de pipeliner l'accès à une mémoire RAM, en utilisant des techniques dites d''''entrelacement'''. Précisons cependant que cette optimisation n'est pas utilisée sur les mémoires SDRAM ou DDR modernes, du moins pas sans modifications majeures. Mais divers ordinateurs assez anciens, dont des superordinateurs, ont utilisé cette technique. La technique demande d'utiliser plusieurs mémoires séparées. Sur les anciens ordinateurs, les mémoires en question sont des chips mémoire, à savoir des circuits intégrés de DRAM. Pour faire plus général, nous allons utiliser le terme de '''banques''', ou encore de bancs mémoire. La différence est qu'il existe des mémoires multi-banque, qui regroupent plusieurs banques indépendantes dans un seul boitier, dans un seul chip mémoire. En clair, de telles mémoires regroupent plusieurs sous-mémoires dans un seul circuit intégré. Il est aussi possible d'utiliser plusieurs chips mémoire séparés, chacun ayant une banque. Reste à combiner ces banques pour former un pseudo-pipeline. ===Utiliser plusieurs banques sans entrelacement=== Sans optimisation particulière, les adresses sont réparties dans les banques comme indiqué ci-dessous. Il s'agit de ce qui s'appelle un arrangement horizontal, et nous avions vu celui-ci dans le chapiutre sur les mémoires SDRAM. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Avec cet arrangement, les bits de poids fort de l'adresse sont utilisées pour sélectionner la banque adéquate, et le reste de l'adresse est envoyé sur le bus d'adresse. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Adresse de banque !! Adresse dans la banque |- | Quelques bits de poids fort || Reste de l'adresse |} L'entrelacement change cette répartition, afin d'accélérer les accès mémoire. L'entrelacement classique vise à améliorer les accès à des adresses mémoires consécutives, mais des formes plus évoluées d'entrelacement visent à optimiser des accès mémoire arbitraires. Voyons-les dans l'ordre, du plus simple au plus complexe. ===L'entrelacement classique=== Avec l''''entrelacement classique''', des adresses consécutives sont réparties dans des banques consécutives. Précisons que cette attribution des adresses n'implique absolument pas la mémoire virtuelle ou n'importe quel mécanisme dans le processeur. La répartition décide que telle adresse mémoire va dans telle banque, à telle adresse dans la banque. Elle est donc le fait du contrôleur mémoire, donc en dehors du processeur (les contrôleurs mémoires n’étaient pas encore intégrés dans le processeur à l'époque). [[File:Répartition des adresses dans une mémoire interleaved.png|centre|vignette|upright=1.5|Répartition des adresses dans une mémoire interleaved.]] L'entrelacement simple permet d'accélérer les accès mémoire qui se font à des adresses consécutives. Avec l'entrelacement, chaque accès mémoire tombe dans une banque mémoire différente, ce qui fait qu'on peut démarrer un nouvel accès mémoire à chaque cycle d'horloge. Pendant qu'une banque est occupée par un accès mémoire, on démarre le suivant dans une autre banque, et ainsi de suite. Le tout se termine soit quand on a épuisé toutes les banques libres, soit quand l'accès en rafales se termine. Pas besoin d'attendre que la mémoire ait fini sa lecture/écriture avant de démarrer la lecture/écriture suivante. [[File:Pipemining mémoire.png|centre|vignette|upright=2.5|Pipelining mémoire]] L'animation suivante illustre bien le processus, avec quatres banques. [[File:Interleaving.gif|centre|vignette|upright=2.5|Pipelining mémoire]] L'entrelacement simple utilise les bits de poids faible pour sélectionner la banque, et les bits de poids fort pour la case mémoire. [[File:Adresse mémoire d'une mémoire entrelacée.png|centre|vignette|upright=2|Adresse mémoire d'une mémoire entrelacée]] Précisons que le temps d'accès mémoire ne change pas beaucoup avec l'entrelacement. Par contre, on peut faire plus d'accès mémoire simultanés. On peut démarrer un accès mémoire par cycle d'horloge, mais l'accès en lui-même prend plusieurs cycles. Les mémoires à entrelacement ont donc un débit supérieur aux mémoires qui ne l'utilisent pas. L'entrelacement classique pourrait être utilisé pour accélérer les transferts entre mémoire RAM et cache, qui se font en blocs d'adresses consécutives. Mais de nos jours, il n'est pas utilisé dans cette optique. Il faut dire qu'il est redondant avec le mode rafale des mémoires DRAM. Les deux font globalement la même chose et on voit mal comment utiliser les deux en même temps. Le mode rafale étant plus simple à implémenter, et plus léger niveau utilisation du bus de commande mémoire, il est préféré à l'entrelacement. Par contre, l'entrelacement a eu son heure de gloire sur d'anciens processeurs qui n'avaient pas de mémoire cache, alors qu'ils disposaient d'un pipeline. L'exemple typique est celui des nombreux ''processeurs vectoriels'', que nous n'avons pas encore abordé à ce stade du cours. De tels processeurs avaient un pipeline, une unité de calcul fortement pipelinée, et parfois même de l'exécution dans le désordre et du renommage de registres ! ===L'entrelacement par décalage=== L'entrelacement simple est très utile pour les accès en rafle ou équivalents. Par contre, il existe de rares situations où il n'est pas optimal. Une de ces situations est celle des accès en enjambée (en ''stride''), où l'on accède en série à des adresses sont séparées par N mots mémoires. [[File:Accès par enjambées.png|centre|vignette|upright=2|Accès par enjambées.]] De tels accès surviennent quand un logiciel accède à des structures de données spécifiques, à savoir des tableaux de structures, des matrices, ou d'autres structures de données dans le genre. Un cas classique est celui du parcours d'une matrice colonne par colonne. Les matrices de nombres sont mémorisées ligne par ligne, ce qui fait que parcourir une colonne demande de passer d'une ligne à la suivante et de faire des grands sauts en mémoire RAM, mais tous espacés par la même distance. Avec l'entrelacement simple, les accès en enjambée sont moins performants que les accès à des adresses consécutives, mais cela ne fonctionne pas trop mal. Le pire cas est celui où l'on a N banques et où les données sont justement placées toutes les N adresses. Des accès consécutifs vont tous tomber dans la même banque, on ne peut plus accéder à des banques différentes en parallèle. Mais en dehors de ce cas, on voit une amélioration par rapport à la situation sans entrelacement. Pour obtenir des performances maximales pour les accès en enjambées, il faut répartir les mots mémoires dans la mémoire autrement. L'organisation idéale est la suivante. : Dans les explications qui vont suivre, la variable N représente le nombre de banques, qui sont numérotées de 0 à N-1. On commence par organiser les N premières adresses comme une mémoire entrelacée simple : l'adresse 0 correspond à la banque 0, l'adresse 1 à la banque 1, etc. Pour le bloc suivant, on décale tout d'une adresse, à savoir qu'on commence à remplir les banques à partir de la seconde, non de la première. Une fois la fin du bloc atteinte, on finit de remplir le bloc en repartant du début du bloc. Le troisième bloc d'adresse subit le même traitement, sauf qu'on commence à remplir à partir de la seconde banque. Et on poursuit l’assignation des adresses en décalant d'un cran en plus à chaque bloc. Ainsi, chaque bloc verra ses adresses décalées d'un cran en plus comparé au bloc précédent. Si jamais le décalage dépasse la fin d'un bloc, alors on reprend au début. [[File:Mémoire interleaved par décalage.png|centre|vignette|upright=1.5|Mémoire entrelacée par décalage.]] En faisant cela, on remarque que les banques situées à N adresses d'intervalle sont différentes. Dans l'exemple du dessus, nous avons ajouté un décalage de 1 à chaque nouveau bloc à remplir. Mais on aurait tout aussi bien pu prendre un décalage de 2, 3, etc. Dans tous les cas, on obtient un entrelacement par décalage. Ce décalage est appelé le pas d'entrelacement, noté P. Le calcul de l'adresse à envoyer à la banque, ainsi que la banque à sélectionner se fait en utilisant les formules suivantes : * adresse à envoyer à la banque = adresse totale / N ; * numéro de la banque = (adresse + décalage) modulo N ; * décalage = (adresse totale * P) mod N. Avec cet entrelacement par décalage, on peut prouver que la bande passante maximale est atteinte si le nombre de banques est un nombre premier. Seulement, utiliser un nombre de banques premier peut créer des trous dans la mémoire, des mots mémoires inadressables. Malgré ce défaut, la technique a été utilisée sur quelques ordinateurs, avec l'exemple notable du superordinateur ''Burroughs Scientific Processor''. Pour éviter cela, il y a plusieurs solutions. Par exemple, on peut faire en sorte que N et la taille d'une banque soient premiers entre eux : ils ne doivent pas avoir de diviseur commun. Mais en pratique, elles n'ont pas vraiment été implémentées dans une vraie machine, et sont restées à l'état de recherche, aussi je les passe sous silence. ===L'entrelacement pseudo-aléatoire=== Une dernière méthode de répartition consiste à répartir les adresses dans les banques de manière "pseudo-aléatoire". L'idée exacte est de faire passer l'adresse dans une '''fonction de hachage''', plus ou moins complexe. Pour rappel, une fonction de hachage prend une entrée de grande taille et fournit en sortie un résultat de petite taille. Idéalement, elles doivent donner des résultats aussi différents que possible pour des entrées similaires, histoire de simuler une sorte de pseudo-aléatoire. Ici, une adresse est transformée en un numéro de banque. Une fonction de hachage simple ne fait que permuter des bits de l'adresse pour obtenir son résultat. Elle ne fait qu'échanger des bits de place avant de couper l'adresse en deux morceaux : un pour la sélection de la banque, et un autre pour la sélection de l'adresse dans la banque. Cette permutation est fixe, et ne change pas suivant l'adresse. Des fonctions de hachage plus complexes font des XOR bit à bit entre certains bits de l'adresse. ==Les contrôleurs SDRAM/DDR avec "exécution dans le désordre"== Après avoir vu le cas des mémoires RAM, nous allons nous concentrer sur le cas particulier des mémoires SDRAM. Les mémoires SDRAM et DDR modernes sont capables de gérer plusieurs accès simultanés à la mémoire RAM, et leurs contrôleurs mémoire en font tout autant. C'est une différence majeure avec les mémoires asynchrones FPM/EDO, qui n'acceptaient qu'un seul accès mémoire à la fois. Leur contrôleur mémoire n'acceptait qu'un seul accès mémoire à la fois, c'était un contrôleur mémoire bloquant. Les contrôleurs mémoires des SDRAM sont eux non-bloquants et peuvent encaisser une dizaine d'accès mémoire à la fois. Peu de choses sont connues sur les contrôleurs de SDRAM/DDR modernes, les fabricants ne donnant que peu de détails dessus. Les rares simulateurs qui tentent de décrire leur fonctionnement, comme DRAM SIM I et II, sont particulièrement simples et ne vont pas dans le détail. Néanmoins, le peu qu'on sait est tout de même instructif. ===Rappel sur les SDRAM : tampon de ligne et commandes=== Les mémoires SDRAM sont des mémoires à tampon de ligne. Elles font un accès mémoire en deux étapes. La première étape recopie une ligne de N * 64 bits dans un tampon interne à la SDRAM. La seconde étape sélectionne une colonne, à savoir une donnée de 64 bits, qui est soit envoyée sur le bus de données pour une lecture, soit modifiée par une écriture. [[File:Mémoire à tampon de ligne.png|centre|vignette|upright=2|Mémoire à tampon de ligne]] Les accès mémoire sont traduits par un séquenceur mémoire en une série de commandes mémoires, qui sont séparées par des délais mémoire de quelques cycles d'horloge. Les délais sont très précis, et sont à respecter à la lettre. Une lecture ou une écriture se fait en maximum trois commandes : une commande PRECHARGE qui ferme la ligne précédemment utilisée, une commande ACT qui précise l'adresse de la ligne, et une commande READ ou WRITE qui précise l'adresse de la colonne et éventuellement la donnée à écrire. : Pour être plus précis, la commande PRECHARGE précharge les lignes de bits du plan mémoire à une tension particulière, ce qui les vide de leur contenu. Mais c'est un détail sans importance pour ce qui va suivre. Les SDRAM permettent de se passer de la première étape si des accès consécutifs se font dans la même ligne. Une fois activée, la ligne reste ouverte et on peut accéder plusieurs fois de suite dedans. On a alors juste à préciser l'adresse de colonne dedans. Si un accès mémoire accède à une ligne déjà activée, on dit que c'est un '''succès de page'''. Si ce n'est pas le cas, on doit fermer la ligne courante, rouvrir la ligne vboulue, et préciser la colonne. C'est alors un '''défaut de page'''. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | bgcolor="#FFA0FF" | PRECHARGE || bgcolor="#FFA0FF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || bgcolor="#A0FFFF" | READ (2) || bgcolor="#A0FFFF" | READ (3) || || || bgcolor="#A0FFFF" | READ (4) || bgcolor="#A0FFFF" | READ (5) || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | Donnée READ 1 || ||bgcolor="#A0FFFF" | Donnée READ 2 | bgcolor="#A0FFFF" | Donnée READ 3 || || || bgcolor="#A0FFFF" | Donnée READ 4 || bgcolor="#A0FFFF" | Donnée READ 5 |} Les SDRAM sont parfois capables de démarrer une commande avant que la précédente soit terminée. Mais le respect des délais mémoire est très limitant, ce qui fait qu'on ne peut pas parler de réel pipeline, comme c'est le cas sur les processeurs. Parlons plutot de '''commandes anticipées'''. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | || bgcolor="#A0FFFF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || || || bgcolor="#FFA0FF" | READ (2) || || || || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 | bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 | |} Un dernier point est que les chips mémoires de SDRAM sont composés de plusieurs banques, chacune étant une sorte de mini-mémoire miniature. Chaque banque a son propre décodeur, son propre tampon de ligne, ses propres multiplexeurs de colonne, sa logique de rafraichissement mémoire, etc. C'est comme si une SDRAM regroupait plusieurs mémoires séparées dans un même circuit intégré. [[File:Arrangement vertical.jpg|centre|vignette|upright=2.5|Mémoire multi-banques.]] ===La mise en attente des accès mémoire=== Un contrôleur de SDRAM peut accepter plusieurs accès mémoire en même temps. Mais cela ne signifie pas que le contrôleur sera prêt à les traiter immédiatement. Pour éviter tout problème de disponibilité, le contrôleur met en attente les accès mémoire que le processeur lui envoie, pour les exécuter dès que possible. Les accès mémoire sont mis en attente dans une mémoire FIFO, histoire de les exécuter dans leur ordre d'arrivée, notamment pour renvoyer les lectures dans l'ordre demandé. Il y a aussi une mémoire FIFO pour les données à écrire et une FIFO pour les données lues. Cette dernière sert au cas où le cache ne soit pas disponible quand on lui envoie la donnée lue. [[File:Module d'interface avec la mémoire.png|centre|vignette|upright=2.5|Contrôleur mémoire avec mise en attente des requêtes processeur et autres optimisations.]] Des optimisations sont possibles dès la mémoire FIFO. Par exemple, on peut regrouper plusieurs accès à des données consécutives en un seul accès en rafale. Le contrôleur mémoire analyse les requêtes mises en attente et détecte si certaines se font à des adresses consécutives. Si c'est le cas, il fusionne ces requêtes en une lecture/écriture en rafale. Une telle optimisation est appelée la '''combinaison de lecture''' pour les lectures, et la '''combinaison d'écriture''' pour les écritures. L'optimisation précédente ne paraissent pas payer de mine, mais elles deviennent plus compréhensible quand on sait que certains contrôleurs mémoire peuvent mettre en attente beaucoup de données. Par exemple, l'''Intel E8870 - Scalable Memory Controller'' peut mettre en attente 64 lignes de cache dans la mémoire FIFO, soit 8 kibioctets de RAM ! Deux pages mémoire si la mémoire virtuelle utilise des pages de 4 kibioctets ! ===Les lectures anticipées : une forme d'OOO mémoire=== La mise en attente des accès mémoire est une optimisation intéressante, mais pas mirifique. Elle permet surtout de ne pas bloquer le processeur si le contrôleur mémoire a du travail sur la planche. Cependant, elle peut être optimisée quand on se rend compte d'une chose : le processeur ne voit que les lectures, pas les écritures. Pour les écritures, il les envoie au contrôleur mémoire et ce dernier fait le travail demandé, le processeur n'est pas prévenu quand une écriture se termine. Mais pour les lectures, c'est différent, car il reçoit la donnée lue. En conséquence, il est possible d'optimiser les écritures sans que le processeur ne voit quoique ce soit. Une optimisation possible est alors de changer l'ordre des accès mémoire, histoire de retarder les écritures ou au contraire de les faire en avance. La raison est que les mémoires SDRAM n'aiment pas quand on alterne lectures et écritures. Les délais mémoire, les fameux ''timings'' mémoire, sont clairs : faire une suite de lecture ou une suite d'écriture est plus performant que d'alterner entre lectures et écritures. L'idée est alors de changer l'ordre des écritures de manière à les faire en bloc, idem pour les lectures. L’optimisation est possible, mais à condition que le processeur n'y verra que du feu. Or, vous l'avez deviné, il y a des cas où ces réorganisations ne sont pas possibles, et vous avez sans doute trouvé que la situation était familière. L'optimisation en question est une forme d'exécution dans le désordre des accès mémoire, réalisée par le contrôleur mémoire lui-même ! Et qui dit exécution dans le désordre dit : problèmes liés aux dépendances de données. Changer l'ordre des écritures n'est pas toujours possible, notamment en raison des dépendances RAW, WAR et WAW. Pour que le processeur n'y voit que du feu, il faut respecter plusieurs critères. Premièrement, les lectures doivent être renvoyées au processeur dans l'ordre. Et quand je dis dans l'ordre, cela veut dire : dans l'ordre d'envoi des lectures au contrôleur mémoire. Les écritures aussi doivent se faire dans l'ordre, histoire qu'une série d'écriture donne le bon résultat final. Le troisième critère est qu'une lecture doit donner le résultat de la dernière écriture ''à la même adresse''. Pour le dire autrement, il faut juste détecter les dépendances RAW. Concrètement, si on a une série de lectures et d'écritures alternées, on peut regrouper les lectures et les écritures dans deux paquets séparés, à condition qu'aucune lecture ne lise une adresse écrite par une écriture. Mais si une lecture est dépendante d'une écriture, alors la lecture doit attendre que l'écriture se termine, idem pour les lectures suivantes. Une implémentation basique remplace la mémoire FIFO vue au-dessus, par deux mémoires FIFOs : une pour les lectures, une pour les écritures. Lorsqu'un accès mémoire arrive au séquenceur, il regarde si c'est une lecture ou une écriture et place l'accès mémoire dans la file adéquate. Le fait que ce soit des FIFOs garantit que les lectures se font dans l'ordre, idem pour les écritures. En sortie des deux FIFOs, le séquenceur mémoire détecte les dépendances RAW. Précisément, quand une lecture sort de la mémoire FIFO, il consulte la file d'attente des écriture pour voir si la lecture est dépendante d'une écriture en attente. La lecture attend si c'est le cas, et les écritures sont exécutées à la place. Séparer lectures et écriture est une source d'optimisation. Il est par exemple possible d'utiliser le réacheminement lecture sur écriture (''Store to load forwarding''). Il s'agit d'un équivalent de l’optimisation du même nom, utilisée dans le cadre de la désambiguïsation mémoire. Imaginez qu'une lecture accède à une donnée pas encore écrite, en attente dans le contrôleur mémoire. C'est une dépendance RAW assez claire. Dans ce cas, le contrôleur mémoire peut renvoyer la donnée directement depuis ses mémoires FIFOs, sans faire d'accès mémoire en lecture. Le contrôleur mémoire a tendance à privilégier les lectures, car celles-ci sont cruciales pour l'exécution dans le désordre. Plus elles se font tôt, plus vite le processeur recevra les données et pourra démarrer les instructions dépendantes de cette donnée. Le séquenceur a donc tendance à piocher en priorité dans la file de lecture, plutôt que dans la file d'écriture. A vrai dire, dans le cas idéal, les écritures ne sont faites que quand la file d'écriture est pleine ou quasi-pleine... Prenons l'exemple des coprocesseurs IO 81341 et 81342, qui étaient en réalité des ''chipsets'' intégrant un contrôleur SDRAM. Le ''chipset'' avait 5 ports : un pour les processeurs, un pour le pont sud (''southbridge'') et trois pour des canaux DMA. Pour le port processeur, il n'y avait pas de réordonnancement et ce port utilisait une seule mémoire FIFO. Les autres ports utilisaient l'optimisation qu'on vient de voir, et avaient vraisemblablement des files séparées pour les lectures et écritures. Le séquenceur mémoire vérifiait les dépendances mémoire de type RAW et autres. [[File:Double file d'attente pour les lectures et écritures.png|centre|vignette|upright=2.5|Double file d'attente pour les lectures et écritures]] ===Le ré-ordonnancement des commandes mémoires=== L'optimisation précédente est peu poussée, comparé aux formes d'exécution dans le désordre que les processeurs utilisent. Et on peut se demander si une exécution dans le désordre plus poussée est possible. La réponse est oui : un contrôleur mémoire peut faire des réorganisations bien plus poussées. Par contre, il faut faire une précision très importante : le contrôleur mémoire ne remet pas les accès mémoire dans l'ordre avant de les envoyer au processeur. C'est le processeur qui remet en ordre les accès mémoire, dans sa ''load queue''. En clair : le processeur voit les accès mémoire dans le désordre, c'est lui qui les remet en ordre. Pour cela, les requêtes mémoire sont "numérotées", à savoir qu'on leur attribue un identifiant binaire. Le contrôleur mémoire exécute les lectures/écritures dans le désordre, mais garde la trace de leur identifiant. Il sait dans quel ordre il exécute les accès mémoire et connait leur latence. Ce qui fait qu'il sait que la donnée lue à tel instant est associée à la requête mémoire numéro X. Pour les lectures, il renvoie la donnée lue avec l'identifiant. Le processeur sait alors à quelle lecture la donnée lue correspond et il se débrouille en interne pour gérer la situation. Maintenant que ces précisions sont faites, posons cette question : dans quelles situations est-il pertinent de faire des accès mémoire dans le désordre ? La réponse est : quand plusieurs accès à une même ligne ne sont pas consécutifs, qu'il y a des accès entre les deux. Après ré-ordonnancement, ces accès mémoire à une même ligne sont exécutés l'un à la suite de l'autre, ce qui est beaucoup plus rapide. Pour rendre le tout plus concret, voici un exemple. Imaginez que l'on sait les 3 accès mémoire suivants : * Une lecture ligne A ; * PRECHARGE + ACT ; * Une écriture ligne B ; * PRECHARGE + ACT ; * Une lecture ligne A. Sans réordonnancent, on doit émettre deux commandes PRECHARGE quand on passe d'une ligne à l'autre, et il faut ajouter les commandes ACT avec. pour éviter cela, le contrôleur mémoire peut retarder l'écriture, ou au contraire avancer la seconde lecture. le résultat est alors le suivant : * Lecture ligne A ; * Lecture ligne A * PRECHARGE + ACT ; * Une écriture ligne B. On a donc deux accès consécutifs à la même ligne, suivi par une écriture dans une autre ligne. La technique marche parce que l'on a un mix adéquat de lectures et d'écritures. Et encore une fois, il faut éviter d'intervertir lectures et écritures à une même adresse, sous peine de problèmes. Encore une fois, les dépendances RAW posent problème ! L'implémentation est assez simple : la ou les mémoires FIFOs précédentes sont remplacées par des mémoires similaires aux fenêtres d'instruction. Le contrôleur mémoire vérifie à chaque cycle les accès en attente, et vérifie à quelle ligne ils accèdent. Il priorise alors les accès qui tombent dans la ligne ouverte : ceux-là sont exécutés avant les autres. S'il n'y en a pas, il prend l'accès mémoire le plus ancien (ordre FIFO). Il teste aussi les dépendances mémoires RAW avant d'envoyer des commandes à la mémoire DDR/SDRAM. une telle solution est appelée l''''algorithme ''FR-FCFS''''' (''First Ready-First Come First Serve''). ===Les optimisations liées à la présence de plusieurs banques=== Les optimisations précédentes sont décuplées par la présence de plusieurs banques dans la mémoire SDRAM. Il est en effet possible de faire plusieurs accès mémoire en même temps, dans des banques différentes. Il est possible de lancer un accès mémoire dans deux banques en même temps, par exemple. La seule contrainte est que la SDRAM est limitée à 4 banques actives en même temps. {|class="wikitable" |+ Pipelining basique sur les SDRAM |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 |- ! Banque Numéro 1 | || || || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || || |- ! Banque Numéro 2 | || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || |- ! Banque Numéro 3 | || || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || |} Les optimisations précédentes peuvent s'appliquer par banque. Il est par exemple possible d'utiliser une mémoire FIFO par banque. Ainsi, les accès dans une même banque se feront en série, dans l'ordre d'arrivée, mais des accès à des banques différentes se feront dans le désordre. Cela ne pose pas de problème car les accès se font à des adresses différentes, ce qui fait qu'il n'y a pas de conflits majeurs. Il y a cependant un risque que les lectures se fassent dans le désordre si on n'y prend pas garde. [[File:Gestion parallèle des banques.png|centre|vignette|upright=2.5|Gestion parallèle des banques.]] il est aussi possible d'utiliser une file séparée pour les lectures et les écriture, pour chaque banque. Les performances sont alors améliorées comparé à deux files globales pour toute la SDRAM. En idem avec la réorganisation des accès mémoire, pour regrouper les accès à une même ligne. ==L'entrelacement sur les mémoires SDRAM== L''''entrelacement''' fonctionne sur les mémoires SDRAM, mais il doit être fortement modifié. Concrètement, tout ce qui a été dit plus est inapplicable pour les SDRAM, du fait de la présence du tampon de ligne. Des adresses consécutives atterrissent déjà dans la même ligne, ce qui fait qu'on n'a pas besoin d'entrelacement au niveau d'une ligne. Et ne parlons pas de ce la présence de rangées et de canaux mémoire ! Cependant, il peut y avoir un entrelacement lié à la présence des banques SDRAM. L'entrelacement est géré juste avant le séquenceur mémoire. Le '''circuit d'entrelacement''' intervertit certains bits de l'adresse lors des accès mémoires. On trouve aussi des mémoires FIFOs pour mettre en attente les requêtes mémoire provenant du processeur. Elles permettent de recevoir plusieurs accès mémoire consécutifs. Si vous vous demandez comment plusieurs accès mémoire consécutifs peuvent avoir lieu, sachez que c'est une bonne question. Pour le moment, nous allons dire que ces accès proviennent du processeur ou de la mémoire cache, sans expliquer comment. Le contrôleur mémoire reçoit une série d'accès mémoire assez rapprochés, qu'il doit exécuter au mieux. [[File:Controleur mémoire d'une SDRAM avec entrelacement.png|centre|vignette|upright=2.5|Contrôleur mémoire d'une SDRAM avec entrelacement.]] Pour gérer l'entrelacement, le contrôleur de SDRAM prend en entrée l'adresse envoyée par le processeur, et la découpe en plusieurs champs : un pour sélectionner la banque adéquate, un autre pour la ligne, un autre pour la colonne, un autre pour la rangée. Une solution simple désactive l'entrelacement. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de banque || Adresse de ligne || Adresse de colonne |} Si cette solution fonctionne bien si les accès mémoire sont assez aléatoires, en pratique, mieux vaut utiliser l'entrelacement. ===L'entrelacement de banques/lignes=== Une première méthode d'entrelacement est l''''entrelacement de banques'''. Elle répartit deux lignes consécutives dans deux banques différentes. Pour comprendre l'idée, prenons un exemple. Imaginons une mémoire avec deux banques et 4 lignes. Imaginons qu'on parcoure/balaye la mémoire RAM en partant des adresses basses. Sans entrelacement de ligne, les accès se feront comme suit, de gauche à droite : {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Banque numéro 1 !! colspan="4" | Banque numéro 2 |- | Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 || Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 |} Avec l'entrelacement de banques, les accès se feront comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 |- | Ligne 1 || Ligne 1 || Ligne 2 || Ligne 2 || Ligne 3 || Ligne 3 || Ligne 4 || Ligne 4 |} Avec N banques, la première ligne ira dans la première banque, la seconde ligne dans la seconde, et ainsi de suite jusqu'à la énième banque qui va dans la banque numéro N. Et la ligne N+1 repart dans la première banque. L'avantage est que passer d'une ligne à la suivante est assez rapide. Au lieu de devoir fermer la première ligne et ouvrir la suivante on ouvrira directement la ligne suivante dans une banque différente, ce qui sera plus rapide. La commande PRECHARGE pourra être envoyée à la seconde banque en avance. Pour cela, l'adresse est découpée comme suit. {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne (précise à l'octet) |} ===L'entrelacement de rangées=== Il est aussi possible de faire la même chose, mais avec les rangées. Pour simplifier fortement, une rangée est simplement un chip mémoire. En réalité, les rangées ne sont pas des chip mémoire, mais un ensemble de chips mémoire regroupés ensemble histoire d'atteindre les 64 bits du bus de données. Par exemple, une rangée peut combiner 8 chips mémoire avec un bus de données de 8 bits chacun pour obtenir les 64 bits du bus de données d'une SDRAM. Mais nous passons ce détail sous silence dans les explications qui vont suivre, par souci de simplification. Pour faire comprendre l'entrelacement de rangée, le mieux est d'utiliser un exemple, le même que précédemment. Sans entrelacement de rangée, on change de chip mémoire une fois qu'on a balayé toutes ses adresses. {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Rangée numéro 1 !! colspan="4" | Rangée numéro 2 |- | Banque 1 || Banque 2 || Banque 3 || Banque 4 || Banque 1 || Banque 2 || Banque 3 || Banque 4 |} Avec l'entrelacement de ligne, on change de rangée dès qu'on change de banque/ligne. {|class="wikitable" |+ Adresse mémoire |- ! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 |- | Banque 1 || Banque 1 || Banque 2 || Banque 2 || Banque 3 || Banque 3 || Banque 4 || Banque 4 |} En clair, quand on a épuisé toutes les banques dans une même rangée, on passe à la rangée suivante au lieu de rester dans la même rangée. Notons que dans l'exemple précédent, on a combiné l'entrelacement de rangée et de banque, mais on aurait pu utiliser l'entrelacement de rangée seul. Mais ce n'est pas le cas le plus courant en pratique. Toujours est-il qu'en combinant entrelacement de rangée et de ligne, le découpage de l'adresse est le suivant : {|class="wikitable" |+ Adresse mémoire |- | Adresse de ligne || Numéro de rangée || Adresse de banque || Adresse de colonne (précise à l'octet) |} D'autres méthodes d'entrelacement sont possibles, mais elles sont plus complexes. Chaque contrôleur mémoire fait un peu à sa sauce. Ils permettent en général de choisir entre plusieurs entrelacements. Au minimum, ils permettent de désactiver l'entrelacement, d'activer un entrelacement de ligne, et d'autres entrelacements plus complexes, dont certains sont propriétaires et tenus secrets par le fabricant. ===L'entrelacement avec le ''dual channel''=== [[File:Dual channel slots.jpg|vignette|Slots mémoires avec ''dual channel''.]] L'usage du ''dual channel'' complique encore l'entrelacement. Là encore, il y a deux grandes solutions : avec et sans entrelacement des canaux mémoire. Rappelons le principe : deux barrettes de RAM sont accédées en parallèle. Et pour cela, il faut utiliser l'entrelacement. Typiquement, chaque barrette mémoire fournit 64 bits, ce qui fait que l'on peut accéder à 128 bits d'un seul coup, par exemple avec un accès en rafale. Sans ''dual channel'', la première barrette correspond à la moitié haute de la RAM, la seconde barrette correspond à la moitié basse. Avec ''dual channel'', une forme spécifique d'entrelacement est activée. Concrétement, deux blocs de 64 bits sont placés dans des canaux mémoire séparés. Idem avec du triple ou quadruple canal, mais c'est alors trois ou quatre blocs de 64 bits qui sont dispersés dans des canaux consécutifs. Le découpage de l'adresse est alors le suivant : {|class="wikitable" |+ Adresse mémoire |- ! colspan="6" | Sans entrelacement |- | Numéro de canal/barrette || Reste de l'adresse || Position de l'octet dans un bloc de 64 bits |- | colspan="6" | |- ! colspan="6" | Avec entrelacement |- | Reste de l'adresse || Numéro de canal/barrette || Position de l'octet dans un bloc de 64 bits |} [[File:Décodage d'adresse avec dual channel.png|centre|vignette|upright=2|Décodage d'adresse avec dual channel.]] Les contrôleurs de SDRAM précédents sont assez basiques et ne représentent pas les contrôleurs les plus évolués. Ils étaient utilisés sur les anciens PC, à une époque où ils étaient encore sur la carte mère du processeur. Mais de nos jours, le contrôleur mémoire est intégré au processeur. Et il incorpore de nombreuses optimisations afin de gagner en performances. Malheureusement, ces optimisations sont elles-mêmes dépendantes des optimisations intégrées dans le processeur, ce qui fait qu'on ne peut pas en parler ici. Elles demandent que le contrôleur mémoire reçoive plusieurs accès mémoire simultanés, ce qui n'a aucun sens à ce stade du cours. Aussi, nous allons les passer sous silence pour le moment. Nous les verrons dans le chapitre sur le parallélisme mémoire. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=La désambiguïsation mémoire | prevText=La désambiguïsation mémoire | next=Les processeurs superscalaires | nextText=Les processeurs superscalaires }} </noinclude> flx4mmyl7vi3m5288v1r766qg700wp0 764769 764749 2026-04-24T09:12:13Z Mewtow 31375 /* L'entrelacement avec le dual channel */ 764769 wikitext text/x-wiki Dans ce chapitre, nous allons voir les techniques qui permettent de gérer plusieurs accès mémoire simultanés directement au niveau du cache ou de la mémoire RAM. Par plusieurs accès mémoire simultanés, vous pensez sans doute à l'usage de cache multiports, voire de mémoires RAM multiport. C'est en effet une solution, mais ce n'est clairement pas la seule. Par exemple, il est possible de pipeliner l'accès au cache, voire à la mémoire RAM. Il existe de nombreuses techniques de '''parallélisme mémoire''', et nous allons les voir dans ce chapitre. Pour donner un exemple, il est possible de pipeliner les accès mémoire. Il est possible de pipeliner l'accès au cache et/ou l'accès à la mémoire, et nous verrons comment faire dans ce chapitre. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, qu'ils aient ou non un pipeline dynamique. Par contre, pipeliner la mémoire est une technique ancienne, aujourd'hui peu utilisée. Elle était utilisée sur des très vieux ordinateurs, qui avaient un pipeline mais pas de mémoire cache. Mais de nos jours, les mémoires SDRAM et DDR ne sont pas adaptées pour ça, il est très difficile de les pipeliner correctement. Le parallélisme mémoire est utile aussi bien sur des processeurs à émission dans l'ordre que dans le désordre. Par exemple, un processeur ''in-order'' avec des lectures non-bloquantes peut e, profiter. Il peut techniquement lancer une seconde lecture, après avoir rencontré une première lecture non-bloquante. En général, le nombre de lectures consécutives est cependant limité à moins d'une dizaine. À l'opposé, les processeurs sans lectures non-bloquantes profitent pas du parallélisme mémoire. À la rigueur, ils peuvent en profiter s'ils intègrent des techniques de préchargement. Pendant que le processeur exécute une lecture/écriture, il peut précharger une autre donnée en parallèle. Inutile de dire que sans pipeline processeur, le parallélisme mémoire ne sert pas à grand-chose. ==Le ''line fill buffer'' et les techniques associées== Lors d'un défaut de cache, le processeur doit attendre que toute la ligne de cache soit chargée avant d'être utilisable. Or, la taille d'une ligne de cache est supérieure à la largeur du bus mémoire, ce qui fait qu'une ligne de cache est chargée en plusieurs fois, morceaux par morceaux, mot mémoire par mot mémoire. Le chargement peut se faire directement dans le cache, mais ce n'est pas une solution très pratique. À la place, beaucoup de processeurs ajoutent une mémoire tampon entre la RAM et le cache, appelée le '''tampon de remplissage de ligne''' (''line-fill buffer'') dans les processeurs modernes. Lors d'un défaut de cache, le processeur charge la donnée de la RAM dans le tampon de remplissage de ligne, mot mémoire par mot mémoire. Une fois plein, le tampon de remplissage de ligne est recopié dans la ligne de cache. Le tampon de remplissage de ligne contient une ligne de cache, si ce n'est que certains bits de contrôle ne sont pas présents. Le tampon de remplissage de ligne contient un bit de validité pour chaque mot mémoire de la ligne de cache, qui indiquent si le mot mémoire a été chargé. Par exemple, prenons un processeur 64 bits, qui gère donc des mots mémoire de 8 octets, avec des lignes de cache 256 octets/32 mots mémoire. Le tampon de remplissage de ligne contiendra 32 bits de validité. Si le processeur a chargé les 6 premiers mots mémoire, les 6 premiers bits de validité seront mis à 1, les autres seront encore à zéro. Il faut noter que la disponibilité de la ligne complète se détermine assez facilement en faisant un ET logique entre tous les bits de validité. Le processeur sait ainsi quand la ligne de cache est disponible entièrement et donc quand la transférer dans le cache. La même chose existe avec une hiérarchie de cache, sauf que l'on trouve une mémoire tampon entre chaque niveau de cache. Il y a un tampon de remplissage de ligne entre le cache L1 et le cache L2, entre le cache L2 et le L3, etc. Si le cache ne gère qu'un seul défaut de cache à la fois, le ''fill line buffer'' est une mémoire très simple, qui ne mémorise qu'une seule ligne de cache. Et cela vaut aussi bien pour un cache bloquant que non-bloquant. Mais sur les caches capables de gérer plusieurs défauts simultanés, le tampon de remplissage de ligne est une mémoire de type FIFO ou LIFO, capable de mémoriser plusieurs lignes de cache. Notons que le tampon de remplissage de ligne est très utile pour implémenter certaines techniques, par exemple le contournement du cache. Nous avions vu dans le chapitre sur le cache que certains accès mémoire doivent contourner le cache, pour des raisons de cohérence des caches. C'est notamment nécessaire pour accéder aux périphériques, mais c'est aussi utile pour des raisons de performances dans des cas très spécifiques. Les accès qui contournent le cache se font directement dans le tampon de remplissage de ligne : le processeur écrit ou lit les données depuis ce tampon de remplissage de ligne, sans accéder au cache. ===L'''early restart'' et le ''critical word load''=== La présence du ''line buffer'' permet une optimisation assez intéressante, qui permet de réduire la latence des défauts de cache. L'optimisation consiste à lire un mot mémoire dans le tampon de remplissage de ligne, même si la ligne de cache complète n'a pas encore été chargée. Il existe deux manières de faire cela, qui portent les noms d'''early restart'' et de ''critical word load''. La première est la version la plus simple, la seconde est plus complexe mais plus performante. Avec la technique d''''''early restart''''', la ligne de cache est chargée normalement, en partant de son premier mot mémoire. Dès que le mot mémoire lu/écrit par le processeur est copié dans le ''line fill buffer'', il est envoyé au processeur immédiatement. Illustrons le tout par un exemple, où une ligne de cache fait 16 mots mémoire. Le processeur effectue une lecture, qui lit le 5ème mot mémoire. Sans ''early restart'', le processeur doit charger les 16 mots mémoire avant de faire la lecture dans le cache. Avec ''early restart'', le processeur reçoit la donnée dès que le 5ème mot mémoire est disponible. Le processeur doit attendre que les 4 premiers mots mémoire soient chargés, puis le 5ème mot mémoire arrive et est envoyé directement au processeur, il est lu directement depuis le ''line fill buffer''. Les 11 mots mémoire suivants sont ensuite chargés dans le cache pendant que le processeur fait des calculs dans son coin. Le ''critical word load'' est une optimisation de la technique précédente où le chargement de la ligne de cache commence directement à la donnée demandée par le processeur. Pour reprendre l'exemple précédent, où le processeur demande le 5ème mot mémoire sur 16, le ''critical word load'' charge le 5ème mot mémoire en premier et l'envoie au processeur, ce qui fait qu'il est chargé très rapidement. Pas besoin d'attendre que le processeur charge les 4 mots mémoire précédents comme avec l'''early restart''. Dans le détail, le processeur charge le 5ème mot mémoire en premier, puis charge les 11 suivants, et termine par les 4 mots mémoire du début. En clair, le ''critical word load'' commence par charger le mot mémoire lu, puis les blocs suivants, avant de revenir au début du bloc pour charger les blocs restants. Ainsi, la donnée demandée par le processeur sera la première disponible. Pour cela, l'organisation du tampon de remplissage de ligne est modifiée de manière à rendre cela possible. Il a une taille égale à une ligne de cache complète, qui contient elle-même plusieurs mots mémoire. Dans le ''line-fill buffer'', chaque mot mémoire est stocké avec un tag, qui indique l'adresse du mot mémoire stocké dans le ''line-fill buffer''. Le ''line-fill buffer'' est donc un cache un peu particulier, qui fonctionne comme un cache du point de vue du processeur, comme une mémoire FIFO pour les transferts avec le cache. Ainsi, un processeur qui veut lire dans le cache après un défaut peut accéder à la donnée directement depuis le tampon de remplissage de ligne, alors que la ligne de cache n'a pas encore été totalement recopiée en mémoire. ==Les caches pipelinés== Il est possible de pipeliner l'accès au cache, ce qui demande juste de rajouter des registres au bon endroits et quelques circuits de contrôle. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, même ceux avec un pipeline dynamique. Et l'implémentation peut se faire de plusieurs manières différentes. Il faut dire que découper une mémoire cache en plusieurs étapes peut se faire de plusieurs manière. Commençons par la plus simple. Beaucoup de processeurs des années 2000 avaient une fréquence peu élevée comparée aux standards d'aujourd'hui, ce qui fait que leur cache avait bien un temps d'accès d'un cycle d'horloge. Mais l'accès au cache se faisait en deux cycles d'horloge : un cycle pour calculer une adresse et un cycle pour l'accès mémoire proprement dit. Et ces deux étapes étaient pipelinées, à savoir que deux micro-opérations mémoire peuvent s'exécuter en même temps : une dans l'étage de calcul d'adresse, une autre dans le cache. L'unité mémoire était donc pipelinée, alors que l'accès au cache ne l'était pas. Les processeurs qui implémentaient cette technique regroupent les micro-architectures K5 et K6 d'AMD, les processeurs Intel de micro-architecture P6 (Pentium 2 et 3) et quelques autres. Il est aussi possible de pipeliner l'accès au cache lui-même. Avec un cache pipeliné, l'accès au cache ne se fait pas en un seul cycle, mais en plusieurs. Cependant, on peut lancer un nouvel accès au cache à chaque cycle d'horloge, comme un processeur pipeliné lance une nouvelle instruction à chaque cycle. L'implémentation est assez simple : il suffit d'ajouter des registres dans le cache. Pour cela, on profite que le cache est composé de plusieurs composants séparés, qui échangent des données dans un ordre bien précis, d'un composant à un autre. De plus, le trajet des informations dans un cache est linéaire, ce qui les rend parfois pour l'usage d'un pipeline. ===Le ''pipelining'' des caches ''direct-mapped''=== Voici ce qui se passe avec un cache directement adressé. Pour rappel ce genre de cache est conçu en combinant une mémoire RAM, généralement une SRAM, avec quelques circuits de comparaison et un MUX. L'accès au cache se fait globalement en deux étapes : la première lit la donnée et le ''tag'' dans la SRAM, et on utilise les deux dans une seconde étape. Nous avions vu il y a quelques chapitre comment pipeliner des mémoires, dans le chapitre sur les mémoires évoluées. Et bien ces méthodes peuvent s'utiliser pour la mémoire RAM interne au cache !L'idée est d'insérer un registre entre la sortie de la RAM et la suite du cache, pour en faire un cache pipéliné, à deux étages. Le premier étage lit dans la SRAM, le second fait le reste. L'implémentation sur les caches associatifs à plusieurs voies est globalement la même à quelques détails près. [[File:Cache directement adressé pipeliné - deux étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - deux étages]] Avec le circuit précédent, il est possible d'aller plus loin, cette fois en pipelinant l'accès à la mémoire RAM interne au cache. Pour comprendre comment, rappelons qu'une mémoire SRAM est composée d'un plan mémoire et d'un décodeur. L'accès à la mémoire demande d'abord que le décodeur fasse son travail pour sélectionner la case mémoire adéquate, puis ensuite la lecture ou écriture a lieu dans cette case mémoire. L'accès se fait donc en deux étapes successives séparées, on a juste à mettre un registre entre les deux. Il suffit donc de mettre un gros registre entre le décodeur et le plan mémoire. [[File:Cache directement adressé pipeliné - trois étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - trois étages]] Et on peut aller encore plus loin en découpant le décodeur en deux circuits séparés. En effet, rappelez-vous le chapitre sur les circuits de sélection : nous avions vu qu'il est possible de créer des décodeurs en assemblant des décodeurs plus petits, contrôlés par un circuit de prédécodage. Et bien on peut encore une ajouter un registre entre ce circuit de prédécodage et les petits décodeurs. : Théoriquement, toute l'adresse est fournie d'un seul coup au cache, la quasi-totalité des processeurs présentent l'adresse complète à un cache pipeliné. Mais le Pentium 4 fait autrement. Il faut noter que les premiers étages manipulent l'indice dans la SRAM, qui est dans les bits de poids faible, alors que les étages ultérieurs manipulent le ''tag'' qui est dans les bits de poids fort. Les concepteurs du Pentium 4 ont alors décidé de présenter les bits de poids faible lors du premier cycle d'accès au cache, puis ceux de poids fort au second cycle. Pour cela, l'ALU fonctionnait à une fréquence double de celle du processeur, tout comme le cache L1. Il n'y avait pas de pipeline proprement dit, mais cela réduisait grandement la latence d'accès au cache. ===Le ''pipelining'' des caches splittés/sériels=== Il est aussi possible de pipeliner un cache dits splittés, aussi appelés à accès sériel. Pour rappel, les caches à accès sériel vérifient si il y a succès ou défaut de cache, avant d'accéder aux lignes de cache en cas de succès. Ils font donc différemment des autres caches, qui accèdent à une ligne de cache, avant de déterminer s'il y a succès ou défaut en lisant le tag de la ligne de cache. Les caches sériels disposent de deux SRAM : une pour les tags des lignes de cache et une pour les données. Ils accèdent à la SRAM pour les tags, avant d’accéder à la SRAM des données en cas de succès de cache. Vu que l'accès se fait en deux étapes, une vérification des tags suivie de la lecture/écriture des données, il est facile à pipeliner. Pipeliner le cache permet de régler le problème des accès au cache L1, et elle est tout le temps utilisé sur les processeurs modernes. Mais que faire en cas de défaut de cache ? ==Les caches non bloquants== Un '''cache bloquant''' est un cache auquel le processeur ne peut pas accéder pendant un défaut de cache. Il faut attendre que la lecture ou écriture en RAM soit terminée avant de pouvoir utiliser de nouveau le cache. Un '''cache non bloquant''' n'a pas ce problème : on peut l'utiliser pendant un défaut de cache. Les caches non bloquants permettent de démarrer une nouvelle lecture ou écriture alors qu'une autre est en cours, ce qui permet d'exécuter plusieurs lectures ou écritures en même temps. ===Les ''Miss Handling Status Registers''=== Lors d'un défaut de cache, la mémoire RAM est consultée pendant le défaut de cache, mais le cache est inutilisé. Un défaut de cache n'utilise pas le cache, ce n'est qu'un accès en mémoire RAM, sur le bus mémoire (ou un accès aux niveaux de cache inférieurs, peu importe). Le cache en lui-même est laissé libre, rien n’empêche d'y accéder, il est en réalité intrinsèquement non-bloquant. Les caches, bloquants comme non-bloquants, sont en réalité composés d'une mémoire cache proprement dite, entourée de circuits qui servent d'interface entre le processeur et le cache lui-même. Et parmi les circuits tout autour, certains gèrent l'accès au cache lors d'un défaut de cache. Ils sont regroupés sous le terme de '''''Miss Handling Architecture''''' (MHA). La différence entre un cache bloquant et un cache non-bloquant est en réalité liée à l'implémentation de la MHA. Les caches bloquants coupent volontairement l'accès au cache lors d'un défaut, car les défauts sont plus simples à gérer ainsi. Le défaut de cache rapatrie une donnée depuis la RAM, qui sera écrite dans le cache. Et il ne faut pas qu'une tentative d'accès à cette donnée ait lieu avant qu'elle ne soit chargée. Mais ce blocage est général et touche tout le cache, alors que seule une ligne de cache est concernée par le défaut de cache. L'idée derrière un cache non-bloquant est que seule la ligne de cache est bloquée, mais les autres sont accessibles. L'idée est alors de mémoriser les lignes de cache concernées par le défaut de cache, afin d'en bloquer l'accès. À chaque accès, on vérifie si la ligne de cache est déjà réservée par un défaut de cache. La lecture/écriture est alors bloquée si c'est le cas, mais elle accepte les accès sinon. Pour cela, la ''Miss Handling Architecture'' contient des registres qui mémorisent des informations sur les défauts de cache en cours. Ils portent le nom de '''''miss status handling registers''''', que j’appellerais dorénavant MSHR, qui sont aussi appelés des '''''miss buffer'''''. Le contenu des MSHR varie beaucoup suivant le processeur, mais ces derniers stockent au minimum les informations suivantes : * Le numéro de la ligne de cache dans laquelle les données sont chargées. * Un bit de validité qui indique si le MSHR est vide ou pas, qui est mis à 0 quand le défaut de cache est résolu. * Un ou plusieurs champs de lecture/écriture, qui contiennent des informations sur la lecture/écriture. ** Pour une lecture, elle contient des informations sur la destination de la donnée, à savoir qui prévenir quand le défaut de cache est terminé. C'est parfois un nom/numéro de registre (celui dans lequel charger la donnée), mais c'est souvent le numéro de l'entrée dans le ''load/store queue''. ** Pour les écritures, elle contient la donnée à écrire, ou éventuellement un numéro de ''load/store queue'' où se trouve la donnée à écrire. Il faut noter que le bus mémoire ne peut gérer qu'un seul défaut de cache à la fois. Aussi, il est intéressant de regarder ce qui se passe lorsqu'un second défaut de cache survient, pendant qu'un premier est en cours. Dans ce cas, il y a deux réponses qui correspondent à deux types de caches non-bloquants, qui portent les noms barbares de caches de type succès après défaut et défaut après défaut. Sur le premier type, il ne peut pas y avoir plusieurs défauts de cache simultanés. Dès qu'un second défaut de cache survient, le cache stoppe son activité et on ne peut plus démarrer de nouvelle lecture/écriture, tant que le premier défaut de cache n'est pas résolu. Le second type est plus souple et autorise la survenue de plusieurs défauts de cache simultanés. Du moins, jusqu'à une certaine limite, car le cache ne peut supporter qu'un nombre limité d'accès mémoires simultanés (pipelinés). ===Les accès simultanés à une même ligne de cache=== Il arrive que le processeur fasse plusieurs accès mémoire simultanés à la même ligne de cache. Si la ligne de cache en question n'a pas encore été chargée dans le cache, alors on a plusieurs défauts de cache consécutifs pour la même ligne de cache, et le cache non-bloquant doit gérer la situation. Pour la suite, il va falloir faire une petite distinction entre les défauts primaires et secondaires. Imaginons qu'un défaut de cache ait lieu et demande à charger une donnée dans la ligne de cache numéro N. Il s'agit du premier défaut impliquant cette ligne de cache précise, ce qui lui vaut le nom de '''défaut de cache primaire'''. Mais par la suite, d'autres accès mémoire à la même ligne de cache ont lieu, alors que la ligne de cache n'est pas encore disponible. Dans ce cas, il s'agit de '''défauts de cache secondaires'''. Pour l'unité d'accès mémoire, les défauts de cache primaire et secondaire sont différents (ils prennent tous une entrée dans la ''load/store queue''). Mais pour le cache, ils ne correspondent qu'à un seul accès au cache : celui qui demande de charger la ligne de cache demandée. Les défauts de cache primaire et secondaire à la même ligne de cache se voient attribuer un MSHR unique. La gestion des défauts de cache secondaires dépend du cache non-bloquant. La solution la plus simple ne permet pas les défauts de cache secondaires. Le cache ne permet pas deux défauts de cache simultanés pour la même ligne de cache. Les autres solutions le permettent, en fusionnant des accès simultanés à la même ligne de cache en un seul au niveau des MSHR. Dans tous les cas, détecter les défauts de cache secondaires sont un problème qu'il faut détecter. La ''Miss Handling Architecture'' doit détecter les défauts de cache secondaire. Pour cela, elle procède comme suit. Lors de chaque défaut de cache, la MHA récupère le numéro de la ligne de cache associé. Il vérifie alors chaque MSHR pour vérifier s'il contient le numéro en question. S'il n'y a aucune correspondance dans les MSHR, alors c'est signe que le défaut de cache est un défaut primaire. Mais s'il y en a une, alors c'est un défaut secondaire. Évidemment, cela signifie que lors d'un défaut de cache, le numéro de ligne de cache est envoyé à tous les MSHR, pour comparaison. Les MSHR sont donc regroupés dans une mémoire associative, une sorte de mini-cache, faciliter l'implémentation. Lors d'un défaut de cache primaire, l'accès à un cache non-bloquant se fait comme suit : le processeur envoie une adresse au cache, accède à celui-ci, et détecte la survenue d'un défaut de cache. Il en profite alors pour attribuer une ligne de cache dans laquelle sera chargée ce défaut. L'attribution est très simple dans le cas des caches ''direct mapped'', ou associatifs par voie, pour lesquels l'attribution se fait assez simplement. Il mémorise alors cette information dans les MSHR, après avoir vérifié que le défaut de cache n'était pas un défaut secondaire. Lors des accès ultérieurs à une adresse proche, censée être dans la même ligne de cache, le processeur va encore une fois rencontrer un défaut de cache. Il va alors déterminer le numéro de la ligne de cache associée à l'adresse, et comparer ce numéro avec les MSHRs. Si un MSHR contient ce numéro, c'est signe que le défaut de cache est un défaut secondaire. La MHA réagit alors différemment selon le processeur considéré. Une première solution n'autorise pas les défauts de cache secondaires. Si l'un d'entre eux survient, le processeur est gelé par un ''pipeline stall'', une bulle de pipeline. Une autre solution fusionne les défauts de cache secondaires avec le défaut de cache primaire : tout cela ne correspond qu'à un seul défaut de cache pour lui. ===Les MSHR simples=== Un cache non-bloquant à '''MSHR simple''' contient juste plusieurs MSHR qui mémorisent juste un numéro de ligne de cache, un bit de validité, le champ de lecture/écriture, et l'adresse exacte de lecture/écriture. Avec cette organisation, il est possible d'avoir plusieurs défauts de cache séparés, mais à la condition que chaque défaut accède à une ligne de cache différente. Deux accès simultanés à une même ligne de cache ne sont pas possibles, les défauts de cache secondaires ne sont pas autorisés. Ainsi, chaque défaut de cache se voit attribuer son propre MSHR, chacun contient un numéro de ligne de cache différent. Pour comprendre pourquoi c'est impossible de gérer les défauts secondaires, il faut regarder le champ de lecture/écriture. Si on veut effectuer plusieurs écritures consécutives à la même adresse, le MSHR n'aura pas de quoi mémoriser les deux données à écrire. Il pourra mémoriser la première donnée à écrire, pas la seconde. Même chose lors d'une lecture : le champ lecture/écriture peut mémorisr la destination de la première lecture, pas de la seconde. L'avantage est que la MHA n'a besoin que des MSHR et de quelques circuits annexes. Les autres solutions rajoutent des circuits annexes pour gérer les défauts de cache secondaires, qui utilisent beaucoup de circuits. Le cout en circuit est donc élevé, mais le gain en performance est là. Passons maintenant aux caches non-bloquants qui autorisent les défauts de cache secondaire. La solution la plus simple consiste à utiliser ===Les MSHR adressés implicitement=== Avec les '''MSHR adressés implicitement''', il est possible de fusionner plusieurs accès mémoire à une même ligne de cache, mais sous une condition très importante : ces accès lisent/écrivent des mots mémoire différents. Par exemple, imaginons qu'une ligne de cache contienne 8 mots mémoire de 64 bits. Si un premier accès mémoire lit le mot mémoire numéro 7 (dernier mot mémoire de la ligne), et le second accès le mot mémoire numéro 3, alors la fusion est possible. Mais si deux accès mémoire veulent lire/écrire le mot mémoire numéro 7, alors la fusion n'est pas possible et le processeur se bloque, un ''pipeline stall'' survient. Un MSHR adressé implicitement est un MSHR simple, qui contient naturellement un numéro de ligne de cache (tag) et un bit de validité, sauf que le champ de lecture/écriture est dupliqué. Les différents champs lecture/écriture d'un MSHR sont regroupés dans une mémoire RAM/cache qui contient autant d'entrées qu'il y a de mot mémoire dans une ligne de cache. Chaque entrée de la table est associée à un mot mémoire de la ligne de cache et stocke des informations sur celui-ci. Une entrée mémorise, au minimum : * Un champ de lecture/écriture qui contient soit la destination de la lecture, soit la donnée à écrire. * Un bit R/W permettant d'interpréter correctement le champ de lecture/écriture. * Un bit de validité pour chaque entrée de la table, qui dit si un défaut de cache antérieur accède déjà à ce mot mémoire. La fusion de deux défauts de cache est ainsi assez simple. Un défaut de cache primaire/secondaire configure l'entrée associée au mot mémoire lu/écrit. Si un défaut secondaire ultérieur lit/écrit un mot mémoire différent (dont le bit de validité de l'entrée dans la table est à zéro), pas de conflit : il configure une autre entrée, vide. Mais s'il lit/écrit un mot mémoire pour lequel l'entrée de la table est occupée, il y a conflit, le processeur est bloqué par un ''pipeline stall'', une bulle de pipeline. Avec cette organisation, le nombre de MSHR indique combien de lignes de cache peuvent être lues en même temps depuis la mémoire. Quant au nombre d'entrées par MSHR, il détermine combien d'accès mémoires qui ne se recouvrent pas peuvent avoir lieu en même temps. ===Les MSHR adressés explicitement=== Les '''MSHR adressés explicitement''' n'ont pas les contraintes des MSHR adressés implicitement. Avec eux, il est possible d'avoir plusieurs défauts de cache pointant vers la même ligne de cache, mais aussi vers le même mot mémoire. De tels défauts de cache apparaissent sur les processeurs à exécution dans le désordre, mais sont très rares, voire inexistants, sur les processeurs ''in-order''. L'idée est encore que chaque MSHR est associée à une table mémoire qui mémorise des entrées. Cette table n'est autre qu'une mémoire FIFO. Sauf que cette fois-ci, une entrée n'est pas associée à un mot mémoire. Une entrée est associée à un défaut de cache. Une entrée mémorise là encore la destination de la lecture ou la donnée de l'écriture suivi du bit R/W, ainsi que d'un bit de validité par entrée, mais aussi : la position du mot mémoire lu dans la ligne de cache. C'est cette dernière information qui n'était pas présente dans les MSHR adressés implicitement. De plus, le nombre d'entrée par MSHR n'est pas égal au nombre de mots mémoires dans une ligne de cache, mais peut être arbitrairement grand. L'avantage d'un tel cache est qu'il est capable de traité les défauts secondaires concernant un même mot mémoire, et ce, car les accès à la ligne de cache concernée se produisent dans l'ordre de génération des défauts (la table mémoire est une FIFO). ===Les MSHRs inversés=== Généralement, plus on veut supporter de défauts de cache, plus le nombre de MSHR et d'entrées augmente. Mais au-delà d'un certain nombre d'entrées et de MSHR, les MSHR adressés implicitement et explicitement ont tendance à bouffer un peu trop de circuits. Utiliser une organisation un peu moins gourmande en circuits est donc une nécessité. Cette organisation plus économe se base sur des MSHR inversés. Les MSHR inversés ne contiennent qu'une seule entrée, en quelque sorte : au lieu d’utiliser n MSHR de m entrées chacun, on va utiliser n × m MSHR inversés. La différence, c'est que plusieurs MSHR peuvent contenir un tag identique, contrairement aux MSHR adressés implicitement et explicitement. Lorsqu'un défaut de cache a lieu, chaque MSHR est vérifié. Si jamais aucun MSHR ne contient de tag identique à celui utilisé par le défaut, un MSHR vide est choisi pour stocker ce défaut, et une requête de lecture en mémoire est lancée. Dans le cas contraire, un MSHR est réservé au défaut de cache, mais la requête n'est pas lancée. Quand la donnée est disponible, les MSHR correspondant à la ligne qui vient d'être chargée vont être utilisés un par un pour résoudre les défauts de cache en attente. ===Les MSHR intégrés au cache=== Certains chercheurs ont remarqué que pendant qu'une ligne de cache est en train de subir un défaut de cache, celle-ci reste inutilisée, et son contenu est destiné à être perdu une fois le défaut de cache résolu. Ils se sont dit que, plutôt que d'utiliser des MSHR séparés, il vaudrait mieux utiliser la ligne de cache pour stocker les informations sur les défauts de cache en attente dans cette ligne de cache. Pour éviter tout problème, il faut rajouter un bit dans les bits de contrôle de la ligne de cache, qui sert à indiquer que la ligne de cache est occupée : un défaut de cache a eu lieu dans cette ligne, et elle stocke donc des informations pour résoudre les défauts de cache. ==L'entrelacement mémoire== Nous venons de voir comment une mémoire cache peut gérer plusieurs accès mémoire simultanés. Il se trouve que ce genre d'optimisation dépasse largement le cadre du cache. Les mémoires RAM ne sont pas en reste, elles aussi ! Il est possible de pipeliner l'accès à une mémoire RAM, en utilisant des techniques dites d''''entrelacement'''. Précisons cependant que cette optimisation n'est pas utilisée sur les mémoires SDRAM ou DDR modernes, du moins pas sans modifications majeures. Mais divers ordinateurs assez anciens, dont des superordinateurs, ont utilisé cette technique. La technique demande d'utiliser plusieurs mémoires séparées. Sur les anciens ordinateurs, les mémoires en question sont des chips mémoire, à savoir des circuits intégrés de DRAM. Pour faire plus général, nous allons utiliser le terme de '''banques''', ou encore de bancs mémoire. La différence est qu'il existe des mémoires multi-banque, qui regroupent plusieurs banques indépendantes dans un seul boitier, dans un seul chip mémoire. En clair, de telles mémoires regroupent plusieurs sous-mémoires dans un seul circuit intégré. Il est aussi possible d'utiliser plusieurs chips mémoire séparés, chacun ayant une banque. Reste à combiner ces banques pour former un pseudo-pipeline. ===Utiliser plusieurs banques sans entrelacement=== Sans optimisation particulière, les adresses sont réparties dans les banques comme indiqué ci-dessous. Il s'agit de ce qui s'appelle un arrangement horizontal, et nous avions vu celui-ci dans le chapiutre sur les mémoires SDRAM. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Avec cet arrangement, les bits de poids fort de l'adresse sont utilisées pour sélectionner la banque adéquate, et le reste de l'adresse est envoyé sur le bus d'adresse. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Adresse de banque !! Adresse dans la banque |- | Quelques bits de poids fort || Reste de l'adresse |} L'entrelacement change cette répartition, afin d'accélérer les accès mémoire. L'entrelacement classique vise à améliorer les accès à des adresses mémoires consécutives, mais des formes plus évoluées d'entrelacement visent à optimiser des accès mémoire arbitraires. Voyons-les dans l'ordre, du plus simple au plus complexe. ===L'entrelacement classique=== Avec l''''entrelacement classique''', des adresses consécutives sont réparties dans des banques consécutives. Précisons que cette attribution des adresses n'implique absolument pas la mémoire virtuelle ou n'importe quel mécanisme dans le processeur. La répartition décide que telle adresse mémoire va dans telle banque, à telle adresse dans la banque. Elle est donc le fait du contrôleur mémoire, donc en dehors du processeur (les contrôleurs mémoires n’étaient pas encore intégrés dans le processeur à l'époque). [[File:Répartition des adresses dans une mémoire interleaved.png|centre|vignette|upright=1.5|Répartition des adresses dans une mémoire interleaved.]] L'entrelacement simple permet d'accélérer les accès mémoire qui se font à des adresses consécutives. Avec l'entrelacement, chaque accès mémoire tombe dans une banque mémoire différente, ce qui fait qu'on peut démarrer un nouvel accès mémoire à chaque cycle d'horloge. Pendant qu'une banque est occupée par un accès mémoire, on démarre le suivant dans une autre banque, et ainsi de suite. Le tout se termine soit quand on a épuisé toutes les banques libres, soit quand l'accès en rafales se termine. Pas besoin d'attendre que la mémoire ait fini sa lecture/écriture avant de démarrer la lecture/écriture suivante. [[File:Pipemining mémoire.png|centre|vignette|upright=2.5|Pipelining mémoire]] L'animation suivante illustre bien le processus, avec quatres banques. [[File:Interleaving.gif|centre|vignette|upright=2.5|Pipelining mémoire]] L'entrelacement simple utilise les bits de poids faible pour sélectionner la banque, et les bits de poids fort pour la case mémoire. [[File:Adresse mémoire d'une mémoire entrelacée.png|centre|vignette|upright=2|Adresse mémoire d'une mémoire entrelacée]] Précisons que le temps d'accès mémoire ne change pas beaucoup avec l'entrelacement. Par contre, on peut faire plus d'accès mémoire simultanés. On peut démarrer un accès mémoire par cycle d'horloge, mais l'accès en lui-même prend plusieurs cycles. Les mémoires à entrelacement ont donc un débit supérieur aux mémoires qui ne l'utilisent pas. L'entrelacement classique pourrait être utilisé pour accélérer les transferts entre mémoire RAM et cache, qui se font en blocs d'adresses consécutives. Mais de nos jours, il n'est pas utilisé dans cette optique. Il faut dire qu'il est redondant avec le mode rafale des mémoires DRAM. Les deux font globalement la même chose et on voit mal comment utiliser les deux en même temps. Le mode rafale étant plus simple à implémenter, et plus léger niveau utilisation du bus de commande mémoire, il est préféré à l'entrelacement. Par contre, l'entrelacement a eu son heure de gloire sur d'anciens processeurs qui n'avaient pas de mémoire cache, alors qu'ils disposaient d'un pipeline. L'exemple typique est celui des nombreux ''processeurs vectoriels'', que nous n'avons pas encore abordé à ce stade du cours. De tels processeurs avaient un pipeline, une unité de calcul fortement pipelinée, et parfois même de l'exécution dans le désordre et du renommage de registres ! ===L'entrelacement par décalage=== L'entrelacement simple est très utile pour les accès en rafle ou équivalents. Par contre, il existe de rares situations où il n'est pas optimal. Une de ces situations est celle des accès en enjambée (en ''stride''), où l'on accède en série à des adresses sont séparées par N mots mémoires. [[File:Accès par enjambées.png|centre|vignette|upright=2|Accès par enjambées.]] De tels accès surviennent quand un logiciel accède à des structures de données spécifiques, à savoir des tableaux de structures, des matrices, ou d'autres structures de données dans le genre. Un cas classique est celui du parcours d'une matrice colonne par colonne. Les matrices de nombres sont mémorisées ligne par ligne, ce qui fait que parcourir une colonne demande de passer d'une ligne à la suivante et de faire des grands sauts en mémoire RAM, mais tous espacés par la même distance. Avec l'entrelacement simple, les accès en enjambée sont moins performants que les accès à des adresses consécutives, mais cela ne fonctionne pas trop mal. Le pire cas est celui où l'on a N banques et où les données sont justement placées toutes les N adresses. Des accès consécutifs vont tous tomber dans la même banque, on ne peut plus accéder à des banques différentes en parallèle. Mais en dehors de ce cas, on voit une amélioration par rapport à la situation sans entrelacement. Pour obtenir des performances maximales pour les accès en enjambées, il faut répartir les mots mémoires dans la mémoire autrement. L'organisation idéale est la suivante. : Dans les explications qui vont suivre, la variable N représente le nombre de banques, qui sont numérotées de 0 à N-1. On commence par organiser les N premières adresses comme une mémoire entrelacée simple : l'adresse 0 correspond à la banque 0, l'adresse 1 à la banque 1, etc. Pour le bloc suivant, on décale tout d'une adresse, à savoir qu'on commence à remplir les banques à partir de la seconde, non de la première. Une fois la fin du bloc atteinte, on finit de remplir le bloc en repartant du début du bloc. Le troisième bloc d'adresse subit le même traitement, sauf qu'on commence à remplir à partir de la seconde banque. Et on poursuit l’assignation des adresses en décalant d'un cran en plus à chaque bloc. Ainsi, chaque bloc verra ses adresses décalées d'un cran en plus comparé au bloc précédent. Si jamais le décalage dépasse la fin d'un bloc, alors on reprend au début. [[File:Mémoire interleaved par décalage.png|centre|vignette|upright=1.5|Mémoire entrelacée par décalage.]] En faisant cela, on remarque que les banques situées à N adresses d'intervalle sont différentes. Dans l'exemple du dessus, nous avons ajouté un décalage de 1 à chaque nouveau bloc à remplir. Mais on aurait tout aussi bien pu prendre un décalage de 2, 3, etc. Dans tous les cas, on obtient un entrelacement par décalage. Ce décalage est appelé le pas d'entrelacement, noté P. Le calcul de l'adresse à envoyer à la banque, ainsi que la banque à sélectionner se fait en utilisant les formules suivantes : * adresse à envoyer à la banque = adresse totale / N ; * numéro de la banque = (adresse + décalage) modulo N ; * décalage = (adresse totale * P) mod N. Avec cet entrelacement par décalage, on peut prouver que la bande passante maximale est atteinte si le nombre de banques est un nombre premier. Seulement, utiliser un nombre de banques premier peut créer des trous dans la mémoire, des mots mémoires inadressables. Malgré ce défaut, la technique a été utilisée sur quelques ordinateurs, avec l'exemple notable du superordinateur ''Burroughs Scientific Processor''. Pour éviter cela, il y a plusieurs solutions. Par exemple, on peut faire en sorte que N et la taille d'une banque soient premiers entre eux : ils ne doivent pas avoir de diviseur commun. Mais en pratique, elles n'ont pas vraiment été implémentées dans une vraie machine, et sont restées à l'état de recherche, aussi je les passe sous silence. ===L'entrelacement pseudo-aléatoire=== Une dernière méthode de répartition consiste à répartir les adresses dans les banques de manière "pseudo-aléatoire". L'idée exacte est de faire passer l'adresse dans une '''fonction de hachage''', plus ou moins complexe. Pour rappel, une fonction de hachage prend une entrée de grande taille et fournit en sortie un résultat de petite taille. Idéalement, elles doivent donner des résultats aussi différents que possible pour des entrées similaires, histoire de simuler une sorte de pseudo-aléatoire. Ici, une adresse est transformée en un numéro de banque. Une fonction de hachage simple ne fait que permuter des bits de l'adresse pour obtenir son résultat. Elle ne fait qu'échanger des bits de place avant de couper l'adresse en deux morceaux : un pour la sélection de la banque, et un autre pour la sélection de l'adresse dans la banque. Cette permutation est fixe, et ne change pas suivant l'adresse. Des fonctions de hachage plus complexes font des XOR bit à bit entre certains bits de l'adresse. ==Les contrôleurs SDRAM/DDR avec "exécution dans le désordre"== Après avoir vu le cas des mémoires RAM, nous allons nous concentrer sur le cas particulier des mémoires SDRAM. Les mémoires SDRAM et DDR modernes sont capables de gérer plusieurs accès simultanés à la mémoire RAM, et leurs contrôleurs mémoire en font tout autant. C'est une différence majeure avec les mémoires asynchrones FPM/EDO, qui n'acceptaient qu'un seul accès mémoire à la fois. Leur contrôleur mémoire n'acceptait qu'un seul accès mémoire à la fois, c'était un contrôleur mémoire bloquant. Les contrôleurs mémoires des SDRAM sont eux non-bloquants et peuvent encaisser une dizaine d'accès mémoire à la fois. Peu de choses sont connues sur les contrôleurs de SDRAM/DDR modernes, les fabricants ne donnant que peu de détails dessus. Les rares simulateurs qui tentent de décrire leur fonctionnement, comme DRAM SIM I et II, sont particulièrement simples et ne vont pas dans le détail. Néanmoins, le peu qu'on sait est tout de même instructif. ===Rappel sur les SDRAM : tampon de ligne et commandes=== Les mémoires SDRAM sont des mémoires à tampon de ligne. Elles font un accès mémoire en deux étapes. La première étape recopie une ligne de N * 64 bits dans un tampon interne à la SDRAM. La seconde étape sélectionne une colonne, à savoir une donnée de 64 bits, qui est soit envoyée sur le bus de données pour une lecture, soit modifiée par une écriture. [[File:Mémoire à tampon de ligne.png|centre|vignette|upright=2|Mémoire à tampon de ligne]] Les accès mémoire sont traduits par un séquenceur mémoire en une série de commandes mémoires, qui sont séparées par des délais mémoire de quelques cycles d'horloge. Les délais sont très précis, et sont à respecter à la lettre. Une lecture ou une écriture se fait en maximum trois commandes : une commande PRECHARGE qui ferme la ligne précédemment utilisée, une commande ACT qui précise l'adresse de la ligne, et une commande READ ou WRITE qui précise l'adresse de la colonne et éventuellement la donnée à écrire. : Pour être plus précis, la commande PRECHARGE précharge les lignes de bits du plan mémoire à une tension particulière, ce qui les vide de leur contenu. Mais c'est un détail sans importance pour ce qui va suivre. Les SDRAM permettent de se passer de la première étape si des accès consécutifs se font dans la même ligne. Une fois activée, la ligne reste ouverte et on peut accéder plusieurs fois de suite dedans. On a alors juste à préciser l'adresse de colonne dedans. Si un accès mémoire accède à une ligne déjà activée, on dit que c'est un '''succès de page'''. Si ce n'est pas le cas, on doit fermer la ligne courante, rouvrir la ligne vboulue, et préciser la colonne. C'est alors un '''défaut de page'''. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | bgcolor="#FFA0FF" | PRECHARGE || bgcolor="#FFA0FF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || bgcolor="#A0FFFF" | READ (2) || bgcolor="#A0FFFF" | READ (3) || || || bgcolor="#A0FFFF" | READ (4) || bgcolor="#A0FFFF" | READ (5) || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | Donnée READ 1 || ||bgcolor="#A0FFFF" | Donnée READ 2 | bgcolor="#A0FFFF" | Donnée READ 3 || || || bgcolor="#A0FFFF" | Donnée READ 4 || bgcolor="#A0FFFF" | Donnée READ 5 |} Les SDRAM sont parfois capables de démarrer une commande avant que la précédente soit terminée. Mais le respect des délais mémoire est très limitant, ce qui fait qu'on ne peut pas parler de réel pipeline, comme c'est le cas sur les processeurs. Parlons plutot de '''commandes anticipées'''. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | || bgcolor="#A0FFFF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || || || bgcolor="#FFA0FF" | READ (2) || || || || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 | bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 | |} Un dernier point est que les chips mémoires de SDRAM sont composés de plusieurs banques, chacune étant une sorte de mini-mémoire miniature. Chaque banque a son propre décodeur, son propre tampon de ligne, ses propres multiplexeurs de colonne, sa logique de rafraichissement mémoire, etc. C'est comme si une SDRAM regroupait plusieurs mémoires séparées dans un même circuit intégré. [[File:Arrangement vertical.jpg|centre|vignette|upright=2.5|Mémoire multi-banques.]] ===La mise en attente des accès mémoire=== Un contrôleur de SDRAM peut accepter plusieurs accès mémoire en même temps. Mais cela ne signifie pas que le contrôleur sera prêt à les traiter immédiatement. Pour éviter tout problème de disponibilité, le contrôleur met en attente les accès mémoire que le processeur lui envoie, pour les exécuter dès que possible. Les accès mémoire sont mis en attente dans une mémoire FIFO, histoire de les exécuter dans leur ordre d'arrivée, notamment pour renvoyer les lectures dans l'ordre demandé. Il y a aussi une mémoire FIFO pour les données à écrire et une FIFO pour les données lues. Cette dernière sert au cas où le cache ne soit pas disponible quand on lui envoie la donnée lue. [[File:Module d'interface avec la mémoire.png|centre|vignette|upright=2.5|Contrôleur mémoire avec mise en attente des requêtes processeur et autres optimisations.]] Des optimisations sont possibles dès la mémoire FIFO. Par exemple, on peut regrouper plusieurs accès à des données consécutives en un seul accès en rafale. Le contrôleur mémoire analyse les requêtes mises en attente et détecte si certaines se font à des adresses consécutives. Si c'est le cas, il fusionne ces requêtes en une lecture/écriture en rafale. Une telle optimisation est appelée la '''combinaison de lecture''' pour les lectures, et la '''combinaison d'écriture''' pour les écritures. L'optimisation précédente ne paraissent pas payer de mine, mais elles deviennent plus compréhensible quand on sait que certains contrôleurs mémoire peuvent mettre en attente beaucoup de données. Par exemple, l'''Intel E8870 - Scalable Memory Controller'' peut mettre en attente 64 lignes de cache dans la mémoire FIFO, soit 8 kibioctets de RAM ! Deux pages mémoire si la mémoire virtuelle utilise des pages de 4 kibioctets ! ===Les lectures anticipées : une forme d'OOO mémoire=== La mise en attente des accès mémoire est une optimisation intéressante, mais pas mirifique. Elle permet surtout de ne pas bloquer le processeur si le contrôleur mémoire a du travail sur la planche. Cependant, elle peut être optimisée quand on se rend compte d'une chose : le processeur ne voit que les lectures, pas les écritures. Pour les écritures, il les envoie au contrôleur mémoire et ce dernier fait le travail demandé, le processeur n'est pas prévenu quand une écriture se termine. Mais pour les lectures, c'est différent, car il reçoit la donnée lue. En conséquence, il est possible d'optimiser les écritures sans que le processeur ne voit quoique ce soit. Une optimisation possible est alors de changer l'ordre des accès mémoire, histoire de retarder les écritures ou au contraire de les faire en avance. La raison est que les mémoires SDRAM n'aiment pas quand on alterne lectures et écritures. Les délais mémoire, les fameux ''timings'' mémoire, sont clairs : faire une suite de lecture ou une suite d'écriture est plus performant que d'alterner entre lectures et écritures. L'idée est alors de changer l'ordre des écritures de manière à les faire en bloc, idem pour les lectures. L’optimisation est possible, mais à condition que le processeur n'y verra que du feu. Or, vous l'avez deviné, il y a des cas où ces réorganisations ne sont pas possibles, et vous avez sans doute trouvé que la situation était familière. L'optimisation en question est une forme d'exécution dans le désordre des accès mémoire, réalisée par le contrôleur mémoire lui-même ! Et qui dit exécution dans le désordre dit : problèmes liés aux dépendances de données. Changer l'ordre des écritures n'est pas toujours possible, notamment en raison des dépendances RAW, WAR et WAW. Pour que le processeur n'y voit que du feu, il faut respecter plusieurs critères. Premièrement, les lectures doivent être renvoyées au processeur dans l'ordre. Et quand je dis dans l'ordre, cela veut dire : dans l'ordre d'envoi des lectures au contrôleur mémoire. Les écritures aussi doivent se faire dans l'ordre, histoire qu'une série d'écriture donne le bon résultat final. Le troisième critère est qu'une lecture doit donner le résultat de la dernière écriture ''à la même adresse''. Pour le dire autrement, il faut juste détecter les dépendances RAW. Concrètement, si on a une série de lectures et d'écritures alternées, on peut regrouper les lectures et les écritures dans deux paquets séparés, à condition qu'aucune lecture ne lise une adresse écrite par une écriture. Mais si une lecture est dépendante d'une écriture, alors la lecture doit attendre que l'écriture se termine, idem pour les lectures suivantes. Une implémentation basique remplace la mémoire FIFO vue au-dessus, par deux mémoires FIFOs : une pour les lectures, une pour les écritures. Lorsqu'un accès mémoire arrive au séquenceur, il regarde si c'est une lecture ou une écriture et place l'accès mémoire dans la file adéquate. Le fait que ce soit des FIFOs garantit que les lectures se font dans l'ordre, idem pour les écritures. En sortie des deux FIFOs, le séquenceur mémoire détecte les dépendances RAW. Précisément, quand une lecture sort de la mémoire FIFO, il consulte la file d'attente des écriture pour voir si la lecture est dépendante d'une écriture en attente. La lecture attend si c'est le cas, et les écritures sont exécutées à la place. Séparer lectures et écriture est une source d'optimisation. Il est par exemple possible d'utiliser le réacheminement lecture sur écriture (''Store to load forwarding''). Il s'agit d'un équivalent de l’optimisation du même nom, utilisée dans le cadre de la désambiguïsation mémoire. Imaginez qu'une lecture accède à une donnée pas encore écrite, en attente dans le contrôleur mémoire. C'est une dépendance RAW assez claire. Dans ce cas, le contrôleur mémoire peut renvoyer la donnée directement depuis ses mémoires FIFOs, sans faire d'accès mémoire en lecture. Le contrôleur mémoire a tendance à privilégier les lectures, car celles-ci sont cruciales pour l'exécution dans le désordre. Plus elles se font tôt, plus vite le processeur recevra les données et pourra démarrer les instructions dépendantes de cette donnée. Le séquenceur a donc tendance à piocher en priorité dans la file de lecture, plutôt que dans la file d'écriture. A vrai dire, dans le cas idéal, les écritures ne sont faites que quand la file d'écriture est pleine ou quasi-pleine... Prenons l'exemple des coprocesseurs IO 81341 et 81342, qui étaient en réalité des ''chipsets'' intégrant un contrôleur SDRAM. Le ''chipset'' avait 5 ports : un pour les processeurs, un pour le pont sud (''southbridge'') et trois pour des canaux DMA. Pour le port processeur, il n'y avait pas de réordonnancement et ce port utilisait une seule mémoire FIFO. Les autres ports utilisaient l'optimisation qu'on vient de voir, et avaient vraisemblablement des files séparées pour les lectures et écritures. Le séquenceur mémoire vérifiait les dépendances mémoire de type RAW et autres. [[File:Double file d'attente pour les lectures et écritures.png|centre|vignette|upright=2.5|Double file d'attente pour les lectures et écritures]] ===Le ré-ordonnancement des commandes mémoires=== L'optimisation précédente est peu poussée, comparé aux formes d'exécution dans le désordre que les processeurs utilisent. Et on peut se demander si une exécution dans le désordre plus poussée est possible. La réponse est oui : un contrôleur mémoire peut faire des réorganisations bien plus poussées. Par contre, il faut faire une précision très importante : le contrôleur mémoire ne remet pas les accès mémoire dans l'ordre avant de les envoyer au processeur. C'est le processeur qui remet en ordre les accès mémoire, dans sa ''load queue''. En clair : le processeur voit les accès mémoire dans le désordre, c'est lui qui les remet en ordre. Pour cela, les requêtes mémoire sont "numérotées", à savoir qu'on leur attribue un identifiant binaire. Le contrôleur mémoire exécute les lectures/écritures dans le désordre, mais garde la trace de leur identifiant. Il sait dans quel ordre il exécute les accès mémoire et connait leur latence. Ce qui fait qu'il sait que la donnée lue à tel instant est associée à la requête mémoire numéro X. Pour les lectures, il renvoie la donnée lue avec l'identifiant. Le processeur sait alors à quelle lecture la donnée lue correspond et il se débrouille en interne pour gérer la situation. Maintenant que ces précisions sont faites, posons cette question : dans quelles situations est-il pertinent de faire des accès mémoire dans le désordre ? La réponse est : quand plusieurs accès à une même ligne ne sont pas consécutifs, qu'il y a des accès entre les deux. Après ré-ordonnancement, ces accès mémoire à une même ligne sont exécutés l'un à la suite de l'autre, ce qui est beaucoup plus rapide. Pour rendre le tout plus concret, voici un exemple. Imaginez que l'on sait les 3 accès mémoire suivants : * Une lecture ligne A ; * PRECHARGE + ACT ; * Une écriture ligne B ; * PRECHARGE + ACT ; * Une lecture ligne A. Sans réordonnancent, on doit émettre deux commandes PRECHARGE quand on passe d'une ligne à l'autre, et il faut ajouter les commandes ACT avec. pour éviter cela, le contrôleur mémoire peut retarder l'écriture, ou au contraire avancer la seconde lecture. le résultat est alors le suivant : * Lecture ligne A ; * Lecture ligne A * PRECHARGE + ACT ; * Une écriture ligne B. On a donc deux accès consécutifs à la même ligne, suivi par une écriture dans une autre ligne. La technique marche parce que l'on a un mix adéquat de lectures et d'écritures. Et encore une fois, il faut éviter d'intervertir lectures et écritures à une même adresse, sous peine de problèmes. Encore une fois, les dépendances RAW posent problème ! L'implémentation est assez simple : la ou les mémoires FIFOs précédentes sont remplacées par des mémoires similaires aux fenêtres d'instruction. Le contrôleur mémoire vérifie à chaque cycle les accès en attente, et vérifie à quelle ligne ils accèdent. Il priorise alors les accès qui tombent dans la ligne ouverte : ceux-là sont exécutés avant les autres. S'il n'y en a pas, il prend l'accès mémoire le plus ancien (ordre FIFO). Il teste aussi les dépendances mémoires RAW avant d'envoyer des commandes à la mémoire DDR/SDRAM. une telle solution est appelée l''''algorithme ''FR-FCFS''''' (''First Ready-First Come First Serve''). ===Les optimisations liées à la présence de plusieurs banques=== Les optimisations précédentes sont décuplées par la présence de plusieurs banques dans la mémoire SDRAM. Il est en effet possible de faire plusieurs accès mémoire en même temps, dans des banques différentes. Il est possible de lancer un accès mémoire dans deux banques en même temps, par exemple. La seule contrainte est que la SDRAM est limitée à 4 banques actives en même temps. {|class="wikitable" |+ Pipelining basique sur les SDRAM |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 |- ! Banque Numéro 1 | || || || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || || |- ! Banque Numéro 2 | || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || |- ! Banque Numéro 3 | || || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || |} Les optimisations précédentes peuvent s'appliquer par banque. Il est par exemple possible d'utiliser une mémoire FIFO par banque. Ainsi, les accès dans une même banque se feront en série, dans l'ordre d'arrivée, mais des accès à des banques différentes se feront dans le désordre. Cela ne pose pas de problème car les accès se font à des adresses différentes, ce qui fait qu'il n'y a pas de conflits majeurs. Il y a cependant un risque que les lectures se fassent dans le désordre si on n'y prend pas garde. [[File:Gestion parallèle des banques.png|centre|vignette|upright=2.5|Gestion parallèle des banques.]] il est aussi possible d'utiliser une file séparée pour les lectures et les écriture, pour chaque banque. Les performances sont alors améliorées comparé à deux files globales pour toute la SDRAM. En idem avec la réorganisation des accès mémoire, pour regrouper les accès à une même ligne. ==L'entrelacement sur les mémoires SDRAM== L''''entrelacement''' fonctionne sur les mémoires SDRAM, mais il doit être fortement modifié. Concrètement, tout ce qui a été dit plus est inapplicable pour les SDRAM, du fait de la présence du tampon de ligne. Des adresses consécutives atterrissent déjà dans la même ligne, ce qui fait qu'on n'a pas besoin d'entrelacement au niveau d'une ligne. Et ne parlons pas de ce la présence de rangées et de canaux mémoire ! Cependant, il peut y avoir un entrelacement lié à la présence des banques SDRAM. L'entrelacement est géré juste avant le séquenceur mémoire. Le '''circuit d'entrelacement''' intervertit certains bits de l'adresse lors des accès mémoires. On trouve aussi des mémoires FIFOs pour mettre en attente les requêtes mémoire provenant du processeur. Elles permettent de recevoir plusieurs accès mémoire consécutifs. Si vous vous demandez comment plusieurs accès mémoire consécutifs peuvent avoir lieu, sachez que c'est une bonne question. Pour le moment, nous allons dire que ces accès proviennent du processeur ou de la mémoire cache, sans expliquer comment. Le contrôleur mémoire reçoit une série d'accès mémoire assez rapprochés, qu'il doit exécuter au mieux. [[File:Controleur mémoire d'une SDRAM avec entrelacement.png|centre|vignette|upright=2.5|Contrôleur mémoire d'une SDRAM avec entrelacement.]] Pour gérer l'entrelacement, le contrôleur de SDRAM prend en entrée l'adresse envoyée par le processeur, et la découpe en plusieurs champs : un pour sélectionner la banque adéquate, un autre pour la ligne, un autre pour la colonne, un autre pour la rangée. Une solution simple désactive l'entrelacement. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de banque || Adresse de ligne || Adresse de colonne |} Si cette solution fonctionne bien si les accès mémoire sont assez aléatoires, en pratique, mieux vaut utiliser l'entrelacement. ===L'entrelacement de banques/lignes=== Une première méthode d'entrelacement est l''''entrelacement de banques'''. Elle répartit deux lignes consécutives dans deux banques différentes. Pour comprendre l'idée, prenons un exemple. Imaginons une mémoire avec deux banques et 4 lignes. Imaginons qu'on parcoure/balaye la mémoire RAM en partant des adresses basses. Sans entrelacement de ligne, les accès se feront comme suit, de gauche à droite : {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Banque numéro 1 !! colspan="4" | Banque numéro 2 |- | Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 || Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 |} Avec l'entrelacement de banques, les accès se feront comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 |- | Ligne 1 || Ligne 1 || Ligne 2 || Ligne 2 || Ligne 3 || Ligne 3 || Ligne 4 || Ligne 4 |} Avec N banques, la première ligne ira dans la première banque, la seconde ligne dans la seconde, et ainsi de suite jusqu'à la énième banque qui va dans la banque numéro N. Et la ligne N+1 repart dans la première banque. L'avantage est que passer d'une ligne à la suivante est assez rapide. Au lieu de devoir fermer la première ligne et ouvrir la suivante on ouvrira directement la ligne suivante dans une banque différente, ce qui sera plus rapide. La commande PRECHARGE pourra être envoyée à la seconde banque en avance. Pour cela, l'adresse est découpée comme suit. {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne (précise à l'octet) |} ===L'entrelacement de rangées=== Il est aussi possible de faire la même chose, mais avec les rangées. Pour simplifier fortement, une rangée est simplement un chip mémoire. En réalité, les rangées ne sont pas des chip mémoire, mais un ensemble de chips mémoire regroupés ensemble histoire d'atteindre les 64 bits du bus de données. Par exemple, une rangée peut combiner 8 chips mémoire avec un bus de données de 8 bits chacun pour obtenir les 64 bits du bus de données d'une SDRAM. Mais nous passons ce détail sous silence dans les explications qui vont suivre, par souci de simplification. Pour faire comprendre l'entrelacement de rangée, le mieux est d'utiliser un exemple, le même que précédemment. Sans entrelacement de rangée, on change de chip mémoire une fois qu'on a balayé toutes ses adresses. {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Rangée numéro 1 !! colspan="4" | Rangée numéro 2 |- | Banque 1 || Banque 2 || Banque 3 || Banque 4 || Banque 1 || Banque 2 || Banque 3 || Banque 4 |} Avec l'entrelacement de ligne, on change de rangée dès qu'on change de banque/ligne. {|class="wikitable" |+ Adresse mémoire |- ! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 |- | Banque 1 || Banque 1 || Banque 2 || Banque 2 || Banque 3 || Banque 3 || Banque 4 || Banque 4 |} En clair, quand on a épuisé toutes les banques dans une même rangée, on passe à la rangée suivante au lieu de rester dans la même rangée. Notons que dans l'exemple précédent, on a combiné l'entrelacement de rangée et de banque, mais on aurait pu utiliser l'entrelacement de rangée seul. Mais ce n'est pas le cas le plus courant en pratique. Toujours est-il qu'en combinant entrelacement de rangée et de ligne, le découpage de l'adresse est le suivant : {|class="wikitable" |+ Adresse mémoire |- | Adresse de ligne || Numéro de rangée || Adresse de banque || Adresse de colonne (précise à l'octet) |} D'autres méthodes d'entrelacement sont possibles, mais elles sont plus complexes. Chaque contrôleur mémoire fait un peu à sa sauce. Ils permettent en général de choisir entre plusieurs entrelacements. Au minimum, ils permettent de désactiver l'entrelacement, d'activer un entrelacement de ligne, et d'autres entrelacements plus complexes, dont certains sont propriétaires et tenus secrets par le fabricant. : L'usage du ''dual channel'' complique encore l'entrelacement. Mais je vais passer cela sous silence. Les contrôleurs de SDRAM précédents sont assez basiques et ne représentent pas les contrôleurs les plus évolués. Ils étaient utilisés sur les anciens PC, à une époque où ils étaient encore sur la carte mère du processeur. Mais de nos jours, le contrôleur mémoire est intégré au processeur. Et il incorpore de nombreuses optimisations afin de gagner en performances. Malheureusement, ces optimisations sont elles-mêmes dépendantes des optimisations intégrées dans le processeur, ce qui fait qu'on ne peut pas en parler ici. Elles demandent que le contrôleur mémoire reçoive plusieurs accès mémoire simultanés, ce qui n'a aucun sens à ce stade du cours. Aussi, nous allons les passer sous silence pour le moment. Nous les verrons dans le chapitre sur le parallélisme mémoire. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=La désambiguïsation mémoire | prevText=La désambiguïsation mémoire | next=Les processeurs superscalaires | nextText=Les processeurs superscalaires }} </noinclude> h07je29jt0iasrykbjsmf29jupu8mjl 764810 764769 2026-04-24T10:34:43Z Mewtow 31375 /* Les optimisations liées à la présence de plusieurs banques */ 764810 wikitext text/x-wiki Dans ce chapitre, nous allons voir les techniques qui permettent de gérer plusieurs accès mémoire simultanés directement au niveau du cache ou de la mémoire RAM. Par plusieurs accès mémoire simultanés, vous pensez sans doute à l'usage de cache multiports, voire de mémoires RAM multiport. C'est en effet une solution, mais ce n'est clairement pas la seule. Par exemple, il est possible de pipeliner l'accès au cache, voire à la mémoire RAM. Il existe de nombreuses techniques de '''parallélisme mémoire''', et nous allons les voir dans ce chapitre. Pour donner un exemple, il est possible de pipeliner les accès mémoire. Il est possible de pipeliner l'accès au cache et/ou l'accès à la mémoire, et nous verrons comment faire dans ce chapitre. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, qu'ils aient ou non un pipeline dynamique. Par contre, pipeliner la mémoire est une technique ancienne, aujourd'hui peu utilisée. Elle était utilisée sur des très vieux ordinateurs, qui avaient un pipeline mais pas de mémoire cache. Mais de nos jours, les mémoires SDRAM et DDR ne sont pas adaptées pour ça, il est très difficile de les pipeliner correctement. Le parallélisme mémoire est utile aussi bien sur des processeurs à émission dans l'ordre que dans le désordre. Par exemple, un processeur ''in-order'' avec des lectures non-bloquantes peut e, profiter. Il peut techniquement lancer une seconde lecture, après avoir rencontré une première lecture non-bloquante. En général, le nombre de lectures consécutives est cependant limité à moins d'une dizaine. À l'opposé, les processeurs sans lectures non-bloquantes profitent pas du parallélisme mémoire. À la rigueur, ils peuvent en profiter s'ils intègrent des techniques de préchargement. Pendant que le processeur exécute une lecture/écriture, il peut précharger une autre donnée en parallèle. Inutile de dire que sans pipeline processeur, le parallélisme mémoire ne sert pas à grand-chose. ==Le ''line fill buffer'' et les techniques associées== Lors d'un défaut de cache, le processeur doit attendre que toute la ligne de cache soit chargée avant d'être utilisable. Or, la taille d'une ligne de cache est supérieure à la largeur du bus mémoire, ce qui fait qu'une ligne de cache est chargée en plusieurs fois, morceaux par morceaux, mot mémoire par mot mémoire. Le chargement peut se faire directement dans le cache, mais ce n'est pas une solution très pratique. À la place, beaucoup de processeurs ajoutent une mémoire tampon entre la RAM et le cache, appelée le '''tampon de remplissage de ligne''' (''line-fill buffer'') dans les processeurs modernes. Lors d'un défaut de cache, le processeur charge la donnée de la RAM dans le tampon de remplissage de ligne, mot mémoire par mot mémoire. Une fois plein, le tampon de remplissage de ligne est recopié dans la ligne de cache. Le tampon de remplissage de ligne contient une ligne de cache, si ce n'est que certains bits de contrôle ne sont pas présents. Le tampon de remplissage de ligne contient un bit de validité pour chaque mot mémoire de la ligne de cache, qui indiquent si le mot mémoire a été chargé. Par exemple, prenons un processeur 64 bits, qui gère donc des mots mémoire de 8 octets, avec des lignes de cache 256 octets/32 mots mémoire. Le tampon de remplissage de ligne contiendra 32 bits de validité. Si le processeur a chargé les 6 premiers mots mémoire, les 6 premiers bits de validité seront mis à 1, les autres seront encore à zéro. Il faut noter que la disponibilité de la ligne complète se détermine assez facilement en faisant un ET logique entre tous les bits de validité. Le processeur sait ainsi quand la ligne de cache est disponible entièrement et donc quand la transférer dans le cache. La même chose existe avec une hiérarchie de cache, sauf que l'on trouve une mémoire tampon entre chaque niveau de cache. Il y a un tampon de remplissage de ligne entre le cache L1 et le cache L2, entre le cache L2 et le L3, etc. Si le cache ne gère qu'un seul défaut de cache à la fois, le ''fill line buffer'' est une mémoire très simple, qui ne mémorise qu'une seule ligne de cache. Et cela vaut aussi bien pour un cache bloquant que non-bloquant. Mais sur les caches capables de gérer plusieurs défauts simultanés, le tampon de remplissage de ligne est une mémoire de type FIFO ou LIFO, capable de mémoriser plusieurs lignes de cache. Notons que le tampon de remplissage de ligne est très utile pour implémenter certaines techniques, par exemple le contournement du cache. Nous avions vu dans le chapitre sur le cache que certains accès mémoire doivent contourner le cache, pour des raisons de cohérence des caches. C'est notamment nécessaire pour accéder aux périphériques, mais c'est aussi utile pour des raisons de performances dans des cas très spécifiques. Les accès qui contournent le cache se font directement dans le tampon de remplissage de ligne : le processeur écrit ou lit les données depuis ce tampon de remplissage de ligne, sans accéder au cache. ===L'''early restart'' et le ''critical word load''=== La présence du ''line buffer'' permet une optimisation assez intéressante, qui permet de réduire la latence des défauts de cache. L'optimisation consiste à lire un mot mémoire dans le tampon de remplissage de ligne, même si la ligne de cache complète n'a pas encore été chargée. Il existe deux manières de faire cela, qui portent les noms d'''early restart'' et de ''critical word load''. La première est la version la plus simple, la seconde est plus complexe mais plus performante. Avec la technique d''''''early restart''''', la ligne de cache est chargée normalement, en partant de son premier mot mémoire. Dès que le mot mémoire lu/écrit par le processeur est copié dans le ''line fill buffer'', il est envoyé au processeur immédiatement. Illustrons le tout par un exemple, où une ligne de cache fait 16 mots mémoire. Le processeur effectue une lecture, qui lit le 5ème mot mémoire. Sans ''early restart'', le processeur doit charger les 16 mots mémoire avant de faire la lecture dans le cache. Avec ''early restart'', le processeur reçoit la donnée dès que le 5ème mot mémoire est disponible. Le processeur doit attendre que les 4 premiers mots mémoire soient chargés, puis le 5ème mot mémoire arrive et est envoyé directement au processeur, il est lu directement depuis le ''line fill buffer''. Les 11 mots mémoire suivants sont ensuite chargés dans le cache pendant que le processeur fait des calculs dans son coin. Le ''critical word load'' est une optimisation de la technique précédente où le chargement de la ligne de cache commence directement à la donnée demandée par le processeur. Pour reprendre l'exemple précédent, où le processeur demande le 5ème mot mémoire sur 16, le ''critical word load'' charge le 5ème mot mémoire en premier et l'envoie au processeur, ce qui fait qu'il est chargé très rapidement. Pas besoin d'attendre que le processeur charge les 4 mots mémoire précédents comme avec l'''early restart''. Dans le détail, le processeur charge le 5ème mot mémoire en premier, puis charge les 11 suivants, et termine par les 4 mots mémoire du début. En clair, le ''critical word load'' commence par charger le mot mémoire lu, puis les blocs suivants, avant de revenir au début du bloc pour charger les blocs restants. Ainsi, la donnée demandée par le processeur sera la première disponible. Pour cela, l'organisation du tampon de remplissage de ligne est modifiée de manière à rendre cela possible. Il a une taille égale à une ligne de cache complète, qui contient elle-même plusieurs mots mémoire. Dans le ''line-fill buffer'', chaque mot mémoire est stocké avec un tag, qui indique l'adresse du mot mémoire stocké dans le ''line-fill buffer''. Le ''line-fill buffer'' est donc un cache un peu particulier, qui fonctionne comme un cache du point de vue du processeur, comme une mémoire FIFO pour les transferts avec le cache. Ainsi, un processeur qui veut lire dans le cache après un défaut peut accéder à la donnée directement depuis le tampon de remplissage de ligne, alors que la ligne de cache n'a pas encore été totalement recopiée en mémoire. ==Les caches pipelinés== Il est possible de pipeliner l'accès au cache, ce qui demande juste de rajouter des registres au bon endroits et quelques circuits de contrôle. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, même ceux avec un pipeline dynamique. Et l'implémentation peut se faire de plusieurs manières différentes. Il faut dire que découper une mémoire cache en plusieurs étapes peut se faire de plusieurs manière. Commençons par la plus simple. Beaucoup de processeurs des années 2000 avaient une fréquence peu élevée comparée aux standards d'aujourd'hui, ce qui fait que leur cache avait bien un temps d'accès d'un cycle d'horloge. Mais l'accès au cache se faisait en deux cycles d'horloge : un cycle pour calculer une adresse et un cycle pour l'accès mémoire proprement dit. Et ces deux étapes étaient pipelinées, à savoir que deux micro-opérations mémoire peuvent s'exécuter en même temps : une dans l'étage de calcul d'adresse, une autre dans le cache. L'unité mémoire était donc pipelinée, alors que l'accès au cache ne l'était pas. Les processeurs qui implémentaient cette technique regroupent les micro-architectures K5 et K6 d'AMD, les processeurs Intel de micro-architecture P6 (Pentium 2 et 3) et quelques autres. Il est aussi possible de pipeliner l'accès au cache lui-même. Avec un cache pipeliné, l'accès au cache ne se fait pas en un seul cycle, mais en plusieurs. Cependant, on peut lancer un nouvel accès au cache à chaque cycle d'horloge, comme un processeur pipeliné lance une nouvelle instruction à chaque cycle. L'implémentation est assez simple : il suffit d'ajouter des registres dans le cache. Pour cela, on profite que le cache est composé de plusieurs composants séparés, qui échangent des données dans un ordre bien précis, d'un composant à un autre. De plus, le trajet des informations dans un cache est linéaire, ce qui les rend parfois pour l'usage d'un pipeline. ===Le ''pipelining'' des caches ''direct-mapped''=== Voici ce qui se passe avec un cache directement adressé. Pour rappel ce genre de cache est conçu en combinant une mémoire RAM, généralement une SRAM, avec quelques circuits de comparaison et un MUX. L'accès au cache se fait globalement en deux étapes : la première lit la donnée et le ''tag'' dans la SRAM, et on utilise les deux dans une seconde étape. Nous avions vu il y a quelques chapitre comment pipeliner des mémoires, dans le chapitre sur les mémoires évoluées. Et bien ces méthodes peuvent s'utiliser pour la mémoire RAM interne au cache !L'idée est d'insérer un registre entre la sortie de la RAM et la suite du cache, pour en faire un cache pipéliné, à deux étages. Le premier étage lit dans la SRAM, le second fait le reste. L'implémentation sur les caches associatifs à plusieurs voies est globalement la même à quelques détails près. [[File:Cache directement adressé pipeliné - deux étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - deux étages]] Avec le circuit précédent, il est possible d'aller plus loin, cette fois en pipelinant l'accès à la mémoire RAM interne au cache. Pour comprendre comment, rappelons qu'une mémoire SRAM est composée d'un plan mémoire et d'un décodeur. L'accès à la mémoire demande d'abord que le décodeur fasse son travail pour sélectionner la case mémoire adéquate, puis ensuite la lecture ou écriture a lieu dans cette case mémoire. L'accès se fait donc en deux étapes successives séparées, on a juste à mettre un registre entre les deux. Il suffit donc de mettre un gros registre entre le décodeur et le plan mémoire. [[File:Cache directement adressé pipeliné - trois étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - trois étages]] Et on peut aller encore plus loin en découpant le décodeur en deux circuits séparés. En effet, rappelez-vous le chapitre sur les circuits de sélection : nous avions vu qu'il est possible de créer des décodeurs en assemblant des décodeurs plus petits, contrôlés par un circuit de prédécodage. Et bien on peut encore une ajouter un registre entre ce circuit de prédécodage et les petits décodeurs. : Théoriquement, toute l'adresse est fournie d'un seul coup au cache, la quasi-totalité des processeurs présentent l'adresse complète à un cache pipeliné. Mais le Pentium 4 fait autrement. Il faut noter que les premiers étages manipulent l'indice dans la SRAM, qui est dans les bits de poids faible, alors que les étages ultérieurs manipulent le ''tag'' qui est dans les bits de poids fort. Les concepteurs du Pentium 4 ont alors décidé de présenter les bits de poids faible lors du premier cycle d'accès au cache, puis ceux de poids fort au second cycle. Pour cela, l'ALU fonctionnait à une fréquence double de celle du processeur, tout comme le cache L1. Il n'y avait pas de pipeline proprement dit, mais cela réduisait grandement la latence d'accès au cache. ===Le ''pipelining'' des caches splittés/sériels=== Il est aussi possible de pipeliner un cache dits splittés, aussi appelés à accès sériel. Pour rappel, les caches à accès sériel vérifient si il y a succès ou défaut de cache, avant d'accéder aux lignes de cache en cas de succès. Ils font donc différemment des autres caches, qui accèdent à une ligne de cache, avant de déterminer s'il y a succès ou défaut en lisant le tag de la ligne de cache. Les caches sériels disposent de deux SRAM : une pour les tags des lignes de cache et une pour les données. Ils accèdent à la SRAM pour les tags, avant d’accéder à la SRAM des données en cas de succès de cache. Vu que l'accès se fait en deux étapes, une vérification des tags suivie de la lecture/écriture des données, il est facile à pipeliner. Pipeliner le cache permet de régler le problème des accès au cache L1, et elle est tout le temps utilisé sur les processeurs modernes. Mais que faire en cas de défaut de cache ? ==Les caches non bloquants== Un '''cache bloquant''' est un cache auquel le processeur ne peut pas accéder pendant un défaut de cache. Il faut attendre que la lecture ou écriture en RAM soit terminée avant de pouvoir utiliser de nouveau le cache. Un '''cache non bloquant''' n'a pas ce problème : on peut l'utiliser pendant un défaut de cache. Les caches non bloquants permettent de démarrer une nouvelle lecture ou écriture alors qu'une autre est en cours, ce qui permet d'exécuter plusieurs lectures ou écritures en même temps. ===Les ''Miss Handling Status Registers''=== Lors d'un défaut de cache, la mémoire RAM est consultée pendant le défaut de cache, mais le cache est inutilisé. Un défaut de cache n'utilise pas le cache, ce n'est qu'un accès en mémoire RAM, sur le bus mémoire (ou un accès aux niveaux de cache inférieurs, peu importe). Le cache en lui-même est laissé libre, rien n’empêche d'y accéder, il est en réalité intrinsèquement non-bloquant. Les caches, bloquants comme non-bloquants, sont en réalité composés d'une mémoire cache proprement dite, entourée de circuits qui servent d'interface entre le processeur et le cache lui-même. Et parmi les circuits tout autour, certains gèrent l'accès au cache lors d'un défaut de cache. Ils sont regroupés sous le terme de '''''Miss Handling Architecture''''' (MHA). La différence entre un cache bloquant et un cache non-bloquant est en réalité liée à l'implémentation de la MHA. Les caches bloquants coupent volontairement l'accès au cache lors d'un défaut, car les défauts sont plus simples à gérer ainsi. Le défaut de cache rapatrie une donnée depuis la RAM, qui sera écrite dans le cache. Et il ne faut pas qu'une tentative d'accès à cette donnée ait lieu avant qu'elle ne soit chargée. Mais ce blocage est général et touche tout le cache, alors que seule une ligne de cache est concernée par le défaut de cache. L'idée derrière un cache non-bloquant est que seule la ligne de cache est bloquée, mais les autres sont accessibles. L'idée est alors de mémoriser les lignes de cache concernées par le défaut de cache, afin d'en bloquer l'accès. À chaque accès, on vérifie si la ligne de cache est déjà réservée par un défaut de cache. La lecture/écriture est alors bloquée si c'est le cas, mais elle accepte les accès sinon. Pour cela, la ''Miss Handling Architecture'' contient des registres qui mémorisent des informations sur les défauts de cache en cours. Ils portent le nom de '''''miss status handling registers''''', que j’appellerais dorénavant MSHR, qui sont aussi appelés des '''''miss buffer'''''. Le contenu des MSHR varie beaucoup suivant le processeur, mais ces derniers stockent au minimum les informations suivantes : * Le numéro de la ligne de cache dans laquelle les données sont chargées. * Un bit de validité qui indique si le MSHR est vide ou pas, qui est mis à 0 quand le défaut de cache est résolu. * Un ou plusieurs champs de lecture/écriture, qui contiennent des informations sur la lecture/écriture. ** Pour une lecture, elle contient des informations sur la destination de la donnée, à savoir qui prévenir quand le défaut de cache est terminé. C'est parfois un nom/numéro de registre (celui dans lequel charger la donnée), mais c'est souvent le numéro de l'entrée dans le ''load/store queue''. ** Pour les écritures, elle contient la donnée à écrire, ou éventuellement un numéro de ''load/store queue'' où se trouve la donnée à écrire. Il faut noter que le bus mémoire ne peut gérer qu'un seul défaut de cache à la fois. Aussi, il est intéressant de regarder ce qui se passe lorsqu'un second défaut de cache survient, pendant qu'un premier est en cours. Dans ce cas, il y a deux réponses qui correspondent à deux types de caches non-bloquants, qui portent les noms barbares de caches de type succès après défaut et défaut après défaut. Sur le premier type, il ne peut pas y avoir plusieurs défauts de cache simultanés. Dès qu'un second défaut de cache survient, le cache stoppe son activité et on ne peut plus démarrer de nouvelle lecture/écriture, tant que le premier défaut de cache n'est pas résolu. Le second type est plus souple et autorise la survenue de plusieurs défauts de cache simultanés. Du moins, jusqu'à une certaine limite, car le cache ne peut supporter qu'un nombre limité d'accès mémoires simultanés (pipelinés). ===Les accès simultanés à une même ligne de cache=== Il arrive que le processeur fasse plusieurs accès mémoire simultanés à la même ligne de cache. Si la ligne de cache en question n'a pas encore été chargée dans le cache, alors on a plusieurs défauts de cache consécutifs pour la même ligne de cache, et le cache non-bloquant doit gérer la situation. Pour la suite, il va falloir faire une petite distinction entre les défauts primaires et secondaires. Imaginons qu'un défaut de cache ait lieu et demande à charger une donnée dans la ligne de cache numéro N. Il s'agit du premier défaut impliquant cette ligne de cache précise, ce qui lui vaut le nom de '''défaut de cache primaire'''. Mais par la suite, d'autres accès mémoire à la même ligne de cache ont lieu, alors que la ligne de cache n'est pas encore disponible. Dans ce cas, il s'agit de '''défauts de cache secondaires'''. Pour l'unité d'accès mémoire, les défauts de cache primaire et secondaire sont différents (ils prennent tous une entrée dans la ''load/store queue''). Mais pour le cache, ils ne correspondent qu'à un seul accès au cache : celui qui demande de charger la ligne de cache demandée. Les défauts de cache primaire et secondaire à la même ligne de cache se voient attribuer un MSHR unique. La gestion des défauts de cache secondaires dépend du cache non-bloquant. La solution la plus simple ne permet pas les défauts de cache secondaires. Le cache ne permet pas deux défauts de cache simultanés pour la même ligne de cache. Les autres solutions le permettent, en fusionnant des accès simultanés à la même ligne de cache en un seul au niveau des MSHR. Dans tous les cas, détecter les défauts de cache secondaires sont un problème qu'il faut détecter. La ''Miss Handling Architecture'' doit détecter les défauts de cache secondaire. Pour cela, elle procède comme suit. Lors de chaque défaut de cache, la MHA récupère le numéro de la ligne de cache associé. Il vérifie alors chaque MSHR pour vérifier s'il contient le numéro en question. S'il n'y a aucune correspondance dans les MSHR, alors c'est signe que le défaut de cache est un défaut primaire. Mais s'il y en a une, alors c'est un défaut secondaire. Évidemment, cela signifie que lors d'un défaut de cache, le numéro de ligne de cache est envoyé à tous les MSHR, pour comparaison. Les MSHR sont donc regroupés dans une mémoire associative, une sorte de mini-cache, faciliter l'implémentation. Lors d'un défaut de cache primaire, l'accès à un cache non-bloquant se fait comme suit : le processeur envoie une adresse au cache, accède à celui-ci, et détecte la survenue d'un défaut de cache. Il en profite alors pour attribuer une ligne de cache dans laquelle sera chargée ce défaut. L'attribution est très simple dans le cas des caches ''direct mapped'', ou associatifs par voie, pour lesquels l'attribution se fait assez simplement. Il mémorise alors cette information dans les MSHR, après avoir vérifié que le défaut de cache n'était pas un défaut secondaire. Lors des accès ultérieurs à une adresse proche, censée être dans la même ligne de cache, le processeur va encore une fois rencontrer un défaut de cache. Il va alors déterminer le numéro de la ligne de cache associée à l'adresse, et comparer ce numéro avec les MSHRs. Si un MSHR contient ce numéro, c'est signe que le défaut de cache est un défaut secondaire. La MHA réagit alors différemment selon le processeur considéré. Une première solution n'autorise pas les défauts de cache secondaires. Si l'un d'entre eux survient, le processeur est gelé par un ''pipeline stall'', une bulle de pipeline. Une autre solution fusionne les défauts de cache secondaires avec le défaut de cache primaire : tout cela ne correspond qu'à un seul défaut de cache pour lui. ===Les MSHR simples=== Un cache non-bloquant à '''MSHR simple''' contient juste plusieurs MSHR qui mémorisent juste un numéro de ligne de cache, un bit de validité, le champ de lecture/écriture, et l'adresse exacte de lecture/écriture. Avec cette organisation, il est possible d'avoir plusieurs défauts de cache séparés, mais à la condition que chaque défaut accède à une ligne de cache différente. Deux accès simultanés à une même ligne de cache ne sont pas possibles, les défauts de cache secondaires ne sont pas autorisés. Ainsi, chaque défaut de cache se voit attribuer son propre MSHR, chacun contient un numéro de ligne de cache différent. Pour comprendre pourquoi c'est impossible de gérer les défauts secondaires, il faut regarder le champ de lecture/écriture. Si on veut effectuer plusieurs écritures consécutives à la même adresse, le MSHR n'aura pas de quoi mémoriser les deux données à écrire. Il pourra mémoriser la première donnée à écrire, pas la seconde. Même chose lors d'une lecture : le champ lecture/écriture peut mémorisr la destination de la première lecture, pas de la seconde. L'avantage est que la MHA n'a besoin que des MSHR et de quelques circuits annexes. Les autres solutions rajoutent des circuits annexes pour gérer les défauts de cache secondaires, qui utilisent beaucoup de circuits. Le cout en circuit est donc élevé, mais le gain en performance est là. Passons maintenant aux caches non-bloquants qui autorisent les défauts de cache secondaire. La solution la plus simple consiste à utiliser ===Les MSHR adressés implicitement=== Avec les '''MSHR adressés implicitement''', il est possible de fusionner plusieurs accès mémoire à une même ligne de cache, mais sous une condition très importante : ces accès lisent/écrivent des mots mémoire différents. Par exemple, imaginons qu'une ligne de cache contienne 8 mots mémoire de 64 bits. Si un premier accès mémoire lit le mot mémoire numéro 7 (dernier mot mémoire de la ligne), et le second accès le mot mémoire numéro 3, alors la fusion est possible. Mais si deux accès mémoire veulent lire/écrire le mot mémoire numéro 7, alors la fusion n'est pas possible et le processeur se bloque, un ''pipeline stall'' survient. Un MSHR adressé implicitement est un MSHR simple, qui contient naturellement un numéro de ligne de cache (tag) et un bit de validité, sauf que le champ de lecture/écriture est dupliqué. Les différents champs lecture/écriture d'un MSHR sont regroupés dans une mémoire RAM/cache qui contient autant d'entrées qu'il y a de mot mémoire dans une ligne de cache. Chaque entrée de la table est associée à un mot mémoire de la ligne de cache et stocke des informations sur celui-ci. Une entrée mémorise, au minimum : * Un champ de lecture/écriture qui contient soit la destination de la lecture, soit la donnée à écrire. * Un bit R/W permettant d'interpréter correctement le champ de lecture/écriture. * Un bit de validité pour chaque entrée de la table, qui dit si un défaut de cache antérieur accède déjà à ce mot mémoire. La fusion de deux défauts de cache est ainsi assez simple. Un défaut de cache primaire/secondaire configure l'entrée associée au mot mémoire lu/écrit. Si un défaut secondaire ultérieur lit/écrit un mot mémoire différent (dont le bit de validité de l'entrée dans la table est à zéro), pas de conflit : il configure une autre entrée, vide. Mais s'il lit/écrit un mot mémoire pour lequel l'entrée de la table est occupée, il y a conflit, le processeur est bloqué par un ''pipeline stall'', une bulle de pipeline. Avec cette organisation, le nombre de MSHR indique combien de lignes de cache peuvent être lues en même temps depuis la mémoire. Quant au nombre d'entrées par MSHR, il détermine combien d'accès mémoires qui ne se recouvrent pas peuvent avoir lieu en même temps. ===Les MSHR adressés explicitement=== Les '''MSHR adressés explicitement''' n'ont pas les contraintes des MSHR adressés implicitement. Avec eux, il est possible d'avoir plusieurs défauts de cache pointant vers la même ligne de cache, mais aussi vers le même mot mémoire. De tels défauts de cache apparaissent sur les processeurs à exécution dans le désordre, mais sont très rares, voire inexistants, sur les processeurs ''in-order''. L'idée est encore que chaque MSHR est associée à une table mémoire qui mémorise des entrées. Cette table n'est autre qu'une mémoire FIFO. Sauf que cette fois-ci, une entrée n'est pas associée à un mot mémoire. Une entrée est associée à un défaut de cache. Une entrée mémorise là encore la destination de la lecture ou la donnée de l'écriture suivi du bit R/W, ainsi que d'un bit de validité par entrée, mais aussi : la position du mot mémoire lu dans la ligne de cache. C'est cette dernière information qui n'était pas présente dans les MSHR adressés implicitement. De plus, le nombre d'entrée par MSHR n'est pas égal au nombre de mots mémoires dans une ligne de cache, mais peut être arbitrairement grand. L'avantage d'un tel cache est qu'il est capable de traité les défauts secondaires concernant un même mot mémoire, et ce, car les accès à la ligne de cache concernée se produisent dans l'ordre de génération des défauts (la table mémoire est une FIFO). ===Les MSHRs inversés=== Généralement, plus on veut supporter de défauts de cache, plus le nombre de MSHR et d'entrées augmente. Mais au-delà d'un certain nombre d'entrées et de MSHR, les MSHR adressés implicitement et explicitement ont tendance à bouffer un peu trop de circuits. Utiliser une organisation un peu moins gourmande en circuits est donc une nécessité. Cette organisation plus économe se base sur des MSHR inversés. Les MSHR inversés ne contiennent qu'une seule entrée, en quelque sorte : au lieu d’utiliser n MSHR de m entrées chacun, on va utiliser n × m MSHR inversés. La différence, c'est que plusieurs MSHR peuvent contenir un tag identique, contrairement aux MSHR adressés implicitement et explicitement. Lorsqu'un défaut de cache a lieu, chaque MSHR est vérifié. Si jamais aucun MSHR ne contient de tag identique à celui utilisé par le défaut, un MSHR vide est choisi pour stocker ce défaut, et une requête de lecture en mémoire est lancée. Dans le cas contraire, un MSHR est réservé au défaut de cache, mais la requête n'est pas lancée. Quand la donnée est disponible, les MSHR correspondant à la ligne qui vient d'être chargée vont être utilisés un par un pour résoudre les défauts de cache en attente. ===Les MSHR intégrés au cache=== Certains chercheurs ont remarqué que pendant qu'une ligne de cache est en train de subir un défaut de cache, celle-ci reste inutilisée, et son contenu est destiné à être perdu une fois le défaut de cache résolu. Ils se sont dit que, plutôt que d'utiliser des MSHR séparés, il vaudrait mieux utiliser la ligne de cache pour stocker les informations sur les défauts de cache en attente dans cette ligne de cache. Pour éviter tout problème, il faut rajouter un bit dans les bits de contrôle de la ligne de cache, qui sert à indiquer que la ligne de cache est occupée : un défaut de cache a eu lieu dans cette ligne, et elle stocke donc des informations pour résoudre les défauts de cache. ==L'entrelacement mémoire== Nous venons de voir comment une mémoire cache peut gérer plusieurs accès mémoire simultanés. Il se trouve que ce genre d'optimisation dépasse largement le cadre du cache. Les mémoires RAM ne sont pas en reste, elles aussi ! Il est possible de pipeliner l'accès à une mémoire RAM, en utilisant des techniques dites d''''entrelacement'''. Précisons cependant que cette optimisation n'est pas utilisée sur les mémoires SDRAM ou DDR modernes, du moins pas sans modifications majeures. Mais divers ordinateurs assez anciens, dont des superordinateurs, ont utilisé cette technique. La technique demande d'utiliser plusieurs mémoires séparées. Sur les anciens ordinateurs, les mémoires en question sont des chips mémoire, à savoir des circuits intégrés de DRAM. Pour faire plus général, nous allons utiliser le terme de '''banques''', ou encore de bancs mémoire. La différence est qu'il existe des mémoires multi-banque, qui regroupent plusieurs banques indépendantes dans un seul boitier, dans un seul chip mémoire. En clair, de telles mémoires regroupent plusieurs sous-mémoires dans un seul circuit intégré. Il est aussi possible d'utiliser plusieurs chips mémoire séparés, chacun ayant une banque. Reste à combiner ces banques pour former un pseudo-pipeline. ===Utiliser plusieurs banques sans entrelacement=== Sans optimisation particulière, les adresses sont réparties dans les banques comme indiqué ci-dessous. Il s'agit de ce qui s'appelle un arrangement horizontal, et nous avions vu celui-ci dans le chapiutre sur les mémoires SDRAM. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Avec cet arrangement, les bits de poids fort de l'adresse sont utilisées pour sélectionner la banque adéquate, et le reste de l'adresse est envoyé sur le bus d'adresse. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Adresse de banque !! Adresse dans la banque |- | Quelques bits de poids fort || Reste de l'adresse |} L'entrelacement change cette répartition, afin d'accélérer les accès mémoire. L'entrelacement classique vise à améliorer les accès à des adresses mémoires consécutives, mais des formes plus évoluées d'entrelacement visent à optimiser des accès mémoire arbitraires. Voyons-les dans l'ordre, du plus simple au plus complexe. ===L'entrelacement classique=== Avec l''''entrelacement classique''', des adresses consécutives sont réparties dans des banques consécutives. Précisons que cette attribution des adresses n'implique absolument pas la mémoire virtuelle ou n'importe quel mécanisme dans le processeur. La répartition décide que telle adresse mémoire va dans telle banque, à telle adresse dans la banque. Elle est donc le fait du contrôleur mémoire, donc en dehors du processeur (les contrôleurs mémoires n’étaient pas encore intégrés dans le processeur à l'époque). [[File:Répartition des adresses dans une mémoire interleaved.png|centre|vignette|upright=1.5|Répartition des adresses dans une mémoire interleaved.]] L'entrelacement simple permet d'accélérer les accès mémoire qui se font à des adresses consécutives. Avec l'entrelacement, chaque accès mémoire tombe dans une banque mémoire différente, ce qui fait qu'on peut démarrer un nouvel accès mémoire à chaque cycle d'horloge. Pendant qu'une banque est occupée par un accès mémoire, on démarre le suivant dans une autre banque, et ainsi de suite. Le tout se termine soit quand on a épuisé toutes les banques libres, soit quand l'accès en rafales se termine. Pas besoin d'attendre que la mémoire ait fini sa lecture/écriture avant de démarrer la lecture/écriture suivante. [[File:Pipemining mémoire.png|centre|vignette|upright=2.5|Pipelining mémoire]] L'animation suivante illustre bien le processus, avec quatres banques. [[File:Interleaving.gif|centre|vignette|upright=2.5|Pipelining mémoire]] L'entrelacement simple utilise les bits de poids faible pour sélectionner la banque, et les bits de poids fort pour la case mémoire. [[File:Adresse mémoire d'une mémoire entrelacée.png|centre|vignette|upright=2|Adresse mémoire d'une mémoire entrelacée]] Précisons que le temps d'accès mémoire ne change pas beaucoup avec l'entrelacement. Par contre, on peut faire plus d'accès mémoire simultanés. On peut démarrer un accès mémoire par cycle d'horloge, mais l'accès en lui-même prend plusieurs cycles. Les mémoires à entrelacement ont donc un débit supérieur aux mémoires qui ne l'utilisent pas. L'entrelacement classique pourrait être utilisé pour accélérer les transferts entre mémoire RAM et cache, qui se font en blocs d'adresses consécutives. Mais de nos jours, il n'est pas utilisé dans cette optique. Il faut dire qu'il est redondant avec le mode rafale des mémoires DRAM. Les deux font globalement la même chose et on voit mal comment utiliser les deux en même temps. Le mode rafale étant plus simple à implémenter, et plus léger niveau utilisation du bus de commande mémoire, il est préféré à l'entrelacement. Par contre, l'entrelacement a eu son heure de gloire sur d'anciens processeurs qui n'avaient pas de mémoire cache, alors qu'ils disposaient d'un pipeline. L'exemple typique est celui des nombreux ''processeurs vectoriels'', que nous n'avons pas encore abordé à ce stade du cours. De tels processeurs avaient un pipeline, une unité de calcul fortement pipelinée, et parfois même de l'exécution dans le désordre et du renommage de registres ! ===L'entrelacement par décalage=== L'entrelacement simple est très utile pour les accès en rafle ou équivalents. Par contre, il existe de rares situations où il n'est pas optimal. Une de ces situations est celle des accès en enjambée (en ''stride''), où l'on accède en série à des adresses sont séparées par N mots mémoires. [[File:Accès par enjambées.png|centre|vignette|upright=2|Accès par enjambées.]] De tels accès surviennent quand un logiciel accède à des structures de données spécifiques, à savoir des tableaux de structures, des matrices, ou d'autres structures de données dans le genre. Un cas classique est celui du parcours d'une matrice colonne par colonne. Les matrices de nombres sont mémorisées ligne par ligne, ce qui fait que parcourir une colonne demande de passer d'une ligne à la suivante et de faire des grands sauts en mémoire RAM, mais tous espacés par la même distance. Avec l'entrelacement simple, les accès en enjambée sont moins performants que les accès à des adresses consécutives, mais cela ne fonctionne pas trop mal. Le pire cas est celui où l'on a N banques et où les données sont justement placées toutes les N adresses. Des accès consécutifs vont tous tomber dans la même banque, on ne peut plus accéder à des banques différentes en parallèle. Mais en dehors de ce cas, on voit une amélioration par rapport à la situation sans entrelacement. Pour obtenir des performances maximales pour les accès en enjambées, il faut répartir les mots mémoires dans la mémoire autrement. L'organisation idéale est la suivante. : Dans les explications qui vont suivre, la variable N représente le nombre de banques, qui sont numérotées de 0 à N-1. On commence par organiser les N premières adresses comme une mémoire entrelacée simple : l'adresse 0 correspond à la banque 0, l'adresse 1 à la banque 1, etc. Pour le bloc suivant, on décale tout d'une adresse, à savoir qu'on commence à remplir les banques à partir de la seconde, non de la première. Une fois la fin du bloc atteinte, on finit de remplir le bloc en repartant du début du bloc. Le troisième bloc d'adresse subit le même traitement, sauf qu'on commence à remplir à partir de la seconde banque. Et on poursuit l’assignation des adresses en décalant d'un cran en plus à chaque bloc. Ainsi, chaque bloc verra ses adresses décalées d'un cran en plus comparé au bloc précédent. Si jamais le décalage dépasse la fin d'un bloc, alors on reprend au début. [[File:Mémoire interleaved par décalage.png|centre|vignette|upright=1.5|Mémoire entrelacée par décalage.]] En faisant cela, on remarque que les banques situées à N adresses d'intervalle sont différentes. Dans l'exemple du dessus, nous avons ajouté un décalage de 1 à chaque nouveau bloc à remplir. Mais on aurait tout aussi bien pu prendre un décalage de 2, 3, etc. Dans tous les cas, on obtient un entrelacement par décalage. Ce décalage est appelé le pas d'entrelacement, noté P. Le calcul de l'adresse à envoyer à la banque, ainsi que la banque à sélectionner se fait en utilisant les formules suivantes : * adresse à envoyer à la banque = adresse totale / N ; * numéro de la banque = (adresse + décalage) modulo N ; * décalage = (adresse totale * P) mod N. Avec cet entrelacement par décalage, on peut prouver que la bande passante maximale est atteinte si le nombre de banques est un nombre premier. Seulement, utiliser un nombre de banques premier peut créer des trous dans la mémoire, des mots mémoires inadressables. Malgré ce défaut, la technique a été utilisée sur quelques ordinateurs, avec l'exemple notable du superordinateur ''Burroughs Scientific Processor''. Pour éviter cela, il y a plusieurs solutions. Par exemple, on peut faire en sorte que N et la taille d'une banque soient premiers entre eux : ils ne doivent pas avoir de diviseur commun. Mais en pratique, elles n'ont pas vraiment été implémentées dans une vraie machine, et sont restées à l'état de recherche, aussi je les passe sous silence. ===L'entrelacement pseudo-aléatoire=== Une dernière méthode de répartition consiste à répartir les adresses dans les banques de manière "pseudo-aléatoire". L'idée exacte est de faire passer l'adresse dans une '''fonction de hachage''', plus ou moins complexe. Pour rappel, une fonction de hachage prend une entrée de grande taille et fournit en sortie un résultat de petite taille. Idéalement, elles doivent donner des résultats aussi différents que possible pour des entrées similaires, histoire de simuler une sorte de pseudo-aléatoire. Ici, une adresse est transformée en un numéro de banque. Une fonction de hachage simple ne fait que permuter des bits de l'adresse pour obtenir son résultat. Elle ne fait qu'échanger des bits de place avant de couper l'adresse en deux morceaux : un pour la sélection de la banque, et un autre pour la sélection de l'adresse dans la banque. Cette permutation est fixe, et ne change pas suivant l'adresse. Des fonctions de hachage plus complexes font des XOR bit à bit entre certains bits de l'adresse. ==Les contrôleurs SDRAM/DDR avec "exécution dans le désordre"== Après avoir vu le cas des mémoires RAM, nous allons nous concentrer sur le cas particulier des mémoires SDRAM. Les mémoires SDRAM et DDR modernes sont capables de gérer plusieurs accès simultanés à la mémoire RAM, et leurs contrôleurs mémoire en font tout autant. C'est une différence majeure avec les mémoires asynchrones FPM/EDO, qui n'acceptaient qu'un seul accès mémoire à la fois. Leur contrôleur mémoire n'acceptait qu'un seul accès mémoire à la fois, c'était un contrôleur mémoire bloquant. Les contrôleurs mémoires des SDRAM sont eux non-bloquants et peuvent encaisser une dizaine d'accès mémoire à la fois. Peu de choses sont connues sur les contrôleurs de SDRAM/DDR modernes, les fabricants ne donnant que peu de détails dessus. Les rares simulateurs qui tentent de décrire leur fonctionnement, comme DRAM SIM I et II, sont particulièrement simples et ne vont pas dans le détail. Néanmoins, le peu qu'on sait est tout de même instructif. ===Rappel sur les SDRAM : tampon de ligne et commandes=== Les mémoires SDRAM sont des mémoires à tampon de ligne. Elles font un accès mémoire en deux étapes. La première étape recopie une ligne de N * 64 bits dans un tampon interne à la SDRAM. La seconde étape sélectionne une colonne, à savoir une donnée de 64 bits, qui est soit envoyée sur le bus de données pour une lecture, soit modifiée par une écriture. [[File:Mémoire à tampon de ligne.png|centre|vignette|upright=2|Mémoire à tampon de ligne]] Les accès mémoire sont traduits par un séquenceur mémoire en une série de commandes mémoires, qui sont séparées par des délais mémoire de quelques cycles d'horloge. Les délais sont très précis, et sont à respecter à la lettre. Une lecture ou une écriture se fait en maximum trois commandes : une commande PRECHARGE qui ferme la ligne précédemment utilisée, une commande ACT qui précise l'adresse de la ligne, et une commande READ ou WRITE qui précise l'adresse de la colonne et éventuellement la donnée à écrire. : Pour être plus précis, la commande PRECHARGE précharge les lignes de bits du plan mémoire à une tension particulière, ce qui les vide de leur contenu. Mais c'est un détail sans importance pour ce qui va suivre. Les SDRAM permettent de se passer de la première étape si des accès consécutifs se font dans la même ligne. Une fois activée, la ligne reste ouverte et on peut accéder plusieurs fois de suite dedans. On a alors juste à préciser l'adresse de colonne dedans. Si un accès mémoire accède à une ligne déjà activée, on dit que c'est un '''succès de page'''. Si ce n'est pas le cas, on doit fermer la ligne courante, rouvrir la ligne vboulue, et préciser la colonne. C'est alors un '''défaut de page'''. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | bgcolor="#FFA0FF" | PRECHARGE || bgcolor="#FFA0FF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || bgcolor="#A0FFFF" | READ (2) || bgcolor="#A0FFFF" | READ (3) || || || bgcolor="#A0FFFF" | READ (4) || bgcolor="#A0FFFF" | READ (5) || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | Donnée READ 1 || ||bgcolor="#A0FFFF" | Donnée READ 2 | bgcolor="#A0FFFF" | Donnée READ 3 || || || bgcolor="#A0FFFF" | Donnée READ 4 || bgcolor="#A0FFFF" | Donnée READ 5 |} Les SDRAM sont parfois capables de démarrer une commande avant que la précédente soit terminée. Mais le respect des délais mémoire est très limitant, ce qui fait qu'on ne peut pas parler de réel pipeline, comme c'est le cas sur les processeurs. Parlons plutot de '''commandes anticipées'''. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | || bgcolor="#A0FFFF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || || || bgcolor="#FFA0FF" | READ (2) || || || || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 | bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 | |} Un dernier point est que les chips mémoires de SDRAM sont composés de plusieurs banques, chacune étant une sorte de mini-mémoire miniature. Chaque banque a son propre décodeur, son propre tampon de ligne, ses propres multiplexeurs de colonne, sa logique de rafraichissement mémoire, etc. C'est comme si une SDRAM regroupait plusieurs mémoires séparées dans un même circuit intégré. [[File:Arrangement vertical.jpg|centre|vignette|upright=2.5|Mémoire multi-banques.]] ===La mise en attente des accès mémoire=== Un contrôleur de SDRAM peut accepter plusieurs accès mémoire en même temps. Mais cela ne signifie pas que le contrôleur sera prêt à les traiter immédiatement. Pour éviter tout problème de disponibilité, le contrôleur met en attente les accès mémoire que le processeur lui envoie, pour les exécuter dès que possible. Les accès mémoire sont mis en attente dans une mémoire FIFO, histoire de les exécuter dans leur ordre d'arrivée, notamment pour renvoyer les lectures dans l'ordre demandé. Il y a aussi une mémoire FIFO pour les données à écrire et une FIFO pour les données lues. Cette dernière sert au cas où le cache ne soit pas disponible quand on lui envoie la donnée lue. [[File:Module d'interface avec la mémoire.png|centre|vignette|upright=2.5|Contrôleur mémoire avec mise en attente des requêtes processeur et autres optimisations.]] Des optimisations sont possibles dès la mémoire FIFO. Par exemple, on peut regrouper plusieurs accès à des données consécutives en un seul accès en rafale. Le contrôleur mémoire analyse les requêtes mises en attente et détecte si certaines se font à des adresses consécutives. Si c'est le cas, il fusionne ces requêtes en une lecture/écriture en rafale. Une telle optimisation est appelée la '''combinaison de lecture''' pour les lectures, et la '''combinaison d'écriture''' pour les écritures. L'optimisation précédente ne paraissent pas payer de mine, mais elles deviennent plus compréhensible quand on sait que certains contrôleurs mémoire peuvent mettre en attente beaucoup de données. Par exemple, l'''Intel E8870 - Scalable Memory Controller'' peut mettre en attente 64 lignes de cache dans la mémoire FIFO, soit 8 kibioctets de RAM ! Deux pages mémoire si la mémoire virtuelle utilise des pages de 4 kibioctets ! ===Les lectures anticipées : une forme d'OOO mémoire=== La mise en attente des accès mémoire est une optimisation intéressante, mais pas mirifique. Elle permet surtout de ne pas bloquer le processeur si le contrôleur mémoire a du travail sur la planche. Cependant, elle peut être optimisée quand on se rend compte d'une chose : le processeur ne voit que les lectures, pas les écritures. Pour les écritures, il les envoie au contrôleur mémoire et ce dernier fait le travail demandé, le processeur n'est pas prévenu quand une écriture se termine. Mais pour les lectures, c'est différent, car il reçoit la donnée lue. En conséquence, il est possible d'optimiser les écritures sans que le processeur ne voit quoique ce soit. Une optimisation possible est alors de changer l'ordre des accès mémoire, histoire de retarder les écritures ou au contraire de les faire en avance. La raison est que les mémoires SDRAM n'aiment pas quand on alterne lectures et écritures. Les délais mémoire, les fameux ''timings'' mémoire, sont clairs : faire une suite de lecture ou une suite d'écriture est plus performant que d'alterner entre lectures et écritures. L'idée est alors de changer l'ordre des écritures de manière à les faire en bloc, idem pour les lectures. L’optimisation est possible, mais à condition que le processeur n'y verra que du feu. Or, vous l'avez deviné, il y a des cas où ces réorganisations ne sont pas possibles, et vous avez sans doute trouvé que la situation était familière. L'optimisation en question est une forme d'exécution dans le désordre des accès mémoire, réalisée par le contrôleur mémoire lui-même ! Et qui dit exécution dans le désordre dit : problèmes liés aux dépendances de données. Changer l'ordre des écritures n'est pas toujours possible, notamment en raison des dépendances RAW, WAR et WAW. Pour que le processeur n'y voit que du feu, il faut respecter plusieurs critères. Premièrement, les lectures doivent être renvoyées au processeur dans l'ordre. Et quand je dis dans l'ordre, cela veut dire : dans l'ordre d'envoi des lectures au contrôleur mémoire. Les écritures aussi doivent se faire dans l'ordre, histoire qu'une série d'écriture donne le bon résultat final. Le troisième critère est qu'une lecture doit donner le résultat de la dernière écriture ''à la même adresse''. Pour le dire autrement, il faut juste détecter les dépendances RAW. Concrètement, si on a une série de lectures et d'écritures alternées, on peut regrouper les lectures et les écritures dans deux paquets séparés, à condition qu'aucune lecture ne lise une adresse écrite par une écriture. Mais si une lecture est dépendante d'une écriture, alors la lecture doit attendre que l'écriture se termine, idem pour les lectures suivantes. Une implémentation basique remplace la mémoire FIFO vue au-dessus, par deux mémoires FIFOs : une pour les lectures, une pour les écritures. Lorsqu'un accès mémoire arrive au séquenceur, il regarde si c'est une lecture ou une écriture et place l'accès mémoire dans la file adéquate. Le fait que ce soit des FIFOs garantit que les lectures se font dans l'ordre, idem pour les écritures. En sortie des deux FIFOs, le séquenceur mémoire détecte les dépendances RAW. Précisément, quand une lecture sort de la mémoire FIFO, il consulte la file d'attente des écriture pour voir si la lecture est dépendante d'une écriture en attente. La lecture attend si c'est le cas, et les écritures sont exécutées à la place. Séparer lectures et écriture est une source d'optimisation. Il est par exemple possible d'utiliser le réacheminement lecture sur écriture (''Store to load forwarding''). Il s'agit d'un équivalent de l’optimisation du même nom, utilisée dans le cadre de la désambiguïsation mémoire. Imaginez qu'une lecture accède à une donnée pas encore écrite, en attente dans le contrôleur mémoire. C'est une dépendance RAW assez claire. Dans ce cas, le contrôleur mémoire peut renvoyer la donnée directement depuis ses mémoires FIFOs, sans faire d'accès mémoire en lecture. Le contrôleur mémoire a tendance à privilégier les lectures, car celles-ci sont cruciales pour l'exécution dans le désordre. Plus elles se font tôt, plus vite le processeur recevra les données et pourra démarrer les instructions dépendantes de cette donnée. Le séquenceur a donc tendance à piocher en priorité dans la file de lecture, plutôt que dans la file d'écriture. A vrai dire, dans le cas idéal, les écritures ne sont faites que quand la file d'écriture est pleine ou quasi-pleine... Prenons l'exemple des coprocesseurs IO 81341 et 81342, qui étaient en réalité des ''chipsets'' intégrant un contrôleur SDRAM. Le ''chipset'' avait 5 ports : un pour les processeurs, un pour le pont sud (''southbridge'') et trois pour des canaux DMA. Pour le port processeur, il n'y avait pas de réordonnancement et ce port utilisait une seule mémoire FIFO. Les autres ports utilisaient l'optimisation qu'on vient de voir, et avaient vraisemblablement des files séparées pour les lectures et écritures. Le séquenceur mémoire vérifiait les dépendances mémoire de type RAW et autres. [[File:Double file d'attente pour les lectures et écritures.png|centre|vignette|upright=2.5|Double file d'attente pour les lectures et écritures]] ===Le ré-ordonnancement des commandes mémoires=== L'optimisation précédente est peu poussée, comparé aux formes d'exécution dans le désordre que les processeurs utilisent. Et on peut se demander si une exécution dans le désordre plus poussée est possible. La réponse est oui : un contrôleur mémoire peut faire des réorganisations bien plus poussées. Par contre, il faut faire une précision très importante : le contrôleur mémoire ne remet pas les accès mémoire dans l'ordre avant de les envoyer au processeur. C'est le processeur qui remet en ordre les accès mémoire, dans sa ''load queue''. En clair : le processeur voit les accès mémoire dans le désordre, c'est lui qui les remet en ordre. Pour cela, les requêtes mémoire sont "numérotées", à savoir qu'on leur attribue un identifiant binaire. Le contrôleur mémoire exécute les lectures/écritures dans le désordre, mais garde la trace de leur identifiant. Il sait dans quel ordre il exécute les accès mémoire et connait leur latence. Ce qui fait qu'il sait que la donnée lue à tel instant est associée à la requête mémoire numéro X. Pour les lectures, il renvoie la donnée lue avec l'identifiant. Le processeur sait alors à quelle lecture la donnée lue correspond et il se débrouille en interne pour gérer la situation. Maintenant que ces précisions sont faites, posons cette question : dans quelles situations est-il pertinent de faire des accès mémoire dans le désordre ? La réponse est : quand plusieurs accès à une même ligne ne sont pas consécutifs, qu'il y a des accès entre les deux. Après ré-ordonnancement, ces accès mémoire à une même ligne sont exécutés l'un à la suite de l'autre, ce qui est beaucoup plus rapide. Pour rendre le tout plus concret, voici un exemple. Imaginez que l'on sait les 3 accès mémoire suivants : * Une lecture ligne A ; * PRECHARGE + ACT ; * Une écriture ligne B ; * PRECHARGE + ACT ; * Une lecture ligne A. Sans réordonnancent, on doit émettre deux commandes PRECHARGE quand on passe d'une ligne à l'autre, et il faut ajouter les commandes ACT avec. pour éviter cela, le contrôleur mémoire peut retarder l'écriture, ou au contraire avancer la seconde lecture. le résultat est alors le suivant : * Lecture ligne A ; * Lecture ligne A * PRECHARGE + ACT ; * Une écriture ligne B. On a donc deux accès consécutifs à la même ligne, suivi par une écriture dans une autre ligne. La technique marche parce que l'on a un mix adéquat de lectures et d'écritures. Et encore une fois, il faut éviter d'intervertir lectures et écritures à une même adresse, sous peine de problèmes. Encore une fois, les dépendances RAW posent problème ! L'implémentation est assez simple : la ou les mémoires FIFOs précédentes sont remplacées par des mémoires similaires aux fenêtres d'instruction. Le contrôleur mémoire vérifie à chaque cycle les accès en attente, et vérifie à quelle ligne ils accèdent. Il priorise alors les accès qui tombent dans la ligne ouverte : ceux-là sont exécutés avant les autres. S'il n'y en a pas, il prend l'accès mémoire le plus ancien (ordre FIFO). Il teste aussi les dépendances mémoires RAW avant d'envoyer des commandes à la mémoire DDR/SDRAM. une telle solution est appelée l''''algorithme ''FR-FCFS''''' (''First Ready-First Come First Serve''). ===Les optimisations liées à la présence de plusieurs banques=== Les optimisations précédentes sont décuplées par la présence de plusieurs banques dans la mémoire SDRAM. Il est en effet possible de faire plusieurs accès mémoire en même temps, dans des banques différentes. Il est possible de lancer un accès mémoire dans deux banques en même temps, par exemple. La seule contrainte est que la SDRAM est limitée à 4 banques actives en même temps. {|class="wikitable" |+ Pipelining basique sur les SDRAM |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 |- ! Banque Numéro 1 | || || || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || || |- ! Banque Numéro 2 | || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || |- ! Banque Numéro 3 | || || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || |} Les optimisations précédentes peuvent s'appliquer par banque. Il est par exemple possible d'utiliser une mémoire FIFO par banque. Ainsi, les accès dans une même banque se feront en série, dans l'ordre d'arrivée, mais des accès à des banques différentes se feront dans le désordre. Cela ne pose pas de problème car les accès se font à des adresses différentes, ce qui fait qu'il n'y a pas de conflits majeurs. Il y a cependant un risque que les lectures se fassent dans le désordre si on n'y prend pas garde. [[File:Gestion parallèle des banques.png|centre|vignette|upright=2.5|Gestion parallèle des banques.]] Il est aussi possible d'utiliser une file séparée pour les lectures et les écriture, pour chaque banque. Les performances sont alors améliorées comparé à deux files globales pour toute la SDRAM. En idem avec la réorganisation des accès mémoire, pour regrouper les accès à une même ligne. ==L'entrelacement sur les mémoires SDRAM== L''''entrelacement''' fonctionne sur les mémoires SDRAM, mais il doit être fortement modifié. Concrètement, tout ce qui a été dit plus est inapplicable pour les SDRAM, du fait de la présence du tampon de ligne. Des adresses consécutives atterrissent déjà dans la même ligne, ce qui fait qu'on n'a pas besoin d'entrelacement au niveau d'une ligne. Et ne parlons pas de ce la présence de rangées et de canaux mémoire ! Cependant, il peut y avoir un entrelacement lié à la présence des banques SDRAM. L'entrelacement est géré juste avant le séquenceur mémoire. Le '''circuit d'entrelacement''' intervertit certains bits de l'adresse lors des accès mémoires. On trouve aussi des mémoires FIFOs pour mettre en attente les requêtes mémoire provenant du processeur. Elles permettent de recevoir plusieurs accès mémoire consécutifs. Si vous vous demandez comment plusieurs accès mémoire consécutifs peuvent avoir lieu, sachez que c'est une bonne question. Pour le moment, nous allons dire que ces accès proviennent du processeur ou de la mémoire cache, sans expliquer comment. Le contrôleur mémoire reçoit une série d'accès mémoire assez rapprochés, qu'il doit exécuter au mieux. [[File:Controleur mémoire d'une SDRAM avec entrelacement.png|centre|vignette|upright=2.5|Contrôleur mémoire d'une SDRAM avec entrelacement.]] Pour gérer l'entrelacement, le contrôleur de SDRAM prend en entrée l'adresse envoyée par le processeur, et la découpe en plusieurs champs : un pour sélectionner la banque adéquate, un autre pour la ligne, un autre pour la colonne, un autre pour la rangée. Une solution simple désactive l'entrelacement. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de banque || Adresse de ligne || Adresse de colonne |} Si cette solution fonctionne bien si les accès mémoire sont assez aléatoires, en pratique, mieux vaut utiliser l'entrelacement. ===L'entrelacement de banques/lignes=== Une première méthode d'entrelacement est l''''entrelacement de banques'''. Elle répartit deux lignes consécutives dans deux banques différentes. Pour comprendre l'idée, prenons un exemple. Imaginons une mémoire avec deux banques et 4 lignes. Imaginons qu'on parcoure/balaye la mémoire RAM en partant des adresses basses. Sans entrelacement de ligne, les accès se feront comme suit, de gauche à droite : {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Banque numéro 1 !! colspan="4" | Banque numéro 2 |- | Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 || Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 |} Avec l'entrelacement de banques, les accès se feront comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 |- | Ligne 1 || Ligne 1 || Ligne 2 || Ligne 2 || Ligne 3 || Ligne 3 || Ligne 4 || Ligne 4 |} Avec N banques, la première ligne ira dans la première banque, la seconde ligne dans la seconde, et ainsi de suite jusqu'à la énième banque qui va dans la banque numéro N. Et la ligne N+1 repart dans la première banque. L'avantage est que passer d'une ligne à la suivante est assez rapide. Au lieu de devoir fermer la première ligne et ouvrir la suivante on ouvrira directement la ligne suivante dans une banque différente, ce qui sera plus rapide. La commande PRECHARGE pourra être envoyée à la seconde banque en avance. Pour cela, l'adresse est découpée comme suit. {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne (précise à l'octet) |} ===L'entrelacement de rangées=== Il est aussi possible de faire la même chose, mais avec les rangées. Pour simplifier fortement, une rangée est simplement un chip mémoire. En réalité, les rangées ne sont pas des chip mémoire, mais un ensemble de chips mémoire regroupés ensemble histoire d'atteindre les 64 bits du bus de données. Par exemple, une rangée peut combiner 8 chips mémoire avec un bus de données de 8 bits chacun pour obtenir les 64 bits du bus de données d'une SDRAM. Mais nous passons ce détail sous silence dans les explications qui vont suivre, par souci de simplification. Pour faire comprendre l'entrelacement de rangée, le mieux est d'utiliser un exemple, le même que précédemment. Sans entrelacement de rangée, on change de chip mémoire une fois qu'on a balayé toutes ses adresses. {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Rangée numéro 1 !! colspan="4" | Rangée numéro 2 |- | Banque 1 || Banque 2 || Banque 3 || Banque 4 || Banque 1 || Banque 2 || Banque 3 || Banque 4 |} Avec l'entrelacement de ligne, on change de rangée dès qu'on change de banque/ligne. {|class="wikitable" |+ Adresse mémoire |- ! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 |- | Banque 1 || Banque 1 || Banque 2 || Banque 2 || Banque 3 || Banque 3 || Banque 4 || Banque 4 |} En clair, quand on a épuisé toutes les banques dans une même rangée, on passe à la rangée suivante au lieu de rester dans la même rangée. Notons que dans l'exemple précédent, on a combiné l'entrelacement de rangée et de banque, mais on aurait pu utiliser l'entrelacement de rangée seul. Mais ce n'est pas le cas le plus courant en pratique. Toujours est-il qu'en combinant entrelacement de rangée et de ligne, le découpage de l'adresse est le suivant : {|class="wikitable" |+ Adresse mémoire |- | Adresse de ligne || Numéro de rangée || Adresse de banque || Adresse de colonne (précise à l'octet) |} D'autres méthodes d'entrelacement sont possibles, mais elles sont plus complexes. Chaque contrôleur mémoire fait un peu à sa sauce. Ils permettent en général de choisir entre plusieurs entrelacements. Au minimum, ils permettent de désactiver l'entrelacement, d'activer un entrelacement de ligne, et d'autres entrelacements plus complexes, dont certains sont propriétaires et tenus secrets par le fabricant. : L'usage du ''dual channel'' complique encore l'entrelacement. Mais je vais passer cela sous silence. Les contrôleurs de SDRAM précédents sont assez basiques et ne représentent pas les contrôleurs les plus évolués. Ils étaient utilisés sur les anciens PC, à une époque où ils étaient encore sur la carte mère du processeur. Mais de nos jours, le contrôleur mémoire est intégré au processeur. Et il incorpore de nombreuses optimisations afin de gagner en performances. Malheureusement, ces optimisations sont elles-mêmes dépendantes des optimisations intégrées dans le processeur, ce qui fait qu'on ne peut pas en parler ici. Elles demandent que le contrôleur mémoire reçoive plusieurs accès mémoire simultanés, ce qui n'a aucun sens à ce stade du cours. Aussi, nous allons les passer sous silence pour le moment. Nous les verrons dans le chapitre sur le parallélisme mémoire. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=La désambiguïsation mémoire | prevText=La désambiguïsation mémoire | next=Les processeurs superscalaires | nextText=Les processeurs superscalaires }} </noinclude> 7s6ieq67uy6q6z4gbzvc0dwcyf3zvo5 764813 764810 2026-04-24T10:45:14Z Mewtow 31375 /* L'entrelacement sur les mémoires SDRAM */ 764813 wikitext text/x-wiki Dans ce chapitre, nous allons voir les techniques qui permettent de gérer plusieurs accès mémoire simultanés directement au niveau du cache ou de la mémoire RAM. Par plusieurs accès mémoire simultanés, vous pensez sans doute à l'usage de cache multiports, voire de mémoires RAM multiport. C'est en effet une solution, mais ce n'est clairement pas la seule. Par exemple, il est possible de pipeliner l'accès au cache, voire à la mémoire RAM. Il existe de nombreuses techniques de '''parallélisme mémoire''', et nous allons les voir dans ce chapitre. Pour donner un exemple, il est possible de pipeliner les accès mémoire. Il est possible de pipeliner l'accès au cache et/ou l'accès à la mémoire, et nous verrons comment faire dans ce chapitre. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, qu'ils aient ou non un pipeline dynamique. Par contre, pipeliner la mémoire est une technique ancienne, aujourd'hui peu utilisée. Elle était utilisée sur des très vieux ordinateurs, qui avaient un pipeline mais pas de mémoire cache. Mais de nos jours, les mémoires SDRAM et DDR ne sont pas adaptées pour ça, il est très difficile de les pipeliner correctement. Le parallélisme mémoire est utile aussi bien sur des processeurs à émission dans l'ordre que dans le désordre. Par exemple, un processeur ''in-order'' avec des lectures non-bloquantes peut e, profiter. Il peut techniquement lancer une seconde lecture, après avoir rencontré une première lecture non-bloquante. En général, le nombre de lectures consécutives est cependant limité à moins d'une dizaine. À l'opposé, les processeurs sans lectures non-bloquantes profitent pas du parallélisme mémoire. À la rigueur, ils peuvent en profiter s'ils intègrent des techniques de préchargement. Pendant que le processeur exécute une lecture/écriture, il peut précharger une autre donnée en parallèle. Inutile de dire que sans pipeline processeur, le parallélisme mémoire ne sert pas à grand-chose. ==Le ''line fill buffer'' et les techniques associées== Lors d'un défaut de cache, le processeur doit attendre que toute la ligne de cache soit chargée avant d'être utilisable. Or, la taille d'une ligne de cache est supérieure à la largeur du bus mémoire, ce qui fait qu'une ligne de cache est chargée en plusieurs fois, morceaux par morceaux, mot mémoire par mot mémoire. Le chargement peut se faire directement dans le cache, mais ce n'est pas une solution très pratique. À la place, beaucoup de processeurs ajoutent une mémoire tampon entre la RAM et le cache, appelée le '''tampon de remplissage de ligne''' (''line-fill buffer'') dans les processeurs modernes. Lors d'un défaut de cache, le processeur charge la donnée de la RAM dans le tampon de remplissage de ligne, mot mémoire par mot mémoire. Une fois plein, le tampon de remplissage de ligne est recopié dans la ligne de cache. Le tampon de remplissage de ligne contient une ligne de cache, si ce n'est que certains bits de contrôle ne sont pas présents. Le tampon de remplissage de ligne contient un bit de validité pour chaque mot mémoire de la ligne de cache, qui indiquent si le mot mémoire a été chargé. Par exemple, prenons un processeur 64 bits, qui gère donc des mots mémoire de 8 octets, avec des lignes de cache 256 octets/32 mots mémoire. Le tampon de remplissage de ligne contiendra 32 bits de validité. Si le processeur a chargé les 6 premiers mots mémoire, les 6 premiers bits de validité seront mis à 1, les autres seront encore à zéro. Il faut noter que la disponibilité de la ligne complète se détermine assez facilement en faisant un ET logique entre tous les bits de validité. Le processeur sait ainsi quand la ligne de cache est disponible entièrement et donc quand la transférer dans le cache. La même chose existe avec une hiérarchie de cache, sauf que l'on trouve une mémoire tampon entre chaque niveau de cache. Il y a un tampon de remplissage de ligne entre le cache L1 et le cache L2, entre le cache L2 et le L3, etc. Si le cache ne gère qu'un seul défaut de cache à la fois, le ''fill line buffer'' est une mémoire très simple, qui ne mémorise qu'une seule ligne de cache. Et cela vaut aussi bien pour un cache bloquant que non-bloquant. Mais sur les caches capables de gérer plusieurs défauts simultanés, le tampon de remplissage de ligne est une mémoire de type FIFO ou LIFO, capable de mémoriser plusieurs lignes de cache. Notons que le tampon de remplissage de ligne est très utile pour implémenter certaines techniques, par exemple le contournement du cache. Nous avions vu dans le chapitre sur le cache que certains accès mémoire doivent contourner le cache, pour des raisons de cohérence des caches. C'est notamment nécessaire pour accéder aux périphériques, mais c'est aussi utile pour des raisons de performances dans des cas très spécifiques. Les accès qui contournent le cache se font directement dans le tampon de remplissage de ligne : le processeur écrit ou lit les données depuis ce tampon de remplissage de ligne, sans accéder au cache. ===L'''early restart'' et le ''critical word load''=== La présence du ''line buffer'' permet une optimisation assez intéressante, qui permet de réduire la latence des défauts de cache. L'optimisation consiste à lire un mot mémoire dans le tampon de remplissage de ligne, même si la ligne de cache complète n'a pas encore été chargée. Il existe deux manières de faire cela, qui portent les noms d'''early restart'' et de ''critical word load''. La première est la version la plus simple, la seconde est plus complexe mais plus performante. Avec la technique d''''''early restart''''', la ligne de cache est chargée normalement, en partant de son premier mot mémoire. Dès que le mot mémoire lu/écrit par le processeur est copié dans le ''line fill buffer'', il est envoyé au processeur immédiatement. Illustrons le tout par un exemple, où une ligne de cache fait 16 mots mémoire. Le processeur effectue une lecture, qui lit le 5ème mot mémoire. Sans ''early restart'', le processeur doit charger les 16 mots mémoire avant de faire la lecture dans le cache. Avec ''early restart'', le processeur reçoit la donnée dès que le 5ème mot mémoire est disponible. Le processeur doit attendre que les 4 premiers mots mémoire soient chargés, puis le 5ème mot mémoire arrive et est envoyé directement au processeur, il est lu directement depuis le ''line fill buffer''. Les 11 mots mémoire suivants sont ensuite chargés dans le cache pendant que le processeur fait des calculs dans son coin. Le ''critical word load'' est une optimisation de la technique précédente où le chargement de la ligne de cache commence directement à la donnée demandée par le processeur. Pour reprendre l'exemple précédent, où le processeur demande le 5ème mot mémoire sur 16, le ''critical word load'' charge le 5ème mot mémoire en premier et l'envoie au processeur, ce qui fait qu'il est chargé très rapidement. Pas besoin d'attendre que le processeur charge les 4 mots mémoire précédents comme avec l'''early restart''. Dans le détail, le processeur charge le 5ème mot mémoire en premier, puis charge les 11 suivants, et termine par les 4 mots mémoire du début. En clair, le ''critical word load'' commence par charger le mot mémoire lu, puis les blocs suivants, avant de revenir au début du bloc pour charger les blocs restants. Ainsi, la donnée demandée par le processeur sera la première disponible. Pour cela, l'organisation du tampon de remplissage de ligne est modifiée de manière à rendre cela possible. Il a une taille égale à une ligne de cache complète, qui contient elle-même plusieurs mots mémoire. Dans le ''line-fill buffer'', chaque mot mémoire est stocké avec un tag, qui indique l'adresse du mot mémoire stocké dans le ''line-fill buffer''. Le ''line-fill buffer'' est donc un cache un peu particulier, qui fonctionne comme un cache du point de vue du processeur, comme une mémoire FIFO pour les transferts avec le cache. Ainsi, un processeur qui veut lire dans le cache après un défaut peut accéder à la donnée directement depuis le tampon de remplissage de ligne, alors que la ligne de cache n'a pas encore été totalement recopiée en mémoire. ==Les caches pipelinés== Il est possible de pipeliner l'accès au cache, ce qui demande juste de rajouter des registres au bon endroits et quelques circuits de contrôle. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, même ceux avec un pipeline dynamique. Et l'implémentation peut se faire de plusieurs manières différentes. Il faut dire que découper une mémoire cache en plusieurs étapes peut se faire de plusieurs manière. Commençons par la plus simple. Beaucoup de processeurs des années 2000 avaient une fréquence peu élevée comparée aux standards d'aujourd'hui, ce qui fait que leur cache avait bien un temps d'accès d'un cycle d'horloge. Mais l'accès au cache se faisait en deux cycles d'horloge : un cycle pour calculer une adresse et un cycle pour l'accès mémoire proprement dit. Et ces deux étapes étaient pipelinées, à savoir que deux micro-opérations mémoire peuvent s'exécuter en même temps : une dans l'étage de calcul d'adresse, une autre dans le cache. L'unité mémoire était donc pipelinée, alors que l'accès au cache ne l'était pas. Les processeurs qui implémentaient cette technique regroupent les micro-architectures K5 et K6 d'AMD, les processeurs Intel de micro-architecture P6 (Pentium 2 et 3) et quelques autres. Il est aussi possible de pipeliner l'accès au cache lui-même. Avec un cache pipeliné, l'accès au cache ne se fait pas en un seul cycle, mais en plusieurs. Cependant, on peut lancer un nouvel accès au cache à chaque cycle d'horloge, comme un processeur pipeliné lance une nouvelle instruction à chaque cycle. L'implémentation est assez simple : il suffit d'ajouter des registres dans le cache. Pour cela, on profite que le cache est composé de plusieurs composants séparés, qui échangent des données dans un ordre bien précis, d'un composant à un autre. De plus, le trajet des informations dans un cache est linéaire, ce qui les rend parfois pour l'usage d'un pipeline. ===Le ''pipelining'' des caches ''direct-mapped''=== Voici ce qui se passe avec un cache directement adressé. Pour rappel ce genre de cache est conçu en combinant une mémoire RAM, généralement une SRAM, avec quelques circuits de comparaison et un MUX. L'accès au cache se fait globalement en deux étapes : la première lit la donnée et le ''tag'' dans la SRAM, et on utilise les deux dans une seconde étape. Nous avions vu il y a quelques chapitre comment pipeliner des mémoires, dans le chapitre sur les mémoires évoluées. Et bien ces méthodes peuvent s'utiliser pour la mémoire RAM interne au cache !L'idée est d'insérer un registre entre la sortie de la RAM et la suite du cache, pour en faire un cache pipéliné, à deux étages. Le premier étage lit dans la SRAM, le second fait le reste. L'implémentation sur les caches associatifs à plusieurs voies est globalement la même à quelques détails près. [[File:Cache directement adressé pipeliné - deux étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - deux étages]] Avec le circuit précédent, il est possible d'aller plus loin, cette fois en pipelinant l'accès à la mémoire RAM interne au cache. Pour comprendre comment, rappelons qu'une mémoire SRAM est composée d'un plan mémoire et d'un décodeur. L'accès à la mémoire demande d'abord que le décodeur fasse son travail pour sélectionner la case mémoire adéquate, puis ensuite la lecture ou écriture a lieu dans cette case mémoire. L'accès se fait donc en deux étapes successives séparées, on a juste à mettre un registre entre les deux. Il suffit donc de mettre un gros registre entre le décodeur et le plan mémoire. [[File:Cache directement adressé pipeliné - trois étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - trois étages]] Et on peut aller encore plus loin en découpant le décodeur en deux circuits séparés. En effet, rappelez-vous le chapitre sur les circuits de sélection : nous avions vu qu'il est possible de créer des décodeurs en assemblant des décodeurs plus petits, contrôlés par un circuit de prédécodage. Et bien on peut encore une ajouter un registre entre ce circuit de prédécodage et les petits décodeurs. : Théoriquement, toute l'adresse est fournie d'un seul coup au cache, la quasi-totalité des processeurs présentent l'adresse complète à un cache pipeliné. Mais le Pentium 4 fait autrement. Il faut noter que les premiers étages manipulent l'indice dans la SRAM, qui est dans les bits de poids faible, alors que les étages ultérieurs manipulent le ''tag'' qui est dans les bits de poids fort. Les concepteurs du Pentium 4 ont alors décidé de présenter les bits de poids faible lors du premier cycle d'accès au cache, puis ceux de poids fort au second cycle. Pour cela, l'ALU fonctionnait à une fréquence double de celle du processeur, tout comme le cache L1. Il n'y avait pas de pipeline proprement dit, mais cela réduisait grandement la latence d'accès au cache. ===Le ''pipelining'' des caches splittés/sériels=== Il est aussi possible de pipeliner un cache dits splittés, aussi appelés à accès sériel. Pour rappel, les caches à accès sériel vérifient si il y a succès ou défaut de cache, avant d'accéder aux lignes de cache en cas de succès. Ils font donc différemment des autres caches, qui accèdent à une ligne de cache, avant de déterminer s'il y a succès ou défaut en lisant le tag de la ligne de cache. Les caches sériels disposent de deux SRAM : une pour les tags des lignes de cache et une pour les données. Ils accèdent à la SRAM pour les tags, avant d’accéder à la SRAM des données en cas de succès de cache. Vu que l'accès se fait en deux étapes, une vérification des tags suivie de la lecture/écriture des données, il est facile à pipeliner. Pipeliner le cache permet de régler le problème des accès au cache L1, et elle est tout le temps utilisé sur les processeurs modernes. Mais que faire en cas de défaut de cache ? ==Les caches non bloquants== Un '''cache bloquant''' est un cache auquel le processeur ne peut pas accéder pendant un défaut de cache. Il faut attendre que la lecture ou écriture en RAM soit terminée avant de pouvoir utiliser de nouveau le cache. Un '''cache non bloquant''' n'a pas ce problème : on peut l'utiliser pendant un défaut de cache. Les caches non bloquants permettent de démarrer une nouvelle lecture ou écriture alors qu'une autre est en cours, ce qui permet d'exécuter plusieurs lectures ou écritures en même temps. ===Les ''Miss Handling Status Registers''=== Lors d'un défaut de cache, la mémoire RAM est consultée pendant le défaut de cache, mais le cache est inutilisé. Un défaut de cache n'utilise pas le cache, ce n'est qu'un accès en mémoire RAM, sur le bus mémoire (ou un accès aux niveaux de cache inférieurs, peu importe). Le cache en lui-même est laissé libre, rien n’empêche d'y accéder, il est en réalité intrinsèquement non-bloquant. Les caches, bloquants comme non-bloquants, sont en réalité composés d'une mémoire cache proprement dite, entourée de circuits qui servent d'interface entre le processeur et le cache lui-même. Et parmi les circuits tout autour, certains gèrent l'accès au cache lors d'un défaut de cache. Ils sont regroupés sous le terme de '''''Miss Handling Architecture''''' (MHA). La différence entre un cache bloquant et un cache non-bloquant est en réalité liée à l'implémentation de la MHA. Les caches bloquants coupent volontairement l'accès au cache lors d'un défaut, car les défauts sont plus simples à gérer ainsi. Le défaut de cache rapatrie une donnée depuis la RAM, qui sera écrite dans le cache. Et il ne faut pas qu'une tentative d'accès à cette donnée ait lieu avant qu'elle ne soit chargée. Mais ce blocage est général et touche tout le cache, alors que seule une ligne de cache est concernée par le défaut de cache. L'idée derrière un cache non-bloquant est que seule la ligne de cache est bloquée, mais les autres sont accessibles. L'idée est alors de mémoriser les lignes de cache concernées par le défaut de cache, afin d'en bloquer l'accès. À chaque accès, on vérifie si la ligne de cache est déjà réservée par un défaut de cache. La lecture/écriture est alors bloquée si c'est le cas, mais elle accepte les accès sinon. Pour cela, la ''Miss Handling Architecture'' contient des registres qui mémorisent des informations sur les défauts de cache en cours. Ils portent le nom de '''''miss status handling registers''''', que j’appellerais dorénavant MSHR, qui sont aussi appelés des '''''miss buffer'''''. Le contenu des MSHR varie beaucoup suivant le processeur, mais ces derniers stockent au minimum les informations suivantes : * Le numéro de la ligne de cache dans laquelle les données sont chargées. * Un bit de validité qui indique si le MSHR est vide ou pas, qui est mis à 0 quand le défaut de cache est résolu. * Un ou plusieurs champs de lecture/écriture, qui contiennent des informations sur la lecture/écriture. ** Pour une lecture, elle contient des informations sur la destination de la donnée, à savoir qui prévenir quand le défaut de cache est terminé. C'est parfois un nom/numéro de registre (celui dans lequel charger la donnée), mais c'est souvent le numéro de l'entrée dans le ''load/store queue''. ** Pour les écritures, elle contient la donnée à écrire, ou éventuellement un numéro de ''load/store queue'' où se trouve la donnée à écrire. Il faut noter que le bus mémoire ne peut gérer qu'un seul défaut de cache à la fois. Aussi, il est intéressant de regarder ce qui se passe lorsqu'un second défaut de cache survient, pendant qu'un premier est en cours. Dans ce cas, il y a deux réponses qui correspondent à deux types de caches non-bloquants, qui portent les noms barbares de caches de type succès après défaut et défaut après défaut. Sur le premier type, il ne peut pas y avoir plusieurs défauts de cache simultanés. Dès qu'un second défaut de cache survient, le cache stoppe son activité et on ne peut plus démarrer de nouvelle lecture/écriture, tant que le premier défaut de cache n'est pas résolu. Le second type est plus souple et autorise la survenue de plusieurs défauts de cache simultanés. Du moins, jusqu'à une certaine limite, car le cache ne peut supporter qu'un nombre limité d'accès mémoires simultanés (pipelinés). ===Les accès simultanés à une même ligne de cache=== Il arrive que le processeur fasse plusieurs accès mémoire simultanés à la même ligne de cache. Si la ligne de cache en question n'a pas encore été chargée dans le cache, alors on a plusieurs défauts de cache consécutifs pour la même ligne de cache, et le cache non-bloquant doit gérer la situation. Pour la suite, il va falloir faire une petite distinction entre les défauts primaires et secondaires. Imaginons qu'un défaut de cache ait lieu et demande à charger une donnée dans la ligne de cache numéro N. Il s'agit du premier défaut impliquant cette ligne de cache précise, ce qui lui vaut le nom de '''défaut de cache primaire'''. Mais par la suite, d'autres accès mémoire à la même ligne de cache ont lieu, alors que la ligne de cache n'est pas encore disponible. Dans ce cas, il s'agit de '''défauts de cache secondaires'''. Pour l'unité d'accès mémoire, les défauts de cache primaire et secondaire sont différents (ils prennent tous une entrée dans la ''load/store queue''). Mais pour le cache, ils ne correspondent qu'à un seul accès au cache : celui qui demande de charger la ligne de cache demandée. Les défauts de cache primaire et secondaire à la même ligne de cache se voient attribuer un MSHR unique. La gestion des défauts de cache secondaires dépend du cache non-bloquant. La solution la plus simple ne permet pas les défauts de cache secondaires. Le cache ne permet pas deux défauts de cache simultanés pour la même ligne de cache. Les autres solutions le permettent, en fusionnant des accès simultanés à la même ligne de cache en un seul au niveau des MSHR. Dans tous les cas, détecter les défauts de cache secondaires sont un problème qu'il faut détecter. La ''Miss Handling Architecture'' doit détecter les défauts de cache secondaire. Pour cela, elle procède comme suit. Lors de chaque défaut de cache, la MHA récupère le numéro de la ligne de cache associé. Il vérifie alors chaque MSHR pour vérifier s'il contient le numéro en question. S'il n'y a aucune correspondance dans les MSHR, alors c'est signe que le défaut de cache est un défaut primaire. Mais s'il y en a une, alors c'est un défaut secondaire. Évidemment, cela signifie que lors d'un défaut de cache, le numéro de ligne de cache est envoyé à tous les MSHR, pour comparaison. Les MSHR sont donc regroupés dans une mémoire associative, une sorte de mini-cache, faciliter l'implémentation. Lors d'un défaut de cache primaire, l'accès à un cache non-bloquant se fait comme suit : le processeur envoie une adresse au cache, accède à celui-ci, et détecte la survenue d'un défaut de cache. Il en profite alors pour attribuer une ligne de cache dans laquelle sera chargée ce défaut. L'attribution est très simple dans le cas des caches ''direct mapped'', ou associatifs par voie, pour lesquels l'attribution se fait assez simplement. Il mémorise alors cette information dans les MSHR, après avoir vérifié que le défaut de cache n'était pas un défaut secondaire. Lors des accès ultérieurs à une adresse proche, censée être dans la même ligne de cache, le processeur va encore une fois rencontrer un défaut de cache. Il va alors déterminer le numéro de la ligne de cache associée à l'adresse, et comparer ce numéro avec les MSHRs. Si un MSHR contient ce numéro, c'est signe que le défaut de cache est un défaut secondaire. La MHA réagit alors différemment selon le processeur considéré. Une première solution n'autorise pas les défauts de cache secondaires. Si l'un d'entre eux survient, le processeur est gelé par un ''pipeline stall'', une bulle de pipeline. Une autre solution fusionne les défauts de cache secondaires avec le défaut de cache primaire : tout cela ne correspond qu'à un seul défaut de cache pour lui. ===Les MSHR simples=== Un cache non-bloquant à '''MSHR simple''' contient juste plusieurs MSHR qui mémorisent juste un numéro de ligne de cache, un bit de validité, le champ de lecture/écriture, et l'adresse exacte de lecture/écriture. Avec cette organisation, il est possible d'avoir plusieurs défauts de cache séparés, mais à la condition que chaque défaut accède à une ligne de cache différente. Deux accès simultanés à une même ligne de cache ne sont pas possibles, les défauts de cache secondaires ne sont pas autorisés. Ainsi, chaque défaut de cache se voit attribuer son propre MSHR, chacun contient un numéro de ligne de cache différent. Pour comprendre pourquoi c'est impossible de gérer les défauts secondaires, il faut regarder le champ de lecture/écriture. Si on veut effectuer plusieurs écritures consécutives à la même adresse, le MSHR n'aura pas de quoi mémoriser les deux données à écrire. Il pourra mémoriser la première donnée à écrire, pas la seconde. Même chose lors d'une lecture : le champ lecture/écriture peut mémorisr la destination de la première lecture, pas de la seconde. L'avantage est que la MHA n'a besoin que des MSHR et de quelques circuits annexes. Les autres solutions rajoutent des circuits annexes pour gérer les défauts de cache secondaires, qui utilisent beaucoup de circuits. Le cout en circuit est donc élevé, mais le gain en performance est là. Passons maintenant aux caches non-bloquants qui autorisent les défauts de cache secondaire. La solution la plus simple consiste à utiliser ===Les MSHR adressés implicitement=== Avec les '''MSHR adressés implicitement''', il est possible de fusionner plusieurs accès mémoire à une même ligne de cache, mais sous une condition très importante : ces accès lisent/écrivent des mots mémoire différents. Par exemple, imaginons qu'une ligne de cache contienne 8 mots mémoire de 64 bits. Si un premier accès mémoire lit le mot mémoire numéro 7 (dernier mot mémoire de la ligne), et le second accès le mot mémoire numéro 3, alors la fusion est possible. Mais si deux accès mémoire veulent lire/écrire le mot mémoire numéro 7, alors la fusion n'est pas possible et le processeur se bloque, un ''pipeline stall'' survient. Un MSHR adressé implicitement est un MSHR simple, qui contient naturellement un numéro de ligne de cache (tag) et un bit de validité, sauf que le champ de lecture/écriture est dupliqué. Les différents champs lecture/écriture d'un MSHR sont regroupés dans une mémoire RAM/cache qui contient autant d'entrées qu'il y a de mot mémoire dans une ligne de cache. Chaque entrée de la table est associée à un mot mémoire de la ligne de cache et stocke des informations sur celui-ci. Une entrée mémorise, au minimum : * Un champ de lecture/écriture qui contient soit la destination de la lecture, soit la donnée à écrire. * Un bit R/W permettant d'interpréter correctement le champ de lecture/écriture. * Un bit de validité pour chaque entrée de la table, qui dit si un défaut de cache antérieur accède déjà à ce mot mémoire. La fusion de deux défauts de cache est ainsi assez simple. Un défaut de cache primaire/secondaire configure l'entrée associée au mot mémoire lu/écrit. Si un défaut secondaire ultérieur lit/écrit un mot mémoire différent (dont le bit de validité de l'entrée dans la table est à zéro), pas de conflit : il configure une autre entrée, vide. Mais s'il lit/écrit un mot mémoire pour lequel l'entrée de la table est occupée, il y a conflit, le processeur est bloqué par un ''pipeline stall'', une bulle de pipeline. Avec cette organisation, le nombre de MSHR indique combien de lignes de cache peuvent être lues en même temps depuis la mémoire. Quant au nombre d'entrées par MSHR, il détermine combien d'accès mémoires qui ne se recouvrent pas peuvent avoir lieu en même temps. ===Les MSHR adressés explicitement=== Les '''MSHR adressés explicitement''' n'ont pas les contraintes des MSHR adressés implicitement. Avec eux, il est possible d'avoir plusieurs défauts de cache pointant vers la même ligne de cache, mais aussi vers le même mot mémoire. De tels défauts de cache apparaissent sur les processeurs à exécution dans le désordre, mais sont très rares, voire inexistants, sur les processeurs ''in-order''. L'idée est encore que chaque MSHR est associée à une table mémoire qui mémorise des entrées. Cette table n'est autre qu'une mémoire FIFO. Sauf que cette fois-ci, une entrée n'est pas associée à un mot mémoire. Une entrée est associée à un défaut de cache. Une entrée mémorise là encore la destination de la lecture ou la donnée de l'écriture suivi du bit R/W, ainsi que d'un bit de validité par entrée, mais aussi : la position du mot mémoire lu dans la ligne de cache. C'est cette dernière information qui n'était pas présente dans les MSHR adressés implicitement. De plus, le nombre d'entrée par MSHR n'est pas égal au nombre de mots mémoires dans une ligne de cache, mais peut être arbitrairement grand. L'avantage d'un tel cache est qu'il est capable de traité les défauts secondaires concernant un même mot mémoire, et ce, car les accès à la ligne de cache concernée se produisent dans l'ordre de génération des défauts (la table mémoire est une FIFO). ===Les MSHRs inversés=== Généralement, plus on veut supporter de défauts de cache, plus le nombre de MSHR et d'entrées augmente. Mais au-delà d'un certain nombre d'entrées et de MSHR, les MSHR adressés implicitement et explicitement ont tendance à bouffer un peu trop de circuits. Utiliser une organisation un peu moins gourmande en circuits est donc une nécessité. Cette organisation plus économe se base sur des MSHR inversés. Les MSHR inversés ne contiennent qu'une seule entrée, en quelque sorte : au lieu d’utiliser n MSHR de m entrées chacun, on va utiliser n × m MSHR inversés. La différence, c'est que plusieurs MSHR peuvent contenir un tag identique, contrairement aux MSHR adressés implicitement et explicitement. Lorsqu'un défaut de cache a lieu, chaque MSHR est vérifié. Si jamais aucun MSHR ne contient de tag identique à celui utilisé par le défaut, un MSHR vide est choisi pour stocker ce défaut, et une requête de lecture en mémoire est lancée. Dans le cas contraire, un MSHR est réservé au défaut de cache, mais la requête n'est pas lancée. Quand la donnée est disponible, les MSHR correspondant à la ligne qui vient d'être chargée vont être utilisés un par un pour résoudre les défauts de cache en attente. ===Les MSHR intégrés au cache=== Certains chercheurs ont remarqué que pendant qu'une ligne de cache est en train de subir un défaut de cache, celle-ci reste inutilisée, et son contenu est destiné à être perdu une fois le défaut de cache résolu. Ils se sont dit que, plutôt que d'utiliser des MSHR séparés, il vaudrait mieux utiliser la ligne de cache pour stocker les informations sur les défauts de cache en attente dans cette ligne de cache. Pour éviter tout problème, il faut rajouter un bit dans les bits de contrôle de la ligne de cache, qui sert à indiquer que la ligne de cache est occupée : un défaut de cache a eu lieu dans cette ligne, et elle stocke donc des informations pour résoudre les défauts de cache. ==L'entrelacement mémoire== Nous venons de voir comment une mémoire cache peut gérer plusieurs accès mémoire simultanés. Il se trouve que ce genre d'optimisation dépasse largement le cadre du cache. Les mémoires RAM ne sont pas en reste, elles aussi ! Il est possible de pipeliner l'accès à une mémoire RAM, en utilisant des techniques dites d''''entrelacement'''. Précisons cependant que cette optimisation n'est pas utilisée sur les mémoires SDRAM ou DDR modernes, du moins pas sans modifications majeures. Mais divers ordinateurs assez anciens, dont des superordinateurs, ont utilisé cette technique. La technique demande d'utiliser plusieurs mémoires séparées. Sur les anciens ordinateurs, les mémoires en question sont des chips mémoire, à savoir des circuits intégrés de DRAM. Pour faire plus général, nous allons utiliser le terme de '''banques''', ou encore de bancs mémoire. La différence est qu'il existe des mémoires multi-banque, qui regroupent plusieurs banques indépendantes dans un seul boitier, dans un seul chip mémoire. En clair, de telles mémoires regroupent plusieurs sous-mémoires dans un seul circuit intégré. Il est aussi possible d'utiliser plusieurs chips mémoire séparés, chacun ayant une banque. Reste à combiner ces banques pour former un pseudo-pipeline. ===Utiliser plusieurs banques sans entrelacement=== Sans optimisation particulière, les adresses sont réparties dans les banques comme indiqué ci-dessous. Il s'agit de ce qui s'appelle un arrangement horizontal, et nous avions vu celui-ci dans le chapiutre sur les mémoires SDRAM. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Avec cet arrangement, les bits de poids fort de l'adresse sont utilisées pour sélectionner la banque adéquate, et le reste de l'adresse est envoyé sur le bus d'adresse. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Adresse de banque !! Adresse dans la banque |- | Quelques bits de poids fort || Reste de l'adresse |} L'entrelacement change cette répartition, afin d'accélérer les accès mémoire. L'entrelacement classique vise à améliorer les accès à des adresses mémoires consécutives, mais des formes plus évoluées d'entrelacement visent à optimiser des accès mémoire arbitraires. Voyons-les dans l'ordre, du plus simple au plus complexe. ===L'entrelacement classique=== Avec l''''entrelacement classique''', des adresses consécutives sont réparties dans des banques consécutives. Précisons que cette attribution des adresses n'implique absolument pas la mémoire virtuelle ou n'importe quel mécanisme dans le processeur. La répartition décide que telle adresse mémoire va dans telle banque, à telle adresse dans la banque. Elle est donc le fait du contrôleur mémoire, donc en dehors du processeur (les contrôleurs mémoires n’étaient pas encore intégrés dans le processeur à l'époque). [[File:Répartition des adresses dans une mémoire interleaved.png|centre|vignette|upright=1.5|Répartition des adresses dans une mémoire interleaved.]] L'entrelacement simple permet d'accélérer les accès mémoire qui se font à des adresses consécutives. Avec l'entrelacement, chaque accès mémoire tombe dans une banque mémoire différente, ce qui fait qu'on peut démarrer un nouvel accès mémoire à chaque cycle d'horloge. Pendant qu'une banque est occupée par un accès mémoire, on démarre le suivant dans une autre banque, et ainsi de suite. Le tout se termine soit quand on a épuisé toutes les banques libres, soit quand l'accès en rafales se termine. Pas besoin d'attendre que la mémoire ait fini sa lecture/écriture avant de démarrer la lecture/écriture suivante. [[File:Pipemining mémoire.png|centre|vignette|upright=2.5|Pipelining mémoire]] L'animation suivante illustre bien le processus, avec quatres banques. [[File:Interleaving.gif|centre|vignette|upright=2.5|Pipelining mémoire]] L'entrelacement simple utilise les bits de poids faible pour sélectionner la banque, et les bits de poids fort pour la case mémoire. [[File:Adresse mémoire d'une mémoire entrelacée.png|centre|vignette|upright=2|Adresse mémoire d'une mémoire entrelacée]] Précisons que le temps d'accès mémoire ne change pas beaucoup avec l'entrelacement. Par contre, on peut faire plus d'accès mémoire simultanés. On peut démarrer un accès mémoire par cycle d'horloge, mais l'accès en lui-même prend plusieurs cycles. Les mémoires à entrelacement ont donc un débit supérieur aux mémoires qui ne l'utilisent pas. L'entrelacement classique pourrait être utilisé pour accélérer les transferts entre mémoire RAM et cache, qui se font en blocs d'adresses consécutives. Mais de nos jours, il n'est pas utilisé dans cette optique. Il faut dire qu'il est redondant avec le mode rafale des mémoires DRAM. Les deux font globalement la même chose et on voit mal comment utiliser les deux en même temps. Le mode rafale étant plus simple à implémenter, et plus léger niveau utilisation du bus de commande mémoire, il est préféré à l'entrelacement. Par contre, l'entrelacement a eu son heure de gloire sur d'anciens processeurs qui n'avaient pas de mémoire cache, alors qu'ils disposaient d'un pipeline. L'exemple typique est celui des nombreux ''processeurs vectoriels'', que nous n'avons pas encore abordé à ce stade du cours. De tels processeurs avaient un pipeline, une unité de calcul fortement pipelinée, et parfois même de l'exécution dans le désordre et du renommage de registres ! ===L'entrelacement par décalage=== L'entrelacement simple est très utile pour les accès en rafle ou équivalents. Par contre, il existe de rares situations où il n'est pas optimal. Une de ces situations est celle des accès en enjambée (en ''stride''), où l'on accède en série à des adresses sont séparées par N mots mémoires. [[File:Accès par enjambées.png|centre|vignette|upright=2|Accès par enjambées.]] De tels accès surviennent quand un logiciel accède à des structures de données spécifiques, à savoir des tableaux de structures, des matrices, ou d'autres structures de données dans le genre. Un cas classique est celui du parcours d'une matrice colonne par colonne. Les matrices de nombres sont mémorisées ligne par ligne, ce qui fait que parcourir une colonne demande de passer d'une ligne à la suivante et de faire des grands sauts en mémoire RAM, mais tous espacés par la même distance. Avec l'entrelacement simple, les accès en enjambée sont moins performants que les accès à des adresses consécutives, mais cela ne fonctionne pas trop mal. Le pire cas est celui où l'on a N banques et où les données sont justement placées toutes les N adresses. Des accès consécutifs vont tous tomber dans la même banque, on ne peut plus accéder à des banques différentes en parallèle. Mais en dehors de ce cas, on voit une amélioration par rapport à la situation sans entrelacement. Pour obtenir des performances maximales pour les accès en enjambées, il faut répartir les mots mémoires dans la mémoire autrement. L'organisation idéale est la suivante. : Dans les explications qui vont suivre, la variable N représente le nombre de banques, qui sont numérotées de 0 à N-1. On commence par organiser les N premières adresses comme une mémoire entrelacée simple : l'adresse 0 correspond à la banque 0, l'adresse 1 à la banque 1, etc. Pour le bloc suivant, on décale tout d'une adresse, à savoir qu'on commence à remplir les banques à partir de la seconde, non de la première. Une fois la fin du bloc atteinte, on finit de remplir le bloc en repartant du début du bloc. Le troisième bloc d'adresse subit le même traitement, sauf qu'on commence à remplir à partir de la seconde banque. Et on poursuit l’assignation des adresses en décalant d'un cran en plus à chaque bloc. Ainsi, chaque bloc verra ses adresses décalées d'un cran en plus comparé au bloc précédent. Si jamais le décalage dépasse la fin d'un bloc, alors on reprend au début. [[File:Mémoire interleaved par décalage.png|centre|vignette|upright=1.5|Mémoire entrelacée par décalage.]] En faisant cela, on remarque que les banques situées à N adresses d'intervalle sont différentes. Dans l'exemple du dessus, nous avons ajouté un décalage de 1 à chaque nouveau bloc à remplir. Mais on aurait tout aussi bien pu prendre un décalage de 2, 3, etc. Dans tous les cas, on obtient un entrelacement par décalage. Ce décalage est appelé le pas d'entrelacement, noté P. Le calcul de l'adresse à envoyer à la banque, ainsi que la banque à sélectionner se fait en utilisant les formules suivantes : * adresse à envoyer à la banque = adresse totale / N ; * numéro de la banque = (adresse + décalage) modulo N ; * décalage = (adresse totale * P) mod N. Avec cet entrelacement par décalage, on peut prouver que la bande passante maximale est atteinte si le nombre de banques est un nombre premier. Seulement, utiliser un nombre de banques premier peut créer des trous dans la mémoire, des mots mémoires inadressables. Malgré ce défaut, la technique a été utilisée sur quelques ordinateurs, avec l'exemple notable du superordinateur ''Burroughs Scientific Processor''. Pour éviter cela, il y a plusieurs solutions. Par exemple, on peut faire en sorte que N et la taille d'une banque soient premiers entre eux : ils ne doivent pas avoir de diviseur commun. Mais en pratique, elles n'ont pas vraiment été implémentées dans une vraie machine, et sont restées à l'état de recherche, aussi je les passe sous silence. ===L'entrelacement pseudo-aléatoire=== Une dernière méthode de répartition consiste à répartir les adresses dans les banques de manière "pseudo-aléatoire". L'idée exacte est de faire passer l'adresse dans une '''fonction de hachage''', plus ou moins complexe. Pour rappel, une fonction de hachage prend une entrée de grande taille et fournit en sortie un résultat de petite taille. Idéalement, elles doivent donner des résultats aussi différents que possible pour des entrées similaires, histoire de simuler une sorte de pseudo-aléatoire. Ici, une adresse est transformée en un numéro de banque. Une fonction de hachage simple ne fait que permuter des bits de l'adresse pour obtenir son résultat. Elle ne fait qu'échanger des bits de place avant de couper l'adresse en deux morceaux : un pour la sélection de la banque, et un autre pour la sélection de l'adresse dans la banque. Cette permutation est fixe, et ne change pas suivant l'adresse. Des fonctions de hachage plus complexes font des XOR bit à bit entre certains bits de l'adresse. ==Les contrôleurs SDRAM/DDR avec "exécution dans le désordre"== Après avoir vu le cas des mémoires RAM, nous allons nous concentrer sur le cas particulier des mémoires SDRAM. Les mémoires SDRAM et DDR modernes sont capables de gérer plusieurs accès simultanés à la mémoire RAM, et leurs contrôleurs mémoire en font tout autant. C'est une différence majeure avec les mémoires asynchrones FPM/EDO, qui n'acceptaient qu'un seul accès mémoire à la fois. Leur contrôleur mémoire n'acceptait qu'un seul accès mémoire à la fois, c'était un contrôleur mémoire bloquant. Les contrôleurs mémoires des SDRAM sont eux non-bloquants et peuvent encaisser une dizaine d'accès mémoire à la fois. Peu de choses sont connues sur les contrôleurs de SDRAM/DDR modernes, les fabricants ne donnant que peu de détails dessus. Les rares simulateurs qui tentent de décrire leur fonctionnement, comme DRAM SIM I et II, sont particulièrement simples et ne vont pas dans le détail. Néanmoins, le peu qu'on sait est tout de même instructif. ===Rappel sur les SDRAM : tampon de ligne et commandes=== Les mémoires SDRAM sont des mémoires à tampon de ligne. Elles font un accès mémoire en deux étapes. La première étape recopie une ligne de N * 64 bits dans un tampon interne à la SDRAM. La seconde étape sélectionne une colonne, à savoir une donnée de 64 bits, qui est soit envoyée sur le bus de données pour une lecture, soit modifiée par une écriture. [[File:Mémoire à tampon de ligne.png|centre|vignette|upright=2|Mémoire à tampon de ligne]] Les accès mémoire sont traduits par un séquenceur mémoire en une série de commandes mémoires, qui sont séparées par des délais mémoire de quelques cycles d'horloge. Les délais sont très précis, et sont à respecter à la lettre. Une lecture ou une écriture se fait en maximum trois commandes : une commande PRECHARGE qui ferme la ligne précédemment utilisée, une commande ACT qui précise l'adresse de la ligne, et une commande READ ou WRITE qui précise l'adresse de la colonne et éventuellement la donnée à écrire. : Pour être plus précis, la commande PRECHARGE précharge les lignes de bits du plan mémoire à une tension particulière, ce qui les vide de leur contenu. Mais c'est un détail sans importance pour ce qui va suivre. Les SDRAM permettent de se passer de la première étape si des accès consécutifs se font dans la même ligne. Une fois activée, la ligne reste ouverte et on peut accéder plusieurs fois de suite dedans. On a alors juste à préciser l'adresse de colonne dedans. Si un accès mémoire accède à une ligne déjà activée, on dit que c'est un '''succès de page'''. Si ce n'est pas le cas, on doit fermer la ligne courante, rouvrir la ligne vboulue, et préciser la colonne. C'est alors un '''défaut de page'''. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | bgcolor="#FFA0FF" | PRECHARGE || bgcolor="#FFA0FF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || bgcolor="#A0FFFF" | READ (2) || bgcolor="#A0FFFF" | READ (3) || || || bgcolor="#A0FFFF" | READ (4) || bgcolor="#A0FFFF" | READ (5) || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | Donnée READ 1 || ||bgcolor="#A0FFFF" | Donnée READ 2 | bgcolor="#A0FFFF" | Donnée READ 3 || || || bgcolor="#A0FFFF" | Donnée READ 4 || bgcolor="#A0FFFF" | Donnée READ 5 |} Les SDRAM sont parfois capables de démarrer une commande avant que la précédente soit terminée. Mais le respect des délais mémoire est très limitant, ce qui fait qu'on ne peut pas parler de réel pipeline, comme c'est le cas sur les processeurs. Parlons plutot de '''commandes anticipées'''. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | || bgcolor="#A0FFFF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || || || bgcolor="#FFA0FF" | READ (2) || || || || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 | bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 | |} Un dernier point est que les chips mémoires de SDRAM sont composés de plusieurs banques, chacune étant une sorte de mini-mémoire miniature. Chaque banque a son propre décodeur, son propre tampon de ligne, ses propres multiplexeurs de colonne, sa logique de rafraichissement mémoire, etc. C'est comme si une SDRAM regroupait plusieurs mémoires séparées dans un même circuit intégré. [[File:Arrangement vertical.jpg|centre|vignette|upright=2.5|Mémoire multi-banques.]] ===La mise en attente des accès mémoire=== Un contrôleur de SDRAM peut accepter plusieurs accès mémoire en même temps. Mais cela ne signifie pas que le contrôleur sera prêt à les traiter immédiatement. Pour éviter tout problème de disponibilité, le contrôleur met en attente les accès mémoire que le processeur lui envoie, pour les exécuter dès que possible. Les accès mémoire sont mis en attente dans une mémoire FIFO, histoire de les exécuter dans leur ordre d'arrivée, notamment pour renvoyer les lectures dans l'ordre demandé. Il y a aussi une mémoire FIFO pour les données à écrire et une FIFO pour les données lues. Cette dernière sert au cas où le cache ne soit pas disponible quand on lui envoie la donnée lue. [[File:Module d'interface avec la mémoire.png|centre|vignette|upright=2.5|Contrôleur mémoire avec mise en attente des requêtes processeur et autres optimisations.]] Des optimisations sont possibles dès la mémoire FIFO. Par exemple, on peut regrouper plusieurs accès à des données consécutives en un seul accès en rafale. Le contrôleur mémoire analyse les requêtes mises en attente et détecte si certaines se font à des adresses consécutives. Si c'est le cas, il fusionne ces requêtes en une lecture/écriture en rafale. Une telle optimisation est appelée la '''combinaison de lecture''' pour les lectures, et la '''combinaison d'écriture''' pour les écritures. L'optimisation précédente ne paraissent pas payer de mine, mais elles deviennent plus compréhensible quand on sait que certains contrôleurs mémoire peuvent mettre en attente beaucoup de données. Par exemple, l'''Intel E8870 - Scalable Memory Controller'' peut mettre en attente 64 lignes de cache dans la mémoire FIFO, soit 8 kibioctets de RAM ! Deux pages mémoire si la mémoire virtuelle utilise des pages de 4 kibioctets ! ===Les lectures anticipées : une forme d'OOO mémoire=== La mise en attente des accès mémoire est une optimisation intéressante, mais pas mirifique. Elle permet surtout de ne pas bloquer le processeur si le contrôleur mémoire a du travail sur la planche. Cependant, elle peut être optimisée quand on se rend compte d'une chose : le processeur ne voit que les lectures, pas les écritures. Pour les écritures, il les envoie au contrôleur mémoire et ce dernier fait le travail demandé, le processeur n'est pas prévenu quand une écriture se termine. Mais pour les lectures, c'est différent, car il reçoit la donnée lue. En conséquence, il est possible d'optimiser les écritures sans que le processeur ne voit quoique ce soit. Une optimisation possible est alors de changer l'ordre des accès mémoire, histoire de retarder les écritures ou au contraire de les faire en avance. La raison est que les mémoires SDRAM n'aiment pas quand on alterne lectures et écritures. Les délais mémoire, les fameux ''timings'' mémoire, sont clairs : faire une suite de lecture ou une suite d'écriture est plus performant que d'alterner entre lectures et écritures. L'idée est alors de changer l'ordre des écritures de manière à les faire en bloc, idem pour les lectures. L’optimisation est possible, mais à condition que le processeur n'y verra que du feu. Or, vous l'avez deviné, il y a des cas où ces réorganisations ne sont pas possibles, et vous avez sans doute trouvé que la situation était familière. L'optimisation en question est une forme d'exécution dans le désordre des accès mémoire, réalisée par le contrôleur mémoire lui-même ! Et qui dit exécution dans le désordre dit : problèmes liés aux dépendances de données. Changer l'ordre des écritures n'est pas toujours possible, notamment en raison des dépendances RAW, WAR et WAW. Pour que le processeur n'y voit que du feu, il faut respecter plusieurs critères. Premièrement, les lectures doivent être renvoyées au processeur dans l'ordre. Et quand je dis dans l'ordre, cela veut dire : dans l'ordre d'envoi des lectures au contrôleur mémoire. Les écritures aussi doivent se faire dans l'ordre, histoire qu'une série d'écriture donne le bon résultat final. Le troisième critère est qu'une lecture doit donner le résultat de la dernière écriture ''à la même adresse''. Pour le dire autrement, il faut juste détecter les dépendances RAW. Concrètement, si on a une série de lectures et d'écritures alternées, on peut regrouper les lectures et les écritures dans deux paquets séparés, à condition qu'aucune lecture ne lise une adresse écrite par une écriture. Mais si une lecture est dépendante d'une écriture, alors la lecture doit attendre que l'écriture se termine, idem pour les lectures suivantes. Une implémentation basique remplace la mémoire FIFO vue au-dessus, par deux mémoires FIFOs : une pour les lectures, une pour les écritures. Lorsqu'un accès mémoire arrive au séquenceur, il regarde si c'est une lecture ou une écriture et place l'accès mémoire dans la file adéquate. Le fait que ce soit des FIFOs garantit que les lectures se font dans l'ordre, idem pour les écritures. En sortie des deux FIFOs, le séquenceur mémoire détecte les dépendances RAW. Précisément, quand une lecture sort de la mémoire FIFO, il consulte la file d'attente des écriture pour voir si la lecture est dépendante d'une écriture en attente. La lecture attend si c'est le cas, et les écritures sont exécutées à la place. Séparer lectures et écriture est une source d'optimisation. Il est par exemple possible d'utiliser le réacheminement lecture sur écriture (''Store to load forwarding''). Il s'agit d'un équivalent de l’optimisation du même nom, utilisée dans le cadre de la désambiguïsation mémoire. Imaginez qu'une lecture accède à une donnée pas encore écrite, en attente dans le contrôleur mémoire. C'est une dépendance RAW assez claire. Dans ce cas, le contrôleur mémoire peut renvoyer la donnée directement depuis ses mémoires FIFOs, sans faire d'accès mémoire en lecture. Le contrôleur mémoire a tendance à privilégier les lectures, car celles-ci sont cruciales pour l'exécution dans le désordre. Plus elles se font tôt, plus vite le processeur recevra les données et pourra démarrer les instructions dépendantes de cette donnée. Le séquenceur a donc tendance à piocher en priorité dans la file de lecture, plutôt que dans la file d'écriture. A vrai dire, dans le cas idéal, les écritures ne sont faites que quand la file d'écriture est pleine ou quasi-pleine... Prenons l'exemple des coprocesseurs IO 81341 et 81342, qui étaient en réalité des ''chipsets'' intégrant un contrôleur SDRAM. Le ''chipset'' avait 5 ports : un pour les processeurs, un pour le pont sud (''southbridge'') et trois pour des canaux DMA. Pour le port processeur, il n'y avait pas de réordonnancement et ce port utilisait une seule mémoire FIFO. Les autres ports utilisaient l'optimisation qu'on vient de voir, et avaient vraisemblablement des files séparées pour les lectures et écritures. Le séquenceur mémoire vérifiait les dépendances mémoire de type RAW et autres. [[File:Double file d'attente pour les lectures et écritures.png|centre|vignette|upright=2.5|Double file d'attente pour les lectures et écritures]] ===Le ré-ordonnancement des commandes mémoires=== L'optimisation précédente est peu poussée, comparé aux formes d'exécution dans le désordre que les processeurs utilisent. Et on peut se demander si une exécution dans le désordre plus poussée est possible. La réponse est oui : un contrôleur mémoire peut faire des réorganisations bien plus poussées. Par contre, il faut faire une précision très importante : le contrôleur mémoire ne remet pas les accès mémoire dans l'ordre avant de les envoyer au processeur. C'est le processeur qui remet en ordre les accès mémoire, dans sa ''load queue''. En clair : le processeur voit les accès mémoire dans le désordre, c'est lui qui les remet en ordre. Pour cela, les requêtes mémoire sont "numérotées", à savoir qu'on leur attribue un identifiant binaire. Le contrôleur mémoire exécute les lectures/écritures dans le désordre, mais garde la trace de leur identifiant. Il sait dans quel ordre il exécute les accès mémoire et connait leur latence. Ce qui fait qu'il sait que la donnée lue à tel instant est associée à la requête mémoire numéro X. Pour les lectures, il renvoie la donnée lue avec l'identifiant. Le processeur sait alors à quelle lecture la donnée lue correspond et il se débrouille en interne pour gérer la situation. Maintenant que ces précisions sont faites, posons cette question : dans quelles situations est-il pertinent de faire des accès mémoire dans le désordre ? La réponse est : quand plusieurs accès à une même ligne ne sont pas consécutifs, qu'il y a des accès entre les deux. Après ré-ordonnancement, ces accès mémoire à une même ligne sont exécutés l'un à la suite de l'autre, ce qui est beaucoup plus rapide. Pour rendre le tout plus concret, voici un exemple. Imaginez que l'on sait les 3 accès mémoire suivants : * Une lecture ligne A ; * PRECHARGE + ACT ; * Une écriture ligne B ; * PRECHARGE + ACT ; * Une lecture ligne A. Sans réordonnancent, on doit émettre deux commandes PRECHARGE quand on passe d'une ligne à l'autre, et il faut ajouter les commandes ACT avec. pour éviter cela, le contrôleur mémoire peut retarder l'écriture, ou au contraire avancer la seconde lecture. le résultat est alors le suivant : * Lecture ligne A ; * Lecture ligne A * PRECHARGE + ACT ; * Une écriture ligne B. On a donc deux accès consécutifs à la même ligne, suivi par une écriture dans une autre ligne. La technique marche parce que l'on a un mix adéquat de lectures et d'écritures. Et encore une fois, il faut éviter d'intervertir lectures et écritures à une même adresse, sous peine de problèmes. Encore une fois, les dépendances RAW posent problème ! L'implémentation est assez simple : la ou les mémoires FIFOs précédentes sont remplacées par des mémoires similaires aux fenêtres d'instruction. Le contrôleur mémoire vérifie à chaque cycle les accès en attente, et vérifie à quelle ligne ils accèdent. Il priorise alors les accès qui tombent dans la ligne ouverte : ceux-là sont exécutés avant les autres. S'il n'y en a pas, il prend l'accès mémoire le plus ancien (ordre FIFO). Il teste aussi les dépendances mémoires RAW avant d'envoyer des commandes à la mémoire DDR/SDRAM. une telle solution est appelée l''''algorithme ''FR-FCFS''''' (''First Ready-First Come First Serve''). ===Les optimisations liées à la présence de plusieurs banques=== Les optimisations précédentes sont décuplées par la présence de plusieurs banques dans la mémoire SDRAM. Il est en effet possible de faire plusieurs accès mémoire en même temps, dans des banques différentes. Il est possible de lancer un accès mémoire dans deux banques en même temps, par exemple. La seule contrainte est que la SDRAM est limitée à 4 banques actives en même temps. {|class="wikitable" |+ Pipelining basique sur les SDRAM |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 |- ! Banque Numéro 1 | || || || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || || |- ! Banque Numéro 2 | || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || |- ! Banque Numéro 3 | || || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || |} Les optimisations précédentes peuvent s'appliquer par banque. Il est par exemple possible d'utiliser une mémoire FIFO par banque. Ainsi, les accès dans une même banque se feront en série, dans l'ordre d'arrivée, mais des accès à des banques différentes se feront dans le désordre. Cela ne pose pas de problème car les accès se font à des adresses différentes, ce qui fait qu'il n'y a pas de conflits majeurs. Il y a cependant un risque que les lectures se fassent dans le désordre si on n'y prend pas garde. [[File:Gestion parallèle des banques.png|centre|vignette|upright=2.5|Gestion parallèle des banques.]] Il est aussi possible d'utiliser une file séparée pour les lectures et les écriture, pour chaque banque. Les performances sont alors améliorées comparé à deux files globales pour toute la SDRAM. En idem avec la réorganisation des accès mémoire, pour regrouper les accès à une même ligne. Un problème avec ces optimisations est qu'il est rare que des accès mémoire se fassent dans des banques séparées. Les accès mémoire tendent à se faire avec une bonne localité spatiale, ce qui fait qu'ils tombent dans une même banque. Du moins, pas sans optimisations. Car les controleurs SDRAM/DDR modernes incorporent des optimisations pour corriger ce problème. Les optimisations en question sont une forme d'entrelacement adaptée aux mémoires SDRAM. L'entrelacement naïf ne marche pas à cause de la présence du tampon de ligne. Des adresses consécutives atterrissent déjà dans la même ligne, ce qui fait qu'on n'a pas besoin d'entrelacement au niveau d'une ligne. Cependant, il peut y avoir un entrelacemententre banques SDRAM. Voyons ce que ca veut dire. L'entrelacement est géré juste avant le séquenceur mémoire. Le '''circuit d'entrelacement''' intervertit certains bits de l'adresse lors des accès mémoires. On trouve aussi des mémoires FIFOs pour mettre en attente les requêtes mémoire provenant du processeur. Elles permettent de recevoir plusieurs accès mémoire consécutifs. Si vous vous demandez comment plusieurs accès mémoire consécutifs peuvent avoir lieu, sachez que c'est une bonne question. Pour le moment, nous allons dire que ces accès proviennent du processeur ou de la mémoire cache, sans expliquer comment. Le contrôleur mémoire reçoit une série d'accès mémoire assez rapprochés, qu'il doit exécuter au mieux. [[File:Controleur mémoire d'une SDRAM avec entrelacement.png|centre|vignette|upright=2.5|Contrôleur mémoire d'une SDRAM avec entrelacement.]] Pour gérer l'entrelacement, le contrôleur de SDRAM prend en entrée l'adresse envoyée par le processeur, et la découpe en plusieurs champs : un pour sélectionner la banque adéquate, un autre pour la ligne, un autre pour la colonne, un autre pour la rangée. Une solution simple désactive l'entrelacement. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de banque || Adresse de ligne || Adresse de colonne |} Si cette solution fonctionne bien si les accès mémoire sont assez aléatoires, en pratique, mieux vaut utiliser l'entrelacement. Une première méthode d'entrelacement est l''''entrelacement de banques'''. Elle répartit deux lignes consécutives dans deux banques différentes. Pour comprendre l'idée, prenons un exemple. Imaginons une mémoire avec deux banques et 4 lignes. Imaginons qu'on parcoure/balaye la mémoire RAM en partant des adresses basses. Sans entrelacement de ligne, les accès se feront comme suit, de gauche à droite : {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Banque numéro 1 !! colspan="4" | Banque numéro 2 |- | Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 || Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 |} Avec l'entrelacement de banques, les accès se feront comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 |- | Ligne 1 || Ligne 1 || Ligne 2 || Ligne 2 || Ligne 3 || Ligne 3 || Ligne 4 || Ligne 4 |} Avec N banques, la première ligne ira dans la première banque, la seconde ligne dans la seconde, et ainsi de suite jusqu'à la énième banque qui va dans la banque numéro N. Et la ligne N+1 repart dans la première banque. L'avantage est que passer d'une ligne à la suivante est assez rapide. Au lieu de devoir fermer la première ligne et ouvrir la suivante on ouvrira directement la ligne suivante dans une banque différente, ce qui sera plus rapide. La commande PRECHARGE pourra être envoyée à la seconde banque en avance. Pour cela, l'adresse est découpée comme suit. {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne (précise à l'octet) |} Il est aussi possible de faire la même chose, mais avec les rangées. Pour simplifier fortement, une rangée est simplement un chip mémoire. En réalité, les rangées ne sont pas des chip mémoire, mais un ensemble de chips mémoire regroupés ensemble histoire d'atteindre les 64 bits du bus de données. Par exemple, une rangée peut combiner 8 chips mémoire avec un bus de données de 8 bits chacun pour obtenir les 64 bits du bus de données d'une SDRAM. Mais nous passons ce détail sous silence dans les explications qui vont suivre, par souci de simplification. Pour faire comprendre l'entrelacement de rangée, le mieux est d'utiliser un exemple, le même que précédemment. Sans entrelacement de rangée, on change de chip mémoire une fois qu'on a balayé toutes ses adresses. {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Rangée numéro 1 !! colspan="4" | Rangée numéro 2 |- | Banque 1 || Banque 2 || Banque 3 || Banque 4 || Banque 1 || Banque 2 || Banque 3 || Banque 4 |} Avec l'entrelacement de ligne, on change de rangée dès qu'on change de banque/ligne. {|class="wikitable" |+ Adresse mémoire |- ! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 |- | Banque 1 || Banque 1 || Banque 2 || Banque 2 || Banque 3 || Banque 3 || Banque 4 || Banque 4 |} En clair, quand on a épuisé toutes les banques dans une même rangée, on passe à la rangée suivante au lieu de rester dans la même rangée. Notons que dans l'exemple précédent, on a combiné l'entrelacement de rangée et de banque, mais on aurait pu utiliser l'entrelacement de rangée seul. Mais ce n'est pas le cas le plus courant en pratique. Toujours est-il qu'en combinant entrelacement de rangée et de ligne, le découpage de l'adresse est le suivant : {|class="wikitable" |+ Adresse mémoire |- | Adresse de ligne || Numéro de rangée || Adresse de banque || Adresse de colonne (précise à l'octet) |} D'autres méthodes d'entrelacement sont possibles, mais elles sont plus complexes. Chaque contrôleur mémoire fait un peu à sa sauce. Ils permettent en général de choisir entre plusieurs entrelacements. Au minimum, ils permettent de désactiver l'entrelacement, d'activer un entrelacement de ligne, et d'autres entrelacements plus complexes, dont certains sont propriétaires et tenus secrets par le fabricant. : L'usage du ''dual channel'' complique encore l'entrelacement. Mais je vais passer cela sous silence. Les contrôleurs de SDRAM précédents sont assez basiques et ne représentent pas les contrôleurs les plus évolués. Ils étaient utilisés sur les anciens PC, à une époque où ils étaient encore sur la carte mère du processeur. Mais de nos jours, le contrôleur mémoire est intégré au processeur. Et il incorpore de nombreuses optimisations afin de gagner en performances. Malheureusement, ces optimisations sont elles-mêmes dépendantes des optimisations intégrées dans le processeur, ce qui fait qu'on ne peut pas en parler ici. Elles demandent que le contrôleur mémoire reçoive plusieurs accès mémoire simultanés, ce qui n'a aucun sens à ce stade du cours. Aussi, nous allons les passer sous silence pour le moment. Nous les verrons dans le chapitre sur le parallélisme mémoire. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=La désambiguïsation mémoire | prevText=La désambiguïsation mémoire | next=Les processeurs superscalaires | nextText=Les processeurs superscalaires }} </noinclude> fvqo6byx4lwlp2d3voj6u1p4k2gaa4z 764815 764813 2026-04-24T10:46:03Z Mewtow 31375 /* Les optimisations liées à la présence de plusieurs banques */ 764815 wikitext text/x-wiki Dans ce chapitre, nous allons voir les techniques qui permettent de gérer plusieurs accès mémoire simultanés directement au niveau du cache ou de la mémoire RAM. Par plusieurs accès mémoire simultanés, vous pensez sans doute à l'usage de cache multiports, voire de mémoires RAM multiport. C'est en effet une solution, mais ce n'est clairement pas la seule. Par exemple, il est possible de pipeliner l'accès au cache, voire à la mémoire RAM. Il existe de nombreuses techniques de '''parallélisme mémoire''', et nous allons les voir dans ce chapitre. Pour donner un exemple, il est possible de pipeliner les accès mémoire. Il est possible de pipeliner l'accès au cache et/ou l'accès à la mémoire, et nous verrons comment faire dans ce chapitre. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, qu'ils aient ou non un pipeline dynamique. Par contre, pipeliner la mémoire est une technique ancienne, aujourd'hui peu utilisée. Elle était utilisée sur des très vieux ordinateurs, qui avaient un pipeline mais pas de mémoire cache. Mais de nos jours, les mémoires SDRAM et DDR ne sont pas adaptées pour ça, il est très difficile de les pipeliner correctement. Le parallélisme mémoire est utile aussi bien sur des processeurs à émission dans l'ordre que dans le désordre. Par exemple, un processeur ''in-order'' avec des lectures non-bloquantes peut e, profiter. Il peut techniquement lancer une seconde lecture, après avoir rencontré une première lecture non-bloquante. En général, le nombre de lectures consécutives est cependant limité à moins d'une dizaine. À l'opposé, les processeurs sans lectures non-bloquantes profitent pas du parallélisme mémoire. À la rigueur, ils peuvent en profiter s'ils intègrent des techniques de préchargement. Pendant que le processeur exécute une lecture/écriture, il peut précharger une autre donnée en parallèle. Inutile de dire que sans pipeline processeur, le parallélisme mémoire ne sert pas à grand-chose. ==Le ''line fill buffer'' et les techniques associées== Lors d'un défaut de cache, le processeur doit attendre que toute la ligne de cache soit chargée avant d'être utilisable. Or, la taille d'une ligne de cache est supérieure à la largeur du bus mémoire, ce qui fait qu'une ligne de cache est chargée en plusieurs fois, morceaux par morceaux, mot mémoire par mot mémoire. Le chargement peut se faire directement dans le cache, mais ce n'est pas une solution très pratique. À la place, beaucoup de processeurs ajoutent une mémoire tampon entre la RAM et le cache, appelée le '''tampon de remplissage de ligne''' (''line-fill buffer'') dans les processeurs modernes. Lors d'un défaut de cache, le processeur charge la donnée de la RAM dans le tampon de remplissage de ligne, mot mémoire par mot mémoire. Une fois plein, le tampon de remplissage de ligne est recopié dans la ligne de cache. Le tampon de remplissage de ligne contient une ligne de cache, si ce n'est que certains bits de contrôle ne sont pas présents. Le tampon de remplissage de ligne contient un bit de validité pour chaque mot mémoire de la ligne de cache, qui indiquent si le mot mémoire a été chargé. Par exemple, prenons un processeur 64 bits, qui gère donc des mots mémoire de 8 octets, avec des lignes de cache 256 octets/32 mots mémoire. Le tampon de remplissage de ligne contiendra 32 bits de validité. Si le processeur a chargé les 6 premiers mots mémoire, les 6 premiers bits de validité seront mis à 1, les autres seront encore à zéro. Il faut noter que la disponibilité de la ligne complète se détermine assez facilement en faisant un ET logique entre tous les bits de validité. Le processeur sait ainsi quand la ligne de cache est disponible entièrement et donc quand la transférer dans le cache. La même chose existe avec une hiérarchie de cache, sauf que l'on trouve une mémoire tampon entre chaque niveau de cache. Il y a un tampon de remplissage de ligne entre le cache L1 et le cache L2, entre le cache L2 et le L3, etc. Si le cache ne gère qu'un seul défaut de cache à la fois, le ''fill line buffer'' est une mémoire très simple, qui ne mémorise qu'une seule ligne de cache. Et cela vaut aussi bien pour un cache bloquant que non-bloquant. Mais sur les caches capables de gérer plusieurs défauts simultanés, le tampon de remplissage de ligne est une mémoire de type FIFO ou LIFO, capable de mémoriser plusieurs lignes de cache. Notons que le tampon de remplissage de ligne est très utile pour implémenter certaines techniques, par exemple le contournement du cache. Nous avions vu dans le chapitre sur le cache que certains accès mémoire doivent contourner le cache, pour des raisons de cohérence des caches. C'est notamment nécessaire pour accéder aux périphériques, mais c'est aussi utile pour des raisons de performances dans des cas très spécifiques. Les accès qui contournent le cache se font directement dans le tampon de remplissage de ligne : le processeur écrit ou lit les données depuis ce tampon de remplissage de ligne, sans accéder au cache. ===L'''early restart'' et le ''critical word load''=== La présence du ''line buffer'' permet une optimisation assez intéressante, qui permet de réduire la latence des défauts de cache. L'optimisation consiste à lire un mot mémoire dans le tampon de remplissage de ligne, même si la ligne de cache complète n'a pas encore été chargée. Il existe deux manières de faire cela, qui portent les noms d'''early restart'' et de ''critical word load''. La première est la version la plus simple, la seconde est plus complexe mais plus performante. Avec la technique d''''''early restart''''', la ligne de cache est chargée normalement, en partant de son premier mot mémoire. Dès que le mot mémoire lu/écrit par le processeur est copié dans le ''line fill buffer'', il est envoyé au processeur immédiatement. Illustrons le tout par un exemple, où une ligne de cache fait 16 mots mémoire. Le processeur effectue une lecture, qui lit le 5ème mot mémoire. Sans ''early restart'', le processeur doit charger les 16 mots mémoire avant de faire la lecture dans le cache. Avec ''early restart'', le processeur reçoit la donnée dès que le 5ème mot mémoire est disponible. Le processeur doit attendre que les 4 premiers mots mémoire soient chargés, puis le 5ème mot mémoire arrive et est envoyé directement au processeur, il est lu directement depuis le ''line fill buffer''. Les 11 mots mémoire suivants sont ensuite chargés dans le cache pendant que le processeur fait des calculs dans son coin. Le ''critical word load'' est une optimisation de la technique précédente où le chargement de la ligne de cache commence directement à la donnée demandée par le processeur. Pour reprendre l'exemple précédent, où le processeur demande le 5ème mot mémoire sur 16, le ''critical word load'' charge le 5ème mot mémoire en premier et l'envoie au processeur, ce qui fait qu'il est chargé très rapidement. Pas besoin d'attendre que le processeur charge les 4 mots mémoire précédents comme avec l'''early restart''. Dans le détail, le processeur charge le 5ème mot mémoire en premier, puis charge les 11 suivants, et termine par les 4 mots mémoire du début. En clair, le ''critical word load'' commence par charger le mot mémoire lu, puis les blocs suivants, avant de revenir au début du bloc pour charger les blocs restants. Ainsi, la donnée demandée par le processeur sera la première disponible. Pour cela, l'organisation du tampon de remplissage de ligne est modifiée de manière à rendre cela possible. Il a une taille égale à une ligne de cache complète, qui contient elle-même plusieurs mots mémoire. Dans le ''line-fill buffer'', chaque mot mémoire est stocké avec un tag, qui indique l'adresse du mot mémoire stocké dans le ''line-fill buffer''. Le ''line-fill buffer'' est donc un cache un peu particulier, qui fonctionne comme un cache du point de vue du processeur, comme une mémoire FIFO pour les transferts avec le cache. Ainsi, un processeur qui veut lire dans le cache après un défaut peut accéder à la donnée directement depuis le tampon de remplissage de ligne, alors que la ligne de cache n'a pas encore été totalement recopiée en mémoire. ==Les caches pipelinés== Il est possible de pipeliner l'accès au cache, ce qui demande juste de rajouter des registres au bon endroits et quelques circuits de contrôle. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, même ceux avec un pipeline dynamique. Et l'implémentation peut se faire de plusieurs manières différentes. Il faut dire que découper une mémoire cache en plusieurs étapes peut se faire de plusieurs manière. Commençons par la plus simple. Beaucoup de processeurs des années 2000 avaient une fréquence peu élevée comparée aux standards d'aujourd'hui, ce qui fait que leur cache avait bien un temps d'accès d'un cycle d'horloge. Mais l'accès au cache se faisait en deux cycles d'horloge : un cycle pour calculer une adresse et un cycle pour l'accès mémoire proprement dit. Et ces deux étapes étaient pipelinées, à savoir que deux micro-opérations mémoire peuvent s'exécuter en même temps : une dans l'étage de calcul d'adresse, une autre dans le cache. L'unité mémoire était donc pipelinée, alors que l'accès au cache ne l'était pas. Les processeurs qui implémentaient cette technique regroupent les micro-architectures K5 et K6 d'AMD, les processeurs Intel de micro-architecture P6 (Pentium 2 et 3) et quelques autres. Il est aussi possible de pipeliner l'accès au cache lui-même. Avec un cache pipeliné, l'accès au cache ne se fait pas en un seul cycle, mais en plusieurs. Cependant, on peut lancer un nouvel accès au cache à chaque cycle d'horloge, comme un processeur pipeliné lance une nouvelle instruction à chaque cycle. L'implémentation est assez simple : il suffit d'ajouter des registres dans le cache. Pour cela, on profite que le cache est composé de plusieurs composants séparés, qui échangent des données dans un ordre bien précis, d'un composant à un autre. De plus, le trajet des informations dans un cache est linéaire, ce qui les rend parfois pour l'usage d'un pipeline. ===Le ''pipelining'' des caches ''direct-mapped''=== Voici ce qui se passe avec un cache directement adressé. Pour rappel ce genre de cache est conçu en combinant une mémoire RAM, généralement une SRAM, avec quelques circuits de comparaison et un MUX. L'accès au cache se fait globalement en deux étapes : la première lit la donnée et le ''tag'' dans la SRAM, et on utilise les deux dans une seconde étape. Nous avions vu il y a quelques chapitre comment pipeliner des mémoires, dans le chapitre sur les mémoires évoluées. Et bien ces méthodes peuvent s'utiliser pour la mémoire RAM interne au cache !L'idée est d'insérer un registre entre la sortie de la RAM et la suite du cache, pour en faire un cache pipéliné, à deux étages. Le premier étage lit dans la SRAM, le second fait le reste. L'implémentation sur les caches associatifs à plusieurs voies est globalement la même à quelques détails près. [[File:Cache directement adressé pipeliné - deux étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - deux étages]] Avec le circuit précédent, il est possible d'aller plus loin, cette fois en pipelinant l'accès à la mémoire RAM interne au cache. Pour comprendre comment, rappelons qu'une mémoire SRAM est composée d'un plan mémoire et d'un décodeur. L'accès à la mémoire demande d'abord que le décodeur fasse son travail pour sélectionner la case mémoire adéquate, puis ensuite la lecture ou écriture a lieu dans cette case mémoire. L'accès se fait donc en deux étapes successives séparées, on a juste à mettre un registre entre les deux. Il suffit donc de mettre un gros registre entre le décodeur et le plan mémoire. [[File:Cache directement adressé pipeliné - trois étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - trois étages]] Et on peut aller encore plus loin en découpant le décodeur en deux circuits séparés. En effet, rappelez-vous le chapitre sur les circuits de sélection : nous avions vu qu'il est possible de créer des décodeurs en assemblant des décodeurs plus petits, contrôlés par un circuit de prédécodage. Et bien on peut encore une ajouter un registre entre ce circuit de prédécodage et les petits décodeurs. : Théoriquement, toute l'adresse est fournie d'un seul coup au cache, la quasi-totalité des processeurs présentent l'adresse complète à un cache pipeliné. Mais le Pentium 4 fait autrement. Il faut noter que les premiers étages manipulent l'indice dans la SRAM, qui est dans les bits de poids faible, alors que les étages ultérieurs manipulent le ''tag'' qui est dans les bits de poids fort. Les concepteurs du Pentium 4 ont alors décidé de présenter les bits de poids faible lors du premier cycle d'accès au cache, puis ceux de poids fort au second cycle. Pour cela, l'ALU fonctionnait à une fréquence double de celle du processeur, tout comme le cache L1. Il n'y avait pas de pipeline proprement dit, mais cela réduisait grandement la latence d'accès au cache. ===Le ''pipelining'' des caches splittés/sériels=== Il est aussi possible de pipeliner un cache dits splittés, aussi appelés à accès sériel. Pour rappel, les caches à accès sériel vérifient si il y a succès ou défaut de cache, avant d'accéder aux lignes de cache en cas de succès. Ils font donc différemment des autres caches, qui accèdent à une ligne de cache, avant de déterminer s'il y a succès ou défaut en lisant le tag de la ligne de cache. Les caches sériels disposent de deux SRAM : une pour les tags des lignes de cache et une pour les données. Ils accèdent à la SRAM pour les tags, avant d’accéder à la SRAM des données en cas de succès de cache. Vu que l'accès se fait en deux étapes, une vérification des tags suivie de la lecture/écriture des données, il est facile à pipeliner. Pipeliner le cache permet de régler le problème des accès au cache L1, et elle est tout le temps utilisé sur les processeurs modernes. Mais que faire en cas de défaut de cache ? ==Les caches non bloquants== Un '''cache bloquant''' est un cache auquel le processeur ne peut pas accéder pendant un défaut de cache. Il faut attendre que la lecture ou écriture en RAM soit terminée avant de pouvoir utiliser de nouveau le cache. Un '''cache non bloquant''' n'a pas ce problème : on peut l'utiliser pendant un défaut de cache. Les caches non bloquants permettent de démarrer une nouvelle lecture ou écriture alors qu'une autre est en cours, ce qui permet d'exécuter plusieurs lectures ou écritures en même temps. ===Les ''Miss Handling Status Registers''=== Lors d'un défaut de cache, la mémoire RAM est consultée pendant le défaut de cache, mais le cache est inutilisé. Un défaut de cache n'utilise pas le cache, ce n'est qu'un accès en mémoire RAM, sur le bus mémoire (ou un accès aux niveaux de cache inférieurs, peu importe). Le cache en lui-même est laissé libre, rien n’empêche d'y accéder, il est en réalité intrinsèquement non-bloquant. Les caches, bloquants comme non-bloquants, sont en réalité composés d'une mémoire cache proprement dite, entourée de circuits qui servent d'interface entre le processeur et le cache lui-même. Et parmi les circuits tout autour, certains gèrent l'accès au cache lors d'un défaut de cache. Ils sont regroupés sous le terme de '''''Miss Handling Architecture''''' (MHA). La différence entre un cache bloquant et un cache non-bloquant est en réalité liée à l'implémentation de la MHA. Les caches bloquants coupent volontairement l'accès au cache lors d'un défaut, car les défauts sont plus simples à gérer ainsi. Le défaut de cache rapatrie une donnée depuis la RAM, qui sera écrite dans le cache. Et il ne faut pas qu'une tentative d'accès à cette donnée ait lieu avant qu'elle ne soit chargée. Mais ce blocage est général et touche tout le cache, alors que seule une ligne de cache est concernée par le défaut de cache. L'idée derrière un cache non-bloquant est que seule la ligne de cache est bloquée, mais les autres sont accessibles. L'idée est alors de mémoriser les lignes de cache concernées par le défaut de cache, afin d'en bloquer l'accès. À chaque accès, on vérifie si la ligne de cache est déjà réservée par un défaut de cache. La lecture/écriture est alors bloquée si c'est le cas, mais elle accepte les accès sinon. Pour cela, la ''Miss Handling Architecture'' contient des registres qui mémorisent des informations sur les défauts de cache en cours. Ils portent le nom de '''''miss status handling registers''''', que j’appellerais dorénavant MSHR, qui sont aussi appelés des '''''miss buffer'''''. Le contenu des MSHR varie beaucoup suivant le processeur, mais ces derniers stockent au minimum les informations suivantes : * Le numéro de la ligne de cache dans laquelle les données sont chargées. * Un bit de validité qui indique si le MSHR est vide ou pas, qui est mis à 0 quand le défaut de cache est résolu. * Un ou plusieurs champs de lecture/écriture, qui contiennent des informations sur la lecture/écriture. ** Pour une lecture, elle contient des informations sur la destination de la donnée, à savoir qui prévenir quand le défaut de cache est terminé. C'est parfois un nom/numéro de registre (celui dans lequel charger la donnée), mais c'est souvent le numéro de l'entrée dans le ''load/store queue''. ** Pour les écritures, elle contient la donnée à écrire, ou éventuellement un numéro de ''load/store queue'' où se trouve la donnée à écrire. Il faut noter que le bus mémoire ne peut gérer qu'un seul défaut de cache à la fois. Aussi, il est intéressant de regarder ce qui se passe lorsqu'un second défaut de cache survient, pendant qu'un premier est en cours. Dans ce cas, il y a deux réponses qui correspondent à deux types de caches non-bloquants, qui portent les noms barbares de caches de type succès après défaut et défaut après défaut. Sur le premier type, il ne peut pas y avoir plusieurs défauts de cache simultanés. Dès qu'un second défaut de cache survient, le cache stoppe son activité et on ne peut plus démarrer de nouvelle lecture/écriture, tant que le premier défaut de cache n'est pas résolu. Le second type est plus souple et autorise la survenue de plusieurs défauts de cache simultanés. Du moins, jusqu'à une certaine limite, car le cache ne peut supporter qu'un nombre limité d'accès mémoires simultanés (pipelinés). ===Les accès simultanés à une même ligne de cache=== Il arrive que le processeur fasse plusieurs accès mémoire simultanés à la même ligne de cache. Si la ligne de cache en question n'a pas encore été chargée dans le cache, alors on a plusieurs défauts de cache consécutifs pour la même ligne de cache, et le cache non-bloquant doit gérer la situation. Pour la suite, il va falloir faire une petite distinction entre les défauts primaires et secondaires. Imaginons qu'un défaut de cache ait lieu et demande à charger une donnée dans la ligne de cache numéro N. Il s'agit du premier défaut impliquant cette ligne de cache précise, ce qui lui vaut le nom de '''défaut de cache primaire'''. Mais par la suite, d'autres accès mémoire à la même ligne de cache ont lieu, alors que la ligne de cache n'est pas encore disponible. Dans ce cas, il s'agit de '''défauts de cache secondaires'''. Pour l'unité d'accès mémoire, les défauts de cache primaire et secondaire sont différents (ils prennent tous une entrée dans la ''load/store queue''). Mais pour le cache, ils ne correspondent qu'à un seul accès au cache : celui qui demande de charger la ligne de cache demandée. Les défauts de cache primaire et secondaire à la même ligne de cache se voient attribuer un MSHR unique. La gestion des défauts de cache secondaires dépend du cache non-bloquant. La solution la plus simple ne permet pas les défauts de cache secondaires. Le cache ne permet pas deux défauts de cache simultanés pour la même ligne de cache. Les autres solutions le permettent, en fusionnant des accès simultanés à la même ligne de cache en un seul au niveau des MSHR. Dans tous les cas, détecter les défauts de cache secondaires sont un problème qu'il faut détecter. La ''Miss Handling Architecture'' doit détecter les défauts de cache secondaire. Pour cela, elle procède comme suit. Lors de chaque défaut de cache, la MHA récupère le numéro de la ligne de cache associé. Il vérifie alors chaque MSHR pour vérifier s'il contient le numéro en question. S'il n'y a aucune correspondance dans les MSHR, alors c'est signe que le défaut de cache est un défaut primaire. Mais s'il y en a une, alors c'est un défaut secondaire. Évidemment, cela signifie que lors d'un défaut de cache, le numéro de ligne de cache est envoyé à tous les MSHR, pour comparaison. Les MSHR sont donc regroupés dans une mémoire associative, une sorte de mini-cache, faciliter l'implémentation. Lors d'un défaut de cache primaire, l'accès à un cache non-bloquant se fait comme suit : le processeur envoie une adresse au cache, accède à celui-ci, et détecte la survenue d'un défaut de cache. Il en profite alors pour attribuer une ligne de cache dans laquelle sera chargée ce défaut. L'attribution est très simple dans le cas des caches ''direct mapped'', ou associatifs par voie, pour lesquels l'attribution se fait assez simplement. Il mémorise alors cette information dans les MSHR, après avoir vérifié que le défaut de cache n'était pas un défaut secondaire. Lors des accès ultérieurs à une adresse proche, censée être dans la même ligne de cache, le processeur va encore une fois rencontrer un défaut de cache. Il va alors déterminer le numéro de la ligne de cache associée à l'adresse, et comparer ce numéro avec les MSHRs. Si un MSHR contient ce numéro, c'est signe que le défaut de cache est un défaut secondaire. La MHA réagit alors différemment selon le processeur considéré. Une première solution n'autorise pas les défauts de cache secondaires. Si l'un d'entre eux survient, le processeur est gelé par un ''pipeline stall'', une bulle de pipeline. Une autre solution fusionne les défauts de cache secondaires avec le défaut de cache primaire : tout cela ne correspond qu'à un seul défaut de cache pour lui. ===Les MSHR simples=== Un cache non-bloquant à '''MSHR simple''' contient juste plusieurs MSHR qui mémorisent juste un numéro de ligne de cache, un bit de validité, le champ de lecture/écriture, et l'adresse exacte de lecture/écriture. Avec cette organisation, il est possible d'avoir plusieurs défauts de cache séparés, mais à la condition que chaque défaut accède à une ligne de cache différente. Deux accès simultanés à une même ligne de cache ne sont pas possibles, les défauts de cache secondaires ne sont pas autorisés. Ainsi, chaque défaut de cache se voit attribuer son propre MSHR, chacun contient un numéro de ligne de cache différent. Pour comprendre pourquoi c'est impossible de gérer les défauts secondaires, il faut regarder le champ de lecture/écriture. Si on veut effectuer plusieurs écritures consécutives à la même adresse, le MSHR n'aura pas de quoi mémoriser les deux données à écrire. Il pourra mémoriser la première donnée à écrire, pas la seconde. Même chose lors d'une lecture : le champ lecture/écriture peut mémorisr la destination de la première lecture, pas de la seconde. L'avantage est que la MHA n'a besoin que des MSHR et de quelques circuits annexes. Les autres solutions rajoutent des circuits annexes pour gérer les défauts de cache secondaires, qui utilisent beaucoup de circuits. Le cout en circuit est donc élevé, mais le gain en performance est là. Passons maintenant aux caches non-bloquants qui autorisent les défauts de cache secondaire. La solution la plus simple consiste à utiliser ===Les MSHR adressés implicitement=== Avec les '''MSHR adressés implicitement''', il est possible de fusionner plusieurs accès mémoire à une même ligne de cache, mais sous une condition très importante : ces accès lisent/écrivent des mots mémoire différents. Par exemple, imaginons qu'une ligne de cache contienne 8 mots mémoire de 64 bits. Si un premier accès mémoire lit le mot mémoire numéro 7 (dernier mot mémoire de la ligne), et le second accès le mot mémoire numéro 3, alors la fusion est possible. Mais si deux accès mémoire veulent lire/écrire le mot mémoire numéro 7, alors la fusion n'est pas possible et le processeur se bloque, un ''pipeline stall'' survient. Un MSHR adressé implicitement est un MSHR simple, qui contient naturellement un numéro de ligne de cache (tag) et un bit de validité, sauf que le champ de lecture/écriture est dupliqué. Les différents champs lecture/écriture d'un MSHR sont regroupés dans une mémoire RAM/cache qui contient autant d'entrées qu'il y a de mot mémoire dans une ligne de cache. Chaque entrée de la table est associée à un mot mémoire de la ligne de cache et stocke des informations sur celui-ci. Une entrée mémorise, au minimum : * Un champ de lecture/écriture qui contient soit la destination de la lecture, soit la donnée à écrire. * Un bit R/W permettant d'interpréter correctement le champ de lecture/écriture. * Un bit de validité pour chaque entrée de la table, qui dit si un défaut de cache antérieur accède déjà à ce mot mémoire. La fusion de deux défauts de cache est ainsi assez simple. Un défaut de cache primaire/secondaire configure l'entrée associée au mot mémoire lu/écrit. Si un défaut secondaire ultérieur lit/écrit un mot mémoire différent (dont le bit de validité de l'entrée dans la table est à zéro), pas de conflit : il configure une autre entrée, vide. Mais s'il lit/écrit un mot mémoire pour lequel l'entrée de la table est occupée, il y a conflit, le processeur est bloqué par un ''pipeline stall'', une bulle de pipeline. Avec cette organisation, le nombre de MSHR indique combien de lignes de cache peuvent être lues en même temps depuis la mémoire. Quant au nombre d'entrées par MSHR, il détermine combien d'accès mémoires qui ne se recouvrent pas peuvent avoir lieu en même temps. ===Les MSHR adressés explicitement=== Les '''MSHR adressés explicitement''' n'ont pas les contraintes des MSHR adressés implicitement. Avec eux, il est possible d'avoir plusieurs défauts de cache pointant vers la même ligne de cache, mais aussi vers le même mot mémoire. De tels défauts de cache apparaissent sur les processeurs à exécution dans le désordre, mais sont très rares, voire inexistants, sur les processeurs ''in-order''. L'idée est encore que chaque MSHR est associée à une table mémoire qui mémorise des entrées. Cette table n'est autre qu'une mémoire FIFO. Sauf que cette fois-ci, une entrée n'est pas associée à un mot mémoire. Une entrée est associée à un défaut de cache. Une entrée mémorise là encore la destination de la lecture ou la donnée de l'écriture suivi du bit R/W, ainsi que d'un bit de validité par entrée, mais aussi : la position du mot mémoire lu dans la ligne de cache. C'est cette dernière information qui n'était pas présente dans les MSHR adressés implicitement. De plus, le nombre d'entrée par MSHR n'est pas égal au nombre de mots mémoires dans une ligne de cache, mais peut être arbitrairement grand. L'avantage d'un tel cache est qu'il est capable de traité les défauts secondaires concernant un même mot mémoire, et ce, car les accès à la ligne de cache concernée se produisent dans l'ordre de génération des défauts (la table mémoire est une FIFO). ===Les MSHRs inversés=== Généralement, plus on veut supporter de défauts de cache, plus le nombre de MSHR et d'entrées augmente. Mais au-delà d'un certain nombre d'entrées et de MSHR, les MSHR adressés implicitement et explicitement ont tendance à bouffer un peu trop de circuits. Utiliser une organisation un peu moins gourmande en circuits est donc une nécessité. Cette organisation plus économe se base sur des MSHR inversés. Les MSHR inversés ne contiennent qu'une seule entrée, en quelque sorte : au lieu d’utiliser n MSHR de m entrées chacun, on va utiliser n × m MSHR inversés. La différence, c'est que plusieurs MSHR peuvent contenir un tag identique, contrairement aux MSHR adressés implicitement et explicitement. Lorsqu'un défaut de cache a lieu, chaque MSHR est vérifié. Si jamais aucun MSHR ne contient de tag identique à celui utilisé par le défaut, un MSHR vide est choisi pour stocker ce défaut, et une requête de lecture en mémoire est lancée. Dans le cas contraire, un MSHR est réservé au défaut de cache, mais la requête n'est pas lancée. Quand la donnée est disponible, les MSHR correspondant à la ligne qui vient d'être chargée vont être utilisés un par un pour résoudre les défauts de cache en attente. ===Les MSHR intégrés au cache=== Certains chercheurs ont remarqué que pendant qu'une ligne de cache est en train de subir un défaut de cache, celle-ci reste inutilisée, et son contenu est destiné à être perdu une fois le défaut de cache résolu. Ils se sont dit que, plutôt que d'utiliser des MSHR séparés, il vaudrait mieux utiliser la ligne de cache pour stocker les informations sur les défauts de cache en attente dans cette ligne de cache. Pour éviter tout problème, il faut rajouter un bit dans les bits de contrôle de la ligne de cache, qui sert à indiquer que la ligne de cache est occupée : un défaut de cache a eu lieu dans cette ligne, et elle stocke donc des informations pour résoudre les défauts de cache. ==L'entrelacement mémoire== Nous venons de voir comment une mémoire cache peut gérer plusieurs accès mémoire simultanés. Il se trouve que ce genre d'optimisation dépasse largement le cadre du cache. Les mémoires RAM ne sont pas en reste, elles aussi ! Il est possible de pipeliner l'accès à une mémoire RAM, en utilisant des techniques dites d''''entrelacement'''. Précisons cependant que cette optimisation n'est pas utilisée sur les mémoires SDRAM ou DDR modernes, du moins pas sans modifications majeures. Mais divers ordinateurs assez anciens, dont des superordinateurs, ont utilisé cette technique. La technique demande d'utiliser plusieurs mémoires séparées. Sur les anciens ordinateurs, les mémoires en question sont des chips mémoire, à savoir des circuits intégrés de DRAM. Pour faire plus général, nous allons utiliser le terme de '''banques''', ou encore de bancs mémoire. La différence est qu'il existe des mémoires multi-banque, qui regroupent plusieurs banques indépendantes dans un seul boitier, dans un seul chip mémoire. En clair, de telles mémoires regroupent plusieurs sous-mémoires dans un seul circuit intégré. Il est aussi possible d'utiliser plusieurs chips mémoire séparés, chacun ayant une banque. Reste à combiner ces banques pour former un pseudo-pipeline. ===Utiliser plusieurs banques sans entrelacement=== Sans optimisation particulière, les adresses sont réparties dans les banques comme indiqué ci-dessous. Il s'agit de ce qui s'appelle un arrangement horizontal, et nous avions vu celui-ci dans le chapiutre sur les mémoires SDRAM. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Avec cet arrangement, les bits de poids fort de l'adresse sont utilisées pour sélectionner la banque adéquate, et le reste de l'adresse est envoyé sur le bus d'adresse. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Adresse de banque !! Adresse dans la banque |- | Quelques bits de poids fort || Reste de l'adresse |} L'entrelacement change cette répartition, afin d'accélérer les accès mémoire. L'entrelacement classique vise à améliorer les accès à des adresses mémoires consécutives, mais des formes plus évoluées d'entrelacement visent à optimiser des accès mémoire arbitraires. Voyons-les dans l'ordre, du plus simple au plus complexe. ===L'entrelacement classique=== Avec l''''entrelacement classique''', des adresses consécutives sont réparties dans des banques consécutives. Précisons que cette attribution des adresses n'implique absolument pas la mémoire virtuelle ou n'importe quel mécanisme dans le processeur. La répartition décide que telle adresse mémoire va dans telle banque, à telle adresse dans la banque. Elle est donc le fait du contrôleur mémoire, donc en dehors du processeur (les contrôleurs mémoires n’étaient pas encore intégrés dans le processeur à l'époque). [[File:Répartition des adresses dans une mémoire interleaved.png|centre|vignette|upright=1.5|Répartition des adresses dans une mémoire interleaved.]] L'entrelacement simple permet d'accélérer les accès mémoire qui se font à des adresses consécutives. Avec l'entrelacement, chaque accès mémoire tombe dans une banque mémoire différente, ce qui fait qu'on peut démarrer un nouvel accès mémoire à chaque cycle d'horloge. Pendant qu'une banque est occupée par un accès mémoire, on démarre le suivant dans une autre banque, et ainsi de suite. Le tout se termine soit quand on a épuisé toutes les banques libres, soit quand l'accès en rafales se termine. Pas besoin d'attendre que la mémoire ait fini sa lecture/écriture avant de démarrer la lecture/écriture suivante. [[File:Pipemining mémoire.png|centre|vignette|upright=2.5|Pipelining mémoire]] L'animation suivante illustre bien le processus, avec quatres banques. [[File:Interleaving.gif|centre|vignette|upright=2.5|Pipelining mémoire]] L'entrelacement simple utilise les bits de poids faible pour sélectionner la banque, et les bits de poids fort pour la case mémoire. [[File:Adresse mémoire d'une mémoire entrelacée.png|centre|vignette|upright=2|Adresse mémoire d'une mémoire entrelacée]] Précisons que le temps d'accès mémoire ne change pas beaucoup avec l'entrelacement. Par contre, on peut faire plus d'accès mémoire simultanés. On peut démarrer un accès mémoire par cycle d'horloge, mais l'accès en lui-même prend plusieurs cycles. Les mémoires à entrelacement ont donc un débit supérieur aux mémoires qui ne l'utilisent pas. L'entrelacement classique pourrait être utilisé pour accélérer les transferts entre mémoire RAM et cache, qui se font en blocs d'adresses consécutives. Mais de nos jours, il n'est pas utilisé dans cette optique. Il faut dire qu'il est redondant avec le mode rafale des mémoires DRAM. Les deux font globalement la même chose et on voit mal comment utiliser les deux en même temps. Le mode rafale étant plus simple à implémenter, et plus léger niveau utilisation du bus de commande mémoire, il est préféré à l'entrelacement. Par contre, l'entrelacement a eu son heure de gloire sur d'anciens processeurs qui n'avaient pas de mémoire cache, alors qu'ils disposaient d'un pipeline. L'exemple typique est celui des nombreux ''processeurs vectoriels'', que nous n'avons pas encore abordé à ce stade du cours. De tels processeurs avaient un pipeline, une unité de calcul fortement pipelinée, et parfois même de l'exécution dans le désordre et du renommage de registres ! ===L'entrelacement par décalage=== L'entrelacement simple est très utile pour les accès en rafle ou équivalents. Par contre, il existe de rares situations où il n'est pas optimal. Une de ces situations est celle des accès en enjambée (en ''stride''), où l'on accède en série à des adresses sont séparées par N mots mémoires. [[File:Accès par enjambées.png|centre|vignette|upright=2|Accès par enjambées.]] De tels accès surviennent quand un logiciel accède à des structures de données spécifiques, à savoir des tableaux de structures, des matrices, ou d'autres structures de données dans le genre. Un cas classique est celui du parcours d'une matrice colonne par colonne. Les matrices de nombres sont mémorisées ligne par ligne, ce qui fait que parcourir une colonne demande de passer d'une ligne à la suivante et de faire des grands sauts en mémoire RAM, mais tous espacés par la même distance. Avec l'entrelacement simple, les accès en enjambée sont moins performants que les accès à des adresses consécutives, mais cela ne fonctionne pas trop mal. Le pire cas est celui où l'on a N banques et où les données sont justement placées toutes les N adresses. Des accès consécutifs vont tous tomber dans la même banque, on ne peut plus accéder à des banques différentes en parallèle. Mais en dehors de ce cas, on voit une amélioration par rapport à la situation sans entrelacement. Pour obtenir des performances maximales pour les accès en enjambées, il faut répartir les mots mémoires dans la mémoire autrement. L'organisation idéale est la suivante. : Dans les explications qui vont suivre, la variable N représente le nombre de banques, qui sont numérotées de 0 à N-1. On commence par organiser les N premières adresses comme une mémoire entrelacée simple : l'adresse 0 correspond à la banque 0, l'adresse 1 à la banque 1, etc. Pour le bloc suivant, on décale tout d'une adresse, à savoir qu'on commence à remplir les banques à partir de la seconde, non de la première. Une fois la fin du bloc atteinte, on finit de remplir le bloc en repartant du début du bloc. Le troisième bloc d'adresse subit le même traitement, sauf qu'on commence à remplir à partir de la seconde banque. Et on poursuit l’assignation des adresses en décalant d'un cran en plus à chaque bloc. Ainsi, chaque bloc verra ses adresses décalées d'un cran en plus comparé au bloc précédent. Si jamais le décalage dépasse la fin d'un bloc, alors on reprend au début. [[File:Mémoire interleaved par décalage.png|centre|vignette|upright=1.5|Mémoire entrelacée par décalage.]] En faisant cela, on remarque que les banques situées à N adresses d'intervalle sont différentes. Dans l'exemple du dessus, nous avons ajouté un décalage de 1 à chaque nouveau bloc à remplir. Mais on aurait tout aussi bien pu prendre un décalage de 2, 3, etc. Dans tous les cas, on obtient un entrelacement par décalage. Ce décalage est appelé le pas d'entrelacement, noté P. Le calcul de l'adresse à envoyer à la banque, ainsi que la banque à sélectionner se fait en utilisant les formules suivantes : * adresse à envoyer à la banque = adresse totale / N ; * numéro de la banque = (adresse + décalage) modulo N ; * décalage = (adresse totale * P) mod N. Avec cet entrelacement par décalage, on peut prouver que la bande passante maximale est atteinte si le nombre de banques est un nombre premier. Seulement, utiliser un nombre de banques premier peut créer des trous dans la mémoire, des mots mémoires inadressables. Malgré ce défaut, la technique a été utilisée sur quelques ordinateurs, avec l'exemple notable du superordinateur ''Burroughs Scientific Processor''. Pour éviter cela, il y a plusieurs solutions. Par exemple, on peut faire en sorte que N et la taille d'une banque soient premiers entre eux : ils ne doivent pas avoir de diviseur commun. Mais en pratique, elles n'ont pas vraiment été implémentées dans une vraie machine, et sont restées à l'état de recherche, aussi je les passe sous silence. ===L'entrelacement pseudo-aléatoire=== Une dernière méthode de répartition consiste à répartir les adresses dans les banques de manière "pseudo-aléatoire". L'idée exacte est de faire passer l'adresse dans une '''fonction de hachage''', plus ou moins complexe. Pour rappel, une fonction de hachage prend une entrée de grande taille et fournit en sortie un résultat de petite taille. Idéalement, elles doivent donner des résultats aussi différents que possible pour des entrées similaires, histoire de simuler une sorte de pseudo-aléatoire. Ici, une adresse est transformée en un numéro de banque. Une fonction de hachage simple ne fait que permuter des bits de l'adresse pour obtenir son résultat. Elle ne fait qu'échanger des bits de place avant de couper l'adresse en deux morceaux : un pour la sélection de la banque, et un autre pour la sélection de l'adresse dans la banque. Cette permutation est fixe, et ne change pas suivant l'adresse. Des fonctions de hachage plus complexes font des XOR bit à bit entre certains bits de l'adresse. ==Les contrôleurs SDRAM/DDR avec "exécution dans le désordre"== Après avoir vu le cas des mémoires RAM, nous allons nous concentrer sur le cas particulier des mémoires SDRAM. Les mémoires SDRAM et DDR modernes sont capables de gérer plusieurs accès simultanés à la mémoire RAM, et leurs contrôleurs mémoire en font tout autant. C'est une différence majeure avec les mémoires asynchrones FPM/EDO, qui n'acceptaient qu'un seul accès mémoire à la fois. Leur contrôleur mémoire n'acceptait qu'un seul accès mémoire à la fois, c'était un contrôleur mémoire bloquant. Les contrôleurs mémoires des SDRAM sont eux non-bloquants et peuvent encaisser une dizaine d'accès mémoire à la fois. Peu de choses sont connues sur les contrôleurs de SDRAM/DDR modernes, les fabricants ne donnant que peu de détails dessus. Les rares simulateurs qui tentent de décrire leur fonctionnement, comme DRAM SIM I et II, sont particulièrement simples et ne vont pas dans le détail. Néanmoins, le peu qu'on sait est tout de même instructif. ===Rappel sur les SDRAM : tampon de ligne et commandes=== Les mémoires SDRAM sont des mémoires à tampon de ligne. Elles font un accès mémoire en deux étapes. La première étape recopie une ligne de N * 64 bits dans un tampon interne à la SDRAM. La seconde étape sélectionne une colonne, à savoir une donnée de 64 bits, qui est soit envoyée sur le bus de données pour une lecture, soit modifiée par une écriture. [[File:Mémoire à tampon de ligne.png|centre|vignette|upright=2|Mémoire à tampon de ligne]] Les accès mémoire sont traduits par un séquenceur mémoire en une série de commandes mémoires, qui sont séparées par des délais mémoire de quelques cycles d'horloge. Les délais sont très précis, et sont à respecter à la lettre. Une lecture ou une écriture se fait en maximum trois commandes : une commande PRECHARGE qui ferme la ligne précédemment utilisée, une commande ACT qui précise l'adresse de la ligne, et une commande READ ou WRITE qui précise l'adresse de la colonne et éventuellement la donnée à écrire. : Pour être plus précis, la commande PRECHARGE précharge les lignes de bits du plan mémoire à une tension particulière, ce qui les vide de leur contenu. Mais c'est un détail sans importance pour ce qui va suivre. Les SDRAM permettent de se passer de la première étape si des accès consécutifs se font dans la même ligne. Une fois activée, la ligne reste ouverte et on peut accéder plusieurs fois de suite dedans. On a alors juste à préciser l'adresse de colonne dedans. Si un accès mémoire accède à une ligne déjà activée, on dit que c'est un '''succès de page'''. Si ce n'est pas le cas, on doit fermer la ligne courante, rouvrir la ligne vboulue, et préciser la colonne. C'est alors un '''défaut de page'''. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | bgcolor="#FFA0FF" | PRECHARGE || bgcolor="#FFA0FF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || bgcolor="#A0FFFF" | READ (2) || bgcolor="#A0FFFF" | READ (3) || || || bgcolor="#A0FFFF" | READ (4) || bgcolor="#A0FFFF" | READ (5) || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | Donnée READ 1 || ||bgcolor="#A0FFFF" | Donnée READ 2 | bgcolor="#A0FFFF" | Donnée READ 3 || || || bgcolor="#A0FFFF" | Donnée READ 4 || bgcolor="#A0FFFF" | Donnée READ 5 |} Les SDRAM sont parfois capables de démarrer une commande avant que la précédente soit terminée. Mais le respect des délais mémoire est très limitant, ce qui fait qu'on ne peut pas parler de réel pipeline, comme c'est le cas sur les processeurs. Parlons plutot de '''commandes anticipées'''. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | || bgcolor="#A0FFFF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || || || bgcolor="#FFA0FF" | READ (2) || || || || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 | bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 | |} Un dernier point est que les chips mémoires de SDRAM sont composés de plusieurs banques, chacune étant une sorte de mini-mémoire miniature. Chaque banque a son propre décodeur, son propre tampon de ligne, ses propres multiplexeurs de colonne, sa logique de rafraichissement mémoire, etc. C'est comme si une SDRAM regroupait plusieurs mémoires séparées dans un même circuit intégré. [[File:Arrangement vertical.jpg|centre|vignette|upright=2.5|Mémoire multi-banques.]] ===La mise en attente des accès mémoire=== Un contrôleur de SDRAM peut accepter plusieurs accès mémoire en même temps. Mais cela ne signifie pas que le contrôleur sera prêt à les traiter immédiatement. Pour éviter tout problème de disponibilité, le contrôleur met en attente les accès mémoire que le processeur lui envoie, pour les exécuter dès que possible. Les accès mémoire sont mis en attente dans une mémoire FIFO, histoire de les exécuter dans leur ordre d'arrivée, notamment pour renvoyer les lectures dans l'ordre demandé. Il y a aussi une mémoire FIFO pour les données à écrire et une FIFO pour les données lues. Cette dernière sert au cas où le cache ne soit pas disponible quand on lui envoie la donnée lue. [[File:Module d'interface avec la mémoire.png|centre|vignette|upright=2.5|Contrôleur mémoire avec mise en attente des requêtes processeur et autres optimisations.]] Des optimisations sont possibles dès la mémoire FIFO. Par exemple, on peut regrouper plusieurs accès à des données consécutives en un seul accès en rafale. Le contrôleur mémoire analyse les requêtes mises en attente et détecte si certaines se font à des adresses consécutives. Si c'est le cas, il fusionne ces requêtes en une lecture/écriture en rafale. Une telle optimisation est appelée la '''combinaison de lecture''' pour les lectures, et la '''combinaison d'écriture''' pour les écritures. L'optimisation précédente ne paraissent pas payer de mine, mais elles deviennent plus compréhensible quand on sait que certains contrôleurs mémoire peuvent mettre en attente beaucoup de données. Par exemple, l'''Intel E8870 - Scalable Memory Controller'' peut mettre en attente 64 lignes de cache dans la mémoire FIFO, soit 8 kibioctets de RAM ! Deux pages mémoire si la mémoire virtuelle utilise des pages de 4 kibioctets ! ===Les lectures anticipées : une forme d'OOO mémoire=== La mise en attente des accès mémoire est une optimisation intéressante, mais pas mirifique. Elle permet surtout de ne pas bloquer le processeur si le contrôleur mémoire a du travail sur la planche. Cependant, elle peut être optimisée quand on se rend compte d'une chose : le processeur ne voit que les lectures, pas les écritures. Pour les écritures, il les envoie au contrôleur mémoire et ce dernier fait le travail demandé, le processeur n'est pas prévenu quand une écriture se termine. Mais pour les lectures, c'est différent, car il reçoit la donnée lue. En conséquence, il est possible d'optimiser les écritures sans que le processeur ne voit quoique ce soit. Une optimisation possible est alors de changer l'ordre des accès mémoire, histoire de retarder les écritures ou au contraire de les faire en avance. La raison est que les mémoires SDRAM n'aiment pas quand on alterne lectures et écritures. Les délais mémoire, les fameux ''timings'' mémoire, sont clairs : faire une suite de lecture ou une suite d'écriture est plus performant que d'alterner entre lectures et écritures. L'idée est alors de changer l'ordre des écritures de manière à les faire en bloc, idem pour les lectures. L’optimisation est possible, mais à condition que le processeur n'y verra que du feu. Or, vous l'avez deviné, il y a des cas où ces réorganisations ne sont pas possibles, et vous avez sans doute trouvé que la situation était familière. L'optimisation en question est une forme d'exécution dans le désordre des accès mémoire, réalisée par le contrôleur mémoire lui-même ! Et qui dit exécution dans le désordre dit : problèmes liés aux dépendances de données. Changer l'ordre des écritures n'est pas toujours possible, notamment en raison des dépendances RAW, WAR et WAW. Pour que le processeur n'y voit que du feu, il faut respecter plusieurs critères. Premièrement, les lectures doivent être renvoyées au processeur dans l'ordre. Et quand je dis dans l'ordre, cela veut dire : dans l'ordre d'envoi des lectures au contrôleur mémoire. Les écritures aussi doivent se faire dans l'ordre, histoire qu'une série d'écriture donne le bon résultat final. Le troisième critère est qu'une lecture doit donner le résultat de la dernière écriture ''à la même adresse''. Pour le dire autrement, il faut juste détecter les dépendances RAW. Concrètement, si on a une série de lectures et d'écritures alternées, on peut regrouper les lectures et les écritures dans deux paquets séparés, à condition qu'aucune lecture ne lise une adresse écrite par une écriture. Mais si une lecture est dépendante d'une écriture, alors la lecture doit attendre que l'écriture se termine, idem pour les lectures suivantes. Une implémentation basique remplace la mémoire FIFO vue au-dessus, par deux mémoires FIFOs : une pour les lectures, une pour les écritures. Lorsqu'un accès mémoire arrive au séquenceur, il regarde si c'est une lecture ou une écriture et place l'accès mémoire dans la file adéquate. Le fait que ce soit des FIFOs garantit que les lectures se font dans l'ordre, idem pour les écritures. En sortie des deux FIFOs, le séquenceur mémoire détecte les dépendances RAW. Précisément, quand une lecture sort de la mémoire FIFO, il consulte la file d'attente des écriture pour voir si la lecture est dépendante d'une écriture en attente. La lecture attend si c'est le cas, et les écritures sont exécutées à la place. Séparer lectures et écriture est une source d'optimisation. Il est par exemple possible d'utiliser le réacheminement lecture sur écriture (''Store to load forwarding''). Il s'agit d'un équivalent de l’optimisation du même nom, utilisée dans le cadre de la désambiguïsation mémoire. Imaginez qu'une lecture accède à une donnée pas encore écrite, en attente dans le contrôleur mémoire. C'est une dépendance RAW assez claire. Dans ce cas, le contrôleur mémoire peut renvoyer la donnée directement depuis ses mémoires FIFOs, sans faire d'accès mémoire en lecture. Le contrôleur mémoire a tendance à privilégier les lectures, car celles-ci sont cruciales pour l'exécution dans le désordre. Plus elles se font tôt, plus vite le processeur recevra les données et pourra démarrer les instructions dépendantes de cette donnée. Le séquenceur a donc tendance à piocher en priorité dans la file de lecture, plutôt que dans la file d'écriture. A vrai dire, dans le cas idéal, les écritures ne sont faites que quand la file d'écriture est pleine ou quasi-pleine... Prenons l'exemple des coprocesseurs IO 81341 et 81342, qui étaient en réalité des ''chipsets'' intégrant un contrôleur SDRAM. Le ''chipset'' avait 5 ports : un pour les processeurs, un pour le pont sud (''southbridge'') et trois pour des canaux DMA. Pour le port processeur, il n'y avait pas de réordonnancement et ce port utilisait une seule mémoire FIFO. Les autres ports utilisaient l'optimisation qu'on vient de voir, et avaient vraisemblablement des files séparées pour les lectures et écritures. Le séquenceur mémoire vérifiait les dépendances mémoire de type RAW et autres. [[File:Double file d'attente pour les lectures et écritures.png|centre|vignette|upright=2.5|Double file d'attente pour les lectures et écritures]] ===Le ré-ordonnancement des commandes mémoires=== L'optimisation précédente est peu poussée, comparé aux formes d'exécution dans le désordre que les processeurs utilisent. Et on peut se demander si une exécution dans le désordre plus poussée est possible. La réponse est oui : un contrôleur mémoire peut faire des réorganisations bien plus poussées. Par contre, il faut faire une précision très importante : le contrôleur mémoire ne remet pas les accès mémoire dans l'ordre avant de les envoyer au processeur. C'est le processeur qui remet en ordre les accès mémoire, dans sa ''load queue''. En clair : le processeur voit les accès mémoire dans le désordre, c'est lui qui les remet en ordre. Pour cela, les requêtes mémoire sont "numérotées", à savoir qu'on leur attribue un identifiant binaire. Le contrôleur mémoire exécute les lectures/écritures dans le désordre, mais garde la trace de leur identifiant. Il sait dans quel ordre il exécute les accès mémoire et connait leur latence. Ce qui fait qu'il sait que la donnée lue à tel instant est associée à la requête mémoire numéro X. Pour les lectures, il renvoie la donnée lue avec l'identifiant. Le processeur sait alors à quelle lecture la donnée lue correspond et il se débrouille en interne pour gérer la situation. Maintenant que ces précisions sont faites, posons cette question : dans quelles situations est-il pertinent de faire des accès mémoire dans le désordre ? La réponse est : quand plusieurs accès à une même ligne ne sont pas consécutifs, qu'il y a des accès entre les deux. Après ré-ordonnancement, ces accès mémoire à une même ligne sont exécutés l'un à la suite de l'autre, ce qui est beaucoup plus rapide. Pour rendre le tout plus concret, voici un exemple. Imaginez que l'on sait les 3 accès mémoire suivants : * Une lecture ligne A ; * PRECHARGE + ACT ; * Une écriture ligne B ; * PRECHARGE + ACT ; * Une lecture ligne A. Sans réordonnancent, on doit émettre deux commandes PRECHARGE quand on passe d'une ligne à l'autre, et il faut ajouter les commandes ACT avec. pour éviter cela, le contrôleur mémoire peut retarder l'écriture, ou au contraire avancer la seconde lecture. le résultat est alors le suivant : * Lecture ligne A ; * Lecture ligne A * PRECHARGE + ACT ; * Une écriture ligne B. On a donc deux accès consécutifs à la même ligne, suivi par une écriture dans une autre ligne. La technique marche parce que l'on a un mix adéquat de lectures et d'écritures. Et encore une fois, il faut éviter d'intervertir lectures et écritures à une même adresse, sous peine de problèmes. Encore une fois, les dépendances RAW posent problème ! L'implémentation est assez simple : la ou les mémoires FIFOs précédentes sont remplacées par des mémoires similaires aux fenêtres d'instruction. Le contrôleur mémoire vérifie à chaque cycle les accès en attente, et vérifie à quelle ligne ils accèdent. Il priorise alors les accès qui tombent dans la ligne ouverte : ceux-là sont exécutés avant les autres. S'il n'y en a pas, il prend l'accès mémoire le plus ancien (ordre FIFO). Il teste aussi les dépendances mémoires RAW avant d'envoyer des commandes à la mémoire DDR/SDRAM. une telle solution est appelée l''''algorithme ''FR-FCFS''''' (''First Ready-First Come First Serve''). ===Les optimisations liées à la présence de plusieurs banques=== Les optimisations précédentes sont décuplées par la présence de plusieurs banques dans la mémoire SDRAM. Il est en effet possible de faire plusieurs accès mémoire en même temps, dans des banques différentes. Il est possible de lancer un accès mémoire dans deux banques en même temps, par exemple. La seule contrainte est que la SDRAM est limitée à 4 banques actives en même temps. {|class="wikitable" |+ Pipelining basique sur les SDRAM |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 |- ! Banque Numéro 1 | || || || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || || |- ! Banque Numéro 2 | || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || |- ! Banque Numéro 3 | || || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || |} Les optimisations précédentes peuvent s'appliquer par banque. Il est par exemple possible d'utiliser une mémoire FIFO par banque. Ainsi, les accès dans une même banque se feront en série, dans l'ordre d'arrivée, mais des accès à des banques différentes se feront dans le désordre. Cela ne pose pas de problème car les accès se font à des adresses différentes, ce qui fait qu'il n'y a pas de conflits majeurs. Il y a cependant un risque que les lectures se fassent dans le désordre si on n'y prend pas garde. [[File:Gestion parallèle des banques.png|centre|vignette|upright=2.5|Gestion parallèle des banques.]] Il est aussi possible d'utiliser une file séparée pour les lectures et les écriture, pour chaque banque. Les performances sont alors améliorées comparé à deux files globales pour toute la SDRAM. En idem avec la réorganisation des accès mémoire, pour regrouper les accès à une même ligne. Un problème avec ces optimisations est qu'il est rare que des accès mémoire se fassent dans des banques séparées. Les accès mémoire tendent à se faire avec une bonne localité spatiale, ce qui fait qu'ils tombent dans une même banque. Du moins, pas sans optimisations. Car les controleurs SDRAM/DDR modernes incorporent des optimisations pour corriger ce problème. Les optimisations en question sont une forme d'entrelacement adaptée aux mémoires SDRAM. L'entrelacement naïf ne marche pas à cause de la présence du tampon de ligne. Des adresses consécutives atterrissent déjà dans la même ligne, ce qui fait qu'on n'a pas besoin d'entrelacement au niveau d'une ligne. Cependant, il peut y avoir un entrelacemententre banques SDRAM. Voyons ce que ca veut dire. L'entrelacement est géré juste avant le séquenceur mémoire, dans un '''circuit d'entrelacement'''. Ce dernier intervertit certains bits de l'adresse lors des accès mémoires. [[File:Controleur mémoire d'une SDRAM avec entrelacement.png|centre|vignette|upright=2.5|Contrôleur mémoire d'une SDRAM avec entrelacement.]] Pour gérer l'entrelacement, le contrôleur de SDRAM prend en entrée l'adresse envoyée par le processeur, et la découpe en plusieurs champs : un pour sélectionner la banque adéquate, un autre pour la ligne, un autre pour la colonne, un autre pour la rangée. Une solution simple désactive l'entrelacement. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de banque || Adresse de ligne || Adresse de colonne |} Si cette solution fonctionne bien si les accès mémoire sont assez aléatoires, en pratique, mieux vaut utiliser l'entrelacement. Une première méthode d'entrelacement est l''''entrelacement de banques'''. Elle répartit deux lignes consécutives dans deux banques différentes. Pour comprendre l'idée, prenons un exemple. Imaginons une mémoire avec deux banques et 4 lignes. Imaginons qu'on parcoure/balaye la mémoire RAM en partant des adresses basses. Sans entrelacement de ligne, les accès se feront comme suit, de gauche à droite : {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Banque numéro 1 !! colspan="4" | Banque numéro 2 |- | Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 || Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 |} Avec l'entrelacement de banques, les accès se feront comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 |- | Ligne 1 || Ligne 1 || Ligne 2 || Ligne 2 || Ligne 3 || Ligne 3 || Ligne 4 || Ligne 4 |} Avec N banques, la première ligne ira dans la première banque, la seconde ligne dans la seconde, et ainsi de suite jusqu'à la énième banque qui va dans la banque numéro N. Et la ligne N+1 repart dans la première banque. L'avantage est que passer d'une ligne à la suivante est assez rapide. Au lieu de devoir fermer la première ligne et ouvrir la suivante on ouvrira directement la ligne suivante dans une banque différente, ce qui sera plus rapide. La commande PRECHARGE pourra être envoyée à la seconde banque en avance. Pour cela, l'adresse est découpée comme suit. {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de ligne || Adresse de banque || Adresse de colonne (précise à l'octet) |} Il est aussi possible de faire la même chose, mais avec les rangées. Pour simplifier fortement, une rangée est simplement un chip mémoire. En réalité, les rangées ne sont pas des chip mémoire, mais un ensemble de chips mémoire regroupés ensemble histoire d'atteindre les 64 bits du bus de données. Par exemple, une rangée peut combiner 8 chips mémoire avec un bus de données de 8 bits chacun pour obtenir les 64 bits du bus de données d'une SDRAM. Mais nous passons ce détail sous silence dans les explications qui vont suivre, par souci de simplification. Pour faire comprendre l'entrelacement de rangée, le mieux est d'utiliser un exemple, le même que précédemment. Sans entrelacement de rangée, on change de chip mémoire une fois qu'on a balayé toutes ses adresses. {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Rangée numéro 1 !! colspan="4" | Rangée numéro 2 |- | Banque 1 || Banque 2 || Banque 3 || Banque 4 || Banque 1 || Banque 2 || Banque 3 || Banque 4 |} Avec l'entrelacement de ligne, on change de rangée dès qu'on change de banque/ligne. {|class="wikitable" |+ Adresse mémoire |- ! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 !! Rangée numéro 1 !! Rangée numéro 2 |- | Banque 1 || Banque 1 || Banque 2 || Banque 2 || Banque 3 || Banque 3 || Banque 4 || Banque 4 |} En clair, quand on a épuisé toutes les banques dans une même rangée, on passe à la rangée suivante au lieu de rester dans la même rangée. Notons que dans l'exemple précédent, on a combiné l'entrelacement de rangée et de banque, mais on aurait pu utiliser l'entrelacement de rangée seul. Mais ce n'est pas le cas le plus courant en pratique. Toujours est-il qu'en combinant entrelacement de rangée et de ligne, le découpage de l'adresse est le suivant : {|class="wikitable" |+ Adresse mémoire |- | Adresse de ligne || Numéro de rangée || Adresse de banque || Adresse de colonne (précise à l'octet) |} D'autres méthodes d'entrelacement sont possibles, mais elles sont plus complexes. Chaque contrôleur mémoire fait un peu à sa sauce. Ils permettent en général de choisir entre plusieurs entrelacements. Au minimum, ils permettent de désactiver l'entrelacement, d'activer un entrelacement de ligne, et d'autres entrelacements plus complexes, dont certains sont propriétaires et tenus secrets par le fabricant. : L'usage du ''dual channel'' complique encore l'entrelacement. Mais je vais passer cela sous silence. Les contrôleurs de SDRAM précédents sont assez basiques et ne représentent pas les contrôleurs les plus évolués. Ils étaient utilisés sur les anciens PC, à une époque où ils étaient encore sur la carte mère du processeur. Mais de nos jours, le contrôleur mémoire est intégré au processeur. Et il incorpore de nombreuses optimisations afin de gagner en performances. Malheureusement, ces optimisations sont elles-mêmes dépendantes des optimisations intégrées dans le processeur, ce qui fait qu'on ne peut pas en parler ici. Elles demandent que le contrôleur mémoire reçoive plusieurs accès mémoire simultanés, ce qui n'a aucun sens à ce stade du cours. Aussi, nous allons les passer sous silence pour le moment. Nous les verrons dans le chapitre sur le parallélisme mémoire. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=La désambiguïsation mémoire | prevText=La désambiguïsation mémoire | next=Les processeurs superscalaires | nextText=Les processeurs superscalaires }} </noinclude> ow6qzqvr8d4fmn06hjdd06h5d8fr932 764816 764815 2026-04-24T10:48:37Z Mewtow 31375 /* Les optimisations liées à la présence de plusieurs banques */ 764816 wikitext text/x-wiki Dans ce chapitre, nous allons voir les techniques qui permettent de gérer plusieurs accès mémoire simultanés directement au niveau du cache ou de la mémoire RAM. Par plusieurs accès mémoire simultanés, vous pensez sans doute à l'usage de cache multiports, voire de mémoires RAM multiport. C'est en effet une solution, mais ce n'est clairement pas la seule. Par exemple, il est possible de pipeliner l'accès au cache, voire à la mémoire RAM. Il existe de nombreuses techniques de '''parallélisme mémoire''', et nous allons les voir dans ce chapitre. Pour donner un exemple, il est possible de pipeliner les accès mémoire. Il est possible de pipeliner l'accès au cache et/ou l'accès à la mémoire, et nous verrons comment faire dans ce chapitre. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, qu'ils aient ou non un pipeline dynamique. Par contre, pipeliner la mémoire est une technique ancienne, aujourd'hui peu utilisée. Elle était utilisée sur des très vieux ordinateurs, qui avaient un pipeline mais pas de mémoire cache. Mais de nos jours, les mémoires SDRAM et DDR ne sont pas adaptées pour ça, il est très difficile de les pipeliner correctement. Le parallélisme mémoire est utile aussi bien sur des processeurs à émission dans l'ordre que dans le désordre. Par exemple, un processeur ''in-order'' avec des lectures non-bloquantes peut e, profiter. Il peut techniquement lancer une seconde lecture, après avoir rencontré une première lecture non-bloquante. En général, le nombre de lectures consécutives est cependant limité à moins d'une dizaine. À l'opposé, les processeurs sans lectures non-bloquantes profitent pas du parallélisme mémoire. À la rigueur, ils peuvent en profiter s'ils intègrent des techniques de préchargement. Pendant que le processeur exécute une lecture/écriture, il peut précharger une autre donnée en parallèle. Inutile de dire que sans pipeline processeur, le parallélisme mémoire ne sert pas à grand-chose. ==Le ''line fill buffer'' et les techniques associées== Lors d'un défaut de cache, le processeur doit attendre que toute la ligne de cache soit chargée avant d'être utilisable. Or, la taille d'une ligne de cache est supérieure à la largeur du bus mémoire, ce qui fait qu'une ligne de cache est chargée en plusieurs fois, morceaux par morceaux, mot mémoire par mot mémoire. Le chargement peut se faire directement dans le cache, mais ce n'est pas une solution très pratique. À la place, beaucoup de processeurs ajoutent une mémoire tampon entre la RAM et le cache, appelée le '''tampon de remplissage de ligne''' (''line-fill buffer'') dans les processeurs modernes. Lors d'un défaut de cache, le processeur charge la donnée de la RAM dans le tampon de remplissage de ligne, mot mémoire par mot mémoire. Une fois plein, le tampon de remplissage de ligne est recopié dans la ligne de cache. Le tampon de remplissage de ligne contient une ligne de cache, si ce n'est que certains bits de contrôle ne sont pas présents. Le tampon de remplissage de ligne contient un bit de validité pour chaque mot mémoire de la ligne de cache, qui indiquent si le mot mémoire a été chargé. Par exemple, prenons un processeur 64 bits, qui gère donc des mots mémoire de 8 octets, avec des lignes de cache 256 octets/32 mots mémoire. Le tampon de remplissage de ligne contiendra 32 bits de validité. Si le processeur a chargé les 6 premiers mots mémoire, les 6 premiers bits de validité seront mis à 1, les autres seront encore à zéro. Il faut noter que la disponibilité de la ligne complète se détermine assez facilement en faisant un ET logique entre tous les bits de validité. Le processeur sait ainsi quand la ligne de cache est disponible entièrement et donc quand la transférer dans le cache. La même chose existe avec une hiérarchie de cache, sauf que l'on trouve une mémoire tampon entre chaque niveau de cache. Il y a un tampon de remplissage de ligne entre le cache L1 et le cache L2, entre le cache L2 et le L3, etc. Si le cache ne gère qu'un seul défaut de cache à la fois, le ''fill line buffer'' est une mémoire très simple, qui ne mémorise qu'une seule ligne de cache. Et cela vaut aussi bien pour un cache bloquant que non-bloquant. Mais sur les caches capables de gérer plusieurs défauts simultanés, le tampon de remplissage de ligne est une mémoire de type FIFO ou LIFO, capable de mémoriser plusieurs lignes de cache. Notons que le tampon de remplissage de ligne est très utile pour implémenter certaines techniques, par exemple le contournement du cache. Nous avions vu dans le chapitre sur le cache que certains accès mémoire doivent contourner le cache, pour des raisons de cohérence des caches. C'est notamment nécessaire pour accéder aux périphériques, mais c'est aussi utile pour des raisons de performances dans des cas très spécifiques. Les accès qui contournent le cache se font directement dans le tampon de remplissage de ligne : le processeur écrit ou lit les données depuis ce tampon de remplissage de ligne, sans accéder au cache. ===L'''early restart'' et le ''critical word load''=== La présence du ''line buffer'' permet une optimisation assez intéressante, qui permet de réduire la latence des défauts de cache. L'optimisation consiste à lire un mot mémoire dans le tampon de remplissage de ligne, même si la ligne de cache complète n'a pas encore été chargée. Il existe deux manières de faire cela, qui portent les noms d'''early restart'' et de ''critical word load''. La première est la version la plus simple, la seconde est plus complexe mais plus performante. Avec la technique d''''''early restart''''', la ligne de cache est chargée normalement, en partant de son premier mot mémoire. Dès que le mot mémoire lu/écrit par le processeur est copié dans le ''line fill buffer'', il est envoyé au processeur immédiatement. Illustrons le tout par un exemple, où une ligne de cache fait 16 mots mémoire. Le processeur effectue une lecture, qui lit le 5ème mot mémoire. Sans ''early restart'', le processeur doit charger les 16 mots mémoire avant de faire la lecture dans le cache. Avec ''early restart'', le processeur reçoit la donnée dès que le 5ème mot mémoire est disponible. Le processeur doit attendre que les 4 premiers mots mémoire soient chargés, puis le 5ème mot mémoire arrive et est envoyé directement au processeur, il est lu directement depuis le ''line fill buffer''. Les 11 mots mémoire suivants sont ensuite chargés dans le cache pendant que le processeur fait des calculs dans son coin. Le ''critical word load'' est une optimisation de la technique précédente où le chargement de la ligne de cache commence directement à la donnée demandée par le processeur. Pour reprendre l'exemple précédent, où le processeur demande le 5ème mot mémoire sur 16, le ''critical word load'' charge le 5ème mot mémoire en premier et l'envoie au processeur, ce qui fait qu'il est chargé très rapidement. Pas besoin d'attendre que le processeur charge les 4 mots mémoire précédents comme avec l'''early restart''. Dans le détail, le processeur charge le 5ème mot mémoire en premier, puis charge les 11 suivants, et termine par les 4 mots mémoire du début. En clair, le ''critical word load'' commence par charger le mot mémoire lu, puis les blocs suivants, avant de revenir au début du bloc pour charger les blocs restants. Ainsi, la donnée demandée par le processeur sera la première disponible. Pour cela, l'organisation du tampon de remplissage de ligne est modifiée de manière à rendre cela possible. Il a une taille égale à une ligne de cache complète, qui contient elle-même plusieurs mots mémoire. Dans le ''line-fill buffer'', chaque mot mémoire est stocké avec un tag, qui indique l'adresse du mot mémoire stocké dans le ''line-fill buffer''. Le ''line-fill buffer'' est donc un cache un peu particulier, qui fonctionne comme un cache du point de vue du processeur, comme une mémoire FIFO pour les transferts avec le cache. Ainsi, un processeur qui veut lire dans le cache après un défaut peut accéder à la donnée directement depuis le tampon de remplissage de ligne, alors que la ligne de cache n'a pas encore été totalement recopiée en mémoire. ==Les caches pipelinés== Il est possible de pipeliner l'accès au cache, ce qui demande juste de rajouter des registres au bon endroits et quelques circuits de contrôle. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, même ceux avec un pipeline dynamique. Et l'implémentation peut se faire de plusieurs manières différentes. Il faut dire que découper une mémoire cache en plusieurs étapes peut se faire de plusieurs manière. Commençons par la plus simple. Beaucoup de processeurs des années 2000 avaient une fréquence peu élevée comparée aux standards d'aujourd'hui, ce qui fait que leur cache avait bien un temps d'accès d'un cycle d'horloge. Mais l'accès au cache se faisait en deux cycles d'horloge : un cycle pour calculer une adresse et un cycle pour l'accès mémoire proprement dit. Et ces deux étapes étaient pipelinées, à savoir que deux micro-opérations mémoire peuvent s'exécuter en même temps : une dans l'étage de calcul d'adresse, une autre dans le cache. L'unité mémoire était donc pipelinée, alors que l'accès au cache ne l'était pas. Les processeurs qui implémentaient cette technique regroupent les micro-architectures K5 et K6 d'AMD, les processeurs Intel de micro-architecture P6 (Pentium 2 et 3) et quelques autres. Il est aussi possible de pipeliner l'accès au cache lui-même. Avec un cache pipeliné, l'accès au cache ne se fait pas en un seul cycle, mais en plusieurs. Cependant, on peut lancer un nouvel accès au cache à chaque cycle d'horloge, comme un processeur pipeliné lance une nouvelle instruction à chaque cycle. L'implémentation est assez simple : il suffit d'ajouter des registres dans le cache. Pour cela, on profite que le cache est composé de plusieurs composants séparés, qui échangent des données dans un ordre bien précis, d'un composant à un autre. De plus, le trajet des informations dans un cache est linéaire, ce qui les rend parfois pour l'usage d'un pipeline. ===Le ''pipelining'' des caches ''direct-mapped''=== Voici ce qui se passe avec un cache directement adressé. Pour rappel ce genre de cache est conçu en combinant une mémoire RAM, généralement une SRAM, avec quelques circuits de comparaison et un MUX. L'accès au cache se fait globalement en deux étapes : la première lit la donnée et le ''tag'' dans la SRAM, et on utilise les deux dans une seconde étape. Nous avions vu il y a quelques chapitre comment pipeliner des mémoires, dans le chapitre sur les mémoires évoluées. Et bien ces méthodes peuvent s'utiliser pour la mémoire RAM interne au cache !L'idée est d'insérer un registre entre la sortie de la RAM et la suite du cache, pour en faire un cache pipéliné, à deux étages. Le premier étage lit dans la SRAM, le second fait le reste. L'implémentation sur les caches associatifs à plusieurs voies est globalement la même à quelques détails près. [[File:Cache directement adressé pipeliné - deux étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - deux étages]] Avec le circuit précédent, il est possible d'aller plus loin, cette fois en pipelinant l'accès à la mémoire RAM interne au cache. Pour comprendre comment, rappelons qu'une mémoire SRAM est composée d'un plan mémoire et d'un décodeur. L'accès à la mémoire demande d'abord que le décodeur fasse son travail pour sélectionner la case mémoire adéquate, puis ensuite la lecture ou écriture a lieu dans cette case mémoire. L'accès se fait donc en deux étapes successives séparées, on a juste à mettre un registre entre les deux. Il suffit donc de mettre un gros registre entre le décodeur et le plan mémoire. [[File:Cache directement adressé pipeliné - trois étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - trois étages]] Et on peut aller encore plus loin en découpant le décodeur en deux circuits séparés. En effet, rappelez-vous le chapitre sur les circuits de sélection : nous avions vu qu'il est possible de créer des décodeurs en assemblant des décodeurs plus petits, contrôlés par un circuit de prédécodage. Et bien on peut encore une ajouter un registre entre ce circuit de prédécodage et les petits décodeurs. : Théoriquement, toute l'adresse est fournie d'un seul coup au cache, la quasi-totalité des processeurs présentent l'adresse complète à un cache pipeliné. Mais le Pentium 4 fait autrement. Il faut noter que les premiers étages manipulent l'indice dans la SRAM, qui est dans les bits de poids faible, alors que les étages ultérieurs manipulent le ''tag'' qui est dans les bits de poids fort. Les concepteurs du Pentium 4 ont alors décidé de présenter les bits de poids faible lors du premier cycle d'accès au cache, puis ceux de poids fort au second cycle. Pour cela, l'ALU fonctionnait à une fréquence double de celle du processeur, tout comme le cache L1. Il n'y avait pas de pipeline proprement dit, mais cela réduisait grandement la latence d'accès au cache. ===Le ''pipelining'' des caches splittés/sériels=== Il est aussi possible de pipeliner un cache dits splittés, aussi appelés à accès sériel. Pour rappel, les caches à accès sériel vérifient si il y a succès ou défaut de cache, avant d'accéder aux lignes de cache en cas de succès. Ils font donc différemment des autres caches, qui accèdent à une ligne de cache, avant de déterminer s'il y a succès ou défaut en lisant le tag de la ligne de cache. Les caches sériels disposent de deux SRAM : une pour les tags des lignes de cache et une pour les données. Ils accèdent à la SRAM pour les tags, avant d’accéder à la SRAM des données en cas de succès de cache. Vu que l'accès se fait en deux étapes, une vérification des tags suivie de la lecture/écriture des données, il est facile à pipeliner. Pipeliner le cache permet de régler le problème des accès au cache L1, et elle est tout le temps utilisé sur les processeurs modernes. Mais que faire en cas de défaut de cache ? ==Les caches non bloquants== Un '''cache bloquant''' est un cache auquel le processeur ne peut pas accéder pendant un défaut de cache. Il faut attendre que la lecture ou écriture en RAM soit terminée avant de pouvoir utiliser de nouveau le cache. Un '''cache non bloquant''' n'a pas ce problème : on peut l'utiliser pendant un défaut de cache. Les caches non bloquants permettent de démarrer une nouvelle lecture ou écriture alors qu'une autre est en cours, ce qui permet d'exécuter plusieurs lectures ou écritures en même temps. ===Les ''Miss Handling Status Registers''=== Lors d'un défaut de cache, la mémoire RAM est consultée pendant le défaut de cache, mais le cache est inutilisé. Un défaut de cache n'utilise pas le cache, ce n'est qu'un accès en mémoire RAM, sur le bus mémoire (ou un accès aux niveaux de cache inférieurs, peu importe). Le cache en lui-même est laissé libre, rien n’empêche d'y accéder, il est en réalité intrinsèquement non-bloquant. Les caches, bloquants comme non-bloquants, sont en réalité composés d'une mémoire cache proprement dite, entourée de circuits qui servent d'interface entre le processeur et le cache lui-même. Et parmi les circuits tout autour, certains gèrent l'accès au cache lors d'un défaut de cache. Ils sont regroupés sous le terme de '''''Miss Handling Architecture''''' (MHA). La différence entre un cache bloquant et un cache non-bloquant est en réalité liée à l'implémentation de la MHA. Les caches bloquants coupent volontairement l'accès au cache lors d'un défaut, car les défauts sont plus simples à gérer ainsi. Le défaut de cache rapatrie une donnée depuis la RAM, qui sera écrite dans le cache. Et il ne faut pas qu'une tentative d'accès à cette donnée ait lieu avant qu'elle ne soit chargée. Mais ce blocage est général et touche tout le cache, alors que seule une ligne de cache est concernée par le défaut de cache. L'idée derrière un cache non-bloquant est que seule la ligne de cache est bloquée, mais les autres sont accessibles. L'idée est alors de mémoriser les lignes de cache concernées par le défaut de cache, afin d'en bloquer l'accès. À chaque accès, on vérifie si la ligne de cache est déjà réservée par un défaut de cache. La lecture/écriture est alors bloquée si c'est le cas, mais elle accepte les accès sinon. Pour cela, la ''Miss Handling Architecture'' contient des registres qui mémorisent des informations sur les défauts de cache en cours. Ils portent le nom de '''''miss status handling registers''''', que j’appellerais dorénavant MSHR, qui sont aussi appelés des '''''miss buffer'''''. Le contenu des MSHR varie beaucoup suivant le processeur, mais ces derniers stockent au minimum les informations suivantes : * Le numéro de la ligne de cache dans laquelle les données sont chargées. * Un bit de validité qui indique si le MSHR est vide ou pas, qui est mis à 0 quand le défaut de cache est résolu. * Un ou plusieurs champs de lecture/écriture, qui contiennent des informations sur la lecture/écriture. ** Pour une lecture, elle contient des informations sur la destination de la donnée, à savoir qui prévenir quand le défaut de cache est terminé. C'est parfois un nom/numéro de registre (celui dans lequel charger la donnée), mais c'est souvent le numéro de l'entrée dans le ''load/store queue''. ** Pour les écritures, elle contient la donnée à écrire, ou éventuellement un numéro de ''load/store queue'' où se trouve la donnée à écrire. Il faut noter que le bus mémoire ne peut gérer qu'un seul défaut de cache à la fois. Aussi, il est intéressant de regarder ce qui se passe lorsqu'un second défaut de cache survient, pendant qu'un premier est en cours. Dans ce cas, il y a deux réponses qui correspondent à deux types de caches non-bloquants, qui portent les noms barbares de caches de type succès après défaut et défaut après défaut. Sur le premier type, il ne peut pas y avoir plusieurs défauts de cache simultanés. Dès qu'un second défaut de cache survient, le cache stoppe son activité et on ne peut plus démarrer de nouvelle lecture/écriture, tant que le premier défaut de cache n'est pas résolu. Le second type est plus souple et autorise la survenue de plusieurs défauts de cache simultanés. Du moins, jusqu'à une certaine limite, car le cache ne peut supporter qu'un nombre limité d'accès mémoires simultanés (pipelinés). ===Les accès simultanés à une même ligne de cache=== Il arrive que le processeur fasse plusieurs accès mémoire simultanés à la même ligne de cache. Si la ligne de cache en question n'a pas encore été chargée dans le cache, alors on a plusieurs défauts de cache consécutifs pour la même ligne de cache, et le cache non-bloquant doit gérer la situation. Pour la suite, il va falloir faire une petite distinction entre les défauts primaires et secondaires. Imaginons qu'un défaut de cache ait lieu et demande à charger une donnée dans la ligne de cache numéro N. Il s'agit du premier défaut impliquant cette ligne de cache précise, ce qui lui vaut le nom de '''défaut de cache primaire'''. Mais par la suite, d'autres accès mémoire à la même ligne de cache ont lieu, alors que la ligne de cache n'est pas encore disponible. Dans ce cas, il s'agit de '''défauts de cache secondaires'''. Pour l'unité d'accès mémoire, les défauts de cache primaire et secondaire sont différents (ils prennent tous une entrée dans la ''load/store queue''). Mais pour le cache, ils ne correspondent qu'à un seul accès au cache : celui qui demande de charger la ligne de cache demandée. Les défauts de cache primaire et secondaire à la même ligne de cache se voient attribuer un MSHR unique. La gestion des défauts de cache secondaires dépend du cache non-bloquant. La solution la plus simple ne permet pas les défauts de cache secondaires. Le cache ne permet pas deux défauts de cache simultanés pour la même ligne de cache. Les autres solutions le permettent, en fusionnant des accès simultanés à la même ligne de cache en un seul au niveau des MSHR. Dans tous les cas, détecter les défauts de cache secondaires sont un problème qu'il faut détecter. La ''Miss Handling Architecture'' doit détecter les défauts de cache secondaire. Pour cela, elle procède comme suit. Lors de chaque défaut de cache, la MHA récupère le numéro de la ligne de cache associé. Il vérifie alors chaque MSHR pour vérifier s'il contient le numéro en question. S'il n'y a aucune correspondance dans les MSHR, alors c'est signe que le défaut de cache est un défaut primaire. Mais s'il y en a une, alors c'est un défaut secondaire. Évidemment, cela signifie que lors d'un défaut de cache, le numéro de ligne de cache est envoyé à tous les MSHR, pour comparaison. Les MSHR sont donc regroupés dans une mémoire associative, une sorte de mini-cache, faciliter l'implémentation. Lors d'un défaut de cache primaire, l'accès à un cache non-bloquant se fait comme suit : le processeur envoie une adresse au cache, accède à celui-ci, et détecte la survenue d'un défaut de cache. Il en profite alors pour attribuer une ligne de cache dans laquelle sera chargée ce défaut. L'attribution est très simple dans le cas des caches ''direct mapped'', ou associatifs par voie, pour lesquels l'attribution se fait assez simplement. Il mémorise alors cette information dans les MSHR, après avoir vérifié que le défaut de cache n'était pas un défaut secondaire. Lors des accès ultérieurs à une adresse proche, censée être dans la même ligne de cache, le processeur va encore une fois rencontrer un défaut de cache. Il va alors déterminer le numéro de la ligne de cache associée à l'adresse, et comparer ce numéro avec les MSHRs. Si un MSHR contient ce numéro, c'est signe que le défaut de cache est un défaut secondaire. La MHA réagit alors différemment selon le processeur considéré. Une première solution n'autorise pas les défauts de cache secondaires. Si l'un d'entre eux survient, le processeur est gelé par un ''pipeline stall'', une bulle de pipeline. Une autre solution fusionne les défauts de cache secondaires avec le défaut de cache primaire : tout cela ne correspond qu'à un seul défaut de cache pour lui. ===Les MSHR simples=== Un cache non-bloquant à '''MSHR simple''' contient juste plusieurs MSHR qui mémorisent juste un numéro de ligne de cache, un bit de validité, le champ de lecture/écriture, et l'adresse exacte de lecture/écriture. Avec cette organisation, il est possible d'avoir plusieurs défauts de cache séparés, mais à la condition que chaque défaut accède à une ligne de cache différente. Deux accès simultanés à une même ligne de cache ne sont pas possibles, les défauts de cache secondaires ne sont pas autorisés. Ainsi, chaque défaut de cache se voit attribuer son propre MSHR, chacun contient un numéro de ligne de cache différent. Pour comprendre pourquoi c'est impossible de gérer les défauts secondaires, il faut regarder le champ de lecture/écriture. Si on veut effectuer plusieurs écritures consécutives à la même adresse, le MSHR n'aura pas de quoi mémoriser les deux données à écrire. Il pourra mémoriser la première donnée à écrire, pas la seconde. Même chose lors d'une lecture : le champ lecture/écriture peut mémorisr la destination de la première lecture, pas de la seconde. L'avantage est que la MHA n'a besoin que des MSHR et de quelques circuits annexes. Les autres solutions rajoutent des circuits annexes pour gérer les défauts de cache secondaires, qui utilisent beaucoup de circuits. Le cout en circuit est donc élevé, mais le gain en performance est là. Passons maintenant aux caches non-bloquants qui autorisent les défauts de cache secondaire. La solution la plus simple consiste à utiliser ===Les MSHR adressés implicitement=== Avec les '''MSHR adressés implicitement''', il est possible de fusionner plusieurs accès mémoire à une même ligne de cache, mais sous une condition très importante : ces accès lisent/écrivent des mots mémoire différents. Par exemple, imaginons qu'une ligne de cache contienne 8 mots mémoire de 64 bits. Si un premier accès mémoire lit le mot mémoire numéro 7 (dernier mot mémoire de la ligne), et le second accès le mot mémoire numéro 3, alors la fusion est possible. Mais si deux accès mémoire veulent lire/écrire le mot mémoire numéro 7, alors la fusion n'est pas possible et le processeur se bloque, un ''pipeline stall'' survient. Un MSHR adressé implicitement est un MSHR simple, qui contient naturellement un numéro de ligne de cache (tag) et un bit de validité, sauf que le champ de lecture/écriture est dupliqué. Les différents champs lecture/écriture d'un MSHR sont regroupés dans une mémoire RAM/cache qui contient autant d'entrées qu'il y a de mot mémoire dans une ligne de cache. Chaque entrée de la table est associée à un mot mémoire de la ligne de cache et stocke des informations sur celui-ci. Une entrée mémorise, au minimum : * Un champ de lecture/écriture qui contient soit la destination de la lecture, soit la donnée à écrire. * Un bit R/W permettant d'interpréter correctement le champ de lecture/écriture. * Un bit de validité pour chaque entrée de la table, qui dit si un défaut de cache antérieur accède déjà à ce mot mémoire. La fusion de deux défauts de cache est ainsi assez simple. Un défaut de cache primaire/secondaire configure l'entrée associée au mot mémoire lu/écrit. Si un défaut secondaire ultérieur lit/écrit un mot mémoire différent (dont le bit de validité de l'entrée dans la table est à zéro), pas de conflit : il configure une autre entrée, vide. Mais s'il lit/écrit un mot mémoire pour lequel l'entrée de la table est occupée, il y a conflit, le processeur est bloqué par un ''pipeline stall'', une bulle de pipeline. Avec cette organisation, le nombre de MSHR indique combien de lignes de cache peuvent être lues en même temps depuis la mémoire. Quant au nombre d'entrées par MSHR, il détermine combien d'accès mémoires qui ne se recouvrent pas peuvent avoir lieu en même temps. ===Les MSHR adressés explicitement=== Les '''MSHR adressés explicitement''' n'ont pas les contraintes des MSHR adressés implicitement. Avec eux, il est possible d'avoir plusieurs défauts de cache pointant vers la même ligne de cache, mais aussi vers le même mot mémoire. De tels défauts de cache apparaissent sur les processeurs à exécution dans le désordre, mais sont très rares, voire inexistants, sur les processeurs ''in-order''. L'idée est encore que chaque MSHR est associée à une table mémoire qui mémorise des entrées. Cette table n'est autre qu'une mémoire FIFO. Sauf que cette fois-ci, une entrée n'est pas associée à un mot mémoire. Une entrée est associée à un défaut de cache. Une entrée mémorise là encore la destination de la lecture ou la donnée de l'écriture suivi du bit R/W, ainsi que d'un bit de validité par entrée, mais aussi : la position du mot mémoire lu dans la ligne de cache. C'est cette dernière information qui n'était pas présente dans les MSHR adressés implicitement. De plus, le nombre d'entrée par MSHR n'est pas égal au nombre de mots mémoires dans une ligne de cache, mais peut être arbitrairement grand. L'avantage d'un tel cache est qu'il est capable de traité les défauts secondaires concernant un même mot mémoire, et ce, car les accès à la ligne de cache concernée se produisent dans l'ordre de génération des défauts (la table mémoire est une FIFO). ===Les MSHRs inversés=== Généralement, plus on veut supporter de défauts de cache, plus le nombre de MSHR et d'entrées augmente. Mais au-delà d'un certain nombre d'entrées et de MSHR, les MSHR adressés implicitement et explicitement ont tendance à bouffer un peu trop de circuits. Utiliser une organisation un peu moins gourmande en circuits est donc une nécessité. Cette organisation plus économe se base sur des MSHR inversés. Les MSHR inversés ne contiennent qu'une seule entrée, en quelque sorte : au lieu d’utiliser n MSHR de m entrées chacun, on va utiliser n × m MSHR inversés. La différence, c'est que plusieurs MSHR peuvent contenir un tag identique, contrairement aux MSHR adressés implicitement et explicitement. Lorsqu'un défaut de cache a lieu, chaque MSHR est vérifié. Si jamais aucun MSHR ne contient de tag identique à celui utilisé par le défaut, un MSHR vide est choisi pour stocker ce défaut, et une requête de lecture en mémoire est lancée. Dans le cas contraire, un MSHR est réservé au défaut de cache, mais la requête n'est pas lancée. Quand la donnée est disponible, les MSHR correspondant à la ligne qui vient d'être chargée vont être utilisés un par un pour résoudre les défauts de cache en attente. ===Les MSHR intégrés au cache=== Certains chercheurs ont remarqué que pendant qu'une ligne de cache est en train de subir un défaut de cache, celle-ci reste inutilisée, et son contenu est destiné à être perdu une fois le défaut de cache résolu. Ils se sont dit que, plutôt que d'utiliser des MSHR séparés, il vaudrait mieux utiliser la ligne de cache pour stocker les informations sur les défauts de cache en attente dans cette ligne de cache. Pour éviter tout problème, il faut rajouter un bit dans les bits de contrôle de la ligne de cache, qui sert à indiquer que la ligne de cache est occupée : un défaut de cache a eu lieu dans cette ligne, et elle stocke donc des informations pour résoudre les défauts de cache. ==L'entrelacement mémoire== Nous venons de voir comment une mémoire cache peut gérer plusieurs accès mémoire simultanés. Il se trouve que ce genre d'optimisation dépasse largement le cadre du cache. Les mémoires RAM ne sont pas en reste, elles aussi ! Il est possible de pipeliner l'accès à une mémoire RAM, en utilisant des techniques dites d''''entrelacement'''. Précisons cependant que cette optimisation n'est pas utilisée sur les mémoires SDRAM ou DDR modernes, du moins pas sans modifications majeures. Mais divers ordinateurs assez anciens, dont des superordinateurs, ont utilisé cette technique. La technique demande d'utiliser plusieurs mémoires séparées. Sur les anciens ordinateurs, les mémoires en question sont des chips mémoire, à savoir des circuits intégrés de DRAM. Pour faire plus général, nous allons utiliser le terme de '''banques''', ou encore de bancs mémoire. La différence est qu'il existe des mémoires multi-banque, qui regroupent plusieurs banques indépendantes dans un seul boitier, dans un seul chip mémoire. En clair, de telles mémoires regroupent plusieurs sous-mémoires dans un seul circuit intégré. Il est aussi possible d'utiliser plusieurs chips mémoire séparés, chacun ayant une banque. Reste à combiner ces banques pour former un pseudo-pipeline. ===Utiliser plusieurs banques sans entrelacement=== Sans optimisation particulière, les adresses sont réparties dans les banques comme indiqué ci-dessous. Il s'agit de ce qui s'appelle un arrangement horizontal, et nous avions vu celui-ci dans le chapiutre sur les mémoires SDRAM. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Avec cet arrangement, les bits de poids fort de l'adresse sont utilisées pour sélectionner la banque adéquate, et le reste de l'adresse est envoyé sur le bus d'adresse. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Adresse de banque !! Adresse dans la banque |- | Quelques bits de poids fort || Reste de l'adresse |} L'entrelacement change cette répartition, afin d'accélérer les accès mémoire. L'entrelacement classique vise à améliorer les accès à des adresses mémoires consécutives, mais des formes plus évoluées d'entrelacement visent à optimiser des accès mémoire arbitraires. Voyons-les dans l'ordre, du plus simple au plus complexe. ===L'entrelacement classique=== Avec l''''entrelacement classique''', des adresses consécutives sont réparties dans des banques consécutives. Précisons que cette attribution des adresses n'implique absolument pas la mémoire virtuelle ou n'importe quel mécanisme dans le processeur. La répartition décide que telle adresse mémoire va dans telle banque, à telle adresse dans la banque. Elle est donc le fait du contrôleur mémoire, donc en dehors du processeur (les contrôleurs mémoires n’étaient pas encore intégrés dans le processeur à l'époque). [[File:Répartition des adresses dans une mémoire interleaved.png|centre|vignette|upright=1.5|Répartition des adresses dans une mémoire interleaved.]] L'entrelacement simple permet d'accélérer les accès mémoire qui se font à des adresses consécutives. Avec l'entrelacement, chaque accès mémoire tombe dans une banque mémoire différente, ce qui fait qu'on peut démarrer un nouvel accès mémoire à chaque cycle d'horloge. Pendant qu'une banque est occupée par un accès mémoire, on démarre le suivant dans une autre banque, et ainsi de suite. Le tout se termine soit quand on a épuisé toutes les banques libres, soit quand l'accès en rafales se termine. Pas besoin d'attendre que la mémoire ait fini sa lecture/écriture avant de démarrer la lecture/écriture suivante. [[File:Pipemining mémoire.png|centre|vignette|upright=2.5|Pipelining mémoire]] L'animation suivante illustre bien le processus, avec quatres banques. [[File:Interleaving.gif|centre|vignette|upright=2.5|Pipelining mémoire]] L'entrelacement simple utilise les bits de poids faible pour sélectionner la banque, et les bits de poids fort pour la case mémoire. [[File:Adresse mémoire d'une mémoire entrelacée.png|centre|vignette|upright=2|Adresse mémoire d'une mémoire entrelacée]] Précisons que le temps d'accès mémoire ne change pas beaucoup avec l'entrelacement. Par contre, on peut faire plus d'accès mémoire simultanés. On peut démarrer un accès mémoire par cycle d'horloge, mais l'accès en lui-même prend plusieurs cycles. Les mémoires à entrelacement ont donc un débit supérieur aux mémoires qui ne l'utilisent pas. L'entrelacement classique pourrait être utilisé pour accélérer les transferts entre mémoire RAM et cache, qui se font en blocs d'adresses consécutives. Mais de nos jours, il n'est pas utilisé dans cette optique. Il faut dire qu'il est redondant avec le mode rafale des mémoires DRAM. Les deux font globalement la même chose et on voit mal comment utiliser les deux en même temps. Le mode rafale étant plus simple à implémenter, et plus léger niveau utilisation du bus de commande mémoire, il est préféré à l'entrelacement. Par contre, l'entrelacement a eu son heure de gloire sur d'anciens processeurs qui n'avaient pas de mémoire cache, alors qu'ils disposaient d'un pipeline. L'exemple typique est celui des nombreux ''processeurs vectoriels'', que nous n'avons pas encore abordé à ce stade du cours. De tels processeurs avaient un pipeline, une unité de calcul fortement pipelinée, et parfois même de l'exécution dans le désordre et du renommage de registres ! ===L'entrelacement par décalage=== L'entrelacement simple est très utile pour les accès en rafle ou équivalents. Par contre, il existe de rares situations où il n'est pas optimal. Une de ces situations est celle des accès en enjambée (en ''stride''), où l'on accède en série à des adresses sont séparées par N mots mémoires. [[File:Accès par enjambées.png|centre|vignette|upright=2|Accès par enjambées.]] De tels accès surviennent quand un logiciel accède à des structures de données spécifiques, à savoir des tableaux de structures, des matrices, ou d'autres structures de données dans le genre. Un cas classique est celui du parcours d'une matrice colonne par colonne. Les matrices de nombres sont mémorisées ligne par ligne, ce qui fait que parcourir une colonne demande de passer d'une ligne à la suivante et de faire des grands sauts en mémoire RAM, mais tous espacés par la même distance. Avec l'entrelacement simple, les accès en enjambée sont moins performants que les accès à des adresses consécutives, mais cela ne fonctionne pas trop mal. Le pire cas est celui où l'on a N banques et où les données sont justement placées toutes les N adresses. Des accès consécutifs vont tous tomber dans la même banque, on ne peut plus accéder à des banques différentes en parallèle. Mais en dehors de ce cas, on voit une amélioration par rapport à la situation sans entrelacement. Pour obtenir des performances maximales pour les accès en enjambées, il faut répartir les mots mémoires dans la mémoire autrement. L'organisation idéale est la suivante. : Dans les explications qui vont suivre, la variable N représente le nombre de banques, qui sont numérotées de 0 à N-1. On commence par organiser les N premières adresses comme une mémoire entrelacée simple : l'adresse 0 correspond à la banque 0, l'adresse 1 à la banque 1, etc. Pour le bloc suivant, on décale tout d'une adresse, à savoir qu'on commence à remplir les banques à partir de la seconde, non de la première. Une fois la fin du bloc atteinte, on finit de remplir le bloc en repartant du début du bloc. Le troisième bloc d'adresse subit le même traitement, sauf qu'on commence à remplir à partir de la seconde banque. Et on poursuit l’assignation des adresses en décalant d'un cran en plus à chaque bloc. Ainsi, chaque bloc verra ses adresses décalées d'un cran en plus comparé au bloc précédent. Si jamais le décalage dépasse la fin d'un bloc, alors on reprend au début. [[File:Mémoire interleaved par décalage.png|centre|vignette|upright=1.5|Mémoire entrelacée par décalage.]] En faisant cela, on remarque que les banques situées à N adresses d'intervalle sont différentes. Dans l'exemple du dessus, nous avons ajouté un décalage de 1 à chaque nouveau bloc à remplir. Mais on aurait tout aussi bien pu prendre un décalage de 2, 3, etc. Dans tous les cas, on obtient un entrelacement par décalage. Ce décalage est appelé le pas d'entrelacement, noté P. Le calcul de l'adresse à envoyer à la banque, ainsi que la banque à sélectionner se fait en utilisant les formules suivantes : * adresse à envoyer à la banque = adresse totale / N ; * numéro de la banque = (adresse + décalage) modulo N ; * décalage = (adresse totale * P) mod N. Avec cet entrelacement par décalage, on peut prouver que la bande passante maximale est atteinte si le nombre de banques est un nombre premier. Seulement, utiliser un nombre de banques premier peut créer des trous dans la mémoire, des mots mémoires inadressables. Malgré ce défaut, la technique a été utilisée sur quelques ordinateurs, avec l'exemple notable du superordinateur ''Burroughs Scientific Processor''. Pour éviter cela, il y a plusieurs solutions. Par exemple, on peut faire en sorte que N et la taille d'une banque soient premiers entre eux : ils ne doivent pas avoir de diviseur commun. Mais en pratique, elles n'ont pas vraiment été implémentées dans une vraie machine, et sont restées à l'état de recherche, aussi je les passe sous silence. ===L'entrelacement pseudo-aléatoire=== Une dernière méthode de répartition consiste à répartir les adresses dans les banques de manière "pseudo-aléatoire". L'idée exacte est de faire passer l'adresse dans une '''fonction de hachage''', plus ou moins complexe. Pour rappel, une fonction de hachage prend une entrée de grande taille et fournit en sortie un résultat de petite taille. Idéalement, elles doivent donner des résultats aussi différents que possible pour des entrées similaires, histoire de simuler une sorte de pseudo-aléatoire. Ici, une adresse est transformée en un numéro de banque. Une fonction de hachage simple ne fait que permuter des bits de l'adresse pour obtenir son résultat. Elle ne fait qu'échanger des bits de place avant de couper l'adresse en deux morceaux : un pour la sélection de la banque, et un autre pour la sélection de l'adresse dans la banque. Cette permutation est fixe, et ne change pas suivant l'adresse. Des fonctions de hachage plus complexes font des XOR bit à bit entre certains bits de l'adresse. ==Les contrôleurs SDRAM/DDR avec "exécution dans le désordre"== Après avoir vu le cas des mémoires RAM, nous allons nous concentrer sur le cas particulier des mémoires SDRAM. Les mémoires SDRAM et DDR modernes sont capables de gérer plusieurs accès simultanés à la mémoire RAM, et leurs contrôleurs mémoire en font tout autant. C'est une différence majeure avec les mémoires asynchrones FPM/EDO, qui n'acceptaient qu'un seul accès mémoire à la fois. Leur contrôleur mémoire n'acceptait qu'un seul accès mémoire à la fois, c'était un contrôleur mémoire bloquant. Les contrôleurs mémoires des SDRAM sont eux non-bloquants et peuvent encaisser une dizaine d'accès mémoire à la fois. Peu de choses sont connues sur les contrôleurs de SDRAM/DDR modernes, les fabricants ne donnant que peu de détails dessus. Les rares simulateurs qui tentent de décrire leur fonctionnement, comme DRAM SIM I et II, sont particulièrement simples et ne vont pas dans le détail. Néanmoins, le peu qu'on sait est tout de même instructif. ===Rappel sur les SDRAM : tampon de ligne et commandes=== Les mémoires SDRAM sont des mémoires à tampon de ligne. Elles font un accès mémoire en deux étapes. La première étape recopie une ligne de N * 64 bits dans un tampon interne à la SDRAM. La seconde étape sélectionne une colonne, à savoir une donnée de 64 bits, qui est soit envoyée sur le bus de données pour une lecture, soit modifiée par une écriture. [[File:Mémoire à tampon de ligne.png|centre|vignette|upright=2|Mémoire à tampon de ligne]] Les accès mémoire sont traduits par un séquenceur mémoire en une série de commandes mémoires, qui sont séparées par des délais mémoire de quelques cycles d'horloge. Les délais sont très précis, et sont à respecter à la lettre. Une lecture ou une écriture se fait en maximum trois commandes : une commande PRECHARGE qui ferme la ligne précédemment utilisée, une commande ACT qui précise l'adresse de la ligne, et une commande READ ou WRITE qui précise l'adresse de la colonne et éventuellement la donnée à écrire. : Pour être plus précis, la commande PRECHARGE précharge les lignes de bits du plan mémoire à une tension particulière, ce qui les vide de leur contenu. Mais c'est un détail sans importance pour ce qui va suivre. Les SDRAM permettent de se passer de la première étape si des accès consécutifs se font dans la même ligne. Une fois activée, la ligne reste ouverte et on peut accéder plusieurs fois de suite dedans. On a alors juste à préciser l'adresse de colonne dedans. Si un accès mémoire accède à une ligne déjà activée, on dit que c'est un '''succès de page'''. Si ce n'est pas le cas, on doit fermer la ligne courante, rouvrir la ligne vboulue, et préciser la colonne. C'est alors un '''défaut de page'''. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | bgcolor="#FFA0FF" | PRECHARGE || bgcolor="#FFA0FF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || bgcolor="#A0FFFF" | READ (2) || bgcolor="#A0FFFF" | READ (3) || || || bgcolor="#A0FFFF" | READ (4) || bgcolor="#A0FFFF" | READ (5) || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | Donnée READ 1 || ||bgcolor="#A0FFFF" | Donnée READ 2 | bgcolor="#A0FFFF" | Donnée READ 3 || || || bgcolor="#A0FFFF" | Donnée READ 4 || bgcolor="#A0FFFF" | Donnée READ 5 |} Les SDRAM sont parfois capables de démarrer une commande avant que la précédente soit terminée. Mais le respect des délais mémoire est très limitant, ce qui fait qu'on ne peut pas parler de réel pipeline, comme c'est le cas sur les processeurs. Parlons plutot de '''commandes anticipées'''. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | || bgcolor="#A0FFFF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || || || bgcolor="#FFA0FF" | READ (2) || || || || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 | bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 | |} Un dernier point est que les chips mémoires de SDRAM sont composés de plusieurs banques, chacune étant une sorte de mini-mémoire miniature. Chaque banque a son propre décodeur, son propre tampon de ligne, ses propres multiplexeurs de colonne, sa logique de rafraichissement mémoire, etc. C'est comme si une SDRAM regroupait plusieurs mémoires séparées dans un même circuit intégré. [[File:Arrangement vertical.jpg|centre|vignette|upright=2.5|Mémoire multi-banques.]] ===La mise en attente des accès mémoire=== Un contrôleur de SDRAM peut accepter plusieurs accès mémoire en même temps. Mais cela ne signifie pas que le contrôleur sera prêt à les traiter immédiatement. Pour éviter tout problème de disponibilité, le contrôleur met en attente les accès mémoire que le processeur lui envoie, pour les exécuter dès que possible. Les accès mémoire sont mis en attente dans une mémoire FIFO, histoire de les exécuter dans leur ordre d'arrivée, notamment pour renvoyer les lectures dans l'ordre demandé. Il y a aussi une mémoire FIFO pour les données à écrire et une FIFO pour les données lues. Cette dernière sert au cas où le cache ne soit pas disponible quand on lui envoie la donnée lue. [[File:Module d'interface avec la mémoire.png|centre|vignette|upright=2.5|Contrôleur mémoire avec mise en attente des requêtes processeur et autres optimisations.]] Des optimisations sont possibles dès la mémoire FIFO. Par exemple, on peut regrouper plusieurs accès à des données consécutives en un seul accès en rafale. Le contrôleur mémoire analyse les requêtes mises en attente et détecte si certaines se font à des adresses consécutives. Si c'est le cas, il fusionne ces requêtes en une lecture/écriture en rafale. Une telle optimisation est appelée la '''combinaison de lecture''' pour les lectures, et la '''combinaison d'écriture''' pour les écritures. L'optimisation précédente ne paraissent pas payer de mine, mais elles deviennent plus compréhensible quand on sait que certains contrôleurs mémoire peuvent mettre en attente beaucoup de données. Par exemple, l'''Intel E8870 - Scalable Memory Controller'' peut mettre en attente 64 lignes de cache dans la mémoire FIFO, soit 8 kibioctets de RAM ! Deux pages mémoire si la mémoire virtuelle utilise des pages de 4 kibioctets ! ===Les lectures anticipées : une forme d'OOO mémoire=== La mise en attente des accès mémoire est une optimisation intéressante, mais pas mirifique. Elle permet surtout de ne pas bloquer le processeur si le contrôleur mémoire a du travail sur la planche. Cependant, elle peut être optimisée quand on se rend compte d'une chose : le processeur ne voit que les lectures, pas les écritures. Pour les écritures, il les envoie au contrôleur mémoire et ce dernier fait le travail demandé, le processeur n'est pas prévenu quand une écriture se termine. Mais pour les lectures, c'est différent, car il reçoit la donnée lue. En conséquence, il est possible d'optimiser les écritures sans que le processeur ne voit quoique ce soit. Une optimisation possible est alors de changer l'ordre des accès mémoire, histoire de retarder les écritures ou au contraire de les faire en avance. La raison est que les mémoires SDRAM n'aiment pas quand on alterne lectures et écritures. Les délais mémoire, les fameux ''timings'' mémoire, sont clairs : faire une suite de lecture ou une suite d'écriture est plus performant que d'alterner entre lectures et écritures. L'idée est alors de changer l'ordre des écritures de manière à les faire en bloc, idem pour les lectures. L’optimisation est possible, mais à condition que le processeur n'y verra que du feu. Or, vous l'avez deviné, il y a des cas où ces réorganisations ne sont pas possibles, et vous avez sans doute trouvé que la situation était familière. L'optimisation en question est une forme d'exécution dans le désordre des accès mémoire, réalisée par le contrôleur mémoire lui-même ! Et qui dit exécution dans le désordre dit : problèmes liés aux dépendances de données. Changer l'ordre des écritures n'est pas toujours possible, notamment en raison des dépendances RAW, WAR et WAW. Pour que le processeur n'y voit que du feu, il faut respecter plusieurs critères. Premièrement, les lectures doivent être renvoyées au processeur dans l'ordre. Et quand je dis dans l'ordre, cela veut dire : dans l'ordre d'envoi des lectures au contrôleur mémoire. Les écritures aussi doivent se faire dans l'ordre, histoire qu'une série d'écriture donne le bon résultat final. Le troisième critère est qu'une lecture doit donner le résultat de la dernière écriture ''à la même adresse''. Pour le dire autrement, il faut juste détecter les dépendances RAW. Concrètement, si on a une série de lectures et d'écritures alternées, on peut regrouper les lectures et les écritures dans deux paquets séparés, à condition qu'aucune lecture ne lise une adresse écrite par une écriture. Mais si une lecture est dépendante d'une écriture, alors la lecture doit attendre que l'écriture se termine, idem pour les lectures suivantes. Une implémentation basique remplace la mémoire FIFO vue au-dessus, par deux mémoires FIFOs : une pour les lectures, une pour les écritures. Lorsqu'un accès mémoire arrive au séquenceur, il regarde si c'est une lecture ou une écriture et place l'accès mémoire dans la file adéquate. Le fait que ce soit des FIFOs garantit que les lectures se font dans l'ordre, idem pour les écritures. En sortie des deux FIFOs, le séquenceur mémoire détecte les dépendances RAW. Précisément, quand une lecture sort de la mémoire FIFO, il consulte la file d'attente des écriture pour voir si la lecture est dépendante d'une écriture en attente. La lecture attend si c'est le cas, et les écritures sont exécutées à la place. Séparer lectures et écriture est une source d'optimisation. Il est par exemple possible d'utiliser le réacheminement lecture sur écriture (''Store to load forwarding''). Il s'agit d'un équivalent de l’optimisation du même nom, utilisée dans le cadre de la désambiguïsation mémoire. Imaginez qu'une lecture accède à une donnée pas encore écrite, en attente dans le contrôleur mémoire. C'est une dépendance RAW assez claire. Dans ce cas, le contrôleur mémoire peut renvoyer la donnée directement depuis ses mémoires FIFOs, sans faire d'accès mémoire en lecture. Le contrôleur mémoire a tendance à privilégier les lectures, car celles-ci sont cruciales pour l'exécution dans le désordre. Plus elles se font tôt, plus vite le processeur recevra les données et pourra démarrer les instructions dépendantes de cette donnée. Le séquenceur a donc tendance à piocher en priorité dans la file de lecture, plutôt que dans la file d'écriture. A vrai dire, dans le cas idéal, les écritures ne sont faites que quand la file d'écriture est pleine ou quasi-pleine... Prenons l'exemple des coprocesseurs IO 81341 et 81342, qui étaient en réalité des ''chipsets'' intégrant un contrôleur SDRAM. Le ''chipset'' avait 5 ports : un pour les processeurs, un pour le pont sud (''southbridge'') et trois pour des canaux DMA. Pour le port processeur, il n'y avait pas de réordonnancement et ce port utilisait une seule mémoire FIFO. Les autres ports utilisaient l'optimisation qu'on vient de voir, et avaient vraisemblablement des files séparées pour les lectures et écritures. Le séquenceur mémoire vérifiait les dépendances mémoire de type RAW et autres. [[File:Double file d'attente pour les lectures et écritures.png|centre|vignette|upright=2.5|Double file d'attente pour les lectures et écritures]] ===Le ré-ordonnancement des commandes mémoires=== L'optimisation précédente est peu poussée, comparé aux formes d'exécution dans le désordre que les processeurs utilisent. Et on peut se demander si une exécution dans le désordre plus poussée est possible. La réponse est oui : un contrôleur mémoire peut faire des réorganisations bien plus poussées. Par contre, il faut faire une précision très importante : le contrôleur mémoire ne remet pas les accès mémoire dans l'ordre avant de les envoyer au processeur. C'est le processeur qui remet en ordre les accès mémoire, dans sa ''load queue''. En clair : le processeur voit les accès mémoire dans le désordre, c'est lui qui les remet en ordre. Pour cela, les requêtes mémoire sont "numérotées", à savoir qu'on leur attribue un identifiant binaire. Le contrôleur mémoire exécute les lectures/écritures dans le désordre, mais garde la trace de leur identifiant. Il sait dans quel ordre il exécute les accès mémoire et connait leur latence. Ce qui fait qu'il sait que la donnée lue à tel instant est associée à la requête mémoire numéro X. Pour les lectures, il renvoie la donnée lue avec l'identifiant. Le processeur sait alors à quelle lecture la donnée lue correspond et il se débrouille en interne pour gérer la situation. Maintenant que ces précisions sont faites, posons cette question : dans quelles situations est-il pertinent de faire des accès mémoire dans le désordre ? La réponse est : quand plusieurs accès à une même ligne ne sont pas consécutifs, qu'il y a des accès entre les deux. Après ré-ordonnancement, ces accès mémoire à une même ligne sont exécutés l'un à la suite de l'autre, ce qui est beaucoup plus rapide. Pour rendre le tout plus concret, voici un exemple. Imaginez que l'on sait les 3 accès mémoire suivants : * Une lecture ligne A ; * PRECHARGE + ACT ; * Une écriture ligne B ; * PRECHARGE + ACT ; * Une lecture ligne A. Sans réordonnancent, on doit émettre deux commandes PRECHARGE quand on passe d'une ligne à l'autre, et il faut ajouter les commandes ACT avec. pour éviter cela, le contrôleur mémoire peut retarder l'écriture, ou au contraire avancer la seconde lecture. le résultat est alors le suivant : * Lecture ligne A ; * Lecture ligne A * PRECHARGE + ACT ; * Une écriture ligne B. On a donc deux accès consécutifs à la même ligne, suivi par une écriture dans une autre ligne. La technique marche parce que l'on a un mix adéquat de lectures et d'écritures. Et encore une fois, il faut éviter d'intervertir lectures et écritures à une même adresse, sous peine de problèmes. Encore une fois, les dépendances RAW posent problème ! L'implémentation est assez simple : la ou les mémoires FIFOs précédentes sont remplacées par des mémoires similaires aux fenêtres d'instruction. Le contrôleur mémoire vérifie à chaque cycle les accès en attente, et vérifie à quelle ligne ils accèdent. Il priorise alors les accès qui tombent dans la ligne ouverte : ceux-là sont exécutés avant les autres. S'il n'y en a pas, il prend l'accès mémoire le plus ancien (ordre FIFO). Il teste aussi les dépendances mémoires RAW avant d'envoyer des commandes à la mémoire DDR/SDRAM. une telle solution est appelée l''''algorithme ''FR-FCFS''''' (''First Ready-First Come First Serve''). ===Les optimisations liées à la présence de plusieurs banques=== Les optimisations précédentes sont décuplées par la présence de plusieurs banques dans la mémoire SDRAM. Il est en effet possible de faire plusieurs accès mémoire en même temps, dans des banques différentes. Il est possible de lancer un accès mémoire dans deux banques en même temps, par exemple. La seule contrainte est que la SDRAM est limitée à 4 banques actives en même temps. {|class="wikitable" |+ Pipelining basique sur les SDRAM |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 |- ! Banque Numéro 1 | || || || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || || |- ! Banque Numéro 2 | || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || |- ! Banque Numéro 3 | || || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || |} Les optimisations précédentes peuvent s'appliquer par banque. Il est par exemple possible d'utiliser une mémoire FIFO par banque. Ainsi, les accès dans une même banque se feront en série, dans l'ordre d'arrivée, mais des accès à des banques différentes se feront dans le désordre. Cela ne pose pas de problème car les accès se font à des adresses différentes, ce qui fait qu'il n'y a pas de conflits majeurs. Il y a cependant un risque que les lectures se fassent dans le désordre si on n'y prend pas garde. [[File:Gestion parallèle des banques.png|centre|vignette|upright=2.5|Gestion parallèle des banques.]] Il est aussi possible d'utiliser une file séparée pour les lectures et les écriture, pour chaque banque. Les performances sont alors améliorées comparé à deux files globales pour toute la SDRAM. En idem avec la réorganisation des accès mémoire, pour regrouper les accès à une même ligne. Un problème avec ces optimisations est qu'il est rare que des accès mémoire se fassent dans des banques séparées. Les accès mémoire tendent à se faire avec une bonne localité spatiale, ce qui fait qu'ils tombent dans une même banque. Heureusement, les contrôleurs SDRAM/DDR modernes incorporent des optimisations pour corriger ce problème. Les optimisations en question sont une forme d'entrelacement adaptée aux mémoires SDRAM. L'entrelacement naïf ne marche pas à cause de la présence du tampon de ligne. Cependant, il peut y avoir un entrelacemententre banques SDRAM. Voyons ce que ca veut dire. Pour gérer l'entrelacement, le contrôleur de SDRAM prend en entrée l'adresse envoyée par le processeur, et la découpe en plusieurs champs : un pour sélectionner la banque adéquate, un autre pour la ligne, un autre pour la colonne, un autre pour la rangée. Sans entrelacement, les adresses mémoire sont découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- | Numéro de rangée || Adresse de banque || Adresse de ligne || Adresse de colonne |} L''''entrelacement de banques''' répartit deux lignes consécutives dans deux banques différentes. Pour comprendre l'idée, prenons un exemple. Imaginons une mémoire avec deux banques et 4 lignes. Imaginons qu'on parcoure/balaye la mémoire RAM en partant des adresses basses. Sans entrelacement de ligne, les accès se feront comme suit, de gauche à droite : {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Banque numéro 1 !! colspan="4" | Banque numéro 2 |- | Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 || Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 |} Avec l'entrelacement de banques, les accès se feront comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 |- | Ligne 1 || Ligne 1 || Ligne 2 || Ligne 2 || Ligne 3 || Ligne 3 || Ligne 4 || Ligne 4 |} Avec N banques, la première ligne ira dans la première banque, la seconde ligne dans la seconde, et ainsi de suite jusqu'à la énième banque qui va dans la banque numéro N. Et la ligne N+1 repart dans la première banque. L'avantage est que passer d'une ligne à la suivante est assez rapide. Au lieu de devoir fermer la première ligne et ouvrir la suivante on ouvrira directement la ligne suivante dans une banque différente, ce qui sera plus rapide. La commande PRECHARGE pourra être envoyée à la seconde banque en avance. Pour cela, l'adresse est découpée comme suit. {|class="wikitable" |+ Adresse mémoire |- | Adresse de ligne || Adresse de banque || Adresse de colonne (précise à l'octet) |} : Il est aussi possible de faire la même chose, mais avec les rangées. L'usage du ''dual channel'' est aussi une forme d'entrelacement entre barrettes de RAM. Mais je vais passer cela sous silence. D'autres méthodes d'entrelacement sont possibles, mais elles sont plus complexes. Chaque contrôleur mémoire fait un peu à sa sauce. Ils permettent en général de choisir entre plusieurs entrelacements. Au minimum, ils permettent de désactiver l'entrelacement, d'activer un entrelacement de ligne, et d'autres entrelacements plus complexes, dont certains sont propriétaires et tenus secrets par le fabricant. L'entrelacement est géré juste avant le séquenceur mémoire, dans un '''circuit d'entrelacement'''. Ce dernier intervertit certains bits de l'adresse lors des accès mémoires. [[File:Controleur mémoire d'une SDRAM avec entrelacement.png|centre|vignette|upright=2.5|Contrôleur mémoire d'une SDRAM avec entrelacement.]] Les contrôleurs de SDRAM précédents sont assez basiques et ne représentent pas les contrôleurs les plus évolués. Ils étaient utilisés sur les anciens PC, à une époque où ils étaient encore sur la carte mère du processeur. Mais de nos jours, le contrôleur mémoire est intégré au processeur. Et il incorpore de nombreuses optimisations afin de gagner en performances. Malheureusement, ces optimisations sont elles-mêmes dépendantes des optimisations intégrées dans le processeur, ce qui fait qu'on ne peut pas en parler ici. Elles demandent que le contrôleur mémoire reçoive plusieurs accès mémoire simultanés, ce qui n'a aucun sens à ce stade du cours. Aussi, nous allons les passer sous silence pour le moment. Nous les verrons dans le chapitre sur le parallélisme mémoire. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=La désambiguïsation mémoire | prevText=La désambiguïsation mémoire | next=Les processeurs superscalaires | nextText=Les processeurs superscalaires }} </noinclude> a2iy8ruoisic91mmanl80at20ha3zur 764817 764816 2026-04-24T10:48:52Z Mewtow 31375 /* Les optimisations liées à la présence de plusieurs banques */ 764817 wikitext text/x-wiki Dans ce chapitre, nous allons voir les techniques qui permettent de gérer plusieurs accès mémoire simultanés directement au niveau du cache ou de la mémoire RAM. Par plusieurs accès mémoire simultanés, vous pensez sans doute à l'usage de cache multiports, voire de mémoires RAM multiport. C'est en effet une solution, mais ce n'est clairement pas la seule. Par exemple, il est possible de pipeliner l'accès au cache, voire à la mémoire RAM. Il existe de nombreuses techniques de '''parallélisme mémoire''', et nous allons les voir dans ce chapitre. Pour donner un exemple, il est possible de pipeliner les accès mémoire. Il est possible de pipeliner l'accès au cache et/ou l'accès à la mémoire, et nous verrons comment faire dans ce chapitre. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, qu'ils aient ou non un pipeline dynamique. Par contre, pipeliner la mémoire est une technique ancienne, aujourd'hui peu utilisée. Elle était utilisée sur des très vieux ordinateurs, qui avaient un pipeline mais pas de mémoire cache. Mais de nos jours, les mémoires SDRAM et DDR ne sont pas adaptées pour ça, il est très difficile de les pipeliner correctement. Le parallélisme mémoire est utile aussi bien sur des processeurs à émission dans l'ordre que dans le désordre. Par exemple, un processeur ''in-order'' avec des lectures non-bloquantes peut e, profiter. Il peut techniquement lancer une seconde lecture, après avoir rencontré une première lecture non-bloquante. En général, le nombre de lectures consécutives est cependant limité à moins d'une dizaine. À l'opposé, les processeurs sans lectures non-bloquantes profitent pas du parallélisme mémoire. À la rigueur, ils peuvent en profiter s'ils intègrent des techniques de préchargement. Pendant que le processeur exécute une lecture/écriture, il peut précharger une autre donnée en parallèle. Inutile de dire que sans pipeline processeur, le parallélisme mémoire ne sert pas à grand-chose. ==Le ''line fill buffer'' et les techniques associées== Lors d'un défaut de cache, le processeur doit attendre que toute la ligne de cache soit chargée avant d'être utilisable. Or, la taille d'une ligne de cache est supérieure à la largeur du bus mémoire, ce qui fait qu'une ligne de cache est chargée en plusieurs fois, morceaux par morceaux, mot mémoire par mot mémoire. Le chargement peut se faire directement dans le cache, mais ce n'est pas une solution très pratique. À la place, beaucoup de processeurs ajoutent une mémoire tampon entre la RAM et le cache, appelée le '''tampon de remplissage de ligne''' (''line-fill buffer'') dans les processeurs modernes. Lors d'un défaut de cache, le processeur charge la donnée de la RAM dans le tampon de remplissage de ligne, mot mémoire par mot mémoire. Une fois plein, le tampon de remplissage de ligne est recopié dans la ligne de cache. Le tampon de remplissage de ligne contient une ligne de cache, si ce n'est que certains bits de contrôle ne sont pas présents. Le tampon de remplissage de ligne contient un bit de validité pour chaque mot mémoire de la ligne de cache, qui indiquent si le mot mémoire a été chargé. Par exemple, prenons un processeur 64 bits, qui gère donc des mots mémoire de 8 octets, avec des lignes de cache 256 octets/32 mots mémoire. Le tampon de remplissage de ligne contiendra 32 bits de validité. Si le processeur a chargé les 6 premiers mots mémoire, les 6 premiers bits de validité seront mis à 1, les autres seront encore à zéro. Il faut noter que la disponibilité de la ligne complète se détermine assez facilement en faisant un ET logique entre tous les bits de validité. Le processeur sait ainsi quand la ligne de cache est disponible entièrement et donc quand la transférer dans le cache. La même chose existe avec une hiérarchie de cache, sauf que l'on trouve une mémoire tampon entre chaque niveau de cache. Il y a un tampon de remplissage de ligne entre le cache L1 et le cache L2, entre le cache L2 et le L3, etc. Si le cache ne gère qu'un seul défaut de cache à la fois, le ''fill line buffer'' est une mémoire très simple, qui ne mémorise qu'une seule ligne de cache. Et cela vaut aussi bien pour un cache bloquant que non-bloquant. Mais sur les caches capables de gérer plusieurs défauts simultanés, le tampon de remplissage de ligne est une mémoire de type FIFO ou LIFO, capable de mémoriser plusieurs lignes de cache. Notons que le tampon de remplissage de ligne est très utile pour implémenter certaines techniques, par exemple le contournement du cache. Nous avions vu dans le chapitre sur le cache que certains accès mémoire doivent contourner le cache, pour des raisons de cohérence des caches. C'est notamment nécessaire pour accéder aux périphériques, mais c'est aussi utile pour des raisons de performances dans des cas très spécifiques. Les accès qui contournent le cache se font directement dans le tampon de remplissage de ligne : le processeur écrit ou lit les données depuis ce tampon de remplissage de ligne, sans accéder au cache. ===L'''early restart'' et le ''critical word load''=== La présence du ''line buffer'' permet une optimisation assez intéressante, qui permet de réduire la latence des défauts de cache. L'optimisation consiste à lire un mot mémoire dans le tampon de remplissage de ligne, même si la ligne de cache complète n'a pas encore été chargée. Il existe deux manières de faire cela, qui portent les noms d'''early restart'' et de ''critical word load''. La première est la version la plus simple, la seconde est plus complexe mais plus performante. Avec la technique d''''''early restart''''', la ligne de cache est chargée normalement, en partant de son premier mot mémoire. Dès que le mot mémoire lu/écrit par le processeur est copié dans le ''line fill buffer'', il est envoyé au processeur immédiatement. Illustrons le tout par un exemple, où une ligne de cache fait 16 mots mémoire. Le processeur effectue une lecture, qui lit le 5ème mot mémoire. Sans ''early restart'', le processeur doit charger les 16 mots mémoire avant de faire la lecture dans le cache. Avec ''early restart'', le processeur reçoit la donnée dès que le 5ème mot mémoire est disponible. Le processeur doit attendre que les 4 premiers mots mémoire soient chargés, puis le 5ème mot mémoire arrive et est envoyé directement au processeur, il est lu directement depuis le ''line fill buffer''. Les 11 mots mémoire suivants sont ensuite chargés dans le cache pendant que le processeur fait des calculs dans son coin. Le ''critical word load'' est une optimisation de la technique précédente où le chargement de la ligne de cache commence directement à la donnée demandée par le processeur. Pour reprendre l'exemple précédent, où le processeur demande le 5ème mot mémoire sur 16, le ''critical word load'' charge le 5ème mot mémoire en premier et l'envoie au processeur, ce qui fait qu'il est chargé très rapidement. Pas besoin d'attendre que le processeur charge les 4 mots mémoire précédents comme avec l'''early restart''. Dans le détail, le processeur charge le 5ème mot mémoire en premier, puis charge les 11 suivants, et termine par les 4 mots mémoire du début. En clair, le ''critical word load'' commence par charger le mot mémoire lu, puis les blocs suivants, avant de revenir au début du bloc pour charger les blocs restants. Ainsi, la donnée demandée par le processeur sera la première disponible. Pour cela, l'organisation du tampon de remplissage de ligne est modifiée de manière à rendre cela possible. Il a une taille égale à une ligne de cache complète, qui contient elle-même plusieurs mots mémoire. Dans le ''line-fill buffer'', chaque mot mémoire est stocké avec un tag, qui indique l'adresse du mot mémoire stocké dans le ''line-fill buffer''. Le ''line-fill buffer'' est donc un cache un peu particulier, qui fonctionne comme un cache du point de vue du processeur, comme une mémoire FIFO pour les transferts avec le cache. Ainsi, un processeur qui veut lire dans le cache après un défaut peut accéder à la donnée directement depuis le tampon de remplissage de ligne, alors que la ligne de cache n'a pas encore été totalement recopiée en mémoire. ==Les caches pipelinés== Il est possible de pipeliner l'accès au cache, ce qui demande juste de rajouter des registres au bon endroits et quelques circuits de contrôle. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, même ceux avec un pipeline dynamique. Et l'implémentation peut se faire de plusieurs manières différentes. Il faut dire que découper une mémoire cache en plusieurs étapes peut se faire de plusieurs manière. Commençons par la plus simple. Beaucoup de processeurs des années 2000 avaient une fréquence peu élevée comparée aux standards d'aujourd'hui, ce qui fait que leur cache avait bien un temps d'accès d'un cycle d'horloge. Mais l'accès au cache se faisait en deux cycles d'horloge : un cycle pour calculer une adresse et un cycle pour l'accès mémoire proprement dit. Et ces deux étapes étaient pipelinées, à savoir que deux micro-opérations mémoire peuvent s'exécuter en même temps : une dans l'étage de calcul d'adresse, une autre dans le cache. L'unité mémoire était donc pipelinée, alors que l'accès au cache ne l'était pas. Les processeurs qui implémentaient cette technique regroupent les micro-architectures K5 et K6 d'AMD, les processeurs Intel de micro-architecture P6 (Pentium 2 et 3) et quelques autres. Il est aussi possible de pipeliner l'accès au cache lui-même. Avec un cache pipeliné, l'accès au cache ne se fait pas en un seul cycle, mais en plusieurs. Cependant, on peut lancer un nouvel accès au cache à chaque cycle d'horloge, comme un processeur pipeliné lance une nouvelle instruction à chaque cycle. L'implémentation est assez simple : il suffit d'ajouter des registres dans le cache. Pour cela, on profite que le cache est composé de plusieurs composants séparés, qui échangent des données dans un ordre bien précis, d'un composant à un autre. De plus, le trajet des informations dans un cache est linéaire, ce qui les rend parfois pour l'usage d'un pipeline. ===Le ''pipelining'' des caches ''direct-mapped''=== Voici ce qui se passe avec un cache directement adressé. Pour rappel ce genre de cache est conçu en combinant une mémoire RAM, généralement une SRAM, avec quelques circuits de comparaison et un MUX. L'accès au cache se fait globalement en deux étapes : la première lit la donnée et le ''tag'' dans la SRAM, et on utilise les deux dans une seconde étape. Nous avions vu il y a quelques chapitre comment pipeliner des mémoires, dans le chapitre sur les mémoires évoluées. Et bien ces méthodes peuvent s'utiliser pour la mémoire RAM interne au cache !L'idée est d'insérer un registre entre la sortie de la RAM et la suite du cache, pour en faire un cache pipéliné, à deux étages. Le premier étage lit dans la SRAM, le second fait le reste. L'implémentation sur les caches associatifs à plusieurs voies est globalement la même à quelques détails près. [[File:Cache directement adressé pipeliné - deux étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - deux étages]] Avec le circuit précédent, il est possible d'aller plus loin, cette fois en pipelinant l'accès à la mémoire RAM interne au cache. Pour comprendre comment, rappelons qu'une mémoire SRAM est composée d'un plan mémoire et d'un décodeur. L'accès à la mémoire demande d'abord que le décodeur fasse son travail pour sélectionner la case mémoire adéquate, puis ensuite la lecture ou écriture a lieu dans cette case mémoire. L'accès se fait donc en deux étapes successives séparées, on a juste à mettre un registre entre les deux. Il suffit donc de mettre un gros registre entre le décodeur et le plan mémoire. [[File:Cache directement adressé pipeliné - trois étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - trois étages]] Et on peut aller encore plus loin en découpant le décodeur en deux circuits séparés. En effet, rappelez-vous le chapitre sur les circuits de sélection : nous avions vu qu'il est possible de créer des décodeurs en assemblant des décodeurs plus petits, contrôlés par un circuit de prédécodage. Et bien on peut encore une ajouter un registre entre ce circuit de prédécodage et les petits décodeurs. : Théoriquement, toute l'adresse est fournie d'un seul coup au cache, la quasi-totalité des processeurs présentent l'adresse complète à un cache pipeliné. Mais le Pentium 4 fait autrement. Il faut noter que les premiers étages manipulent l'indice dans la SRAM, qui est dans les bits de poids faible, alors que les étages ultérieurs manipulent le ''tag'' qui est dans les bits de poids fort. Les concepteurs du Pentium 4 ont alors décidé de présenter les bits de poids faible lors du premier cycle d'accès au cache, puis ceux de poids fort au second cycle. Pour cela, l'ALU fonctionnait à une fréquence double de celle du processeur, tout comme le cache L1. Il n'y avait pas de pipeline proprement dit, mais cela réduisait grandement la latence d'accès au cache. ===Le ''pipelining'' des caches splittés/sériels=== Il est aussi possible de pipeliner un cache dits splittés, aussi appelés à accès sériel. Pour rappel, les caches à accès sériel vérifient si il y a succès ou défaut de cache, avant d'accéder aux lignes de cache en cas de succès. Ils font donc différemment des autres caches, qui accèdent à une ligne de cache, avant de déterminer s'il y a succès ou défaut en lisant le tag de la ligne de cache. Les caches sériels disposent de deux SRAM : une pour les tags des lignes de cache et une pour les données. Ils accèdent à la SRAM pour les tags, avant d’accéder à la SRAM des données en cas de succès de cache. Vu que l'accès se fait en deux étapes, une vérification des tags suivie de la lecture/écriture des données, il est facile à pipeliner. Pipeliner le cache permet de régler le problème des accès au cache L1, et elle est tout le temps utilisé sur les processeurs modernes. Mais que faire en cas de défaut de cache ? ==Les caches non bloquants== Un '''cache bloquant''' est un cache auquel le processeur ne peut pas accéder pendant un défaut de cache. Il faut attendre que la lecture ou écriture en RAM soit terminée avant de pouvoir utiliser de nouveau le cache. Un '''cache non bloquant''' n'a pas ce problème : on peut l'utiliser pendant un défaut de cache. Les caches non bloquants permettent de démarrer une nouvelle lecture ou écriture alors qu'une autre est en cours, ce qui permet d'exécuter plusieurs lectures ou écritures en même temps. ===Les ''Miss Handling Status Registers''=== Lors d'un défaut de cache, la mémoire RAM est consultée pendant le défaut de cache, mais le cache est inutilisé. Un défaut de cache n'utilise pas le cache, ce n'est qu'un accès en mémoire RAM, sur le bus mémoire (ou un accès aux niveaux de cache inférieurs, peu importe). Le cache en lui-même est laissé libre, rien n’empêche d'y accéder, il est en réalité intrinsèquement non-bloquant. Les caches, bloquants comme non-bloquants, sont en réalité composés d'une mémoire cache proprement dite, entourée de circuits qui servent d'interface entre le processeur et le cache lui-même. Et parmi les circuits tout autour, certains gèrent l'accès au cache lors d'un défaut de cache. Ils sont regroupés sous le terme de '''''Miss Handling Architecture''''' (MHA). La différence entre un cache bloquant et un cache non-bloquant est en réalité liée à l'implémentation de la MHA. Les caches bloquants coupent volontairement l'accès au cache lors d'un défaut, car les défauts sont plus simples à gérer ainsi. Le défaut de cache rapatrie une donnée depuis la RAM, qui sera écrite dans le cache. Et il ne faut pas qu'une tentative d'accès à cette donnée ait lieu avant qu'elle ne soit chargée. Mais ce blocage est général et touche tout le cache, alors que seule une ligne de cache est concernée par le défaut de cache. L'idée derrière un cache non-bloquant est que seule la ligne de cache est bloquée, mais les autres sont accessibles. L'idée est alors de mémoriser les lignes de cache concernées par le défaut de cache, afin d'en bloquer l'accès. À chaque accès, on vérifie si la ligne de cache est déjà réservée par un défaut de cache. La lecture/écriture est alors bloquée si c'est le cas, mais elle accepte les accès sinon. Pour cela, la ''Miss Handling Architecture'' contient des registres qui mémorisent des informations sur les défauts de cache en cours. Ils portent le nom de '''''miss status handling registers''''', que j’appellerais dorénavant MSHR, qui sont aussi appelés des '''''miss buffer'''''. Le contenu des MSHR varie beaucoup suivant le processeur, mais ces derniers stockent au minimum les informations suivantes : * Le numéro de la ligne de cache dans laquelle les données sont chargées. * Un bit de validité qui indique si le MSHR est vide ou pas, qui est mis à 0 quand le défaut de cache est résolu. * Un ou plusieurs champs de lecture/écriture, qui contiennent des informations sur la lecture/écriture. ** Pour une lecture, elle contient des informations sur la destination de la donnée, à savoir qui prévenir quand le défaut de cache est terminé. C'est parfois un nom/numéro de registre (celui dans lequel charger la donnée), mais c'est souvent le numéro de l'entrée dans le ''load/store queue''. ** Pour les écritures, elle contient la donnée à écrire, ou éventuellement un numéro de ''load/store queue'' où se trouve la donnée à écrire. Il faut noter que le bus mémoire ne peut gérer qu'un seul défaut de cache à la fois. Aussi, il est intéressant de regarder ce qui se passe lorsqu'un second défaut de cache survient, pendant qu'un premier est en cours. Dans ce cas, il y a deux réponses qui correspondent à deux types de caches non-bloquants, qui portent les noms barbares de caches de type succès après défaut et défaut après défaut. Sur le premier type, il ne peut pas y avoir plusieurs défauts de cache simultanés. Dès qu'un second défaut de cache survient, le cache stoppe son activité et on ne peut plus démarrer de nouvelle lecture/écriture, tant que le premier défaut de cache n'est pas résolu. Le second type est plus souple et autorise la survenue de plusieurs défauts de cache simultanés. Du moins, jusqu'à une certaine limite, car le cache ne peut supporter qu'un nombre limité d'accès mémoires simultanés (pipelinés). ===Les accès simultanés à une même ligne de cache=== Il arrive que le processeur fasse plusieurs accès mémoire simultanés à la même ligne de cache. Si la ligne de cache en question n'a pas encore été chargée dans le cache, alors on a plusieurs défauts de cache consécutifs pour la même ligne de cache, et le cache non-bloquant doit gérer la situation. Pour la suite, il va falloir faire une petite distinction entre les défauts primaires et secondaires. Imaginons qu'un défaut de cache ait lieu et demande à charger une donnée dans la ligne de cache numéro N. Il s'agit du premier défaut impliquant cette ligne de cache précise, ce qui lui vaut le nom de '''défaut de cache primaire'''. Mais par la suite, d'autres accès mémoire à la même ligne de cache ont lieu, alors que la ligne de cache n'est pas encore disponible. Dans ce cas, il s'agit de '''défauts de cache secondaires'''. Pour l'unité d'accès mémoire, les défauts de cache primaire et secondaire sont différents (ils prennent tous une entrée dans la ''load/store queue''). Mais pour le cache, ils ne correspondent qu'à un seul accès au cache : celui qui demande de charger la ligne de cache demandée. Les défauts de cache primaire et secondaire à la même ligne de cache se voient attribuer un MSHR unique. La gestion des défauts de cache secondaires dépend du cache non-bloquant. La solution la plus simple ne permet pas les défauts de cache secondaires. Le cache ne permet pas deux défauts de cache simultanés pour la même ligne de cache. Les autres solutions le permettent, en fusionnant des accès simultanés à la même ligne de cache en un seul au niveau des MSHR. Dans tous les cas, détecter les défauts de cache secondaires sont un problème qu'il faut détecter. La ''Miss Handling Architecture'' doit détecter les défauts de cache secondaire. Pour cela, elle procède comme suit. Lors de chaque défaut de cache, la MHA récupère le numéro de la ligne de cache associé. Il vérifie alors chaque MSHR pour vérifier s'il contient le numéro en question. S'il n'y a aucune correspondance dans les MSHR, alors c'est signe que le défaut de cache est un défaut primaire. Mais s'il y en a une, alors c'est un défaut secondaire. Évidemment, cela signifie que lors d'un défaut de cache, le numéro de ligne de cache est envoyé à tous les MSHR, pour comparaison. Les MSHR sont donc regroupés dans une mémoire associative, une sorte de mini-cache, faciliter l'implémentation. Lors d'un défaut de cache primaire, l'accès à un cache non-bloquant se fait comme suit : le processeur envoie une adresse au cache, accède à celui-ci, et détecte la survenue d'un défaut de cache. Il en profite alors pour attribuer une ligne de cache dans laquelle sera chargée ce défaut. L'attribution est très simple dans le cas des caches ''direct mapped'', ou associatifs par voie, pour lesquels l'attribution se fait assez simplement. Il mémorise alors cette information dans les MSHR, après avoir vérifié que le défaut de cache n'était pas un défaut secondaire. Lors des accès ultérieurs à une adresse proche, censée être dans la même ligne de cache, le processeur va encore une fois rencontrer un défaut de cache. Il va alors déterminer le numéro de la ligne de cache associée à l'adresse, et comparer ce numéro avec les MSHRs. Si un MSHR contient ce numéro, c'est signe que le défaut de cache est un défaut secondaire. La MHA réagit alors différemment selon le processeur considéré. Une première solution n'autorise pas les défauts de cache secondaires. Si l'un d'entre eux survient, le processeur est gelé par un ''pipeline stall'', une bulle de pipeline. Une autre solution fusionne les défauts de cache secondaires avec le défaut de cache primaire : tout cela ne correspond qu'à un seul défaut de cache pour lui. ===Les MSHR simples=== Un cache non-bloquant à '''MSHR simple''' contient juste plusieurs MSHR qui mémorisent juste un numéro de ligne de cache, un bit de validité, le champ de lecture/écriture, et l'adresse exacte de lecture/écriture. Avec cette organisation, il est possible d'avoir plusieurs défauts de cache séparés, mais à la condition que chaque défaut accède à une ligne de cache différente. Deux accès simultanés à une même ligne de cache ne sont pas possibles, les défauts de cache secondaires ne sont pas autorisés. Ainsi, chaque défaut de cache se voit attribuer son propre MSHR, chacun contient un numéro de ligne de cache différent. Pour comprendre pourquoi c'est impossible de gérer les défauts secondaires, il faut regarder le champ de lecture/écriture. Si on veut effectuer plusieurs écritures consécutives à la même adresse, le MSHR n'aura pas de quoi mémoriser les deux données à écrire. Il pourra mémoriser la première donnée à écrire, pas la seconde. Même chose lors d'une lecture : le champ lecture/écriture peut mémorisr la destination de la première lecture, pas de la seconde. L'avantage est que la MHA n'a besoin que des MSHR et de quelques circuits annexes. Les autres solutions rajoutent des circuits annexes pour gérer les défauts de cache secondaires, qui utilisent beaucoup de circuits. Le cout en circuit est donc élevé, mais le gain en performance est là. Passons maintenant aux caches non-bloquants qui autorisent les défauts de cache secondaire. La solution la plus simple consiste à utiliser ===Les MSHR adressés implicitement=== Avec les '''MSHR adressés implicitement''', il est possible de fusionner plusieurs accès mémoire à une même ligne de cache, mais sous une condition très importante : ces accès lisent/écrivent des mots mémoire différents. Par exemple, imaginons qu'une ligne de cache contienne 8 mots mémoire de 64 bits. Si un premier accès mémoire lit le mot mémoire numéro 7 (dernier mot mémoire de la ligne), et le second accès le mot mémoire numéro 3, alors la fusion est possible. Mais si deux accès mémoire veulent lire/écrire le mot mémoire numéro 7, alors la fusion n'est pas possible et le processeur se bloque, un ''pipeline stall'' survient. Un MSHR adressé implicitement est un MSHR simple, qui contient naturellement un numéro de ligne de cache (tag) et un bit de validité, sauf que le champ de lecture/écriture est dupliqué. Les différents champs lecture/écriture d'un MSHR sont regroupés dans une mémoire RAM/cache qui contient autant d'entrées qu'il y a de mot mémoire dans une ligne de cache. Chaque entrée de la table est associée à un mot mémoire de la ligne de cache et stocke des informations sur celui-ci. Une entrée mémorise, au minimum : * Un champ de lecture/écriture qui contient soit la destination de la lecture, soit la donnée à écrire. * Un bit R/W permettant d'interpréter correctement le champ de lecture/écriture. * Un bit de validité pour chaque entrée de la table, qui dit si un défaut de cache antérieur accède déjà à ce mot mémoire. La fusion de deux défauts de cache est ainsi assez simple. Un défaut de cache primaire/secondaire configure l'entrée associée au mot mémoire lu/écrit. Si un défaut secondaire ultérieur lit/écrit un mot mémoire différent (dont le bit de validité de l'entrée dans la table est à zéro), pas de conflit : il configure une autre entrée, vide. Mais s'il lit/écrit un mot mémoire pour lequel l'entrée de la table est occupée, il y a conflit, le processeur est bloqué par un ''pipeline stall'', une bulle de pipeline. Avec cette organisation, le nombre de MSHR indique combien de lignes de cache peuvent être lues en même temps depuis la mémoire. Quant au nombre d'entrées par MSHR, il détermine combien d'accès mémoires qui ne se recouvrent pas peuvent avoir lieu en même temps. ===Les MSHR adressés explicitement=== Les '''MSHR adressés explicitement''' n'ont pas les contraintes des MSHR adressés implicitement. Avec eux, il est possible d'avoir plusieurs défauts de cache pointant vers la même ligne de cache, mais aussi vers le même mot mémoire. De tels défauts de cache apparaissent sur les processeurs à exécution dans le désordre, mais sont très rares, voire inexistants, sur les processeurs ''in-order''. L'idée est encore que chaque MSHR est associée à une table mémoire qui mémorise des entrées. Cette table n'est autre qu'une mémoire FIFO. Sauf que cette fois-ci, une entrée n'est pas associée à un mot mémoire. Une entrée est associée à un défaut de cache. Une entrée mémorise là encore la destination de la lecture ou la donnée de l'écriture suivi du bit R/W, ainsi que d'un bit de validité par entrée, mais aussi : la position du mot mémoire lu dans la ligne de cache. C'est cette dernière information qui n'était pas présente dans les MSHR adressés implicitement. De plus, le nombre d'entrée par MSHR n'est pas égal au nombre de mots mémoires dans une ligne de cache, mais peut être arbitrairement grand. L'avantage d'un tel cache est qu'il est capable de traité les défauts secondaires concernant un même mot mémoire, et ce, car les accès à la ligne de cache concernée se produisent dans l'ordre de génération des défauts (la table mémoire est une FIFO). ===Les MSHRs inversés=== Généralement, plus on veut supporter de défauts de cache, plus le nombre de MSHR et d'entrées augmente. Mais au-delà d'un certain nombre d'entrées et de MSHR, les MSHR adressés implicitement et explicitement ont tendance à bouffer un peu trop de circuits. Utiliser une organisation un peu moins gourmande en circuits est donc une nécessité. Cette organisation plus économe se base sur des MSHR inversés. Les MSHR inversés ne contiennent qu'une seule entrée, en quelque sorte : au lieu d’utiliser n MSHR de m entrées chacun, on va utiliser n × m MSHR inversés. La différence, c'est que plusieurs MSHR peuvent contenir un tag identique, contrairement aux MSHR adressés implicitement et explicitement. Lorsqu'un défaut de cache a lieu, chaque MSHR est vérifié. Si jamais aucun MSHR ne contient de tag identique à celui utilisé par le défaut, un MSHR vide est choisi pour stocker ce défaut, et une requête de lecture en mémoire est lancée. Dans le cas contraire, un MSHR est réservé au défaut de cache, mais la requête n'est pas lancée. Quand la donnée est disponible, les MSHR correspondant à la ligne qui vient d'être chargée vont être utilisés un par un pour résoudre les défauts de cache en attente. ===Les MSHR intégrés au cache=== Certains chercheurs ont remarqué que pendant qu'une ligne de cache est en train de subir un défaut de cache, celle-ci reste inutilisée, et son contenu est destiné à être perdu une fois le défaut de cache résolu. Ils se sont dit que, plutôt que d'utiliser des MSHR séparés, il vaudrait mieux utiliser la ligne de cache pour stocker les informations sur les défauts de cache en attente dans cette ligne de cache. Pour éviter tout problème, il faut rajouter un bit dans les bits de contrôle de la ligne de cache, qui sert à indiquer que la ligne de cache est occupée : un défaut de cache a eu lieu dans cette ligne, et elle stocke donc des informations pour résoudre les défauts de cache. ==L'entrelacement mémoire== Nous venons de voir comment une mémoire cache peut gérer plusieurs accès mémoire simultanés. Il se trouve que ce genre d'optimisation dépasse largement le cadre du cache. Les mémoires RAM ne sont pas en reste, elles aussi ! Il est possible de pipeliner l'accès à une mémoire RAM, en utilisant des techniques dites d''''entrelacement'''. Précisons cependant que cette optimisation n'est pas utilisée sur les mémoires SDRAM ou DDR modernes, du moins pas sans modifications majeures. Mais divers ordinateurs assez anciens, dont des superordinateurs, ont utilisé cette technique. La technique demande d'utiliser plusieurs mémoires séparées. Sur les anciens ordinateurs, les mémoires en question sont des chips mémoire, à savoir des circuits intégrés de DRAM. Pour faire plus général, nous allons utiliser le terme de '''banques''', ou encore de bancs mémoire. La différence est qu'il existe des mémoires multi-banque, qui regroupent plusieurs banques indépendantes dans un seul boitier, dans un seul chip mémoire. En clair, de telles mémoires regroupent plusieurs sous-mémoires dans un seul circuit intégré. Il est aussi possible d'utiliser plusieurs chips mémoire séparés, chacun ayant une banque. Reste à combiner ces banques pour former un pseudo-pipeline. ===Utiliser plusieurs banques sans entrelacement=== Sans optimisation particulière, les adresses sont réparties dans les banques comme indiqué ci-dessous. Il s'agit de ce qui s'appelle un arrangement horizontal, et nous avions vu celui-ci dans le chapiutre sur les mémoires SDRAM. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Avec cet arrangement, les bits de poids fort de l'adresse sont utilisées pour sélectionner la banque adéquate, et le reste de l'adresse est envoyé sur le bus d'adresse. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Adresse de banque !! Adresse dans la banque |- | Quelques bits de poids fort || Reste de l'adresse |} L'entrelacement change cette répartition, afin d'accélérer les accès mémoire. L'entrelacement classique vise à améliorer les accès à des adresses mémoires consécutives, mais des formes plus évoluées d'entrelacement visent à optimiser des accès mémoire arbitraires. Voyons-les dans l'ordre, du plus simple au plus complexe. ===L'entrelacement classique=== Avec l''''entrelacement classique''', des adresses consécutives sont réparties dans des banques consécutives. Précisons que cette attribution des adresses n'implique absolument pas la mémoire virtuelle ou n'importe quel mécanisme dans le processeur. La répartition décide que telle adresse mémoire va dans telle banque, à telle adresse dans la banque. Elle est donc le fait du contrôleur mémoire, donc en dehors du processeur (les contrôleurs mémoires n’étaient pas encore intégrés dans le processeur à l'époque). [[File:Répartition des adresses dans une mémoire interleaved.png|centre|vignette|upright=1.5|Répartition des adresses dans une mémoire interleaved.]] L'entrelacement simple permet d'accélérer les accès mémoire qui se font à des adresses consécutives. Avec l'entrelacement, chaque accès mémoire tombe dans une banque mémoire différente, ce qui fait qu'on peut démarrer un nouvel accès mémoire à chaque cycle d'horloge. Pendant qu'une banque est occupée par un accès mémoire, on démarre le suivant dans une autre banque, et ainsi de suite. Le tout se termine soit quand on a épuisé toutes les banques libres, soit quand l'accès en rafales se termine. Pas besoin d'attendre que la mémoire ait fini sa lecture/écriture avant de démarrer la lecture/écriture suivante. [[File:Pipemining mémoire.png|centre|vignette|upright=2.5|Pipelining mémoire]] L'animation suivante illustre bien le processus, avec quatres banques. [[File:Interleaving.gif|centre|vignette|upright=2.5|Pipelining mémoire]] L'entrelacement simple utilise les bits de poids faible pour sélectionner la banque, et les bits de poids fort pour la case mémoire. [[File:Adresse mémoire d'une mémoire entrelacée.png|centre|vignette|upright=2|Adresse mémoire d'une mémoire entrelacée]] Précisons que le temps d'accès mémoire ne change pas beaucoup avec l'entrelacement. Par contre, on peut faire plus d'accès mémoire simultanés. On peut démarrer un accès mémoire par cycle d'horloge, mais l'accès en lui-même prend plusieurs cycles. Les mémoires à entrelacement ont donc un débit supérieur aux mémoires qui ne l'utilisent pas. L'entrelacement classique pourrait être utilisé pour accélérer les transferts entre mémoire RAM et cache, qui se font en blocs d'adresses consécutives. Mais de nos jours, il n'est pas utilisé dans cette optique. Il faut dire qu'il est redondant avec le mode rafale des mémoires DRAM. Les deux font globalement la même chose et on voit mal comment utiliser les deux en même temps. Le mode rafale étant plus simple à implémenter, et plus léger niveau utilisation du bus de commande mémoire, il est préféré à l'entrelacement. Par contre, l'entrelacement a eu son heure de gloire sur d'anciens processeurs qui n'avaient pas de mémoire cache, alors qu'ils disposaient d'un pipeline. L'exemple typique est celui des nombreux ''processeurs vectoriels'', que nous n'avons pas encore abordé à ce stade du cours. De tels processeurs avaient un pipeline, une unité de calcul fortement pipelinée, et parfois même de l'exécution dans le désordre et du renommage de registres ! ===L'entrelacement par décalage=== L'entrelacement simple est très utile pour les accès en rafle ou équivalents. Par contre, il existe de rares situations où il n'est pas optimal. Une de ces situations est celle des accès en enjambée (en ''stride''), où l'on accède en série à des adresses sont séparées par N mots mémoires. [[File:Accès par enjambées.png|centre|vignette|upright=2|Accès par enjambées.]] De tels accès surviennent quand un logiciel accède à des structures de données spécifiques, à savoir des tableaux de structures, des matrices, ou d'autres structures de données dans le genre. Un cas classique est celui du parcours d'une matrice colonne par colonne. Les matrices de nombres sont mémorisées ligne par ligne, ce qui fait que parcourir une colonne demande de passer d'une ligne à la suivante et de faire des grands sauts en mémoire RAM, mais tous espacés par la même distance. Avec l'entrelacement simple, les accès en enjambée sont moins performants que les accès à des adresses consécutives, mais cela ne fonctionne pas trop mal. Le pire cas est celui où l'on a N banques et où les données sont justement placées toutes les N adresses. Des accès consécutifs vont tous tomber dans la même banque, on ne peut plus accéder à des banques différentes en parallèle. Mais en dehors de ce cas, on voit une amélioration par rapport à la situation sans entrelacement. Pour obtenir des performances maximales pour les accès en enjambées, il faut répartir les mots mémoires dans la mémoire autrement. L'organisation idéale est la suivante. : Dans les explications qui vont suivre, la variable N représente le nombre de banques, qui sont numérotées de 0 à N-1. On commence par organiser les N premières adresses comme une mémoire entrelacée simple : l'adresse 0 correspond à la banque 0, l'adresse 1 à la banque 1, etc. Pour le bloc suivant, on décale tout d'une adresse, à savoir qu'on commence à remplir les banques à partir de la seconde, non de la première. Une fois la fin du bloc atteinte, on finit de remplir le bloc en repartant du début du bloc. Le troisième bloc d'adresse subit le même traitement, sauf qu'on commence à remplir à partir de la seconde banque. Et on poursuit l’assignation des adresses en décalant d'un cran en plus à chaque bloc. Ainsi, chaque bloc verra ses adresses décalées d'un cran en plus comparé au bloc précédent. Si jamais le décalage dépasse la fin d'un bloc, alors on reprend au début. [[File:Mémoire interleaved par décalage.png|centre|vignette|upright=1.5|Mémoire entrelacée par décalage.]] En faisant cela, on remarque que les banques situées à N adresses d'intervalle sont différentes. Dans l'exemple du dessus, nous avons ajouté un décalage de 1 à chaque nouveau bloc à remplir. Mais on aurait tout aussi bien pu prendre un décalage de 2, 3, etc. Dans tous les cas, on obtient un entrelacement par décalage. Ce décalage est appelé le pas d'entrelacement, noté P. Le calcul de l'adresse à envoyer à la banque, ainsi que la banque à sélectionner se fait en utilisant les formules suivantes : * adresse à envoyer à la banque = adresse totale / N ; * numéro de la banque = (adresse + décalage) modulo N ; * décalage = (adresse totale * P) mod N. Avec cet entrelacement par décalage, on peut prouver que la bande passante maximale est atteinte si le nombre de banques est un nombre premier. Seulement, utiliser un nombre de banques premier peut créer des trous dans la mémoire, des mots mémoires inadressables. Malgré ce défaut, la technique a été utilisée sur quelques ordinateurs, avec l'exemple notable du superordinateur ''Burroughs Scientific Processor''. Pour éviter cela, il y a plusieurs solutions. Par exemple, on peut faire en sorte que N et la taille d'une banque soient premiers entre eux : ils ne doivent pas avoir de diviseur commun. Mais en pratique, elles n'ont pas vraiment été implémentées dans une vraie machine, et sont restées à l'état de recherche, aussi je les passe sous silence. ===L'entrelacement pseudo-aléatoire=== Une dernière méthode de répartition consiste à répartir les adresses dans les banques de manière "pseudo-aléatoire". L'idée exacte est de faire passer l'adresse dans une '''fonction de hachage''', plus ou moins complexe. Pour rappel, une fonction de hachage prend une entrée de grande taille et fournit en sortie un résultat de petite taille. Idéalement, elles doivent donner des résultats aussi différents que possible pour des entrées similaires, histoire de simuler une sorte de pseudo-aléatoire. Ici, une adresse est transformée en un numéro de banque. Une fonction de hachage simple ne fait que permuter des bits de l'adresse pour obtenir son résultat. Elle ne fait qu'échanger des bits de place avant de couper l'adresse en deux morceaux : un pour la sélection de la banque, et un autre pour la sélection de l'adresse dans la banque. Cette permutation est fixe, et ne change pas suivant l'adresse. Des fonctions de hachage plus complexes font des XOR bit à bit entre certains bits de l'adresse. ==Les contrôleurs SDRAM/DDR avec "exécution dans le désordre"== Après avoir vu le cas des mémoires RAM, nous allons nous concentrer sur le cas particulier des mémoires SDRAM. Les mémoires SDRAM et DDR modernes sont capables de gérer plusieurs accès simultanés à la mémoire RAM, et leurs contrôleurs mémoire en font tout autant. C'est une différence majeure avec les mémoires asynchrones FPM/EDO, qui n'acceptaient qu'un seul accès mémoire à la fois. Leur contrôleur mémoire n'acceptait qu'un seul accès mémoire à la fois, c'était un contrôleur mémoire bloquant. Les contrôleurs mémoires des SDRAM sont eux non-bloquants et peuvent encaisser une dizaine d'accès mémoire à la fois. Peu de choses sont connues sur les contrôleurs de SDRAM/DDR modernes, les fabricants ne donnant que peu de détails dessus. Les rares simulateurs qui tentent de décrire leur fonctionnement, comme DRAM SIM I et II, sont particulièrement simples et ne vont pas dans le détail. Néanmoins, le peu qu'on sait est tout de même instructif. ===Rappel sur les SDRAM : tampon de ligne et commandes=== Les mémoires SDRAM sont des mémoires à tampon de ligne. Elles font un accès mémoire en deux étapes. La première étape recopie une ligne de N * 64 bits dans un tampon interne à la SDRAM. La seconde étape sélectionne une colonne, à savoir une donnée de 64 bits, qui est soit envoyée sur le bus de données pour une lecture, soit modifiée par une écriture. [[File:Mémoire à tampon de ligne.png|centre|vignette|upright=2|Mémoire à tampon de ligne]] Les accès mémoire sont traduits par un séquenceur mémoire en une série de commandes mémoires, qui sont séparées par des délais mémoire de quelques cycles d'horloge. Les délais sont très précis, et sont à respecter à la lettre. Une lecture ou une écriture se fait en maximum trois commandes : une commande PRECHARGE qui ferme la ligne précédemment utilisée, une commande ACT qui précise l'adresse de la ligne, et une commande READ ou WRITE qui précise l'adresse de la colonne et éventuellement la donnée à écrire. : Pour être plus précis, la commande PRECHARGE précharge les lignes de bits du plan mémoire à une tension particulière, ce qui les vide de leur contenu. Mais c'est un détail sans importance pour ce qui va suivre. Les SDRAM permettent de se passer de la première étape si des accès consécutifs se font dans la même ligne. Une fois activée, la ligne reste ouverte et on peut accéder plusieurs fois de suite dedans. On a alors juste à préciser l'adresse de colonne dedans. Si un accès mémoire accède à une ligne déjà activée, on dit que c'est un '''succès de page'''. Si ce n'est pas le cas, on doit fermer la ligne courante, rouvrir la ligne vboulue, et préciser la colonne. C'est alors un '''défaut de page'''. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | bgcolor="#FFA0FF" | PRECHARGE || bgcolor="#FFA0FF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || bgcolor="#A0FFFF" | READ (2) || bgcolor="#A0FFFF" | READ (3) || || || bgcolor="#A0FFFF" | READ (4) || bgcolor="#A0FFFF" | READ (5) || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | Donnée READ 1 || ||bgcolor="#A0FFFF" | Donnée READ 2 | bgcolor="#A0FFFF" | Donnée READ 3 || || || bgcolor="#A0FFFF" | Donnée READ 4 || bgcolor="#A0FFFF" | Donnée READ 5 |} Les SDRAM sont parfois capables de démarrer une commande avant que la précédente soit terminée. Mais le respect des délais mémoire est très limitant, ce qui fait qu'on ne peut pas parler de réel pipeline, comme c'est le cas sur les processeurs. Parlons plutot de '''commandes anticipées'''. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | || bgcolor="#A0FFFF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || || || bgcolor="#FFA0FF" | READ (2) || || || || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 | bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 | |} Un dernier point est que les chips mémoires de SDRAM sont composés de plusieurs banques, chacune étant une sorte de mini-mémoire miniature. Chaque banque a son propre décodeur, son propre tampon de ligne, ses propres multiplexeurs de colonne, sa logique de rafraichissement mémoire, etc. C'est comme si une SDRAM regroupait plusieurs mémoires séparées dans un même circuit intégré. [[File:Arrangement vertical.jpg|centre|vignette|upright=2.5|Mémoire multi-banques.]] ===La mise en attente des accès mémoire=== Un contrôleur de SDRAM peut accepter plusieurs accès mémoire en même temps. Mais cela ne signifie pas que le contrôleur sera prêt à les traiter immédiatement. Pour éviter tout problème de disponibilité, le contrôleur met en attente les accès mémoire que le processeur lui envoie, pour les exécuter dès que possible. Les accès mémoire sont mis en attente dans une mémoire FIFO, histoire de les exécuter dans leur ordre d'arrivée, notamment pour renvoyer les lectures dans l'ordre demandé. Il y a aussi une mémoire FIFO pour les données à écrire et une FIFO pour les données lues. Cette dernière sert au cas où le cache ne soit pas disponible quand on lui envoie la donnée lue. [[File:Module d'interface avec la mémoire.png|centre|vignette|upright=2.5|Contrôleur mémoire avec mise en attente des requêtes processeur et autres optimisations.]] Des optimisations sont possibles dès la mémoire FIFO. Par exemple, on peut regrouper plusieurs accès à des données consécutives en un seul accès en rafale. Le contrôleur mémoire analyse les requêtes mises en attente et détecte si certaines se font à des adresses consécutives. Si c'est le cas, il fusionne ces requêtes en une lecture/écriture en rafale. Une telle optimisation est appelée la '''combinaison de lecture''' pour les lectures, et la '''combinaison d'écriture''' pour les écritures. L'optimisation précédente ne paraissent pas payer de mine, mais elles deviennent plus compréhensible quand on sait que certains contrôleurs mémoire peuvent mettre en attente beaucoup de données. Par exemple, l'''Intel E8870 - Scalable Memory Controller'' peut mettre en attente 64 lignes de cache dans la mémoire FIFO, soit 8 kibioctets de RAM ! Deux pages mémoire si la mémoire virtuelle utilise des pages de 4 kibioctets ! ===Les lectures anticipées : une forme d'OOO mémoire=== La mise en attente des accès mémoire est une optimisation intéressante, mais pas mirifique. Elle permet surtout de ne pas bloquer le processeur si le contrôleur mémoire a du travail sur la planche. Cependant, elle peut être optimisée quand on se rend compte d'une chose : le processeur ne voit que les lectures, pas les écritures. Pour les écritures, il les envoie au contrôleur mémoire et ce dernier fait le travail demandé, le processeur n'est pas prévenu quand une écriture se termine. Mais pour les lectures, c'est différent, car il reçoit la donnée lue. En conséquence, il est possible d'optimiser les écritures sans que le processeur ne voit quoique ce soit. Une optimisation possible est alors de changer l'ordre des accès mémoire, histoire de retarder les écritures ou au contraire de les faire en avance. La raison est que les mémoires SDRAM n'aiment pas quand on alterne lectures et écritures. Les délais mémoire, les fameux ''timings'' mémoire, sont clairs : faire une suite de lecture ou une suite d'écriture est plus performant que d'alterner entre lectures et écritures. L'idée est alors de changer l'ordre des écritures de manière à les faire en bloc, idem pour les lectures. L’optimisation est possible, mais à condition que le processeur n'y verra que du feu. Or, vous l'avez deviné, il y a des cas où ces réorganisations ne sont pas possibles, et vous avez sans doute trouvé que la situation était familière. L'optimisation en question est une forme d'exécution dans le désordre des accès mémoire, réalisée par le contrôleur mémoire lui-même ! Et qui dit exécution dans le désordre dit : problèmes liés aux dépendances de données. Changer l'ordre des écritures n'est pas toujours possible, notamment en raison des dépendances RAW, WAR et WAW. Pour que le processeur n'y voit que du feu, il faut respecter plusieurs critères. Premièrement, les lectures doivent être renvoyées au processeur dans l'ordre. Et quand je dis dans l'ordre, cela veut dire : dans l'ordre d'envoi des lectures au contrôleur mémoire. Les écritures aussi doivent se faire dans l'ordre, histoire qu'une série d'écriture donne le bon résultat final. Le troisième critère est qu'une lecture doit donner le résultat de la dernière écriture ''à la même adresse''. Pour le dire autrement, il faut juste détecter les dépendances RAW. Concrètement, si on a une série de lectures et d'écritures alternées, on peut regrouper les lectures et les écritures dans deux paquets séparés, à condition qu'aucune lecture ne lise une adresse écrite par une écriture. Mais si une lecture est dépendante d'une écriture, alors la lecture doit attendre que l'écriture se termine, idem pour les lectures suivantes. Une implémentation basique remplace la mémoire FIFO vue au-dessus, par deux mémoires FIFOs : une pour les lectures, une pour les écritures. Lorsqu'un accès mémoire arrive au séquenceur, il regarde si c'est une lecture ou une écriture et place l'accès mémoire dans la file adéquate. Le fait que ce soit des FIFOs garantit que les lectures se font dans l'ordre, idem pour les écritures. En sortie des deux FIFOs, le séquenceur mémoire détecte les dépendances RAW. Précisément, quand une lecture sort de la mémoire FIFO, il consulte la file d'attente des écriture pour voir si la lecture est dépendante d'une écriture en attente. La lecture attend si c'est le cas, et les écritures sont exécutées à la place. Séparer lectures et écriture est une source d'optimisation. Il est par exemple possible d'utiliser le réacheminement lecture sur écriture (''Store to load forwarding''). Il s'agit d'un équivalent de l’optimisation du même nom, utilisée dans le cadre de la désambiguïsation mémoire. Imaginez qu'une lecture accède à une donnée pas encore écrite, en attente dans le contrôleur mémoire. C'est une dépendance RAW assez claire. Dans ce cas, le contrôleur mémoire peut renvoyer la donnée directement depuis ses mémoires FIFOs, sans faire d'accès mémoire en lecture. Le contrôleur mémoire a tendance à privilégier les lectures, car celles-ci sont cruciales pour l'exécution dans le désordre. Plus elles se font tôt, plus vite le processeur recevra les données et pourra démarrer les instructions dépendantes de cette donnée. Le séquenceur a donc tendance à piocher en priorité dans la file de lecture, plutôt que dans la file d'écriture. A vrai dire, dans le cas idéal, les écritures ne sont faites que quand la file d'écriture est pleine ou quasi-pleine... Prenons l'exemple des coprocesseurs IO 81341 et 81342, qui étaient en réalité des ''chipsets'' intégrant un contrôleur SDRAM. Le ''chipset'' avait 5 ports : un pour les processeurs, un pour le pont sud (''southbridge'') et trois pour des canaux DMA. Pour le port processeur, il n'y avait pas de réordonnancement et ce port utilisait une seule mémoire FIFO. Les autres ports utilisaient l'optimisation qu'on vient de voir, et avaient vraisemblablement des files séparées pour les lectures et écritures. Le séquenceur mémoire vérifiait les dépendances mémoire de type RAW et autres. [[File:Double file d'attente pour les lectures et écritures.png|centre|vignette|upright=2.5|Double file d'attente pour les lectures et écritures]] ===Le ré-ordonnancement des commandes mémoires=== L'optimisation précédente est peu poussée, comparé aux formes d'exécution dans le désordre que les processeurs utilisent. Et on peut se demander si une exécution dans le désordre plus poussée est possible. La réponse est oui : un contrôleur mémoire peut faire des réorganisations bien plus poussées. Par contre, il faut faire une précision très importante : le contrôleur mémoire ne remet pas les accès mémoire dans l'ordre avant de les envoyer au processeur. C'est le processeur qui remet en ordre les accès mémoire, dans sa ''load queue''. En clair : le processeur voit les accès mémoire dans le désordre, c'est lui qui les remet en ordre. Pour cela, les requêtes mémoire sont "numérotées", à savoir qu'on leur attribue un identifiant binaire. Le contrôleur mémoire exécute les lectures/écritures dans le désordre, mais garde la trace de leur identifiant. Il sait dans quel ordre il exécute les accès mémoire et connait leur latence. Ce qui fait qu'il sait que la donnée lue à tel instant est associée à la requête mémoire numéro X. Pour les lectures, il renvoie la donnée lue avec l'identifiant. Le processeur sait alors à quelle lecture la donnée lue correspond et il se débrouille en interne pour gérer la situation. Maintenant que ces précisions sont faites, posons cette question : dans quelles situations est-il pertinent de faire des accès mémoire dans le désordre ? La réponse est : quand plusieurs accès à une même ligne ne sont pas consécutifs, qu'il y a des accès entre les deux. Après ré-ordonnancement, ces accès mémoire à une même ligne sont exécutés l'un à la suite de l'autre, ce qui est beaucoup plus rapide. Pour rendre le tout plus concret, voici un exemple. Imaginez que l'on sait les 3 accès mémoire suivants : * Une lecture ligne A ; * PRECHARGE + ACT ; * Une écriture ligne B ; * PRECHARGE + ACT ; * Une lecture ligne A. Sans réordonnancent, on doit émettre deux commandes PRECHARGE quand on passe d'une ligne à l'autre, et il faut ajouter les commandes ACT avec. pour éviter cela, le contrôleur mémoire peut retarder l'écriture, ou au contraire avancer la seconde lecture. le résultat est alors le suivant : * Lecture ligne A ; * Lecture ligne A * PRECHARGE + ACT ; * Une écriture ligne B. On a donc deux accès consécutifs à la même ligne, suivi par une écriture dans une autre ligne. La technique marche parce que l'on a un mix adéquat de lectures et d'écritures. Et encore une fois, il faut éviter d'intervertir lectures et écritures à une même adresse, sous peine de problèmes. Encore une fois, les dépendances RAW posent problème ! L'implémentation est assez simple : la ou les mémoires FIFOs précédentes sont remplacées par des mémoires similaires aux fenêtres d'instruction. Le contrôleur mémoire vérifie à chaque cycle les accès en attente, et vérifie à quelle ligne ils accèdent. Il priorise alors les accès qui tombent dans la ligne ouverte : ceux-là sont exécutés avant les autres. S'il n'y en a pas, il prend l'accès mémoire le plus ancien (ordre FIFO). Il teste aussi les dépendances mémoires RAW avant d'envoyer des commandes à la mémoire DDR/SDRAM. une telle solution est appelée l''''algorithme ''FR-FCFS''''' (''First Ready-First Come First Serve''). ===Les optimisations liées à la présence de plusieurs banques=== Les optimisations précédentes sont décuplées par la présence de plusieurs banques dans la mémoire SDRAM. Il est en effet possible de faire plusieurs accès mémoire en même temps, dans des banques différentes. Il est possible de lancer un accès mémoire dans deux banques en même temps, par exemple. La seule contrainte est que la SDRAM est limitée à 4 banques actives en même temps. {|class="wikitable" |+ Pipelining basique sur les SDRAM |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 |- ! Banque Numéro 1 | || || || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || || |- ! Banque Numéro 2 | || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || |- ! Banque Numéro 3 | || || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || |} Les optimisations précédentes peuvent s'appliquer par banque. Il est par exemple possible d'utiliser une mémoire FIFO par banque. Ainsi, les accès dans une même banque se feront en série, dans l'ordre d'arrivée, mais des accès à des banques différentes se feront dans le désordre. Cela ne pose pas de problème car les accès se font à des adresses différentes, ce qui fait qu'il n'y a pas de conflits majeurs. Il y a cependant un risque que les lectures se fassent dans le désordre si on n'y prend pas garde. [[File:Gestion parallèle des banques.png|centre|vignette|upright=2.5|Gestion parallèle des banques.]] Il est aussi possible d'utiliser une file séparée pour les lectures et les écriture, pour chaque banque. Les performances sont alors améliorées comparé à deux files globales pour toute la SDRAM. En idem avec la réorganisation des accès mémoire, pour regrouper les accès à une même ligne. Un problème avec ces optimisations est qu'il est rare que des accès mémoire se fassent dans des banques séparées. Les accès mémoire tendent à se faire avec une bonne localité spatiale, ce qui fait qu'ils tombent dans une même banque. Heureusement, les contrôleurs SDRAM/DDR modernes incorporent des optimisations pour corriger ce problème. Les optimisations en question sont une forme d'entrelacement adaptée aux mémoires SDRAM. L'entrelacement naïf ne marche pas à cause de la présence du tampon de ligne. Cependant, il peut y avoir un entrelacemententre banques SDRAM. Voyons ce que ca veut dire. Pour gérer l'entrelacement, le contrôleur de SDRAM prend en entrée l'adresse envoyée par le processeur, et la découpe en plusieurs champs : un pour sélectionner la banque adéquate, un autre pour la ligne, un autre pour la colonne, un autre pour la rangée. Sans entrelacement, les adresses mémoire sont découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- | Adresse de banque || Adresse de ligne || Adresse de colonne |} L''''entrelacement de banques''' répartit deux lignes consécutives dans deux banques différentes. Pour comprendre l'idée, prenons un exemple. Imaginons une mémoire avec deux banques et 4 lignes. Imaginons qu'on parcoure/balaye la mémoire RAM en partant des adresses basses. Sans entrelacement de ligne, les accès se feront comme suit, de gauche à droite : {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Banque numéro 1 !! colspan="4" | Banque numéro 2 |- | Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 || Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 |} Avec l'entrelacement de banques, les accès se feront comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 |- | Ligne 1 || Ligne 1 || Ligne 2 || Ligne 2 || Ligne 3 || Ligne 3 || Ligne 4 || Ligne 4 |} Avec N banques, la première ligne ira dans la première banque, la seconde ligne dans la seconde, et ainsi de suite jusqu'à la énième banque qui va dans la banque numéro N. Et la ligne N+1 repart dans la première banque. L'avantage est que passer d'une ligne à la suivante est assez rapide. Au lieu de devoir fermer la première ligne et ouvrir la suivante on ouvrira directement la ligne suivante dans une banque différente, ce qui sera plus rapide. La commande PRECHARGE pourra être envoyée à la seconde banque en avance. Pour cela, l'adresse est découpée comme suit. {|class="wikitable" |+ Adresse mémoire |- | Adresse de ligne || Adresse de banque || Adresse de colonne (précise à l'octet) |} : Il est aussi possible de faire la même chose, mais avec les rangées. L'usage du ''dual channel'' est aussi une forme d'entrelacement entre barrettes de RAM. Mais je vais passer cela sous silence. D'autres méthodes d'entrelacement sont possibles, mais elles sont plus complexes. Chaque contrôleur mémoire fait un peu à sa sauce. Ils permettent en général de choisir entre plusieurs entrelacements. Au minimum, ils permettent de désactiver l'entrelacement, d'activer un entrelacement de ligne, et d'autres entrelacements plus complexes, dont certains sont propriétaires et tenus secrets par le fabricant. L'entrelacement est géré juste avant le séquenceur mémoire, dans un '''circuit d'entrelacement'''. Ce dernier intervertit certains bits de l'adresse lors des accès mémoires. [[File:Controleur mémoire d'une SDRAM avec entrelacement.png|centre|vignette|upright=2.5|Contrôleur mémoire d'une SDRAM avec entrelacement.]] Les contrôleurs de SDRAM précédents sont assez basiques et ne représentent pas les contrôleurs les plus évolués. Ils étaient utilisés sur les anciens PC, à une époque où ils étaient encore sur la carte mère du processeur. Mais de nos jours, le contrôleur mémoire est intégré au processeur. Et il incorpore de nombreuses optimisations afin de gagner en performances. Malheureusement, ces optimisations sont elles-mêmes dépendantes des optimisations intégrées dans le processeur, ce qui fait qu'on ne peut pas en parler ici. Elles demandent que le contrôleur mémoire reçoive plusieurs accès mémoire simultanés, ce qui n'a aucun sens à ce stade du cours. Aussi, nous allons les passer sous silence pour le moment. Nous les verrons dans le chapitre sur le parallélisme mémoire. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=La désambiguïsation mémoire | prevText=La désambiguïsation mémoire | next=Les processeurs superscalaires | nextText=Les processeurs superscalaires }} </noinclude> 3chxqrvj5xih73gpg31r7bok1tsdbim 764818 764817 2026-04-24T10:50:52Z Mewtow 31375 /* Les optimisations liées à la présence de plusieurs banques */ 764818 wikitext text/x-wiki Dans ce chapitre, nous allons voir les techniques qui permettent de gérer plusieurs accès mémoire simultanés directement au niveau du cache ou de la mémoire RAM. Par plusieurs accès mémoire simultanés, vous pensez sans doute à l'usage de cache multiports, voire de mémoires RAM multiport. C'est en effet une solution, mais ce n'est clairement pas la seule. Par exemple, il est possible de pipeliner l'accès au cache, voire à la mémoire RAM. Il existe de nombreuses techniques de '''parallélisme mémoire''', et nous allons les voir dans ce chapitre. Pour donner un exemple, il est possible de pipeliner les accès mémoire. Il est possible de pipeliner l'accès au cache et/ou l'accès à la mémoire, et nous verrons comment faire dans ce chapitre. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, qu'ils aient ou non un pipeline dynamique. Par contre, pipeliner la mémoire est une technique ancienne, aujourd'hui peu utilisée. Elle était utilisée sur des très vieux ordinateurs, qui avaient un pipeline mais pas de mémoire cache. Mais de nos jours, les mémoires SDRAM et DDR ne sont pas adaptées pour ça, il est très difficile de les pipeliner correctement. Le parallélisme mémoire est utile aussi bien sur des processeurs à émission dans l'ordre que dans le désordre. Par exemple, un processeur ''in-order'' avec des lectures non-bloquantes peut e, profiter. Il peut techniquement lancer une seconde lecture, après avoir rencontré une première lecture non-bloquante. En général, le nombre de lectures consécutives est cependant limité à moins d'une dizaine. À l'opposé, les processeurs sans lectures non-bloquantes profitent pas du parallélisme mémoire. À la rigueur, ils peuvent en profiter s'ils intègrent des techniques de préchargement. Pendant que le processeur exécute une lecture/écriture, il peut précharger une autre donnée en parallèle. Inutile de dire que sans pipeline processeur, le parallélisme mémoire ne sert pas à grand-chose. ==Le ''line fill buffer'' et les techniques associées== Lors d'un défaut de cache, le processeur doit attendre que toute la ligne de cache soit chargée avant d'être utilisable. Or, la taille d'une ligne de cache est supérieure à la largeur du bus mémoire, ce qui fait qu'une ligne de cache est chargée en plusieurs fois, morceaux par morceaux, mot mémoire par mot mémoire. Le chargement peut se faire directement dans le cache, mais ce n'est pas une solution très pratique. À la place, beaucoup de processeurs ajoutent une mémoire tampon entre la RAM et le cache, appelée le '''tampon de remplissage de ligne''' (''line-fill buffer'') dans les processeurs modernes. Lors d'un défaut de cache, le processeur charge la donnée de la RAM dans le tampon de remplissage de ligne, mot mémoire par mot mémoire. Une fois plein, le tampon de remplissage de ligne est recopié dans la ligne de cache. Le tampon de remplissage de ligne contient une ligne de cache, si ce n'est que certains bits de contrôle ne sont pas présents. Le tampon de remplissage de ligne contient un bit de validité pour chaque mot mémoire de la ligne de cache, qui indiquent si le mot mémoire a été chargé. Par exemple, prenons un processeur 64 bits, qui gère donc des mots mémoire de 8 octets, avec des lignes de cache 256 octets/32 mots mémoire. Le tampon de remplissage de ligne contiendra 32 bits de validité. Si le processeur a chargé les 6 premiers mots mémoire, les 6 premiers bits de validité seront mis à 1, les autres seront encore à zéro. Il faut noter que la disponibilité de la ligne complète se détermine assez facilement en faisant un ET logique entre tous les bits de validité. Le processeur sait ainsi quand la ligne de cache est disponible entièrement et donc quand la transférer dans le cache. La même chose existe avec une hiérarchie de cache, sauf que l'on trouve une mémoire tampon entre chaque niveau de cache. Il y a un tampon de remplissage de ligne entre le cache L1 et le cache L2, entre le cache L2 et le L3, etc. Si le cache ne gère qu'un seul défaut de cache à la fois, le ''fill line buffer'' est une mémoire très simple, qui ne mémorise qu'une seule ligne de cache. Et cela vaut aussi bien pour un cache bloquant que non-bloquant. Mais sur les caches capables de gérer plusieurs défauts simultanés, le tampon de remplissage de ligne est une mémoire de type FIFO ou LIFO, capable de mémoriser plusieurs lignes de cache. Notons que le tampon de remplissage de ligne est très utile pour implémenter certaines techniques, par exemple le contournement du cache. Nous avions vu dans le chapitre sur le cache que certains accès mémoire doivent contourner le cache, pour des raisons de cohérence des caches. C'est notamment nécessaire pour accéder aux périphériques, mais c'est aussi utile pour des raisons de performances dans des cas très spécifiques. Les accès qui contournent le cache se font directement dans le tampon de remplissage de ligne : le processeur écrit ou lit les données depuis ce tampon de remplissage de ligne, sans accéder au cache. ===L'''early restart'' et le ''critical word load''=== La présence du ''line buffer'' permet une optimisation assez intéressante, qui permet de réduire la latence des défauts de cache. L'optimisation consiste à lire un mot mémoire dans le tampon de remplissage de ligne, même si la ligne de cache complète n'a pas encore été chargée. Il existe deux manières de faire cela, qui portent les noms d'''early restart'' et de ''critical word load''. La première est la version la plus simple, la seconde est plus complexe mais plus performante. Avec la technique d''''''early restart''''', la ligne de cache est chargée normalement, en partant de son premier mot mémoire. Dès que le mot mémoire lu/écrit par le processeur est copié dans le ''line fill buffer'', il est envoyé au processeur immédiatement. Illustrons le tout par un exemple, où une ligne de cache fait 16 mots mémoire. Le processeur effectue une lecture, qui lit le 5ème mot mémoire. Sans ''early restart'', le processeur doit charger les 16 mots mémoire avant de faire la lecture dans le cache. Avec ''early restart'', le processeur reçoit la donnée dès que le 5ème mot mémoire est disponible. Le processeur doit attendre que les 4 premiers mots mémoire soient chargés, puis le 5ème mot mémoire arrive et est envoyé directement au processeur, il est lu directement depuis le ''line fill buffer''. Les 11 mots mémoire suivants sont ensuite chargés dans le cache pendant que le processeur fait des calculs dans son coin. Le ''critical word load'' est une optimisation de la technique précédente où le chargement de la ligne de cache commence directement à la donnée demandée par le processeur. Pour reprendre l'exemple précédent, où le processeur demande le 5ème mot mémoire sur 16, le ''critical word load'' charge le 5ème mot mémoire en premier et l'envoie au processeur, ce qui fait qu'il est chargé très rapidement. Pas besoin d'attendre que le processeur charge les 4 mots mémoire précédents comme avec l'''early restart''. Dans le détail, le processeur charge le 5ème mot mémoire en premier, puis charge les 11 suivants, et termine par les 4 mots mémoire du début. En clair, le ''critical word load'' commence par charger le mot mémoire lu, puis les blocs suivants, avant de revenir au début du bloc pour charger les blocs restants. Ainsi, la donnée demandée par le processeur sera la première disponible. Pour cela, l'organisation du tampon de remplissage de ligne est modifiée de manière à rendre cela possible. Il a une taille égale à une ligne de cache complète, qui contient elle-même plusieurs mots mémoire. Dans le ''line-fill buffer'', chaque mot mémoire est stocké avec un tag, qui indique l'adresse du mot mémoire stocké dans le ''line-fill buffer''. Le ''line-fill buffer'' est donc un cache un peu particulier, qui fonctionne comme un cache du point de vue du processeur, comme une mémoire FIFO pour les transferts avec le cache. Ainsi, un processeur qui veut lire dans le cache après un défaut peut accéder à la donnée directement depuis le tampon de remplissage de ligne, alors que la ligne de cache n'a pas encore été totalement recopiée en mémoire. ==Les caches pipelinés== Il est possible de pipeliner l'accès au cache, ce qui demande juste de rajouter des registres au bon endroits et quelques circuits de contrôle. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, même ceux avec un pipeline dynamique. Et l'implémentation peut se faire de plusieurs manières différentes. Il faut dire que découper une mémoire cache en plusieurs étapes peut se faire de plusieurs manière. Commençons par la plus simple. Beaucoup de processeurs des années 2000 avaient une fréquence peu élevée comparée aux standards d'aujourd'hui, ce qui fait que leur cache avait bien un temps d'accès d'un cycle d'horloge. Mais l'accès au cache se faisait en deux cycles d'horloge : un cycle pour calculer une adresse et un cycle pour l'accès mémoire proprement dit. Et ces deux étapes étaient pipelinées, à savoir que deux micro-opérations mémoire peuvent s'exécuter en même temps : une dans l'étage de calcul d'adresse, une autre dans le cache. L'unité mémoire était donc pipelinée, alors que l'accès au cache ne l'était pas. Les processeurs qui implémentaient cette technique regroupent les micro-architectures K5 et K6 d'AMD, les processeurs Intel de micro-architecture P6 (Pentium 2 et 3) et quelques autres. Il est aussi possible de pipeliner l'accès au cache lui-même. Avec un cache pipeliné, l'accès au cache ne se fait pas en un seul cycle, mais en plusieurs. Cependant, on peut lancer un nouvel accès au cache à chaque cycle d'horloge, comme un processeur pipeliné lance une nouvelle instruction à chaque cycle. L'implémentation est assez simple : il suffit d'ajouter des registres dans le cache. Pour cela, on profite que le cache est composé de plusieurs composants séparés, qui échangent des données dans un ordre bien précis, d'un composant à un autre. De plus, le trajet des informations dans un cache est linéaire, ce qui les rend parfois pour l'usage d'un pipeline. ===Le ''pipelining'' des caches ''direct-mapped''=== Voici ce qui se passe avec un cache directement adressé. Pour rappel ce genre de cache est conçu en combinant une mémoire RAM, généralement une SRAM, avec quelques circuits de comparaison et un MUX. L'accès au cache se fait globalement en deux étapes : la première lit la donnée et le ''tag'' dans la SRAM, et on utilise les deux dans une seconde étape. Nous avions vu il y a quelques chapitre comment pipeliner des mémoires, dans le chapitre sur les mémoires évoluées. Et bien ces méthodes peuvent s'utiliser pour la mémoire RAM interne au cache !L'idée est d'insérer un registre entre la sortie de la RAM et la suite du cache, pour en faire un cache pipéliné, à deux étages. Le premier étage lit dans la SRAM, le second fait le reste. L'implémentation sur les caches associatifs à plusieurs voies est globalement la même à quelques détails près. [[File:Cache directement adressé pipeliné - deux étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - deux étages]] Avec le circuit précédent, il est possible d'aller plus loin, cette fois en pipelinant l'accès à la mémoire RAM interne au cache. Pour comprendre comment, rappelons qu'une mémoire SRAM est composée d'un plan mémoire et d'un décodeur. L'accès à la mémoire demande d'abord que le décodeur fasse son travail pour sélectionner la case mémoire adéquate, puis ensuite la lecture ou écriture a lieu dans cette case mémoire. L'accès se fait donc en deux étapes successives séparées, on a juste à mettre un registre entre les deux. Il suffit donc de mettre un gros registre entre le décodeur et le plan mémoire. [[File:Cache directement adressé pipeliné - trois étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - trois étages]] Et on peut aller encore plus loin en découpant le décodeur en deux circuits séparés. En effet, rappelez-vous le chapitre sur les circuits de sélection : nous avions vu qu'il est possible de créer des décodeurs en assemblant des décodeurs plus petits, contrôlés par un circuit de prédécodage. Et bien on peut encore une ajouter un registre entre ce circuit de prédécodage et les petits décodeurs. : Théoriquement, toute l'adresse est fournie d'un seul coup au cache, la quasi-totalité des processeurs présentent l'adresse complète à un cache pipeliné. Mais le Pentium 4 fait autrement. Il faut noter que les premiers étages manipulent l'indice dans la SRAM, qui est dans les bits de poids faible, alors que les étages ultérieurs manipulent le ''tag'' qui est dans les bits de poids fort. Les concepteurs du Pentium 4 ont alors décidé de présenter les bits de poids faible lors du premier cycle d'accès au cache, puis ceux de poids fort au second cycle. Pour cela, l'ALU fonctionnait à une fréquence double de celle du processeur, tout comme le cache L1. Il n'y avait pas de pipeline proprement dit, mais cela réduisait grandement la latence d'accès au cache. ===Le ''pipelining'' des caches splittés/sériels=== Il est aussi possible de pipeliner un cache dits splittés, aussi appelés à accès sériel. Pour rappel, les caches à accès sériel vérifient si il y a succès ou défaut de cache, avant d'accéder aux lignes de cache en cas de succès. Ils font donc différemment des autres caches, qui accèdent à une ligne de cache, avant de déterminer s'il y a succès ou défaut en lisant le tag de la ligne de cache. Les caches sériels disposent de deux SRAM : une pour les tags des lignes de cache et une pour les données. Ils accèdent à la SRAM pour les tags, avant d’accéder à la SRAM des données en cas de succès de cache. Vu que l'accès se fait en deux étapes, une vérification des tags suivie de la lecture/écriture des données, il est facile à pipeliner. Pipeliner le cache permet de régler le problème des accès au cache L1, et elle est tout le temps utilisé sur les processeurs modernes. Mais que faire en cas de défaut de cache ? ==Les caches non bloquants== Un '''cache bloquant''' est un cache auquel le processeur ne peut pas accéder pendant un défaut de cache. Il faut attendre que la lecture ou écriture en RAM soit terminée avant de pouvoir utiliser de nouveau le cache. Un '''cache non bloquant''' n'a pas ce problème : on peut l'utiliser pendant un défaut de cache. Les caches non bloquants permettent de démarrer une nouvelle lecture ou écriture alors qu'une autre est en cours, ce qui permet d'exécuter plusieurs lectures ou écritures en même temps. ===Les ''Miss Handling Status Registers''=== Lors d'un défaut de cache, la mémoire RAM est consultée pendant le défaut de cache, mais le cache est inutilisé. Un défaut de cache n'utilise pas le cache, ce n'est qu'un accès en mémoire RAM, sur le bus mémoire (ou un accès aux niveaux de cache inférieurs, peu importe). Le cache en lui-même est laissé libre, rien n’empêche d'y accéder, il est en réalité intrinsèquement non-bloquant. Les caches, bloquants comme non-bloquants, sont en réalité composés d'une mémoire cache proprement dite, entourée de circuits qui servent d'interface entre le processeur et le cache lui-même. Et parmi les circuits tout autour, certains gèrent l'accès au cache lors d'un défaut de cache. Ils sont regroupés sous le terme de '''''Miss Handling Architecture''''' (MHA). La différence entre un cache bloquant et un cache non-bloquant est en réalité liée à l'implémentation de la MHA. Les caches bloquants coupent volontairement l'accès au cache lors d'un défaut, car les défauts sont plus simples à gérer ainsi. Le défaut de cache rapatrie une donnée depuis la RAM, qui sera écrite dans le cache. Et il ne faut pas qu'une tentative d'accès à cette donnée ait lieu avant qu'elle ne soit chargée. Mais ce blocage est général et touche tout le cache, alors que seule une ligne de cache est concernée par le défaut de cache. L'idée derrière un cache non-bloquant est que seule la ligne de cache est bloquée, mais les autres sont accessibles. L'idée est alors de mémoriser les lignes de cache concernées par le défaut de cache, afin d'en bloquer l'accès. À chaque accès, on vérifie si la ligne de cache est déjà réservée par un défaut de cache. La lecture/écriture est alors bloquée si c'est le cas, mais elle accepte les accès sinon. Pour cela, la ''Miss Handling Architecture'' contient des registres qui mémorisent des informations sur les défauts de cache en cours. Ils portent le nom de '''''miss status handling registers''''', que j’appellerais dorénavant MSHR, qui sont aussi appelés des '''''miss buffer'''''. Le contenu des MSHR varie beaucoup suivant le processeur, mais ces derniers stockent au minimum les informations suivantes : * Le numéro de la ligne de cache dans laquelle les données sont chargées. * Un bit de validité qui indique si le MSHR est vide ou pas, qui est mis à 0 quand le défaut de cache est résolu. * Un ou plusieurs champs de lecture/écriture, qui contiennent des informations sur la lecture/écriture. ** Pour une lecture, elle contient des informations sur la destination de la donnée, à savoir qui prévenir quand le défaut de cache est terminé. C'est parfois un nom/numéro de registre (celui dans lequel charger la donnée), mais c'est souvent le numéro de l'entrée dans le ''load/store queue''. ** Pour les écritures, elle contient la donnée à écrire, ou éventuellement un numéro de ''load/store queue'' où se trouve la donnée à écrire. Il faut noter que le bus mémoire ne peut gérer qu'un seul défaut de cache à la fois. Aussi, il est intéressant de regarder ce qui se passe lorsqu'un second défaut de cache survient, pendant qu'un premier est en cours. Dans ce cas, il y a deux réponses qui correspondent à deux types de caches non-bloquants, qui portent les noms barbares de caches de type succès après défaut et défaut après défaut. Sur le premier type, il ne peut pas y avoir plusieurs défauts de cache simultanés. Dès qu'un second défaut de cache survient, le cache stoppe son activité et on ne peut plus démarrer de nouvelle lecture/écriture, tant que le premier défaut de cache n'est pas résolu. Le second type est plus souple et autorise la survenue de plusieurs défauts de cache simultanés. Du moins, jusqu'à une certaine limite, car le cache ne peut supporter qu'un nombre limité d'accès mémoires simultanés (pipelinés). ===Les accès simultanés à une même ligne de cache=== Il arrive que le processeur fasse plusieurs accès mémoire simultanés à la même ligne de cache. Si la ligne de cache en question n'a pas encore été chargée dans le cache, alors on a plusieurs défauts de cache consécutifs pour la même ligne de cache, et le cache non-bloquant doit gérer la situation. Pour la suite, il va falloir faire une petite distinction entre les défauts primaires et secondaires. Imaginons qu'un défaut de cache ait lieu et demande à charger une donnée dans la ligne de cache numéro N. Il s'agit du premier défaut impliquant cette ligne de cache précise, ce qui lui vaut le nom de '''défaut de cache primaire'''. Mais par la suite, d'autres accès mémoire à la même ligne de cache ont lieu, alors que la ligne de cache n'est pas encore disponible. Dans ce cas, il s'agit de '''défauts de cache secondaires'''. Pour l'unité d'accès mémoire, les défauts de cache primaire et secondaire sont différents (ils prennent tous une entrée dans la ''load/store queue''). Mais pour le cache, ils ne correspondent qu'à un seul accès au cache : celui qui demande de charger la ligne de cache demandée. Les défauts de cache primaire et secondaire à la même ligne de cache se voient attribuer un MSHR unique. La gestion des défauts de cache secondaires dépend du cache non-bloquant. La solution la plus simple ne permet pas les défauts de cache secondaires. Le cache ne permet pas deux défauts de cache simultanés pour la même ligne de cache. Les autres solutions le permettent, en fusionnant des accès simultanés à la même ligne de cache en un seul au niveau des MSHR. Dans tous les cas, détecter les défauts de cache secondaires sont un problème qu'il faut détecter. La ''Miss Handling Architecture'' doit détecter les défauts de cache secondaire. Pour cela, elle procède comme suit. Lors de chaque défaut de cache, la MHA récupère le numéro de la ligne de cache associé. Il vérifie alors chaque MSHR pour vérifier s'il contient le numéro en question. S'il n'y a aucune correspondance dans les MSHR, alors c'est signe que le défaut de cache est un défaut primaire. Mais s'il y en a une, alors c'est un défaut secondaire. Évidemment, cela signifie que lors d'un défaut de cache, le numéro de ligne de cache est envoyé à tous les MSHR, pour comparaison. Les MSHR sont donc regroupés dans une mémoire associative, une sorte de mini-cache, faciliter l'implémentation. Lors d'un défaut de cache primaire, l'accès à un cache non-bloquant se fait comme suit : le processeur envoie une adresse au cache, accède à celui-ci, et détecte la survenue d'un défaut de cache. Il en profite alors pour attribuer une ligne de cache dans laquelle sera chargée ce défaut. L'attribution est très simple dans le cas des caches ''direct mapped'', ou associatifs par voie, pour lesquels l'attribution se fait assez simplement. Il mémorise alors cette information dans les MSHR, après avoir vérifié que le défaut de cache n'était pas un défaut secondaire. Lors des accès ultérieurs à une adresse proche, censée être dans la même ligne de cache, le processeur va encore une fois rencontrer un défaut de cache. Il va alors déterminer le numéro de la ligne de cache associée à l'adresse, et comparer ce numéro avec les MSHRs. Si un MSHR contient ce numéro, c'est signe que le défaut de cache est un défaut secondaire. La MHA réagit alors différemment selon le processeur considéré. Une première solution n'autorise pas les défauts de cache secondaires. Si l'un d'entre eux survient, le processeur est gelé par un ''pipeline stall'', une bulle de pipeline. Une autre solution fusionne les défauts de cache secondaires avec le défaut de cache primaire : tout cela ne correspond qu'à un seul défaut de cache pour lui. ===Les MSHR simples=== Un cache non-bloquant à '''MSHR simple''' contient juste plusieurs MSHR qui mémorisent juste un numéro de ligne de cache, un bit de validité, le champ de lecture/écriture, et l'adresse exacte de lecture/écriture. Avec cette organisation, il est possible d'avoir plusieurs défauts de cache séparés, mais à la condition que chaque défaut accède à une ligne de cache différente. Deux accès simultanés à une même ligne de cache ne sont pas possibles, les défauts de cache secondaires ne sont pas autorisés. Ainsi, chaque défaut de cache se voit attribuer son propre MSHR, chacun contient un numéro de ligne de cache différent. Pour comprendre pourquoi c'est impossible de gérer les défauts secondaires, il faut regarder le champ de lecture/écriture. Si on veut effectuer plusieurs écritures consécutives à la même adresse, le MSHR n'aura pas de quoi mémoriser les deux données à écrire. Il pourra mémoriser la première donnée à écrire, pas la seconde. Même chose lors d'une lecture : le champ lecture/écriture peut mémorisr la destination de la première lecture, pas de la seconde. L'avantage est que la MHA n'a besoin que des MSHR et de quelques circuits annexes. Les autres solutions rajoutent des circuits annexes pour gérer les défauts de cache secondaires, qui utilisent beaucoup de circuits. Le cout en circuit est donc élevé, mais le gain en performance est là. Passons maintenant aux caches non-bloquants qui autorisent les défauts de cache secondaire. La solution la plus simple consiste à utiliser ===Les MSHR adressés implicitement=== Avec les '''MSHR adressés implicitement''', il est possible de fusionner plusieurs accès mémoire à une même ligne de cache, mais sous une condition très importante : ces accès lisent/écrivent des mots mémoire différents. Par exemple, imaginons qu'une ligne de cache contienne 8 mots mémoire de 64 bits. Si un premier accès mémoire lit le mot mémoire numéro 7 (dernier mot mémoire de la ligne), et le second accès le mot mémoire numéro 3, alors la fusion est possible. Mais si deux accès mémoire veulent lire/écrire le mot mémoire numéro 7, alors la fusion n'est pas possible et le processeur se bloque, un ''pipeline stall'' survient. Un MSHR adressé implicitement est un MSHR simple, qui contient naturellement un numéro de ligne de cache (tag) et un bit de validité, sauf que le champ de lecture/écriture est dupliqué. Les différents champs lecture/écriture d'un MSHR sont regroupés dans une mémoire RAM/cache qui contient autant d'entrées qu'il y a de mot mémoire dans une ligne de cache. Chaque entrée de la table est associée à un mot mémoire de la ligne de cache et stocke des informations sur celui-ci. Une entrée mémorise, au minimum : * Un champ de lecture/écriture qui contient soit la destination de la lecture, soit la donnée à écrire. * Un bit R/W permettant d'interpréter correctement le champ de lecture/écriture. * Un bit de validité pour chaque entrée de la table, qui dit si un défaut de cache antérieur accède déjà à ce mot mémoire. La fusion de deux défauts de cache est ainsi assez simple. Un défaut de cache primaire/secondaire configure l'entrée associée au mot mémoire lu/écrit. Si un défaut secondaire ultérieur lit/écrit un mot mémoire différent (dont le bit de validité de l'entrée dans la table est à zéro), pas de conflit : il configure une autre entrée, vide. Mais s'il lit/écrit un mot mémoire pour lequel l'entrée de la table est occupée, il y a conflit, le processeur est bloqué par un ''pipeline stall'', une bulle de pipeline. Avec cette organisation, le nombre de MSHR indique combien de lignes de cache peuvent être lues en même temps depuis la mémoire. Quant au nombre d'entrées par MSHR, il détermine combien d'accès mémoires qui ne se recouvrent pas peuvent avoir lieu en même temps. ===Les MSHR adressés explicitement=== Les '''MSHR adressés explicitement''' n'ont pas les contraintes des MSHR adressés implicitement. Avec eux, il est possible d'avoir plusieurs défauts de cache pointant vers la même ligne de cache, mais aussi vers le même mot mémoire. De tels défauts de cache apparaissent sur les processeurs à exécution dans le désordre, mais sont très rares, voire inexistants, sur les processeurs ''in-order''. L'idée est encore que chaque MSHR est associée à une table mémoire qui mémorise des entrées. Cette table n'est autre qu'une mémoire FIFO. Sauf que cette fois-ci, une entrée n'est pas associée à un mot mémoire. Une entrée est associée à un défaut de cache. Une entrée mémorise là encore la destination de la lecture ou la donnée de l'écriture suivi du bit R/W, ainsi que d'un bit de validité par entrée, mais aussi : la position du mot mémoire lu dans la ligne de cache. C'est cette dernière information qui n'était pas présente dans les MSHR adressés implicitement. De plus, le nombre d'entrée par MSHR n'est pas égal au nombre de mots mémoires dans une ligne de cache, mais peut être arbitrairement grand. L'avantage d'un tel cache est qu'il est capable de traité les défauts secondaires concernant un même mot mémoire, et ce, car les accès à la ligne de cache concernée se produisent dans l'ordre de génération des défauts (la table mémoire est une FIFO). ===Les MSHRs inversés=== Généralement, plus on veut supporter de défauts de cache, plus le nombre de MSHR et d'entrées augmente. Mais au-delà d'un certain nombre d'entrées et de MSHR, les MSHR adressés implicitement et explicitement ont tendance à bouffer un peu trop de circuits. Utiliser une organisation un peu moins gourmande en circuits est donc une nécessité. Cette organisation plus économe se base sur des MSHR inversés. Les MSHR inversés ne contiennent qu'une seule entrée, en quelque sorte : au lieu d’utiliser n MSHR de m entrées chacun, on va utiliser n × m MSHR inversés. La différence, c'est que plusieurs MSHR peuvent contenir un tag identique, contrairement aux MSHR adressés implicitement et explicitement. Lorsqu'un défaut de cache a lieu, chaque MSHR est vérifié. Si jamais aucun MSHR ne contient de tag identique à celui utilisé par le défaut, un MSHR vide est choisi pour stocker ce défaut, et une requête de lecture en mémoire est lancée. Dans le cas contraire, un MSHR est réservé au défaut de cache, mais la requête n'est pas lancée. Quand la donnée est disponible, les MSHR correspondant à la ligne qui vient d'être chargée vont être utilisés un par un pour résoudre les défauts de cache en attente. ===Les MSHR intégrés au cache=== Certains chercheurs ont remarqué que pendant qu'une ligne de cache est en train de subir un défaut de cache, celle-ci reste inutilisée, et son contenu est destiné à être perdu une fois le défaut de cache résolu. Ils se sont dit que, plutôt que d'utiliser des MSHR séparés, il vaudrait mieux utiliser la ligne de cache pour stocker les informations sur les défauts de cache en attente dans cette ligne de cache. Pour éviter tout problème, il faut rajouter un bit dans les bits de contrôle de la ligne de cache, qui sert à indiquer que la ligne de cache est occupée : un défaut de cache a eu lieu dans cette ligne, et elle stocke donc des informations pour résoudre les défauts de cache. ==L'entrelacement mémoire== Nous venons de voir comment une mémoire cache peut gérer plusieurs accès mémoire simultanés. Il se trouve que ce genre d'optimisation dépasse largement le cadre du cache. Les mémoires RAM ne sont pas en reste, elles aussi ! Il est possible de pipeliner l'accès à une mémoire RAM, en utilisant des techniques dites d''''entrelacement'''. Précisons cependant que cette optimisation n'est pas utilisée sur les mémoires SDRAM ou DDR modernes, du moins pas sans modifications majeures. Mais divers ordinateurs assez anciens, dont des superordinateurs, ont utilisé cette technique. La technique demande d'utiliser plusieurs mémoires séparées. Sur les anciens ordinateurs, les mémoires en question sont des chips mémoire, à savoir des circuits intégrés de DRAM. Pour faire plus général, nous allons utiliser le terme de '''banques''', ou encore de bancs mémoire. La différence est qu'il existe des mémoires multi-banque, qui regroupent plusieurs banques indépendantes dans un seul boitier, dans un seul chip mémoire. En clair, de telles mémoires regroupent plusieurs sous-mémoires dans un seul circuit intégré. Il est aussi possible d'utiliser plusieurs chips mémoire séparés, chacun ayant une banque. Reste à combiner ces banques pour former un pseudo-pipeline. ===Utiliser plusieurs banques sans entrelacement=== Sans optimisation particulière, les adresses sont réparties dans les banques comme indiqué ci-dessous. Il s'agit de ce qui s'appelle un arrangement horizontal, et nous avions vu celui-ci dans le chapiutre sur les mémoires SDRAM. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Avec cet arrangement, les bits de poids fort de l'adresse sont utilisées pour sélectionner la banque adéquate, et le reste de l'adresse est envoyé sur le bus d'adresse. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Adresse de banque !! Adresse dans la banque |- | Quelques bits de poids fort || Reste de l'adresse |} L'entrelacement change cette répartition, afin d'accélérer les accès mémoire. L'entrelacement classique vise à améliorer les accès à des adresses mémoires consécutives, mais des formes plus évoluées d'entrelacement visent à optimiser des accès mémoire arbitraires. Voyons-les dans l'ordre, du plus simple au plus complexe. ===L'entrelacement classique=== Avec l''''entrelacement classique''', des adresses consécutives sont réparties dans des banques consécutives. Précisons que cette attribution des adresses n'implique absolument pas la mémoire virtuelle ou n'importe quel mécanisme dans le processeur. La répartition décide que telle adresse mémoire va dans telle banque, à telle adresse dans la banque. Elle est donc le fait du contrôleur mémoire, donc en dehors du processeur (les contrôleurs mémoires n’étaient pas encore intégrés dans le processeur à l'époque). [[File:Répartition des adresses dans une mémoire interleaved.png|centre|vignette|upright=1.5|Répartition des adresses dans une mémoire interleaved.]] L'entrelacement simple permet d'accélérer les accès mémoire qui se font à des adresses consécutives. Avec l'entrelacement, chaque accès mémoire tombe dans une banque mémoire différente, ce qui fait qu'on peut démarrer un nouvel accès mémoire à chaque cycle d'horloge. Pendant qu'une banque est occupée par un accès mémoire, on démarre le suivant dans une autre banque, et ainsi de suite. Le tout se termine soit quand on a épuisé toutes les banques libres, soit quand l'accès en rafales se termine. Pas besoin d'attendre que la mémoire ait fini sa lecture/écriture avant de démarrer la lecture/écriture suivante. [[File:Pipemining mémoire.png|centre|vignette|upright=2.5|Pipelining mémoire]] L'animation suivante illustre bien le processus, avec quatres banques. [[File:Interleaving.gif|centre|vignette|upright=2.5|Pipelining mémoire]] L'entrelacement simple utilise les bits de poids faible pour sélectionner la banque, et les bits de poids fort pour la case mémoire. [[File:Adresse mémoire d'une mémoire entrelacée.png|centre|vignette|upright=2|Adresse mémoire d'une mémoire entrelacée]] Précisons que le temps d'accès mémoire ne change pas beaucoup avec l'entrelacement. Par contre, on peut faire plus d'accès mémoire simultanés. On peut démarrer un accès mémoire par cycle d'horloge, mais l'accès en lui-même prend plusieurs cycles. Les mémoires à entrelacement ont donc un débit supérieur aux mémoires qui ne l'utilisent pas. L'entrelacement classique pourrait être utilisé pour accélérer les transferts entre mémoire RAM et cache, qui se font en blocs d'adresses consécutives. Mais de nos jours, il n'est pas utilisé dans cette optique. Il faut dire qu'il est redondant avec le mode rafale des mémoires DRAM. Les deux font globalement la même chose et on voit mal comment utiliser les deux en même temps. Le mode rafale étant plus simple à implémenter, et plus léger niveau utilisation du bus de commande mémoire, il est préféré à l'entrelacement. Par contre, l'entrelacement a eu son heure de gloire sur d'anciens processeurs qui n'avaient pas de mémoire cache, alors qu'ils disposaient d'un pipeline. L'exemple typique est celui des nombreux ''processeurs vectoriels'', que nous n'avons pas encore abordé à ce stade du cours. De tels processeurs avaient un pipeline, une unité de calcul fortement pipelinée, et parfois même de l'exécution dans le désordre et du renommage de registres ! ===L'entrelacement par décalage=== L'entrelacement simple est très utile pour les accès en rafle ou équivalents. Par contre, il existe de rares situations où il n'est pas optimal. Une de ces situations est celle des accès en enjambée (en ''stride''), où l'on accède en série à des adresses sont séparées par N mots mémoires. [[File:Accès par enjambées.png|centre|vignette|upright=2|Accès par enjambées.]] De tels accès surviennent quand un logiciel accède à des structures de données spécifiques, à savoir des tableaux de structures, des matrices, ou d'autres structures de données dans le genre. Un cas classique est celui du parcours d'une matrice colonne par colonne. Les matrices de nombres sont mémorisées ligne par ligne, ce qui fait que parcourir une colonne demande de passer d'une ligne à la suivante et de faire des grands sauts en mémoire RAM, mais tous espacés par la même distance. Avec l'entrelacement simple, les accès en enjambée sont moins performants que les accès à des adresses consécutives, mais cela ne fonctionne pas trop mal. Le pire cas est celui où l'on a N banques et où les données sont justement placées toutes les N adresses. Des accès consécutifs vont tous tomber dans la même banque, on ne peut plus accéder à des banques différentes en parallèle. Mais en dehors de ce cas, on voit une amélioration par rapport à la situation sans entrelacement. Pour obtenir des performances maximales pour les accès en enjambées, il faut répartir les mots mémoires dans la mémoire autrement. L'organisation idéale est la suivante. : Dans les explications qui vont suivre, la variable N représente le nombre de banques, qui sont numérotées de 0 à N-1. On commence par organiser les N premières adresses comme une mémoire entrelacée simple : l'adresse 0 correspond à la banque 0, l'adresse 1 à la banque 1, etc. Pour le bloc suivant, on décale tout d'une adresse, à savoir qu'on commence à remplir les banques à partir de la seconde, non de la première. Une fois la fin du bloc atteinte, on finit de remplir le bloc en repartant du début du bloc. Le troisième bloc d'adresse subit le même traitement, sauf qu'on commence à remplir à partir de la seconde banque. Et on poursuit l’assignation des adresses en décalant d'un cran en plus à chaque bloc. Ainsi, chaque bloc verra ses adresses décalées d'un cran en plus comparé au bloc précédent. Si jamais le décalage dépasse la fin d'un bloc, alors on reprend au début. [[File:Mémoire interleaved par décalage.png|centre|vignette|upright=1.5|Mémoire entrelacée par décalage.]] En faisant cela, on remarque que les banques situées à N adresses d'intervalle sont différentes. Dans l'exemple du dessus, nous avons ajouté un décalage de 1 à chaque nouveau bloc à remplir. Mais on aurait tout aussi bien pu prendre un décalage de 2, 3, etc. Dans tous les cas, on obtient un entrelacement par décalage. Ce décalage est appelé le pas d'entrelacement, noté P. Le calcul de l'adresse à envoyer à la banque, ainsi que la banque à sélectionner se fait en utilisant les formules suivantes : * adresse à envoyer à la banque = adresse totale / N ; * numéro de la banque = (adresse + décalage) modulo N ; * décalage = (adresse totale * P) mod N. Avec cet entrelacement par décalage, on peut prouver que la bande passante maximale est atteinte si le nombre de banques est un nombre premier. Seulement, utiliser un nombre de banques premier peut créer des trous dans la mémoire, des mots mémoires inadressables. Malgré ce défaut, la technique a été utilisée sur quelques ordinateurs, avec l'exemple notable du superordinateur ''Burroughs Scientific Processor''. Pour éviter cela, il y a plusieurs solutions. Par exemple, on peut faire en sorte que N et la taille d'une banque soient premiers entre eux : ils ne doivent pas avoir de diviseur commun. Mais en pratique, elles n'ont pas vraiment été implémentées dans une vraie machine, et sont restées à l'état de recherche, aussi je les passe sous silence. ===L'entrelacement pseudo-aléatoire=== Une dernière méthode de répartition consiste à répartir les adresses dans les banques de manière "pseudo-aléatoire". L'idée exacte est de faire passer l'adresse dans une '''fonction de hachage''', plus ou moins complexe. Pour rappel, une fonction de hachage prend une entrée de grande taille et fournit en sortie un résultat de petite taille. Idéalement, elles doivent donner des résultats aussi différents que possible pour des entrées similaires, histoire de simuler une sorte de pseudo-aléatoire. Ici, une adresse est transformée en un numéro de banque. Une fonction de hachage simple ne fait que permuter des bits de l'adresse pour obtenir son résultat. Elle ne fait qu'échanger des bits de place avant de couper l'adresse en deux morceaux : un pour la sélection de la banque, et un autre pour la sélection de l'adresse dans la banque. Cette permutation est fixe, et ne change pas suivant l'adresse. Des fonctions de hachage plus complexes font des XOR bit à bit entre certains bits de l'adresse. ==Les contrôleurs SDRAM/DDR avec "exécution dans le désordre"== Après avoir vu le cas des mémoires RAM, nous allons nous concentrer sur le cas particulier des mémoires SDRAM. Les mémoires SDRAM et DDR modernes sont capables de gérer plusieurs accès simultanés à la mémoire RAM, et leurs contrôleurs mémoire en font tout autant. C'est une différence majeure avec les mémoires asynchrones FPM/EDO, qui n'acceptaient qu'un seul accès mémoire à la fois. Leur contrôleur mémoire n'acceptait qu'un seul accès mémoire à la fois, c'était un contrôleur mémoire bloquant. Les contrôleurs mémoires des SDRAM sont eux non-bloquants et peuvent encaisser une dizaine d'accès mémoire à la fois. Peu de choses sont connues sur les contrôleurs de SDRAM/DDR modernes, les fabricants ne donnant que peu de détails dessus. Les rares simulateurs qui tentent de décrire leur fonctionnement, comme DRAM SIM I et II, sont particulièrement simples et ne vont pas dans le détail. Néanmoins, le peu qu'on sait est tout de même instructif. ===Rappel sur les SDRAM : tampon de ligne et commandes=== Les mémoires SDRAM sont des mémoires à tampon de ligne. Elles font un accès mémoire en deux étapes. La première étape recopie une ligne de N * 64 bits dans un tampon interne à la SDRAM. La seconde étape sélectionne une colonne, à savoir une donnée de 64 bits, qui est soit envoyée sur le bus de données pour une lecture, soit modifiée par une écriture. [[File:Mémoire à tampon de ligne.png|centre|vignette|upright=2|Mémoire à tampon de ligne]] Les accès mémoire sont traduits par un séquenceur mémoire en une série de commandes mémoires, qui sont séparées par des délais mémoire de quelques cycles d'horloge. Les délais sont très précis, et sont à respecter à la lettre. Une lecture ou une écriture se fait en maximum trois commandes : une commande PRECHARGE qui ferme la ligne précédemment utilisée, une commande ACT qui précise l'adresse de la ligne, et une commande READ ou WRITE qui précise l'adresse de la colonne et éventuellement la donnée à écrire. : Pour être plus précis, la commande PRECHARGE précharge les lignes de bits du plan mémoire à une tension particulière, ce qui les vide de leur contenu. Mais c'est un détail sans importance pour ce qui va suivre. Les SDRAM permettent de se passer de la première étape si des accès consécutifs se font dans la même ligne. Une fois activée, la ligne reste ouverte et on peut accéder plusieurs fois de suite dedans. On a alors juste à préciser l'adresse de colonne dedans. Si un accès mémoire accède à une ligne déjà activée, on dit que c'est un '''succès de page'''. Si ce n'est pas le cas, on doit fermer la ligne courante, rouvrir la ligne vboulue, et préciser la colonne. C'est alors un '''défaut de page'''. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | bgcolor="#FFA0FF" | PRECHARGE || bgcolor="#FFA0FF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || bgcolor="#A0FFFF" | READ (2) || bgcolor="#A0FFFF" | READ (3) || || || bgcolor="#A0FFFF" | READ (4) || bgcolor="#A0FFFF" | READ (5) || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | Donnée READ 1 || ||bgcolor="#A0FFFF" | Donnée READ 2 | bgcolor="#A0FFFF" | Donnée READ 3 || || || bgcolor="#A0FFFF" | Donnée READ 4 || bgcolor="#A0FFFF" | Donnée READ 5 |} Les SDRAM sont parfois capables de démarrer une commande avant que la précédente soit terminée. Mais le respect des délais mémoire est très limitant, ce qui fait qu'on ne peut pas parler de réel pipeline, comme c'est le cas sur les processeurs. Parlons plutot de '''commandes anticipées'''. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | || bgcolor="#A0FFFF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || || || bgcolor="#FFA0FF" | READ (2) || || || || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 | bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 | |} Un dernier point est que les chips mémoires de SDRAM sont composés de plusieurs banques, chacune étant une sorte de mini-mémoire miniature. Chaque banque a son propre décodeur, son propre tampon de ligne, ses propres multiplexeurs de colonne, sa logique de rafraichissement mémoire, etc. C'est comme si une SDRAM regroupait plusieurs mémoires séparées dans un même circuit intégré. [[File:Arrangement vertical.jpg|centre|vignette|upright=2.5|Mémoire multi-banques.]] ===La mise en attente des accès mémoire=== Un contrôleur de SDRAM peut accepter plusieurs accès mémoire en même temps. Mais cela ne signifie pas que le contrôleur sera prêt à les traiter immédiatement. Pour éviter tout problème de disponibilité, le contrôleur met en attente les accès mémoire que le processeur lui envoie, pour les exécuter dès que possible. Les accès mémoire sont mis en attente dans une mémoire FIFO, histoire de les exécuter dans leur ordre d'arrivée, notamment pour renvoyer les lectures dans l'ordre demandé. Il y a aussi une mémoire FIFO pour les données à écrire et une FIFO pour les données lues. Cette dernière sert au cas où le cache ne soit pas disponible quand on lui envoie la donnée lue. [[File:Module d'interface avec la mémoire.png|centre|vignette|upright=2.5|Contrôleur mémoire avec mise en attente des requêtes processeur et autres optimisations.]] Des optimisations sont possibles dès la mémoire FIFO. Par exemple, on peut regrouper plusieurs accès à des données consécutives en un seul accès en rafale. Le contrôleur mémoire analyse les requêtes mises en attente et détecte si certaines se font à des adresses consécutives. Si c'est le cas, il fusionne ces requêtes en une lecture/écriture en rafale. Une telle optimisation est appelée la '''combinaison de lecture''' pour les lectures, et la '''combinaison d'écriture''' pour les écritures. L'optimisation précédente ne paraissent pas payer de mine, mais elles deviennent plus compréhensible quand on sait que certains contrôleurs mémoire peuvent mettre en attente beaucoup de données. Par exemple, l'''Intel E8870 - Scalable Memory Controller'' peut mettre en attente 64 lignes de cache dans la mémoire FIFO, soit 8 kibioctets de RAM ! Deux pages mémoire si la mémoire virtuelle utilise des pages de 4 kibioctets ! ===Les lectures anticipées : une forme d'OOO mémoire=== La mise en attente des accès mémoire est une optimisation intéressante, mais pas mirifique. Elle permet surtout de ne pas bloquer le processeur si le contrôleur mémoire a du travail sur la planche. Cependant, elle peut être optimisée quand on se rend compte d'une chose : le processeur ne voit que les lectures, pas les écritures. Pour les écritures, il les envoie au contrôleur mémoire et ce dernier fait le travail demandé, le processeur n'est pas prévenu quand une écriture se termine. Mais pour les lectures, c'est différent, car il reçoit la donnée lue. En conséquence, il est possible d'optimiser les écritures sans que le processeur ne voit quoique ce soit. Une optimisation possible est alors de changer l'ordre des accès mémoire, histoire de retarder les écritures ou au contraire de les faire en avance. La raison est que les mémoires SDRAM n'aiment pas quand on alterne lectures et écritures. Les délais mémoire, les fameux ''timings'' mémoire, sont clairs : faire une suite de lecture ou une suite d'écriture est plus performant que d'alterner entre lectures et écritures. L'idée est alors de changer l'ordre des écritures de manière à les faire en bloc, idem pour les lectures. L’optimisation est possible, mais à condition que le processeur n'y verra que du feu. Or, vous l'avez deviné, il y a des cas où ces réorganisations ne sont pas possibles, et vous avez sans doute trouvé que la situation était familière. L'optimisation en question est une forme d'exécution dans le désordre des accès mémoire, réalisée par le contrôleur mémoire lui-même ! Et qui dit exécution dans le désordre dit : problèmes liés aux dépendances de données. Changer l'ordre des écritures n'est pas toujours possible, notamment en raison des dépendances RAW, WAR et WAW. Pour que le processeur n'y voit que du feu, il faut respecter plusieurs critères. Premièrement, les lectures doivent être renvoyées au processeur dans l'ordre. Et quand je dis dans l'ordre, cela veut dire : dans l'ordre d'envoi des lectures au contrôleur mémoire. Les écritures aussi doivent se faire dans l'ordre, histoire qu'une série d'écriture donne le bon résultat final. Le troisième critère est qu'une lecture doit donner le résultat de la dernière écriture ''à la même adresse''. Pour le dire autrement, il faut juste détecter les dépendances RAW. Concrètement, si on a une série de lectures et d'écritures alternées, on peut regrouper les lectures et les écritures dans deux paquets séparés, à condition qu'aucune lecture ne lise une adresse écrite par une écriture. Mais si une lecture est dépendante d'une écriture, alors la lecture doit attendre que l'écriture se termine, idem pour les lectures suivantes. Une implémentation basique remplace la mémoire FIFO vue au-dessus, par deux mémoires FIFOs : une pour les lectures, une pour les écritures. Lorsqu'un accès mémoire arrive au séquenceur, il regarde si c'est une lecture ou une écriture et place l'accès mémoire dans la file adéquate. Le fait que ce soit des FIFOs garantit que les lectures se font dans l'ordre, idem pour les écritures. En sortie des deux FIFOs, le séquenceur mémoire détecte les dépendances RAW. Précisément, quand une lecture sort de la mémoire FIFO, il consulte la file d'attente des écriture pour voir si la lecture est dépendante d'une écriture en attente. La lecture attend si c'est le cas, et les écritures sont exécutées à la place. Séparer lectures et écriture est une source d'optimisation. Il est par exemple possible d'utiliser le réacheminement lecture sur écriture (''Store to load forwarding''). Il s'agit d'un équivalent de l’optimisation du même nom, utilisée dans le cadre de la désambiguïsation mémoire. Imaginez qu'une lecture accède à une donnée pas encore écrite, en attente dans le contrôleur mémoire. C'est une dépendance RAW assez claire. Dans ce cas, le contrôleur mémoire peut renvoyer la donnée directement depuis ses mémoires FIFOs, sans faire d'accès mémoire en lecture. Le contrôleur mémoire a tendance à privilégier les lectures, car celles-ci sont cruciales pour l'exécution dans le désordre. Plus elles se font tôt, plus vite le processeur recevra les données et pourra démarrer les instructions dépendantes de cette donnée. Le séquenceur a donc tendance à piocher en priorité dans la file de lecture, plutôt que dans la file d'écriture. A vrai dire, dans le cas idéal, les écritures ne sont faites que quand la file d'écriture est pleine ou quasi-pleine... Prenons l'exemple des coprocesseurs IO 81341 et 81342, qui étaient en réalité des ''chipsets'' intégrant un contrôleur SDRAM. Le ''chipset'' avait 5 ports : un pour les processeurs, un pour le pont sud (''southbridge'') et trois pour des canaux DMA. Pour le port processeur, il n'y avait pas de réordonnancement et ce port utilisait une seule mémoire FIFO. Les autres ports utilisaient l'optimisation qu'on vient de voir, et avaient vraisemblablement des files séparées pour les lectures et écritures. Le séquenceur mémoire vérifiait les dépendances mémoire de type RAW et autres. [[File:Double file d'attente pour les lectures et écritures.png|centre|vignette|upright=2.5|Double file d'attente pour les lectures et écritures]] ===Le ré-ordonnancement des commandes mémoires=== L'optimisation précédente est peu poussée, comparé aux formes d'exécution dans le désordre que les processeurs utilisent. Et on peut se demander si une exécution dans le désordre plus poussée est possible. La réponse est oui : un contrôleur mémoire peut faire des réorganisations bien plus poussées. Par contre, il faut faire une précision très importante : le contrôleur mémoire ne remet pas les accès mémoire dans l'ordre avant de les envoyer au processeur. C'est le processeur qui remet en ordre les accès mémoire, dans sa ''load queue''. En clair : le processeur voit les accès mémoire dans le désordre, c'est lui qui les remet en ordre. Pour cela, les requêtes mémoire sont "numérotées", à savoir qu'on leur attribue un identifiant binaire. Le contrôleur mémoire exécute les lectures/écritures dans le désordre, mais garde la trace de leur identifiant. Il sait dans quel ordre il exécute les accès mémoire et connait leur latence. Ce qui fait qu'il sait que la donnée lue à tel instant est associée à la requête mémoire numéro X. Pour les lectures, il renvoie la donnée lue avec l'identifiant. Le processeur sait alors à quelle lecture la donnée lue correspond et il se débrouille en interne pour gérer la situation. Maintenant que ces précisions sont faites, posons cette question : dans quelles situations est-il pertinent de faire des accès mémoire dans le désordre ? La réponse est : quand plusieurs accès à une même ligne ne sont pas consécutifs, qu'il y a des accès entre les deux. Après ré-ordonnancement, ces accès mémoire à une même ligne sont exécutés l'un à la suite de l'autre, ce qui est beaucoup plus rapide. Pour rendre le tout plus concret, voici un exemple. Imaginez que l'on sait les 3 accès mémoire suivants : * Une lecture ligne A ; * PRECHARGE + ACT ; * Une écriture ligne B ; * PRECHARGE + ACT ; * Une lecture ligne A. Sans réordonnancent, on doit émettre deux commandes PRECHARGE quand on passe d'une ligne à l'autre, et il faut ajouter les commandes ACT avec. pour éviter cela, le contrôleur mémoire peut retarder l'écriture, ou au contraire avancer la seconde lecture. le résultat est alors le suivant : * Lecture ligne A ; * Lecture ligne A * PRECHARGE + ACT ; * Une écriture ligne B. On a donc deux accès consécutifs à la même ligne, suivi par une écriture dans une autre ligne. La technique marche parce que l'on a un mix adéquat de lectures et d'écritures. Et encore une fois, il faut éviter d'intervertir lectures et écritures à une même adresse, sous peine de problèmes. Encore une fois, les dépendances RAW posent problème ! L'implémentation est assez simple : la ou les mémoires FIFOs précédentes sont remplacées par des mémoires similaires aux fenêtres d'instruction. Le contrôleur mémoire vérifie à chaque cycle les accès en attente, et vérifie à quelle ligne ils accèdent. Il priorise alors les accès qui tombent dans la ligne ouverte : ceux-là sont exécutés avant les autres. S'il n'y en a pas, il prend l'accès mémoire le plus ancien (ordre FIFO). Il teste aussi les dépendances mémoires RAW avant d'envoyer des commandes à la mémoire DDR/SDRAM. une telle solution est appelée l''''algorithme ''FR-FCFS''''' (''First Ready-First Come First Serve''). ===Les optimisations liées à la présence de plusieurs banques=== Les optimisations précédentes sont décuplées par la présence de plusieurs banques dans la mémoire SDRAM. Il est en effet possible de faire plusieurs accès mémoire en même temps, dans des banques différentes. Il est possible de lancer un accès mémoire dans deux banques en même temps, par exemple. La seule contrainte est que la SDRAM est limitée à 4 banques actives en même temps. {|class="wikitable" |+ Pipelining basique sur les SDRAM |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 |- ! Banque Numéro 1 | || || || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || || |- ! Banque Numéro 2 | || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || |- ! Banque Numéro 3 | || || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || |} Les optimisations précédentes peuvent s'appliquer par banque. Il est par exemple possible d'utiliser une mémoire FIFO par banque. Ainsi, les accès dans une même banque se feront en série, dans l'ordre d'arrivée, mais des accès à des banques différentes se feront dans le désordre. Cela ne pose pas de problème car les accès se font à des adresses différentes, ce qui fait qu'il n'y a pas de conflits majeurs. Il y a cependant un risque que les lectures se fassent dans le désordre si on n'y prend pas garde. [[File:Gestion parallèle des banques.png|centre|vignette|upright=2.5|Gestion parallèle des banques.]] Il est aussi possible d'utiliser une file séparée pour les lectures et les écriture, pour chaque banque. Les performances sont alors améliorées comparé à deux files globales pour toute la SDRAM. En idem avec la réorganisation des accès mémoire, pour regrouper les accès à une même ligne. Un problème avec ces optimisations est qu'il est rare que des accès mémoire se fassent dans des banques séparées. Les accès mémoire tendent à se faire avec une bonne localité spatiale, ce qui fait qu'ils tombent dans une même banque. Heureusement, les contrôleurs SDRAM/DDR modernes incorporent des optimisations pour corriger ce problème. Les optimisations en question sont une forme d'entrelacement adaptée aux mémoires SDRAM. L'entrelacement naïf ne marche pas à cause de la présence du tampon de ligne. Cependant, il peut y avoir un entrelacemententre banques SDRAM. Voyons ce que ca veut dire. L''''entrelacement de banques''' répartit deux lignes consécutives dans deux banques différentes. Pour comprendre l'idée, prenons un exemple. Imaginons une mémoire avec deux banques et 4 lignes. Imaginons qu'on parcoure/balaye la mémoire RAM en partant des adresses basses. Sans entrelacement de ligne, les accès se feront comme suit, de gauche à droite : {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Banque numéro 1 !! colspan="4" | Banque numéro 2 |- | Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 || Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 |} Avec l'entrelacement de banques, les accès se feront comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 |- | Ligne 1 || Ligne 1 || Ligne 2 || Ligne 2 || Ligne 3 || Ligne 3 || Ligne 4 || Ligne 4 |} Avec N banques, la première ligne ira dans la première banque, la seconde ligne dans la seconde, et ainsi de suite jusqu'à la énième banque qui va dans la banque numéro N. Et la ligne N+1 repart dans la première banque. L'avantage est que passer d'une ligne à la suivante est assez rapide. Au lieu de devoir fermer la première ligne et ouvrir la suivante on ouvrira directement la ligne suivante dans une banque différente, ce qui sera plus rapide. La commande PRECHARGE pourra être envoyée à la seconde banque en avance. Pour gérer l'entrelacement, le contrôleur de SDRAM prend en entrée l'adresse envoyée par le processeur, et la découpe en plusieurs champs : un pour sélectionner la banque adéquate, un autre pour la ligne, un autre pour la colonne, un autre pour la rangée. Sans entrelacement, les adresses mémoire sont découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- | Adresse de banque || Adresse de ligne || Adresse de colonne |} Avec l'entrelacement de banques, l'adresse est découpée comme suit. {|class="wikitable" |+ Adresse mémoire |- | Adresse de ligne || Adresse de banque || Adresse de colonne (précise à l'octet) |} : Il est aussi possible de faire la même chose, mais avec les rangées. L'usage du ''dual channel'' est aussi une forme d'entrelacement entre barrettes de RAM. Mais je vais passer cela sous silence. D'autres méthodes d'entrelacement sont possibles, mais elles sont plus complexes. Chaque contrôleur mémoire fait un peu à sa sauce. Ils permettent en général de choisir entre plusieurs entrelacements. Au minimum, ils permettent de désactiver l'entrelacement, d'activer un entrelacement de ligne, et d'autres entrelacements plus complexes, dont certains sont propriétaires et tenus secrets par le fabricant. L'entrelacement est géré juste avant le séquenceur mémoire, dans un '''circuit d'entrelacement'''. Ce dernier intervertit certains bits de l'adresse lors des accès mémoires. [[File:Controleur mémoire d'une SDRAM avec entrelacement.png|centre|vignette|upright=2.5|Contrôleur mémoire d'une SDRAM avec entrelacement.]] Les contrôleurs de SDRAM précédents sont assez basiques et ne représentent pas les contrôleurs les plus évolués. Ils étaient utilisés sur les anciens PC, à une époque où ils étaient encore sur la carte mère du processeur. Mais de nos jours, le contrôleur mémoire est intégré au processeur. Et il incorpore de nombreuses optimisations afin de gagner en performances. Malheureusement, ces optimisations sont elles-mêmes dépendantes des optimisations intégrées dans le processeur, ce qui fait qu'on ne peut pas en parler ici. Elles demandent que le contrôleur mémoire reçoive plusieurs accès mémoire simultanés, ce qui n'a aucun sens à ce stade du cours. Aussi, nous allons les passer sous silence pour le moment. Nous les verrons dans le chapitre sur le parallélisme mémoire. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=La désambiguïsation mémoire | prevText=La désambiguïsation mémoire | next=Les processeurs superscalaires | nextText=Les processeurs superscalaires }} </noinclude> skmozo1vk0jnf8k5mf2wori426x1xj3 764819 764818 2026-04-24T10:52:01Z Mewtow 31375 /* Les optimisations liées à la présence de plusieurs banques */ 764819 wikitext text/x-wiki Dans ce chapitre, nous allons voir les techniques qui permettent de gérer plusieurs accès mémoire simultanés directement au niveau du cache ou de la mémoire RAM. Par plusieurs accès mémoire simultanés, vous pensez sans doute à l'usage de cache multiports, voire de mémoires RAM multiport. C'est en effet une solution, mais ce n'est clairement pas la seule. Par exemple, il est possible de pipeliner l'accès au cache, voire à la mémoire RAM. Il existe de nombreuses techniques de '''parallélisme mémoire''', et nous allons les voir dans ce chapitre. Pour donner un exemple, il est possible de pipeliner les accès mémoire. Il est possible de pipeliner l'accès au cache et/ou l'accès à la mémoire, et nous verrons comment faire dans ce chapitre. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, qu'ils aient ou non un pipeline dynamique. Par contre, pipeliner la mémoire est une technique ancienne, aujourd'hui peu utilisée. Elle était utilisée sur des très vieux ordinateurs, qui avaient un pipeline mais pas de mémoire cache. Mais de nos jours, les mémoires SDRAM et DDR ne sont pas adaptées pour ça, il est très difficile de les pipeliner correctement. Le parallélisme mémoire est utile aussi bien sur des processeurs à émission dans l'ordre que dans le désordre. Par exemple, un processeur ''in-order'' avec des lectures non-bloquantes peut e, profiter. Il peut techniquement lancer une seconde lecture, après avoir rencontré une première lecture non-bloquante. En général, le nombre de lectures consécutives est cependant limité à moins d'une dizaine. À l'opposé, les processeurs sans lectures non-bloquantes profitent pas du parallélisme mémoire. À la rigueur, ils peuvent en profiter s'ils intègrent des techniques de préchargement. Pendant que le processeur exécute une lecture/écriture, il peut précharger une autre donnée en parallèle. Inutile de dire que sans pipeline processeur, le parallélisme mémoire ne sert pas à grand-chose. ==Le ''line fill buffer'' et les techniques associées== Lors d'un défaut de cache, le processeur doit attendre que toute la ligne de cache soit chargée avant d'être utilisable. Or, la taille d'une ligne de cache est supérieure à la largeur du bus mémoire, ce qui fait qu'une ligne de cache est chargée en plusieurs fois, morceaux par morceaux, mot mémoire par mot mémoire. Le chargement peut se faire directement dans le cache, mais ce n'est pas une solution très pratique. À la place, beaucoup de processeurs ajoutent une mémoire tampon entre la RAM et le cache, appelée le '''tampon de remplissage de ligne''' (''line-fill buffer'') dans les processeurs modernes. Lors d'un défaut de cache, le processeur charge la donnée de la RAM dans le tampon de remplissage de ligne, mot mémoire par mot mémoire. Une fois plein, le tampon de remplissage de ligne est recopié dans la ligne de cache. Le tampon de remplissage de ligne contient une ligne de cache, si ce n'est que certains bits de contrôle ne sont pas présents. Le tampon de remplissage de ligne contient un bit de validité pour chaque mot mémoire de la ligne de cache, qui indiquent si le mot mémoire a été chargé. Par exemple, prenons un processeur 64 bits, qui gère donc des mots mémoire de 8 octets, avec des lignes de cache 256 octets/32 mots mémoire. Le tampon de remplissage de ligne contiendra 32 bits de validité. Si le processeur a chargé les 6 premiers mots mémoire, les 6 premiers bits de validité seront mis à 1, les autres seront encore à zéro. Il faut noter que la disponibilité de la ligne complète se détermine assez facilement en faisant un ET logique entre tous les bits de validité. Le processeur sait ainsi quand la ligne de cache est disponible entièrement et donc quand la transférer dans le cache. La même chose existe avec une hiérarchie de cache, sauf que l'on trouve une mémoire tampon entre chaque niveau de cache. Il y a un tampon de remplissage de ligne entre le cache L1 et le cache L2, entre le cache L2 et le L3, etc. Si le cache ne gère qu'un seul défaut de cache à la fois, le ''fill line buffer'' est une mémoire très simple, qui ne mémorise qu'une seule ligne de cache. Et cela vaut aussi bien pour un cache bloquant que non-bloquant. Mais sur les caches capables de gérer plusieurs défauts simultanés, le tampon de remplissage de ligne est une mémoire de type FIFO ou LIFO, capable de mémoriser plusieurs lignes de cache. Notons que le tampon de remplissage de ligne est très utile pour implémenter certaines techniques, par exemple le contournement du cache. Nous avions vu dans le chapitre sur le cache que certains accès mémoire doivent contourner le cache, pour des raisons de cohérence des caches. C'est notamment nécessaire pour accéder aux périphériques, mais c'est aussi utile pour des raisons de performances dans des cas très spécifiques. Les accès qui contournent le cache se font directement dans le tampon de remplissage de ligne : le processeur écrit ou lit les données depuis ce tampon de remplissage de ligne, sans accéder au cache. ===L'''early restart'' et le ''critical word load''=== La présence du ''line buffer'' permet une optimisation assez intéressante, qui permet de réduire la latence des défauts de cache. L'optimisation consiste à lire un mot mémoire dans le tampon de remplissage de ligne, même si la ligne de cache complète n'a pas encore été chargée. Il existe deux manières de faire cela, qui portent les noms d'''early restart'' et de ''critical word load''. La première est la version la plus simple, la seconde est plus complexe mais plus performante. Avec la technique d''''''early restart''''', la ligne de cache est chargée normalement, en partant de son premier mot mémoire. Dès que le mot mémoire lu/écrit par le processeur est copié dans le ''line fill buffer'', il est envoyé au processeur immédiatement. Illustrons le tout par un exemple, où une ligne de cache fait 16 mots mémoire. Le processeur effectue une lecture, qui lit le 5ème mot mémoire. Sans ''early restart'', le processeur doit charger les 16 mots mémoire avant de faire la lecture dans le cache. Avec ''early restart'', le processeur reçoit la donnée dès que le 5ème mot mémoire est disponible. Le processeur doit attendre que les 4 premiers mots mémoire soient chargés, puis le 5ème mot mémoire arrive et est envoyé directement au processeur, il est lu directement depuis le ''line fill buffer''. Les 11 mots mémoire suivants sont ensuite chargés dans le cache pendant que le processeur fait des calculs dans son coin. Le ''critical word load'' est une optimisation de la technique précédente où le chargement de la ligne de cache commence directement à la donnée demandée par le processeur. Pour reprendre l'exemple précédent, où le processeur demande le 5ème mot mémoire sur 16, le ''critical word load'' charge le 5ème mot mémoire en premier et l'envoie au processeur, ce qui fait qu'il est chargé très rapidement. Pas besoin d'attendre que le processeur charge les 4 mots mémoire précédents comme avec l'''early restart''. Dans le détail, le processeur charge le 5ème mot mémoire en premier, puis charge les 11 suivants, et termine par les 4 mots mémoire du début. En clair, le ''critical word load'' commence par charger le mot mémoire lu, puis les blocs suivants, avant de revenir au début du bloc pour charger les blocs restants. Ainsi, la donnée demandée par le processeur sera la première disponible. Pour cela, l'organisation du tampon de remplissage de ligne est modifiée de manière à rendre cela possible. Il a une taille égale à une ligne de cache complète, qui contient elle-même plusieurs mots mémoire. Dans le ''line-fill buffer'', chaque mot mémoire est stocké avec un tag, qui indique l'adresse du mot mémoire stocké dans le ''line-fill buffer''. Le ''line-fill buffer'' est donc un cache un peu particulier, qui fonctionne comme un cache du point de vue du processeur, comme une mémoire FIFO pour les transferts avec le cache. Ainsi, un processeur qui veut lire dans le cache après un défaut peut accéder à la donnée directement depuis le tampon de remplissage de ligne, alors que la ligne de cache n'a pas encore été totalement recopiée en mémoire. ==Les caches pipelinés== Il est possible de pipeliner l'accès au cache, ce qui demande juste de rajouter des registres au bon endroits et quelques circuits de contrôle. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, même ceux avec un pipeline dynamique. Et l'implémentation peut se faire de plusieurs manières différentes. Il faut dire que découper une mémoire cache en plusieurs étapes peut se faire de plusieurs manière. Commençons par la plus simple. Beaucoup de processeurs des années 2000 avaient une fréquence peu élevée comparée aux standards d'aujourd'hui, ce qui fait que leur cache avait bien un temps d'accès d'un cycle d'horloge. Mais l'accès au cache se faisait en deux cycles d'horloge : un cycle pour calculer une adresse et un cycle pour l'accès mémoire proprement dit. Et ces deux étapes étaient pipelinées, à savoir que deux micro-opérations mémoire peuvent s'exécuter en même temps : une dans l'étage de calcul d'adresse, une autre dans le cache. L'unité mémoire était donc pipelinée, alors que l'accès au cache ne l'était pas. Les processeurs qui implémentaient cette technique regroupent les micro-architectures K5 et K6 d'AMD, les processeurs Intel de micro-architecture P6 (Pentium 2 et 3) et quelques autres. Il est aussi possible de pipeliner l'accès au cache lui-même. Avec un cache pipeliné, l'accès au cache ne se fait pas en un seul cycle, mais en plusieurs. Cependant, on peut lancer un nouvel accès au cache à chaque cycle d'horloge, comme un processeur pipeliné lance une nouvelle instruction à chaque cycle. L'implémentation est assez simple : il suffit d'ajouter des registres dans le cache. Pour cela, on profite que le cache est composé de plusieurs composants séparés, qui échangent des données dans un ordre bien précis, d'un composant à un autre. De plus, le trajet des informations dans un cache est linéaire, ce qui les rend parfois pour l'usage d'un pipeline. ===Le ''pipelining'' des caches ''direct-mapped''=== Voici ce qui se passe avec un cache directement adressé. Pour rappel ce genre de cache est conçu en combinant une mémoire RAM, généralement une SRAM, avec quelques circuits de comparaison et un MUX. L'accès au cache se fait globalement en deux étapes : la première lit la donnée et le ''tag'' dans la SRAM, et on utilise les deux dans une seconde étape. Nous avions vu il y a quelques chapitre comment pipeliner des mémoires, dans le chapitre sur les mémoires évoluées. Et bien ces méthodes peuvent s'utiliser pour la mémoire RAM interne au cache !L'idée est d'insérer un registre entre la sortie de la RAM et la suite du cache, pour en faire un cache pipéliné, à deux étages. Le premier étage lit dans la SRAM, le second fait le reste. L'implémentation sur les caches associatifs à plusieurs voies est globalement la même à quelques détails près. [[File:Cache directement adressé pipeliné - deux étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - deux étages]] Avec le circuit précédent, il est possible d'aller plus loin, cette fois en pipelinant l'accès à la mémoire RAM interne au cache. Pour comprendre comment, rappelons qu'une mémoire SRAM est composée d'un plan mémoire et d'un décodeur. L'accès à la mémoire demande d'abord que le décodeur fasse son travail pour sélectionner la case mémoire adéquate, puis ensuite la lecture ou écriture a lieu dans cette case mémoire. L'accès se fait donc en deux étapes successives séparées, on a juste à mettre un registre entre les deux. Il suffit donc de mettre un gros registre entre le décodeur et le plan mémoire. [[File:Cache directement adressé pipeliné - trois étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - trois étages]] Et on peut aller encore plus loin en découpant le décodeur en deux circuits séparés. En effet, rappelez-vous le chapitre sur les circuits de sélection : nous avions vu qu'il est possible de créer des décodeurs en assemblant des décodeurs plus petits, contrôlés par un circuit de prédécodage. Et bien on peut encore une ajouter un registre entre ce circuit de prédécodage et les petits décodeurs. : Théoriquement, toute l'adresse est fournie d'un seul coup au cache, la quasi-totalité des processeurs présentent l'adresse complète à un cache pipeliné. Mais le Pentium 4 fait autrement. Il faut noter que les premiers étages manipulent l'indice dans la SRAM, qui est dans les bits de poids faible, alors que les étages ultérieurs manipulent le ''tag'' qui est dans les bits de poids fort. Les concepteurs du Pentium 4 ont alors décidé de présenter les bits de poids faible lors du premier cycle d'accès au cache, puis ceux de poids fort au second cycle. Pour cela, l'ALU fonctionnait à une fréquence double de celle du processeur, tout comme le cache L1. Il n'y avait pas de pipeline proprement dit, mais cela réduisait grandement la latence d'accès au cache. ===Le ''pipelining'' des caches splittés/sériels=== Il est aussi possible de pipeliner un cache dits splittés, aussi appelés à accès sériel. Pour rappel, les caches à accès sériel vérifient si il y a succès ou défaut de cache, avant d'accéder aux lignes de cache en cas de succès. Ils font donc différemment des autres caches, qui accèdent à une ligne de cache, avant de déterminer s'il y a succès ou défaut en lisant le tag de la ligne de cache. Les caches sériels disposent de deux SRAM : une pour les tags des lignes de cache et une pour les données. Ils accèdent à la SRAM pour les tags, avant d’accéder à la SRAM des données en cas de succès de cache. Vu que l'accès se fait en deux étapes, une vérification des tags suivie de la lecture/écriture des données, il est facile à pipeliner. Pipeliner le cache permet de régler le problème des accès au cache L1, et elle est tout le temps utilisé sur les processeurs modernes. Mais que faire en cas de défaut de cache ? ==Les caches non bloquants== Un '''cache bloquant''' est un cache auquel le processeur ne peut pas accéder pendant un défaut de cache. Il faut attendre que la lecture ou écriture en RAM soit terminée avant de pouvoir utiliser de nouveau le cache. Un '''cache non bloquant''' n'a pas ce problème : on peut l'utiliser pendant un défaut de cache. Les caches non bloquants permettent de démarrer une nouvelle lecture ou écriture alors qu'une autre est en cours, ce qui permet d'exécuter plusieurs lectures ou écritures en même temps. ===Les ''Miss Handling Status Registers''=== Lors d'un défaut de cache, la mémoire RAM est consultée pendant le défaut de cache, mais le cache est inutilisé. Un défaut de cache n'utilise pas le cache, ce n'est qu'un accès en mémoire RAM, sur le bus mémoire (ou un accès aux niveaux de cache inférieurs, peu importe). Le cache en lui-même est laissé libre, rien n’empêche d'y accéder, il est en réalité intrinsèquement non-bloquant. Les caches, bloquants comme non-bloquants, sont en réalité composés d'une mémoire cache proprement dite, entourée de circuits qui servent d'interface entre le processeur et le cache lui-même. Et parmi les circuits tout autour, certains gèrent l'accès au cache lors d'un défaut de cache. Ils sont regroupés sous le terme de '''''Miss Handling Architecture''''' (MHA). La différence entre un cache bloquant et un cache non-bloquant est en réalité liée à l'implémentation de la MHA. Les caches bloquants coupent volontairement l'accès au cache lors d'un défaut, car les défauts sont plus simples à gérer ainsi. Le défaut de cache rapatrie une donnée depuis la RAM, qui sera écrite dans le cache. Et il ne faut pas qu'une tentative d'accès à cette donnée ait lieu avant qu'elle ne soit chargée. Mais ce blocage est général et touche tout le cache, alors que seule une ligne de cache est concernée par le défaut de cache. L'idée derrière un cache non-bloquant est que seule la ligne de cache est bloquée, mais les autres sont accessibles. L'idée est alors de mémoriser les lignes de cache concernées par le défaut de cache, afin d'en bloquer l'accès. À chaque accès, on vérifie si la ligne de cache est déjà réservée par un défaut de cache. La lecture/écriture est alors bloquée si c'est le cas, mais elle accepte les accès sinon. Pour cela, la ''Miss Handling Architecture'' contient des registres qui mémorisent des informations sur les défauts de cache en cours. Ils portent le nom de '''''miss status handling registers''''', que j’appellerais dorénavant MSHR, qui sont aussi appelés des '''''miss buffer'''''. Le contenu des MSHR varie beaucoup suivant le processeur, mais ces derniers stockent au minimum les informations suivantes : * Le numéro de la ligne de cache dans laquelle les données sont chargées. * Un bit de validité qui indique si le MSHR est vide ou pas, qui est mis à 0 quand le défaut de cache est résolu. * Un ou plusieurs champs de lecture/écriture, qui contiennent des informations sur la lecture/écriture. ** Pour une lecture, elle contient des informations sur la destination de la donnée, à savoir qui prévenir quand le défaut de cache est terminé. C'est parfois un nom/numéro de registre (celui dans lequel charger la donnée), mais c'est souvent le numéro de l'entrée dans le ''load/store queue''. ** Pour les écritures, elle contient la donnée à écrire, ou éventuellement un numéro de ''load/store queue'' où se trouve la donnée à écrire. Il faut noter que le bus mémoire ne peut gérer qu'un seul défaut de cache à la fois. Aussi, il est intéressant de regarder ce qui se passe lorsqu'un second défaut de cache survient, pendant qu'un premier est en cours. Dans ce cas, il y a deux réponses qui correspondent à deux types de caches non-bloquants, qui portent les noms barbares de caches de type succès après défaut et défaut après défaut. Sur le premier type, il ne peut pas y avoir plusieurs défauts de cache simultanés. Dès qu'un second défaut de cache survient, le cache stoppe son activité et on ne peut plus démarrer de nouvelle lecture/écriture, tant que le premier défaut de cache n'est pas résolu. Le second type est plus souple et autorise la survenue de plusieurs défauts de cache simultanés. Du moins, jusqu'à une certaine limite, car le cache ne peut supporter qu'un nombre limité d'accès mémoires simultanés (pipelinés). ===Les accès simultanés à une même ligne de cache=== Il arrive que le processeur fasse plusieurs accès mémoire simultanés à la même ligne de cache. Si la ligne de cache en question n'a pas encore été chargée dans le cache, alors on a plusieurs défauts de cache consécutifs pour la même ligne de cache, et le cache non-bloquant doit gérer la situation. Pour la suite, il va falloir faire une petite distinction entre les défauts primaires et secondaires. Imaginons qu'un défaut de cache ait lieu et demande à charger une donnée dans la ligne de cache numéro N. Il s'agit du premier défaut impliquant cette ligne de cache précise, ce qui lui vaut le nom de '''défaut de cache primaire'''. Mais par la suite, d'autres accès mémoire à la même ligne de cache ont lieu, alors que la ligne de cache n'est pas encore disponible. Dans ce cas, il s'agit de '''défauts de cache secondaires'''. Pour l'unité d'accès mémoire, les défauts de cache primaire et secondaire sont différents (ils prennent tous une entrée dans la ''load/store queue''). Mais pour le cache, ils ne correspondent qu'à un seul accès au cache : celui qui demande de charger la ligne de cache demandée. Les défauts de cache primaire et secondaire à la même ligne de cache se voient attribuer un MSHR unique. La gestion des défauts de cache secondaires dépend du cache non-bloquant. La solution la plus simple ne permet pas les défauts de cache secondaires. Le cache ne permet pas deux défauts de cache simultanés pour la même ligne de cache. Les autres solutions le permettent, en fusionnant des accès simultanés à la même ligne de cache en un seul au niveau des MSHR. Dans tous les cas, détecter les défauts de cache secondaires sont un problème qu'il faut détecter. La ''Miss Handling Architecture'' doit détecter les défauts de cache secondaire. Pour cela, elle procède comme suit. Lors de chaque défaut de cache, la MHA récupère le numéro de la ligne de cache associé. Il vérifie alors chaque MSHR pour vérifier s'il contient le numéro en question. S'il n'y a aucune correspondance dans les MSHR, alors c'est signe que le défaut de cache est un défaut primaire. Mais s'il y en a une, alors c'est un défaut secondaire. Évidemment, cela signifie que lors d'un défaut de cache, le numéro de ligne de cache est envoyé à tous les MSHR, pour comparaison. Les MSHR sont donc regroupés dans une mémoire associative, une sorte de mini-cache, faciliter l'implémentation. Lors d'un défaut de cache primaire, l'accès à un cache non-bloquant se fait comme suit : le processeur envoie une adresse au cache, accède à celui-ci, et détecte la survenue d'un défaut de cache. Il en profite alors pour attribuer une ligne de cache dans laquelle sera chargée ce défaut. L'attribution est très simple dans le cas des caches ''direct mapped'', ou associatifs par voie, pour lesquels l'attribution se fait assez simplement. Il mémorise alors cette information dans les MSHR, après avoir vérifié que le défaut de cache n'était pas un défaut secondaire. Lors des accès ultérieurs à une adresse proche, censée être dans la même ligne de cache, le processeur va encore une fois rencontrer un défaut de cache. Il va alors déterminer le numéro de la ligne de cache associée à l'adresse, et comparer ce numéro avec les MSHRs. Si un MSHR contient ce numéro, c'est signe que le défaut de cache est un défaut secondaire. La MHA réagit alors différemment selon le processeur considéré. Une première solution n'autorise pas les défauts de cache secondaires. Si l'un d'entre eux survient, le processeur est gelé par un ''pipeline stall'', une bulle de pipeline. Une autre solution fusionne les défauts de cache secondaires avec le défaut de cache primaire : tout cela ne correspond qu'à un seul défaut de cache pour lui. ===Les MSHR simples=== Un cache non-bloquant à '''MSHR simple''' contient juste plusieurs MSHR qui mémorisent juste un numéro de ligne de cache, un bit de validité, le champ de lecture/écriture, et l'adresse exacte de lecture/écriture. Avec cette organisation, il est possible d'avoir plusieurs défauts de cache séparés, mais à la condition que chaque défaut accède à une ligne de cache différente. Deux accès simultanés à une même ligne de cache ne sont pas possibles, les défauts de cache secondaires ne sont pas autorisés. Ainsi, chaque défaut de cache se voit attribuer son propre MSHR, chacun contient un numéro de ligne de cache différent. Pour comprendre pourquoi c'est impossible de gérer les défauts secondaires, il faut regarder le champ de lecture/écriture. Si on veut effectuer plusieurs écritures consécutives à la même adresse, le MSHR n'aura pas de quoi mémoriser les deux données à écrire. Il pourra mémoriser la première donnée à écrire, pas la seconde. Même chose lors d'une lecture : le champ lecture/écriture peut mémorisr la destination de la première lecture, pas de la seconde. L'avantage est que la MHA n'a besoin que des MSHR et de quelques circuits annexes. Les autres solutions rajoutent des circuits annexes pour gérer les défauts de cache secondaires, qui utilisent beaucoup de circuits. Le cout en circuit est donc élevé, mais le gain en performance est là. Passons maintenant aux caches non-bloquants qui autorisent les défauts de cache secondaire. La solution la plus simple consiste à utiliser ===Les MSHR adressés implicitement=== Avec les '''MSHR adressés implicitement''', il est possible de fusionner plusieurs accès mémoire à une même ligne de cache, mais sous une condition très importante : ces accès lisent/écrivent des mots mémoire différents. Par exemple, imaginons qu'une ligne de cache contienne 8 mots mémoire de 64 bits. Si un premier accès mémoire lit le mot mémoire numéro 7 (dernier mot mémoire de la ligne), et le second accès le mot mémoire numéro 3, alors la fusion est possible. Mais si deux accès mémoire veulent lire/écrire le mot mémoire numéro 7, alors la fusion n'est pas possible et le processeur se bloque, un ''pipeline stall'' survient. Un MSHR adressé implicitement est un MSHR simple, qui contient naturellement un numéro de ligne de cache (tag) et un bit de validité, sauf que le champ de lecture/écriture est dupliqué. Les différents champs lecture/écriture d'un MSHR sont regroupés dans une mémoire RAM/cache qui contient autant d'entrées qu'il y a de mot mémoire dans une ligne de cache. Chaque entrée de la table est associée à un mot mémoire de la ligne de cache et stocke des informations sur celui-ci. Une entrée mémorise, au minimum : * Un champ de lecture/écriture qui contient soit la destination de la lecture, soit la donnée à écrire. * Un bit R/W permettant d'interpréter correctement le champ de lecture/écriture. * Un bit de validité pour chaque entrée de la table, qui dit si un défaut de cache antérieur accède déjà à ce mot mémoire. La fusion de deux défauts de cache est ainsi assez simple. Un défaut de cache primaire/secondaire configure l'entrée associée au mot mémoire lu/écrit. Si un défaut secondaire ultérieur lit/écrit un mot mémoire différent (dont le bit de validité de l'entrée dans la table est à zéro), pas de conflit : il configure une autre entrée, vide. Mais s'il lit/écrit un mot mémoire pour lequel l'entrée de la table est occupée, il y a conflit, le processeur est bloqué par un ''pipeline stall'', une bulle de pipeline. Avec cette organisation, le nombre de MSHR indique combien de lignes de cache peuvent être lues en même temps depuis la mémoire. Quant au nombre d'entrées par MSHR, il détermine combien d'accès mémoires qui ne se recouvrent pas peuvent avoir lieu en même temps. ===Les MSHR adressés explicitement=== Les '''MSHR adressés explicitement''' n'ont pas les contraintes des MSHR adressés implicitement. Avec eux, il est possible d'avoir plusieurs défauts de cache pointant vers la même ligne de cache, mais aussi vers le même mot mémoire. De tels défauts de cache apparaissent sur les processeurs à exécution dans le désordre, mais sont très rares, voire inexistants, sur les processeurs ''in-order''. L'idée est encore que chaque MSHR est associée à une table mémoire qui mémorise des entrées. Cette table n'est autre qu'une mémoire FIFO. Sauf que cette fois-ci, une entrée n'est pas associée à un mot mémoire. Une entrée est associée à un défaut de cache. Une entrée mémorise là encore la destination de la lecture ou la donnée de l'écriture suivi du bit R/W, ainsi que d'un bit de validité par entrée, mais aussi : la position du mot mémoire lu dans la ligne de cache. C'est cette dernière information qui n'était pas présente dans les MSHR adressés implicitement. De plus, le nombre d'entrée par MSHR n'est pas égal au nombre de mots mémoires dans une ligne de cache, mais peut être arbitrairement grand. L'avantage d'un tel cache est qu'il est capable de traité les défauts secondaires concernant un même mot mémoire, et ce, car les accès à la ligne de cache concernée se produisent dans l'ordre de génération des défauts (la table mémoire est une FIFO). ===Les MSHRs inversés=== Généralement, plus on veut supporter de défauts de cache, plus le nombre de MSHR et d'entrées augmente. Mais au-delà d'un certain nombre d'entrées et de MSHR, les MSHR adressés implicitement et explicitement ont tendance à bouffer un peu trop de circuits. Utiliser une organisation un peu moins gourmande en circuits est donc une nécessité. Cette organisation plus économe se base sur des MSHR inversés. Les MSHR inversés ne contiennent qu'une seule entrée, en quelque sorte : au lieu d’utiliser n MSHR de m entrées chacun, on va utiliser n × m MSHR inversés. La différence, c'est que plusieurs MSHR peuvent contenir un tag identique, contrairement aux MSHR adressés implicitement et explicitement. Lorsqu'un défaut de cache a lieu, chaque MSHR est vérifié. Si jamais aucun MSHR ne contient de tag identique à celui utilisé par le défaut, un MSHR vide est choisi pour stocker ce défaut, et une requête de lecture en mémoire est lancée. Dans le cas contraire, un MSHR est réservé au défaut de cache, mais la requête n'est pas lancée. Quand la donnée est disponible, les MSHR correspondant à la ligne qui vient d'être chargée vont être utilisés un par un pour résoudre les défauts de cache en attente. ===Les MSHR intégrés au cache=== Certains chercheurs ont remarqué que pendant qu'une ligne de cache est en train de subir un défaut de cache, celle-ci reste inutilisée, et son contenu est destiné à être perdu une fois le défaut de cache résolu. Ils se sont dit que, plutôt que d'utiliser des MSHR séparés, il vaudrait mieux utiliser la ligne de cache pour stocker les informations sur les défauts de cache en attente dans cette ligne de cache. Pour éviter tout problème, il faut rajouter un bit dans les bits de contrôle de la ligne de cache, qui sert à indiquer que la ligne de cache est occupée : un défaut de cache a eu lieu dans cette ligne, et elle stocke donc des informations pour résoudre les défauts de cache. ==L'entrelacement mémoire== Nous venons de voir comment une mémoire cache peut gérer plusieurs accès mémoire simultanés. Il se trouve que ce genre d'optimisation dépasse largement le cadre du cache. Les mémoires RAM ne sont pas en reste, elles aussi ! Il est possible de pipeliner l'accès à une mémoire RAM, en utilisant des techniques dites d''''entrelacement'''. Précisons cependant que cette optimisation n'est pas utilisée sur les mémoires SDRAM ou DDR modernes, du moins pas sans modifications majeures. Mais divers ordinateurs assez anciens, dont des superordinateurs, ont utilisé cette technique. La technique demande d'utiliser plusieurs mémoires séparées. Sur les anciens ordinateurs, les mémoires en question sont des chips mémoire, à savoir des circuits intégrés de DRAM. Pour faire plus général, nous allons utiliser le terme de '''banques''', ou encore de bancs mémoire. La différence est qu'il existe des mémoires multi-banque, qui regroupent plusieurs banques indépendantes dans un seul boitier, dans un seul chip mémoire. En clair, de telles mémoires regroupent plusieurs sous-mémoires dans un seul circuit intégré. Il est aussi possible d'utiliser plusieurs chips mémoire séparés, chacun ayant une banque. Reste à combiner ces banques pour former un pseudo-pipeline. ===Utiliser plusieurs banques sans entrelacement=== Sans optimisation particulière, les adresses sont réparties dans les banques comme indiqué ci-dessous. Il s'agit de ce qui s'appelle un arrangement horizontal, et nous avions vu celui-ci dans le chapiutre sur les mémoires SDRAM. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Avec cet arrangement, les bits de poids fort de l'adresse sont utilisées pour sélectionner la banque adéquate, et le reste de l'adresse est envoyé sur le bus d'adresse. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Adresse de banque !! Adresse dans la banque |- | Quelques bits de poids fort || Reste de l'adresse |} L'entrelacement change cette répartition, afin d'accélérer les accès mémoire. L'entrelacement classique vise à améliorer les accès à des adresses mémoires consécutives, mais des formes plus évoluées d'entrelacement visent à optimiser des accès mémoire arbitraires. Voyons-les dans l'ordre, du plus simple au plus complexe. ===L'entrelacement classique=== Avec l''''entrelacement classique''', des adresses consécutives sont réparties dans des banques consécutives. Précisons que cette attribution des adresses n'implique absolument pas la mémoire virtuelle ou n'importe quel mécanisme dans le processeur. La répartition décide que telle adresse mémoire va dans telle banque, à telle adresse dans la banque. Elle est donc le fait du contrôleur mémoire, donc en dehors du processeur (les contrôleurs mémoires n’étaient pas encore intégrés dans le processeur à l'époque). [[File:Répartition des adresses dans une mémoire interleaved.png|centre|vignette|upright=1.5|Répartition des adresses dans une mémoire interleaved.]] L'entrelacement simple permet d'accélérer les accès mémoire qui se font à des adresses consécutives. Avec l'entrelacement, chaque accès mémoire tombe dans une banque mémoire différente, ce qui fait qu'on peut démarrer un nouvel accès mémoire à chaque cycle d'horloge. Pendant qu'une banque est occupée par un accès mémoire, on démarre le suivant dans une autre banque, et ainsi de suite. Le tout se termine soit quand on a épuisé toutes les banques libres, soit quand l'accès en rafales se termine. Pas besoin d'attendre que la mémoire ait fini sa lecture/écriture avant de démarrer la lecture/écriture suivante. [[File:Pipemining mémoire.png|centre|vignette|upright=2.5|Pipelining mémoire]] L'animation suivante illustre bien le processus, avec quatres banques. [[File:Interleaving.gif|centre|vignette|upright=2.5|Pipelining mémoire]] L'entrelacement simple utilise les bits de poids faible pour sélectionner la banque, et les bits de poids fort pour la case mémoire. [[File:Adresse mémoire d'une mémoire entrelacée.png|centre|vignette|upright=2|Adresse mémoire d'une mémoire entrelacée]] Précisons que le temps d'accès mémoire ne change pas beaucoup avec l'entrelacement. Par contre, on peut faire plus d'accès mémoire simultanés. On peut démarrer un accès mémoire par cycle d'horloge, mais l'accès en lui-même prend plusieurs cycles. Les mémoires à entrelacement ont donc un débit supérieur aux mémoires qui ne l'utilisent pas. L'entrelacement classique pourrait être utilisé pour accélérer les transferts entre mémoire RAM et cache, qui se font en blocs d'adresses consécutives. Mais de nos jours, il n'est pas utilisé dans cette optique. Il faut dire qu'il est redondant avec le mode rafale des mémoires DRAM. Les deux font globalement la même chose et on voit mal comment utiliser les deux en même temps. Le mode rafale étant plus simple à implémenter, et plus léger niveau utilisation du bus de commande mémoire, il est préféré à l'entrelacement. Par contre, l'entrelacement a eu son heure de gloire sur d'anciens processeurs qui n'avaient pas de mémoire cache, alors qu'ils disposaient d'un pipeline. L'exemple typique est celui des nombreux ''processeurs vectoriels'', que nous n'avons pas encore abordé à ce stade du cours. De tels processeurs avaient un pipeline, une unité de calcul fortement pipelinée, et parfois même de l'exécution dans le désordre et du renommage de registres ! ===L'entrelacement par décalage=== L'entrelacement simple est très utile pour les accès en rafle ou équivalents. Par contre, il existe de rares situations où il n'est pas optimal. Une de ces situations est celle des accès en enjambée (en ''stride''), où l'on accède en série à des adresses sont séparées par N mots mémoires. [[File:Accès par enjambées.png|centre|vignette|upright=2|Accès par enjambées.]] De tels accès surviennent quand un logiciel accède à des structures de données spécifiques, à savoir des tableaux de structures, des matrices, ou d'autres structures de données dans le genre. Un cas classique est celui du parcours d'une matrice colonne par colonne. Les matrices de nombres sont mémorisées ligne par ligne, ce qui fait que parcourir une colonne demande de passer d'une ligne à la suivante et de faire des grands sauts en mémoire RAM, mais tous espacés par la même distance. Avec l'entrelacement simple, les accès en enjambée sont moins performants que les accès à des adresses consécutives, mais cela ne fonctionne pas trop mal. Le pire cas est celui où l'on a N banques et où les données sont justement placées toutes les N adresses. Des accès consécutifs vont tous tomber dans la même banque, on ne peut plus accéder à des banques différentes en parallèle. Mais en dehors de ce cas, on voit une amélioration par rapport à la situation sans entrelacement. Pour obtenir des performances maximales pour les accès en enjambées, il faut répartir les mots mémoires dans la mémoire autrement. L'organisation idéale est la suivante. : Dans les explications qui vont suivre, la variable N représente le nombre de banques, qui sont numérotées de 0 à N-1. On commence par organiser les N premières adresses comme une mémoire entrelacée simple : l'adresse 0 correspond à la banque 0, l'adresse 1 à la banque 1, etc. Pour le bloc suivant, on décale tout d'une adresse, à savoir qu'on commence à remplir les banques à partir de la seconde, non de la première. Une fois la fin du bloc atteinte, on finit de remplir le bloc en repartant du début du bloc. Le troisième bloc d'adresse subit le même traitement, sauf qu'on commence à remplir à partir de la seconde banque. Et on poursuit l’assignation des adresses en décalant d'un cran en plus à chaque bloc. Ainsi, chaque bloc verra ses adresses décalées d'un cran en plus comparé au bloc précédent. Si jamais le décalage dépasse la fin d'un bloc, alors on reprend au début. [[File:Mémoire interleaved par décalage.png|centre|vignette|upright=1.5|Mémoire entrelacée par décalage.]] En faisant cela, on remarque que les banques situées à N adresses d'intervalle sont différentes. Dans l'exemple du dessus, nous avons ajouté un décalage de 1 à chaque nouveau bloc à remplir. Mais on aurait tout aussi bien pu prendre un décalage de 2, 3, etc. Dans tous les cas, on obtient un entrelacement par décalage. Ce décalage est appelé le pas d'entrelacement, noté P. Le calcul de l'adresse à envoyer à la banque, ainsi que la banque à sélectionner se fait en utilisant les formules suivantes : * adresse à envoyer à la banque = adresse totale / N ; * numéro de la banque = (adresse + décalage) modulo N ; * décalage = (adresse totale * P) mod N. Avec cet entrelacement par décalage, on peut prouver que la bande passante maximale est atteinte si le nombre de banques est un nombre premier. Seulement, utiliser un nombre de banques premier peut créer des trous dans la mémoire, des mots mémoires inadressables. Malgré ce défaut, la technique a été utilisée sur quelques ordinateurs, avec l'exemple notable du superordinateur ''Burroughs Scientific Processor''. Pour éviter cela, il y a plusieurs solutions. Par exemple, on peut faire en sorte que N et la taille d'une banque soient premiers entre eux : ils ne doivent pas avoir de diviseur commun. Mais en pratique, elles n'ont pas vraiment été implémentées dans une vraie machine, et sont restées à l'état de recherche, aussi je les passe sous silence. ===L'entrelacement pseudo-aléatoire=== Une dernière méthode de répartition consiste à répartir les adresses dans les banques de manière "pseudo-aléatoire". L'idée exacte est de faire passer l'adresse dans une '''fonction de hachage''', plus ou moins complexe. Pour rappel, une fonction de hachage prend une entrée de grande taille et fournit en sortie un résultat de petite taille. Idéalement, elles doivent donner des résultats aussi différents que possible pour des entrées similaires, histoire de simuler une sorte de pseudo-aléatoire. Ici, une adresse est transformée en un numéro de banque. Une fonction de hachage simple ne fait que permuter des bits de l'adresse pour obtenir son résultat. Elle ne fait qu'échanger des bits de place avant de couper l'adresse en deux morceaux : un pour la sélection de la banque, et un autre pour la sélection de l'adresse dans la banque. Cette permutation est fixe, et ne change pas suivant l'adresse. Des fonctions de hachage plus complexes font des XOR bit à bit entre certains bits de l'adresse. ==Les contrôleurs SDRAM/DDR avec "exécution dans le désordre"== Après avoir vu le cas des mémoires RAM, nous allons nous concentrer sur le cas particulier des mémoires SDRAM. Les mémoires SDRAM et DDR modernes sont capables de gérer plusieurs accès simultanés à la mémoire RAM, et leurs contrôleurs mémoire en font tout autant. C'est une différence majeure avec les mémoires asynchrones FPM/EDO, qui n'acceptaient qu'un seul accès mémoire à la fois. Leur contrôleur mémoire n'acceptait qu'un seul accès mémoire à la fois, c'était un contrôleur mémoire bloquant. Les contrôleurs mémoires des SDRAM sont eux non-bloquants et peuvent encaisser une dizaine d'accès mémoire à la fois. Peu de choses sont connues sur les contrôleurs de SDRAM/DDR modernes, les fabricants ne donnant que peu de détails dessus. Les rares simulateurs qui tentent de décrire leur fonctionnement, comme DRAM SIM I et II, sont particulièrement simples et ne vont pas dans le détail. Néanmoins, le peu qu'on sait est tout de même instructif. ===Rappel sur les SDRAM : tampon de ligne et commandes=== Les mémoires SDRAM sont des mémoires à tampon de ligne. Elles font un accès mémoire en deux étapes. La première étape recopie une ligne de N * 64 bits dans un tampon interne à la SDRAM. La seconde étape sélectionne une colonne, à savoir une donnée de 64 bits, qui est soit envoyée sur le bus de données pour une lecture, soit modifiée par une écriture. [[File:Mémoire à tampon de ligne.png|centre|vignette|upright=2|Mémoire à tampon de ligne]] Les accès mémoire sont traduits par un séquenceur mémoire en une série de commandes mémoires, qui sont séparées par des délais mémoire de quelques cycles d'horloge. Les délais sont très précis, et sont à respecter à la lettre. Une lecture ou une écriture se fait en maximum trois commandes : une commande PRECHARGE qui ferme la ligne précédemment utilisée, une commande ACT qui précise l'adresse de la ligne, et une commande READ ou WRITE qui précise l'adresse de la colonne et éventuellement la donnée à écrire. : Pour être plus précis, la commande PRECHARGE précharge les lignes de bits du plan mémoire à une tension particulière, ce qui les vide de leur contenu. Mais c'est un détail sans importance pour ce qui va suivre. Les SDRAM permettent de se passer de la première étape si des accès consécutifs se font dans la même ligne. Une fois activée, la ligne reste ouverte et on peut accéder plusieurs fois de suite dedans. On a alors juste à préciser l'adresse de colonne dedans. Si un accès mémoire accède à une ligne déjà activée, on dit que c'est un '''succès de page'''. Si ce n'est pas le cas, on doit fermer la ligne courante, rouvrir la ligne vboulue, et préciser la colonne. C'est alors un '''défaut de page'''. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | bgcolor="#FFA0FF" | PRECHARGE || bgcolor="#FFA0FF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || bgcolor="#A0FFFF" | READ (2) || bgcolor="#A0FFFF" | READ (3) || || || bgcolor="#A0FFFF" | READ (4) || bgcolor="#A0FFFF" | READ (5) || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | Donnée READ 1 || ||bgcolor="#A0FFFF" | Donnée READ 2 | bgcolor="#A0FFFF" | Donnée READ 3 || || || bgcolor="#A0FFFF" | Donnée READ 4 || bgcolor="#A0FFFF" | Donnée READ 5 |} Les SDRAM sont parfois capables de démarrer une commande avant que la précédente soit terminée. Mais le respect des délais mémoire est très limitant, ce qui fait qu'on ne peut pas parler de réel pipeline, comme c'est le cas sur les processeurs. Parlons plutot de '''commandes anticipées'''. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | || bgcolor="#A0FFFF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || || || bgcolor="#FFA0FF" | READ (2) || || || || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 | bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 | |} Un dernier point est que les chips mémoires de SDRAM sont composés de plusieurs banques, chacune étant une sorte de mini-mémoire miniature. Chaque banque a son propre décodeur, son propre tampon de ligne, ses propres multiplexeurs de colonne, sa logique de rafraichissement mémoire, etc. C'est comme si une SDRAM regroupait plusieurs mémoires séparées dans un même circuit intégré. [[File:Arrangement vertical.jpg|centre|vignette|upright=2.5|Mémoire multi-banques.]] ===La mise en attente des accès mémoire=== Un contrôleur de SDRAM peut accepter plusieurs accès mémoire en même temps. Mais cela ne signifie pas que le contrôleur sera prêt à les traiter immédiatement. Pour éviter tout problème de disponibilité, le contrôleur met en attente les accès mémoire que le processeur lui envoie, pour les exécuter dès que possible. Les accès mémoire sont mis en attente dans une mémoire FIFO, histoire de les exécuter dans leur ordre d'arrivée, notamment pour renvoyer les lectures dans l'ordre demandé. Il y a aussi une mémoire FIFO pour les données à écrire et une FIFO pour les données lues. Cette dernière sert au cas où le cache ne soit pas disponible quand on lui envoie la donnée lue. [[File:Module d'interface avec la mémoire.png|centre|vignette|upright=2.5|Contrôleur mémoire avec mise en attente des requêtes processeur et autres optimisations.]] Des optimisations sont possibles dès la mémoire FIFO. Par exemple, on peut regrouper plusieurs accès à des données consécutives en un seul accès en rafale. Le contrôleur mémoire analyse les requêtes mises en attente et détecte si certaines se font à des adresses consécutives. Si c'est le cas, il fusionne ces requêtes en une lecture/écriture en rafale. Une telle optimisation est appelée la '''combinaison de lecture''' pour les lectures, et la '''combinaison d'écriture''' pour les écritures. L'optimisation précédente ne paraissent pas payer de mine, mais elles deviennent plus compréhensible quand on sait que certains contrôleurs mémoire peuvent mettre en attente beaucoup de données. Par exemple, l'''Intel E8870 - Scalable Memory Controller'' peut mettre en attente 64 lignes de cache dans la mémoire FIFO, soit 8 kibioctets de RAM ! Deux pages mémoire si la mémoire virtuelle utilise des pages de 4 kibioctets ! ===Les lectures anticipées : une forme d'OOO mémoire=== La mise en attente des accès mémoire est une optimisation intéressante, mais pas mirifique. Elle permet surtout de ne pas bloquer le processeur si le contrôleur mémoire a du travail sur la planche. Cependant, elle peut être optimisée quand on se rend compte d'une chose : le processeur ne voit que les lectures, pas les écritures. Pour les écritures, il les envoie au contrôleur mémoire et ce dernier fait le travail demandé, le processeur n'est pas prévenu quand une écriture se termine. Mais pour les lectures, c'est différent, car il reçoit la donnée lue. En conséquence, il est possible d'optimiser les écritures sans que le processeur ne voit quoique ce soit. Une optimisation possible est alors de changer l'ordre des accès mémoire, histoire de retarder les écritures ou au contraire de les faire en avance. La raison est que les mémoires SDRAM n'aiment pas quand on alterne lectures et écritures. Les délais mémoire, les fameux ''timings'' mémoire, sont clairs : faire une suite de lecture ou une suite d'écriture est plus performant que d'alterner entre lectures et écritures. L'idée est alors de changer l'ordre des écritures de manière à les faire en bloc, idem pour les lectures. L’optimisation est possible, mais à condition que le processeur n'y verra que du feu. Or, vous l'avez deviné, il y a des cas où ces réorganisations ne sont pas possibles, et vous avez sans doute trouvé que la situation était familière. L'optimisation en question est une forme d'exécution dans le désordre des accès mémoire, réalisée par le contrôleur mémoire lui-même ! Et qui dit exécution dans le désordre dit : problèmes liés aux dépendances de données. Changer l'ordre des écritures n'est pas toujours possible, notamment en raison des dépendances RAW, WAR et WAW. Pour que le processeur n'y voit que du feu, il faut respecter plusieurs critères. Premièrement, les lectures doivent être renvoyées au processeur dans l'ordre. Et quand je dis dans l'ordre, cela veut dire : dans l'ordre d'envoi des lectures au contrôleur mémoire. Les écritures aussi doivent se faire dans l'ordre, histoire qu'une série d'écriture donne le bon résultat final. Le troisième critère est qu'une lecture doit donner le résultat de la dernière écriture ''à la même adresse''. Pour le dire autrement, il faut juste détecter les dépendances RAW. Concrètement, si on a une série de lectures et d'écritures alternées, on peut regrouper les lectures et les écritures dans deux paquets séparés, à condition qu'aucune lecture ne lise une adresse écrite par une écriture. Mais si une lecture est dépendante d'une écriture, alors la lecture doit attendre que l'écriture se termine, idem pour les lectures suivantes. Une implémentation basique remplace la mémoire FIFO vue au-dessus, par deux mémoires FIFOs : une pour les lectures, une pour les écritures. Lorsqu'un accès mémoire arrive au séquenceur, il regarde si c'est une lecture ou une écriture et place l'accès mémoire dans la file adéquate. Le fait que ce soit des FIFOs garantit que les lectures se font dans l'ordre, idem pour les écritures. En sortie des deux FIFOs, le séquenceur mémoire détecte les dépendances RAW. Précisément, quand une lecture sort de la mémoire FIFO, il consulte la file d'attente des écriture pour voir si la lecture est dépendante d'une écriture en attente. La lecture attend si c'est le cas, et les écritures sont exécutées à la place. Séparer lectures et écriture est une source d'optimisation. Il est par exemple possible d'utiliser le réacheminement lecture sur écriture (''Store to load forwarding''). Il s'agit d'un équivalent de l’optimisation du même nom, utilisée dans le cadre de la désambiguïsation mémoire. Imaginez qu'une lecture accède à une donnée pas encore écrite, en attente dans le contrôleur mémoire. C'est une dépendance RAW assez claire. Dans ce cas, le contrôleur mémoire peut renvoyer la donnée directement depuis ses mémoires FIFOs, sans faire d'accès mémoire en lecture. Le contrôleur mémoire a tendance à privilégier les lectures, car celles-ci sont cruciales pour l'exécution dans le désordre. Plus elles se font tôt, plus vite le processeur recevra les données et pourra démarrer les instructions dépendantes de cette donnée. Le séquenceur a donc tendance à piocher en priorité dans la file de lecture, plutôt que dans la file d'écriture. A vrai dire, dans le cas idéal, les écritures ne sont faites que quand la file d'écriture est pleine ou quasi-pleine... Prenons l'exemple des coprocesseurs IO 81341 et 81342, qui étaient en réalité des ''chipsets'' intégrant un contrôleur SDRAM. Le ''chipset'' avait 5 ports : un pour les processeurs, un pour le pont sud (''southbridge'') et trois pour des canaux DMA. Pour le port processeur, il n'y avait pas de réordonnancement et ce port utilisait une seule mémoire FIFO. Les autres ports utilisaient l'optimisation qu'on vient de voir, et avaient vraisemblablement des files séparées pour les lectures et écritures. Le séquenceur mémoire vérifiait les dépendances mémoire de type RAW et autres. [[File:Double file d'attente pour les lectures et écritures.png|centre|vignette|upright=2.5|Double file d'attente pour les lectures et écritures]] ===Le ré-ordonnancement des commandes mémoires=== L'optimisation précédente est peu poussée, comparé aux formes d'exécution dans le désordre que les processeurs utilisent. Et on peut se demander si une exécution dans le désordre plus poussée est possible. La réponse est oui : un contrôleur mémoire peut faire des réorganisations bien plus poussées. Par contre, il faut faire une précision très importante : le contrôleur mémoire ne remet pas les accès mémoire dans l'ordre avant de les envoyer au processeur. C'est le processeur qui remet en ordre les accès mémoire, dans sa ''load queue''. En clair : le processeur voit les accès mémoire dans le désordre, c'est lui qui les remet en ordre. Pour cela, les requêtes mémoire sont "numérotées", à savoir qu'on leur attribue un identifiant binaire. Le contrôleur mémoire exécute les lectures/écritures dans le désordre, mais garde la trace de leur identifiant. Il sait dans quel ordre il exécute les accès mémoire et connait leur latence. Ce qui fait qu'il sait que la donnée lue à tel instant est associée à la requête mémoire numéro X. Pour les lectures, il renvoie la donnée lue avec l'identifiant. Le processeur sait alors à quelle lecture la donnée lue correspond et il se débrouille en interne pour gérer la situation. Maintenant que ces précisions sont faites, posons cette question : dans quelles situations est-il pertinent de faire des accès mémoire dans le désordre ? La réponse est : quand plusieurs accès à une même ligne ne sont pas consécutifs, qu'il y a des accès entre les deux. Après ré-ordonnancement, ces accès mémoire à une même ligne sont exécutés l'un à la suite de l'autre, ce qui est beaucoup plus rapide. Pour rendre le tout plus concret, voici un exemple. Imaginez que l'on sait les 3 accès mémoire suivants : * Une lecture ligne A ; * PRECHARGE + ACT ; * Une écriture ligne B ; * PRECHARGE + ACT ; * Une lecture ligne A. Sans réordonnancent, on doit émettre deux commandes PRECHARGE quand on passe d'une ligne à l'autre, et il faut ajouter les commandes ACT avec. pour éviter cela, le contrôleur mémoire peut retarder l'écriture, ou au contraire avancer la seconde lecture. le résultat est alors le suivant : * Lecture ligne A ; * Lecture ligne A * PRECHARGE + ACT ; * Une écriture ligne B. On a donc deux accès consécutifs à la même ligne, suivi par une écriture dans une autre ligne. La technique marche parce que l'on a un mix adéquat de lectures et d'écritures. Et encore une fois, il faut éviter d'intervertir lectures et écritures à une même adresse, sous peine de problèmes. Encore une fois, les dépendances RAW posent problème ! L'implémentation est assez simple : la ou les mémoires FIFOs précédentes sont remplacées par des mémoires similaires aux fenêtres d'instruction. Le contrôleur mémoire vérifie à chaque cycle les accès en attente, et vérifie à quelle ligne ils accèdent. Il priorise alors les accès qui tombent dans la ligne ouverte : ceux-là sont exécutés avant les autres. S'il n'y en a pas, il prend l'accès mémoire le plus ancien (ordre FIFO). Il teste aussi les dépendances mémoires RAW avant d'envoyer des commandes à la mémoire DDR/SDRAM. une telle solution est appelée l''''algorithme ''FR-FCFS''''' (''First Ready-First Come First Serve''). ===Les optimisations liées à la présence de plusieurs banques=== Les optimisations précédentes sont décuplées par la présence de plusieurs banques dans la mémoire SDRAM. Il est en effet possible de faire plusieurs accès mémoire en même temps, dans des banques différentes. Il est possible de lancer un accès mémoire dans deux banques en même temps, par exemple. La seule contrainte est que la SDRAM est limitée à 4 banques actives en même temps. {|class="wikitable" |+ Pipelining basique sur les SDRAM |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 |- ! Banque Numéro 1 | || || || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || || |- ! Banque Numéro 2 | || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || |- ! Banque Numéro 3 | || || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || |} Les optimisations précédentes peuvent s'appliquer par banque. Il est par exemple possible d'utiliser une mémoire FIFO par banque. Ainsi, les accès dans une même banque se feront en série, dans l'ordre d'arrivée, mais des accès à des banques différentes se feront dans le désordre. Cela ne pose pas de problème car les accès se font à des adresses différentes, ce qui fait qu'il n'y a pas de conflits majeurs. Il y a cependant un risque que les lectures se fassent dans le désordre si on n'y prend pas garde. [[File:Gestion parallèle des banques.png|centre|vignette|upright=2.5|Gestion parallèle des banques.]] Il est aussi possible d'utiliser une file séparée pour les lectures et les écriture, pour chaque banque. Les performances sont alors améliorées comparé à deux files globales pour toute la SDRAM. En idem avec la réorganisation des accès mémoire, pour regrouper les accès à une même ligne. Un problème avec ces optimisations est qu'il est rare que des accès mémoire se fassent dans des banques séparées. Les accès mémoire tendent à se faire avec une bonne localité spatiale, ce qui fait qu'ils tombent dans une même banque. Heureusement, les contrôleurs SDRAM/DDR modernes incorporent des optimisations pour corriger ce problème. Les optimisations en question sont une forme d'entrelacement adaptée aux mémoires SDRAM. L'entrelacement naïf ne marche pas à cause de la présence du tampon de ligne. Cependant, il peut y avoir un entrelacemententre banques SDRAM. Voyons ce que ca veut dire. L''''entrelacement de banques''' répartit deux lignes consécutives dans deux banques différentes. Pour comprendre l'idée, prenons un exemple. Imaginons une mémoire avec deux banques et 4 lignes. Imaginons qu'on parcoure/balaye la mémoire RAM en partant des adresses basses. Sans entrelacement de ligne, les accès se feront comme suit, de gauche à droite : {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Banque numéro 1 !! colspan="4" | Banque numéro 2 |- | Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 || Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 |} Avec l'entrelacement de banques, les accès se feront comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 !! Banque numéro 1 !! Banque numéro 2 |- | Ligne 1 || Ligne 1 || Ligne 2 || Ligne 2 || Ligne 3 || Ligne 3 || Ligne 4 || Ligne 4 |} Avec N banques, la première ligne ira dans la première banque, la seconde ligne dans la seconde, et ainsi de suite jusqu'à la énième banque qui va dans la banque numéro N. Et la ligne N+1 repart dans la première banque. L'avantage est que passer d'une ligne à la suivante est assez rapide. Au lieu de devoir fermer la première ligne et ouvrir la suivante on ouvrira directement la ligne suivante dans une banque différente, ce qui sera plus rapide. La commande PRECHARGE pourra être envoyée à la seconde banque en avance. Pour gérer l'entrelacement, le contrôleur de SDRAM prend en entrée l'adresse envoyée par le processeur, et la découpe en plusieurs champs : un pour sélectionner la banque adéquate, un autre pour la ligne, un autre pour la colonne, un autre pour la rangée. Sans entrelacement, les adresses mémoire sont découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- | Adresse de banque || Adresse de ligne || Adresse de colonne |} Avec l'entrelacement de banques, l'adresse est découpée comme suit. {|class="wikitable" |+ Adresse mémoire |- | Adresse de ligne || Adresse de banque || Adresse de colonne (précise à l'octet) |} Il est aussi possible de faire la même chose, mais avec les rangées ou en utilisant du ''dual channel'', mais je vais passer cela sous silence. Toujours est-il que les méthodes d'entrelacement sont nombreuses et chaque contrôleur mémoire fait un peu à sa sauce. Ils permettent en général de choisir entre plusieurs entrelacements. Au minimum, ils permettent de désactiver l'entrelacement, d'activer un entrelacement de ligne, et d'autres entrelacements plus complexes, dont certains sont propriétaires et tenus secrets par le fabricant. L'entrelacement est géré juste avant le séquenceur mémoire, dans un '''circuit d'entrelacement'''. Ce dernier intervertit certains bits de l'adresse lors des accès mémoires. [[File:Controleur mémoire d'une SDRAM avec entrelacement.png|centre|vignette|upright=2.5|Contrôleur mémoire d'une SDRAM avec entrelacement.]] Les contrôleurs de SDRAM précédents sont assez basiques et ne représentent pas les contrôleurs les plus évolués. Ils étaient utilisés sur les anciens PC, à une époque où ils étaient encore sur la carte mère du processeur. Mais de nos jours, le contrôleur mémoire est intégré au processeur. Et il incorpore de nombreuses optimisations afin de gagner en performances. Malheureusement, ces optimisations sont elles-mêmes dépendantes des optimisations intégrées dans le processeur, ce qui fait qu'on ne peut pas en parler ici. Elles demandent que le contrôleur mémoire reçoive plusieurs accès mémoire simultanés, ce qui n'a aucun sens à ce stade du cours. Aussi, nous allons les passer sous silence pour le moment. Nous les verrons dans le chapitre sur le parallélisme mémoire. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=La désambiguïsation mémoire | prevText=La désambiguïsation mémoire | next=Les processeurs superscalaires | nextText=Les processeurs superscalaires }} </noinclude> e6vxvbxbwmc132ma8wqw6rdl95xzjm1 764820 764819 2026-04-24T10:52:37Z Mewtow 31375 /* Les optimisations liées à la présence de plusieurs banques */ 764820 wikitext text/x-wiki Dans ce chapitre, nous allons voir les techniques qui permettent de gérer plusieurs accès mémoire simultanés directement au niveau du cache ou de la mémoire RAM. Par plusieurs accès mémoire simultanés, vous pensez sans doute à l'usage de cache multiports, voire de mémoires RAM multiport. C'est en effet une solution, mais ce n'est clairement pas la seule. Par exemple, il est possible de pipeliner l'accès au cache, voire à la mémoire RAM. Il existe de nombreuses techniques de '''parallélisme mémoire''', et nous allons les voir dans ce chapitre. Pour donner un exemple, il est possible de pipeliner les accès mémoire. Il est possible de pipeliner l'accès au cache et/ou l'accès à la mémoire, et nous verrons comment faire dans ce chapitre. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, qu'ils aient ou non un pipeline dynamique. Par contre, pipeliner la mémoire est une technique ancienne, aujourd'hui peu utilisée. Elle était utilisée sur des très vieux ordinateurs, qui avaient un pipeline mais pas de mémoire cache. Mais de nos jours, les mémoires SDRAM et DDR ne sont pas adaptées pour ça, il est très difficile de les pipeliner correctement. Le parallélisme mémoire est utile aussi bien sur des processeurs à émission dans l'ordre que dans le désordre. Par exemple, un processeur ''in-order'' avec des lectures non-bloquantes peut e, profiter. Il peut techniquement lancer une seconde lecture, après avoir rencontré une première lecture non-bloquante. En général, le nombre de lectures consécutives est cependant limité à moins d'une dizaine. À l'opposé, les processeurs sans lectures non-bloquantes profitent pas du parallélisme mémoire. À la rigueur, ils peuvent en profiter s'ils intègrent des techniques de préchargement. Pendant que le processeur exécute une lecture/écriture, il peut précharger une autre donnée en parallèle. Inutile de dire que sans pipeline processeur, le parallélisme mémoire ne sert pas à grand-chose. ==Le ''line fill buffer'' et les techniques associées== Lors d'un défaut de cache, le processeur doit attendre que toute la ligne de cache soit chargée avant d'être utilisable. Or, la taille d'une ligne de cache est supérieure à la largeur du bus mémoire, ce qui fait qu'une ligne de cache est chargée en plusieurs fois, morceaux par morceaux, mot mémoire par mot mémoire. Le chargement peut se faire directement dans le cache, mais ce n'est pas une solution très pratique. À la place, beaucoup de processeurs ajoutent une mémoire tampon entre la RAM et le cache, appelée le '''tampon de remplissage de ligne''' (''line-fill buffer'') dans les processeurs modernes. Lors d'un défaut de cache, le processeur charge la donnée de la RAM dans le tampon de remplissage de ligne, mot mémoire par mot mémoire. Une fois plein, le tampon de remplissage de ligne est recopié dans la ligne de cache. Le tampon de remplissage de ligne contient une ligne de cache, si ce n'est que certains bits de contrôle ne sont pas présents. Le tampon de remplissage de ligne contient un bit de validité pour chaque mot mémoire de la ligne de cache, qui indiquent si le mot mémoire a été chargé. Par exemple, prenons un processeur 64 bits, qui gère donc des mots mémoire de 8 octets, avec des lignes de cache 256 octets/32 mots mémoire. Le tampon de remplissage de ligne contiendra 32 bits de validité. Si le processeur a chargé les 6 premiers mots mémoire, les 6 premiers bits de validité seront mis à 1, les autres seront encore à zéro. Il faut noter que la disponibilité de la ligne complète se détermine assez facilement en faisant un ET logique entre tous les bits de validité. Le processeur sait ainsi quand la ligne de cache est disponible entièrement et donc quand la transférer dans le cache. La même chose existe avec une hiérarchie de cache, sauf que l'on trouve une mémoire tampon entre chaque niveau de cache. Il y a un tampon de remplissage de ligne entre le cache L1 et le cache L2, entre le cache L2 et le L3, etc. Si le cache ne gère qu'un seul défaut de cache à la fois, le ''fill line buffer'' est une mémoire très simple, qui ne mémorise qu'une seule ligne de cache. Et cela vaut aussi bien pour un cache bloquant que non-bloquant. Mais sur les caches capables de gérer plusieurs défauts simultanés, le tampon de remplissage de ligne est une mémoire de type FIFO ou LIFO, capable de mémoriser plusieurs lignes de cache. Notons que le tampon de remplissage de ligne est très utile pour implémenter certaines techniques, par exemple le contournement du cache. Nous avions vu dans le chapitre sur le cache que certains accès mémoire doivent contourner le cache, pour des raisons de cohérence des caches. C'est notamment nécessaire pour accéder aux périphériques, mais c'est aussi utile pour des raisons de performances dans des cas très spécifiques. Les accès qui contournent le cache se font directement dans le tampon de remplissage de ligne : le processeur écrit ou lit les données depuis ce tampon de remplissage de ligne, sans accéder au cache. ===L'''early restart'' et le ''critical word load''=== La présence du ''line buffer'' permet une optimisation assez intéressante, qui permet de réduire la latence des défauts de cache. L'optimisation consiste à lire un mot mémoire dans le tampon de remplissage de ligne, même si la ligne de cache complète n'a pas encore été chargée. Il existe deux manières de faire cela, qui portent les noms d'''early restart'' et de ''critical word load''. La première est la version la plus simple, la seconde est plus complexe mais plus performante. Avec la technique d''''''early restart''''', la ligne de cache est chargée normalement, en partant de son premier mot mémoire. Dès que le mot mémoire lu/écrit par le processeur est copié dans le ''line fill buffer'', il est envoyé au processeur immédiatement. Illustrons le tout par un exemple, où une ligne de cache fait 16 mots mémoire. Le processeur effectue une lecture, qui lit le 5ème mot mémoire. Sans ''early restart'', le processeur doit charger les 16 mots mémoire avant de faire la lecture dans le cache. Avec ''early restart'', le processeur reçoit la donnée dès que le 5ème mot mémoire est disponible. Le processeur doit attendre que les 4 premiers mots mémoire soient chargés, puis le 5ème mot mémoire arrive et est envoyé directement au processeur, il est lu directement depuis le ''line fill buffer''. Les 11 mots mémoire suivants sont ensuite chargés dans le cache pendant que le processeur fait des calculs dans son coin. Le ''critical word load'' est une optimisation de la technique précédente où le chargement de la ligne de cache commence directement à la donnée demandée par le processeur. Pour reprendre l'exemple précédent, où le processeur demande le 5ème mot mémoire sur 16, le ''critical word load'' charge le 5ème mot mémoire en premier et l'envoie au processeur, ce qui fait qu'il est chargé très rapidement. Pas besoin d'attendre que le processeur charge les 4 mots mémoire précédents comme avec l'''early restart''. Dans le détail, le processeur charge le 5ème mot mémoire en premier, puis charge les 11 suivants, et termine par les 4 mots mémoire du début. En clair, le ''critical word load'' commence par charger le mot mémoire lu, puis les blocs suivants, avant de revenir au début du bloc pour charger les blocs restants. Ainsi, la donnée demandée par le processeur sera la première disponible. Pour cela, l'organisation du tampon de remplissage de ligne est modifiée de manière à rendre cela possible. Il a une taille égale à une ligne de cache complète, qui contient elle-même plusieurs mots mémoire. Dans le ''line-fill buffer'', chaque mot mémoire est stocké avec un tag, qui indique l'adresse du mot mémoire stocké dans le ''line-fill buffer''. Le ''line-fill buffer'' est donc un cache un peu particulier, qui fonctionne comme un cache du point de vue du processeur, comme une mémoire FIFO pour les transferts avec le cache. Ainsi, un processeur qui veut lire dans le cache après un défaut peut accéder à la donnée directement depuis le tampon de remplissage de ligne, alors que la ligne de cache n'a pas encore été totalement recopiée en mémoire. ==Les caches pipelinés== Il est possible de pipeliner l'accès au cache, ce qui demande juste de rajouter des registres au bon endroits et quelques circuits de contrôle. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, même ceux avec un pipeline dynamique. Et l'implémentation peut se faire de plusieurs manières différentes. Il faut dire que découper une mémoire cache en plusieurs étapes peut se faire de plusieurs manière. Commençons par la plus simple. Beaucoup de processeurs des années 2000 avaient une fréquence peu élevée comparée aux standards d'aujourd'hui, ce qui fait que leur cache avait bien un temps d'accès d'un cycle d'horloge. Mais l'accès au cache se faisait en deux cycles d'horloge : un cycle pour calculer une adresse et un cycle pour l'accès mémoire proprement dit. Et ces deux étapes étaient pipelinées, à savoir que deux micro-opérations mémoire peuvent s'exécuter en même temps : une dans l'étage de calcul d'adresse, une autre dans le cache. L'unité mémoire était donc pipelinée, alors que l'accès au cache ne l'était pas. Les processeurs qui implémentaient cette technique regroupent les micro-architectures K5 et K6 d'AMD, les processeurs Intel de micro-architecture P6 (Pentium 2 et 3) et quelques autres. Il est aussi possible de pipeliner l'accès au cache lui-même. Avec un cache pipeliné, l'accès au cache ne se fait pas en un seul cycle, mais en plusieurs. Cependant, on peut lancer un nouvel accès au cache à chaque cycle d'horloge, comme un processeur pipeliné lance une nouvelle instruction à chaque cycle. L'implémentation est assez simple : il suffit d'ajouter des registres dans le cache. Pour cela, on profite que le cache est composé de plusieurs composants séparés, qui échangent des données dans un ordre bien précis, d'un composant à un autre. De plus, le trajet des informations dans un cache est linéaire, ce qui les rend parfois pour l'usage d'un pipeline. ===Le ''pipelining'' des caches ''direct-mapped''=== Voici ce qui se passe avec un cache directement adressé. Pour rappel ce genre de cache est conçu en combinant une mémoire RAM, généralement une SRAM, avec quelques circuits de comparaison et un MUX. L'accès au cache se fait globalement en deux étapes : la première lit la donnée et le ''tag'' dans la SRAM, et on utilise les deux dans une seconde étape. Nous avions vu il y a quelques chapitre comment pipeliner des mémoires, dans le chapitre sur les mémoires évoluées. Et bien ces méthodes peuvent s'utiliser pour la mémoire RAM interne au cache !L'idée est d'insérer un registre entre la sortie de la RAM et la suite du cache, pour en faire un cache pipéliné, à deux étages. Le premier étage lit dans la SRAM, le second fait le reste. L'implémentation sur les caches associatifs à plusieurs voies est globalement la même à quelques détails près. [[File:Cache directement adressé pipeliné - deux étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - deux étages]] Avec le circuit précédent, il est possible d'aller plus loin, cette fois en pipelinant l'accès à la mémoire RAM interne au cache. Pour comprendre comment, rappelons qu'une mémoire SRAM est composée d'un plan mémoire et d'un décodeur. L'accès à la mémoire demande d'abord que le décodeur fasse son travail pour sélectionner la case mémoire adéquate, puis ensuite la lecture ou écriture a lieu dans cette case mémoire. L'accès se fait donc en deux étapes successives séparées, on a juste à mettre un registre entre les deux. Il suffit donc de mettre un gros registre entre le décodeur et le plan mémoire. [[File:Cache directement adressé pipeliné - trois étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - trois étages]] Et on peut aller encore plus loin en découpant le décodeur en deux circuits séparés. En effet, rappelez-vous le chapitre sur les circuits de sélection : nous avions vu qu'il est possible de créer des décodeurs en assemblant des décodeurs plus petits, contrôlés par un circuit de prédécodage. Et bien on peut encore une ajouter un registre entre ce circuit de prédécodage et les petits décodeurs. : Théoriquement, toute l'adresse est fournie d'un seul coup au cache, la quasi-totalité des processeurs présentent l'adresse complète à un cache pipeliné. Mais le Pentium 4 fait autrement. Il faut noter que les premiers étages manipulent l'indice dans la SRAM, qui est dans les bits de poids faible, alors que les étages ultérieurs manipulent le ''tag'' qui est dans les bits de poids fort. Les concepteurs du Pentium 4 ont alors décidé de présenter les bits de poids faible lors du premier cycle d'accès au cache, puis ceux de poids fort au second cycle. Pour cela, l'ALU fonctionnait à une fréquence double de celle du processeur, tout comme le cache L1. Il n'y avait pas de pipeline proprement dit, mais cela réduisait grandement la latence d'accès au cache. ===Le ''pipelining'' des caches splittés/sériels=== Il est aussi possible de pipeliner un cache dits splittés, aussi appelés à accès sériel. Pour rappel, les caches à accès sériel vérifient si il y a succès ou défaut de cache, avant d'accéder aux lignes de cache en cas de succès. Ils font donc différemment des autres caches, qui accèdent à une ligne de cache, avant de déterminer s'il y a succès ou défaut en lisant le tag de la ligne de cache. Les caches sériels disposent de deux SRAM : une pour les tags des lignes de cache et une pour les données. Ils accèdent à la SRAM pour les tags, avant d’accéder à la SRAM des données en cas de succès de cache. Vu que l'accès se fait en deux étapes, une vérification des tags suivie de la lecture/écriture des données, il est facile à pipeliner. Pipeliner le cache permet de régler le problème des accès au cache L1, et elle est tout le temps utilisé sur les processeurs modernes. Mais que faire en cas de défaut de cache ? ==Les caches non bloquants== Un '''cache bloquant''' est un cache auquel le processeur ne peut pas accéder pendant un défaut de cache. Il faut attendre que la lecture ou écriture en RAM soit terminée avant de pouvoir utiliser de nouveau le cache. Un '''cache non bloquant''' n'a pas ce problème : on peut l'utiliser pendant un défaut de cache. Les caches non bloquants permettent de démarrer une nouvelle lecture ou écriture alors qu'une autre est en cours, ce qui permet d'exécuter plusieurs lectures ou écritures en même temps. ===Les ''Miss Handling Status Registers''=== Lors d'un défaut de cache, la mémoire RAM est consultée pendant le défaut de cache, mais le cache est inutilisé. Un défaut de cache n'utilise pas le cache, ce n'est qu'un accès en mémoire RAM, sur le bus mémoire (ou un accès aux niveaux de cache inférieurs, peu importe). Le cache en lui-même est laissé libre, rien n’empêche d'y accéder, il est en réalité intrinsèquement non-bloquant. Les caches, bloquants comme non-bloquants, sont en réalité composés d'une mémoire cache proprement dite, entourée de circuits qui servent d'interface entre le processeur et le cache lui-même. Et parmi les circuits tout autour, certains gèrent l'accès au cache lors d'un défaut de cache. Ils sont regroupés sous le terme de '''''Miss Handling Architecture''''' (MHA). La différence entre un cache bloquant et un cache non-bloquant est en réalité liée à l'implémentation de la MHA. Les caches bloquants coupent volontairement l'accès au cache lors d'un défaut, car les défauts sont plus simples à gérer ainsi. Le défaut de cache rapatrie une donnée depuis la RAM, qui sera écrite dans le cache. Et il ne faut pas qu'une tentative d'accès à cette donnée ait lieu avant qu'elle ne soit chargée. Mais ce blocage est général et touche tout le cache, alors que seule une ligne de cache est concernée par le défaut de cache. L'idée derrière un cache non-bloquant est que seule la ligne de cache est bloquée, mais les autres sont accessibles. L'idée est alors de mémoriser les lignes de cache concernées par le défaut de cache, afin d'en bloquer l'accès. À chaque accès, on vérifie si la ligne de cache est déjà réservée par un défaut de cache. La lecture/écriture est alors bloquée si c'est le cas, mais elle accepte les accès sinon. Pour cela, la ''Miss Handling Architecture'' contient des registres qui mémorisent des informations sur les défauts de cache en cours. Ils portent le nom de '''''miss status handling registers''''', que j’appellerais dorénavant MSHR, qui sont aussi appelés des '''''miss buffer'''''. Le contenu des MSHR varie beaucoup suivant le processeur, mais ces derniers stockent au minimum les informations suivantes : * Le numéro de la ligne de cache dans laquelle les données sont chargées. * Un bit de validité qui indique si le MSHR est vide ou pas, qui est mis à 0 quand le défaut de cache est résolu. * Un ou plusieurs champs de lecture/écriture, qui contiennent des informations sur la lecture/écriture. ** Pour une lecture, elle contient des informations sur la destination de la donnée, à savoir qui prévenir quand le défaut de cache est terminé. C'est parfois un nom/numéro de registre (celui dans lequel charger la donnée), mais c'est souvent le numéro de l'entrée dans le ''load/store queue''. ** Pour les écritures, elle contient la donnée à écrire, ou éventuellement un numéro de ''load/store queue'' où se trouve la donnée à écrire. Il faut noter que le bus mémoire ne peut gérer qu'un seul défaut de cache à la fois. Aussi, il est intéressant de regarder ce qui se passe lorsqu'un second défaut de cache survient, pendant qu'un premier est en cours. Dans ce cas, il y a deux réponses qui correspondent à deux types de caches non-bloquants, qui portent les noms barbares de caches de type succès après défaut et défaut après défaut. Sur le premier type, il ne peut pas y avoir plusieurs défauts de cache simultanés. Dès qu'un second défaut de cache survient, le cache stoppe son activité et on ne peut plus démarrer de nouvelle lecture/écriture, tant que le premier défaut de cache n'est pas résolu. Le second type est plus souple et autorise la survenue de plusieurs défauts de cache simultanés. Du moins, jusqu'à une certaine limite, car le cache ne peut supporter qu'un nombre limité d'accès mémoires simultanés (pipelinés). ===Les accès simultanés à une même ligne de cache=== Il arrive que le processeur fasse plusieurs accès mémoire simultanés à la même ligne de cache. Si la ligne de cache en question n'a pas encore été chargée dans le cache, alors on a plusieurs défauts de cache consécutifs pour la même ligne de cache, et le cache non-bloquant doit gérer la situation. Pour la suite, il va falloir faire une petite distinction entre les défauts primaires et secondaires. Imaginons qu'un défaut de cache ait lieu et demande à charger une donnée dans la ligne de cache numéro N. Il s'agit du premier défaut impliquant cette ligne de cache précise, ce qui lui vaut le nom de '''défaut de cache primaire'''. Mais par la suite, d'autres accès mémoire à la même ligne de cache ont lieu, alors que la ligne de cache n'est pas encore disponible. Dans ce cas, il s'agit de '''défauts de cache secondaires'''. Pour l'unité d'accès mémoire, les défauts de cache primaire et secondaire sont différents (ils prennent tous une entrée dans la ''load/store queue''). Mais pour le cache, ils ne correspondent qu'à un seul accès au cache : celui qui demande de charger la ligne de cache demandée. Les défauts de cache primaire et secondaire à la même ligne de cache se voient attribuer un MSHR unique. La gestion des défauts de cache secondaires dépend du cache non-bloquant. La solution la plus simple ne permet pas les défauts de cache secondaires. Le cache ne permet pas deux défauts de cache simultanés pour la même ligne de cache. Les autres solutions le permettent, en fusionnant des accès simultanés à la même ligne de cache en un seul au niveau des MSHR. Dans tous les cas, détecter les défauts de cache secondaires sont un problème qu'il faut détecter. La ''Miss Handling Architecture'' doit détecter les défauts de cache secondaire. Pour cela, elle procède comme suit. Lors de chaque défaut de cache, la MHA récupère le numéro de la ligne de cache associé. Il vérifie alors chaque MSHR pour vérifier s'il contient le numéro en question. S'il n'y a aucune correspondance dans les MSHR, alors c'est signe que le défaut de cache est un défaut primaire. Mais s'il y en a une, alors c'est un défaut secondaire. Évidemment, cela signifie que lors d'un défaut de cache, le numéro de ligne de cache est envoyé à tous les MSHR, pour comparaison. Les MSHR sont donc regroupés dans une mémoire associative, une sorte de mini-cache, faciliter l'implémentation. Lors d'un défaut de cache primaire, l'accès à un cache non-bloquant se fait comme suit : le processeur envoie une adresse au cache, accède à celui-ci, et détecte la survenue d'un défaut de cache. Il en profite alors pour attribuer une ligne de cache dans laquelle sera chargée ce défaut. L'attribution est très simple dans le cas des caches ''direct mapped'', ou associatifs par voie, pour lesquels l'attribution se fait assez simplement. Il mémorise alors cette information dans les MSHR, après avoir vérifié que le défaut de cache n'était pas un défaut secondaire. Lors des accès ultérieurs à une adresse proche, censée être dans la même ligne de cache, le processeur va encore une fois rencontrer un défaut de cache. Il va alors déterminer le numéro de la ligne de cache associée à l'adresse, et comparer ce numéro avec les MSHRs. Si un MSHR contient ce numéro, c'est signe que le défaut de cache est un défaut secondaire. La MHA réagit alors différemment selon le processeur considéré. Une première solution n'autorise pas les défauts de cache secondaires. Si l'un d'entre eux survient, le processeur est gelé par un ''pipeline stall'', une bulle de pipeline. Une autre solution fusionne les défauts de cache secondaires avec le défaut de cache primaire : tout cela ne correspond qu'à un seul défaut de cache pour lui. ===Les MSHR simples=== Un cache non-bloquant à '''MSHR simple''' contient juste plusieurs MSHR qui mémorisent juste un numéro de ligne de cache, un bit de validité, le champ de lecture/écriture, et l'adresse exacte de lecture/écriture. Avec cette organisation, il est possible d'avoir plusieurs défauts de cache séparés, mais à la condition que chaque défaut accède à une ligne de cache différente. Deux accès simultanés à une même ligne de cache ne sont pas possibles, les défauts de cache secondaires ne sont pas autorisés. Ainsi, chaque défaut de cache se voit attribuer son propre MSHR, chacun contient un numéro de ligne de cache différent. Pour comprendre pourquoi c'est impossible de gérer les défauts secondaires, il faut regarder le champ de lecture/écriture. Si on veut effectuer plusieurs écritures consécutives à la même adresse, le MSHR n'aura pas de quoi mémoriser les deux données à écrire. Il pourra mémoriser la première donnée à écrire, pas la seconde. Même chose lors d'une lecture : le champ lecture/écriture peut mémorisr la destination de la première lecture, pas de la seconde. L'avantage est que la MHA n'a besoin que des MSHR et de quelques circuits annexes. Les autres solutions rajoutent des circuits annexes pour gérer les défauts de cache secondaires, qui utilisent beaucoup de circuits. Le cout en circuit est donc élevé, mais le gain en performance est là. Passons maintenant aux caches non-bloquants qui autorisent les défauts de cache secondaire. La solution la plus simple consiste à utiliser ===Les MSHR adressés implicitement=== Avec les '''MSHR adressés implicitement''', il est possible de fusionner plusieurs accès mémoire à une même ligne de cache, mais sous une condition très importante : ces accès lisent/écrivent des mots mémoire différents. Par exemple, imaginons qu'une ligne de cache contienne 8 mots mémoire de 64 bits. Si un premier accès mémoire lit le mot mémoire numéro 7 (dernier mot mémoire de la ligne), et le second accès le mot mémoire numéro 3, alors la fusion est possible. Mais si deux accès mémoire veulent lire/écrire le mot mémoire numéro 7, alors la fusion n'est pas possible et le processeur se bloque, un ''pipeline stall'' survient. Un MSHR adressé implicitement est un MSHR simple, qui contient naturellement un numéro de ligne de cache (tag) et un bit de validité, sauf que le champ de lecture/écriture est dupliqué. Les différents champs lecture/écriture d'un MSHR sont regroupés dans une mémoire RAM/cache qui contient autant d'entrées qu'il y a de mot mémoire dans une ligne de cache. Chaque entrée de la table est associée à un mot mémoire de la ligne de cache et stocke des informations sur celui-ci. Une entrée mémorise, au minimum : * Un champ de lecture/écriture qui contient soit la destination de la lecture, soit la donnée à écrire. * Un bit R/W permettant d'interpréter correctement le champ de lecture/écriture. * Un bit de validité pour chaque entrée de la table, qui dit si un défaut de cache antérieur accède déjà à ce mot mémoire. La fusion de deux défauts de cache est ainsi assez simple. Un défaut de cache primaire/secondaire configure l'entrée associée au mot mémoire lu/écrit. Si un défaut secondaire ultérieur lit/écrit un mot mémoire différent (dont le bit de validité de l'entrée dans la table est à zéro), pas de conflit : il configure une autre entrée, vide. Mais s'il lit/écrit un mot mémoire pour lequel l'entrée de la table est occupée, il y a conflit, le processeur est bloqué par un ''pipeline stall'', une bulle de pipeline. Avec cette organisation, le nombre de MSHR indique combien de lignes de cache peuvent être lues en même temps depuis la mémoire. Quant au nombre d'entrées par MSHR, il détermine combien d'accès mémoires qui ne se recouvrent pas peuvent avoir lieu en même temps. ===Les MSHR adressés explicitement=== Les '''MSHR adressés explicitement''' n'ont pas les contraintes des MSHR adressés implicitement. Avec eux, il est possible d'avoir plusieurs défauts de cache pointant vers la même ligne de cache, mais aussi vers le même mot mémoire. De tels défauts de cache apparaissent sur les processeurs à exécution dans le désordre, mais sont très rares, voire inexistants, sur les processeurs ''in-order''. L'idée est encore que chaque MSHR est associée à une table mémoire qui mémorise des entrées. Cette table n'est autre qu'une mémoire FIFO. Sauf que cette fois-ci, une entrée n'est pas associée à un mot mémoire. Une entrée est associée à un défaut de cache. Une entrée mémorise là encore la destination de la lecture ou la donnée de l'écriture suivi du bit R/W, ainsi que d'un bit de validité par entrée, mais aussi : la position du mot mémoire lu dans la ligne de cache. C'est cette dernière information qui n'était pas présente dans les MSHR adressés implicitement. De plus, le nombre d'entrée par MSHR n'est pas égal au nombre de mots mémoires dans une ligne de cache, mais peut être arbitrairement grand. L'avantage d'un tel cache est qu'il est capable de traité les défauts secondaires concernant un même mot mémoire, et ce, car les accès à la ligne de cache concernée se produisent dans l'ordre de génération des défauts (la table mémoire est une FIFO). ===Les MSHRs inversés=== Généralement, plus on veut supporter de défauts de cache, plus le nombre de MSHR et d'entrées augmente. Mais au-delà d'un certain nombre d'entrées et de MSHR, les MSHR adressés implicitement et explicitement ont tendance à bouffer un peu trop de circuits. Utiliser une organisation un peu moins gourmande en circuits est donc une nécessité. Cette organisation plus économe se base sur des MSHR inversés. Les MSHR inversés ne contiennent qu'une seule entrée, en quelque sorte : au lieu d’utiliser n MSHR de m entrées chacun, on va utiliser n × m MSHR inversés. La différence, c'est que plusieurs MSHR peuvent contenir un tag identique, contrairement aux MSHR adressés implicitement et explicitement. Lorsqu'un défaut de cache a lieu, chaque MSHR est vérifié. Si jamais aucun MSHR ne contient de tag identique à celui utilisé par le défaut, un MSHR vide est choisi pour stocker ce défaut, et une requête de lecture en mémoire est lancée. Dans le cas contraire, un MSHR est réservé au défaut de cache, mais la requête n'est pas lancée. Quand la donnée est disponible, les MSHR correspondant à la ligne qui vient d'être chargée vont être utilisés un par un pour résoudre les défauts de cache en attente. ===Les MSHR intégrés au cache=== Certains chercheurs ont remarqué que pendant qu'une ligne de cache est en train de subir un défaut de cache, celle-ci reste inutilisée, et son contenu est destiné à être perdu une fois le défaut de cache résolu. Ils se sont dit que, plutôt que d'utiliser des MSHR séparés, il vaudrait mieux utiliser la ligne de cache pour stocker les informations sur les défauts de cache en attente dans cette ligne de cache. Pour éviter tout problème, il faut rajouter un bit dans les bits de contrôle de la ligne de cache, qui sert à indiquer que la ligne de cache est occupée : un défaut de cache a eu lieu dans cette ligne, et elle stocke donc des informations pour résoudre les défauts de cache. ==L'entrelacement mémoire== Nous venons de voir comment une mémoire cache peut gérer plusieurs accès mémoire simultanés. Il se trouve que ce genre d'optimisation dépasse largement le cadre du cache. Les mémoires RAM ne sont pas en reste, elles aussi ! Il est possible de pipeliner l'accès à une mémoire RAM, en utilisant des techniques dites d''''entrelacement'''. Précisons cependant que cette optimisation n'est pas utilisée sur les mémoires SDRAM ou DDR modernes, du moins pas sans modifications majeures. Mais divers ordinateurs assez anciens, dont des superordinateurs, ont utilisé cette technique. La technique demande d'utiliser plusieurs mémoires séparées. Sur les anciens ordinateurs, les mémoires en question sont des chips mémoire, à savoir des circuits intégrés de DRAM. Pour faire plus général, nous allons utiliser le terme de '''banques''', ou encore de bancs mémoire. La différence est qu'il existe des mémoires multi-banque, qui regroupent plusieurs banques indépendantes dans un seul boitier, dans un seul chip mémoire. En clair, de telles mémoires regroupent plusieurs sous-mémoires dans un seul circuit intégré. Il est aussi possible d'utiliser plusieurs chips mémoire séparés, chacun ayant une banque. Reste à combiner ces banques pour former un pseudo-pipeline. ===Utiliser plusieurs banques sans entrelacement=== Sans optimisation particulière, les adresses sont réparties dans les banques comme indiqué ci-dessous. Il s'agit de ce qui s'appelle un arrangement horizontal, et nous avions vu celui-ci dans le chapiutre sur les mémoires SDRAM. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Avec cet arrangement, les bits de poids fort de l'adresse sont utilisées pour sélectionner la banque adéquate, et le reste de l'adresse est envoyé sur le bus d'adresse. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Adresse de banque !! Adresse dans la banque |- | Quelques bits de poids fort || Reste de l'adresse |} L'entrelacement change cette répartition, afin d'accélérer les accès mémoire. L'entrelacement classique vise à améliorer les accès à des adresses mémoires consécutives, mais des formes plus évoluées d'entrelacement visent à optimiser des accès mémoire arbitraires. Voyons-les dans l'ordre, du plus simple au plus complexe. ===L'entrelacement classique=== Avec l''''entrelacement classique''', des adresses consécutives sont réparties dans des banques consécutives. Précisons que cette attribution des adresses n'implique absolument pas la mémoire virtuelle ou n'importe quel mécanisme dans le processeur. La répartition décide que telle adresse mémoire va dans telle banque, à telle adresse dans la banque. Elle est donc le fait du contrôleur mémoire, donc en dehors du processeur (les contrôleurs mémoires n’étaient pas encore intégrés dans le processeur à l'époque). [[File:Répartition des adresses dans une mémoire interleaved.png|centre|vignette|upright=1.5|Répartition des adresses dans une mémoire interleaved.]] L'entrelacement simple permet d'accélérer les accès mémoire qui se font à des adresses consécutives. Avec l'entrelacement, chaque accès mémoire tombe dans une banque mémoire différente, ce qui fait qu'on peut démarrer un nouvel accès mémoire à chaque cycle d'horloge. Pendant qu'une banque est occupée par un accès mémoire, on démarre le suivant dans une autre banque, et ainsi de suite. Le tout se termine soit quand on a épuisé toutes les banques libres, soit quand l'accès en rafales se termine. Pas besoin d'attendre que la mémoire ait fini sa lecture/écriture avant de démarrer la lecture/écriture suivante. [[File:Pipemining mémoire.png|centre|vignette|upright=2.5|Pipelining mémoire]] L'animation suivante illustre bien le processus, avec quatres banques. [[File:Interleaving.gif|centre|vignette|upright=2.5|Pipelining mémoire]] L'entrelacement simple utilise les bits de poids faible pour sélectionner la banque, et les bits de poids fort pour la case mémoire. [[File:Adresse mémoire d'une mémoire entrelacée.png|centre|vignette|upright=2|Adresse mémoire d'une mémoire entrelacée]] Précisons que le temps d'accès mémoire ne change pas beaucoup avec l'entrelacement. Par contre, on peut faire plus d'accès mémoire simultanés. On peut démarrer un accès mémoire par cycle d'horloge, mais l'accès en lui-même prend plusieurs cycles. Les mémoires à entrelacement ont donc un débit supérieur aux mémoires qui ne l'utilisent pas. L'entrelacement classique pourrait être utilisé pour accélérer les transferts entre mémoire RAM et cache, qui se font en blocs d'adresses consécutives. Mais de nos jours, il n'est pas utilisé dans cette optique. Il faut dire qu'il est redondant avec le mode rafale des mémoires DRAM. Les deux font globalement la même chose et on voit mal comment utiliser les deux en même temps. Le mode rafale étant plus simple à implémenter, et plus léger niveau utilisation du bus de commande mémoire, il est préféré à l'entrelacement. Par contre, l'entrelacement a eu son heure de gloire sur d'anciens processeurs qui n'avaient pas de mémoire cache, alors qu'ils disposaient d'un pipeline. L'exemple typique est celui des nombreux ''processeurs vectoriels'', que nous n'avons pas encore abordé à ce stade du cours. De tels processeurs avaient un pipeline, une unité de calcul fortement pipelinée, et parfois même de l'exécution dans le désordre et du renommage de registres ! ===L'entrelacement par décalage=== L'entrelacement simple est très utile pour les accès en rafle ou équivalents. Par contre, il existe de rares situations où il n'est pas optimal. Une de ces situations est celle des accès en enjambée (en ''stride''), où l'on accède en série à des adresses sont séparées par N mots mémoires. [[File:Accès par enjambées.png|centre|vignette|upright=2|Accès par enjambées.]] De tels accès surviennent quand un logiciel accède à des structures de données spécifiques, à savoir des tableaux de structures, des matrices, ou d'autres structures de données dans le genre. Un cas classique est celui du parcours d'une matrice colonne par colonne. Les matrices de nombres sont mémorisées ligne par ligne, ce qui fait que parcourir une colonne demande de passer d'une ligne à la suivante et de faire des grands sauts en mémoire RAM, mais tous espacés par la même distance. Avec l'entrelacement simple, les accès en enjambée sont moins performants que les accès à des adresses consécutives, mais cela ne fonctionne pas trop mal. Le pire cas est celui où l'on a N banques et où les données sont justement placées toutes les N adresses. Des accès consécutifs vont tous tomber dans la même banque, on ne peut plus accéder à des banques différentes en parallèle. Mais en dehors de ce cas, on voit une amélioration par rapport à la situation sans entrelacement. Pour obtenir des performances maximales pour les accès en enjambées, il faut répartir les mots mémoires dans la mémoire autrement. L'organisation idéale est la suivante. : Dans les explications qui vont suivre, la variable N représente le nombre de banques, qui sont numérotées de 0 à N-1. On commence par organiser les N premières adresses comme une mémoire entrelacée simple : l'adresse 0 correspond à la banque 0, l'adresse 1 à la banque 1, etc. Pour le bloc suivant, on décale tout d'une adresse, à savoir qu'on commence à remplir les banques à partir de la seconde, non de la première. Une fois la fin du bloc atteinte, on finit de remplir le bloc en repartant du début du bloc. Le troisième bloc d'adresse subit le même traitement, sauf qu'on commence à remplir à partir de la seconde banque. Et on poursuit l’assignation des adresses en décalant d'un cran en plus à chaque bloc. Ainsi, chaque bloc verra ses adresses décalées d'un cran en plus comparé au bloc précédent. Si jamais le décalage dépasse la fin d'un bloc, alors on reprend au début. [[File:Mémoire interleaved par décalage.png|centre|vignette|upright=1.5|Mémoire entrelacée par décalage.]] En faisant cela, on remarque que les banques situées à N adresses d'intervalle sont différentes. Dans l'exemple du dessus, nous avons ajouté un décalage de 1 à chaque nouveau bloc à remplir. Mais on aurait tout aussi bien pu prendre un décalage de 2, 3, etc. Dans tous les cas, on obtient un entrelacement par décalage. Ce décalage est appelé le pas d'entrelacement, noté P. Le calcul de l'adresse à envoyer à la banque, ainsi que la banque à sélectionner se fait en utilisant les formules suivantes : * adresse à envoyer à la banque = adresse totale / N ; * numéro de la banque = (adresse + décalage) modulo N ; * décalage = (adresse totale * P) mod N. Avec cet entrelacement par décalage, on peut prouver que la bande passante maximale est atteinte si le nombre de banques est un nombre premier. Seulement, utiliser un nombre de banques premier peut créer des trous dans la mémoire, des mots mémoires inadressables. Malgré ce défaut, la technique a été utilisée sur quelques ordinateurs, avec l'exemple notable du superordinateur ''Burroughs Scientific Processor''. Pour éviter cela, il y a plusieurs solutions. Par exemple, on peut faire en sorte que N et la taille d'une banque soient premiers entre eux : ils ne doivent pas avoir de diviseur commun. Mais en pratique, elles n'ont pas vraiment été implémentées dans une vraie machine, et sont restées à l'état de recherche, aussi je les passe sous silence. ===L'entrelacement pseudo-aléatoire=== Une dernière méthode de répartition consiste à répartir les adresses dans les banques de manière "pseudo-aléatoire". L'idée exacte est de faire passer l'adresse dans une '''fonction de hachage''', plus ou moins complexe. Pour rappel, une fonction de hachage prend une entrée de grande taille et fournit en sortie un résultat de petite taille. Idéalement, elles doivent donner des résultats aussi différents que possible pour des entrées similaires, histoire de simuler une sorte de pseudo-aléatoire. Ici, une adresse est transformée en un numéro de banque. Une fonction de hachage simple ne fait que permuter des bits de l'adresse pour obtenir son résultat. Elle ne fait qu'échanger des bits de place avant de couper l'adresse en deux morceaux : un pour la sélection de la banque, et un autre pour la sélection de l'adresse dans la banque. Cette permutation est fixe, et ne change pas suivant l'adresse. Des fonctions de hachage plus complexes font des XOR bit à bit entre certains bits de l'adresse. ==Les contrôleurs SDRAM/DDR avec "exécution dans le désordre"== Après avoir vu le cas des mémoires RAM, nous allons nous concentrer sur le cas particulier des mémoires SDRAM. Les mémoires SDRAM et DDR modernes sont capables de gérer plusieurs accès simultanés à la mémoire RAM, et leurs contrôleurs mémoire en font tout autant. C'est une différence majeure avec les mémoires asynchrones FPM/EDO, qui n'acceptaient qu'un seul accès mémoire à la fois. Leur contrôleur mémoire n'acceptait qu'un seul accès mémoire à la fois, c'était un contrôleur mémoire bloquant. Les contrôleurs mémoires des SDRAM sont eux non-bloquants et peuvent encaisser une dizaine d'accès mémoire à la fois. Peu de choses sont connues sur les contrôleurs de SDRAM/DDR modernes, les fabricants ne donnant que peu de détails dessus. Les rares simulateurs qui tentent de décrire leur fonctionnement, comme DRAM SIM I et II, sont particulièrement simples et ne vont pas dans le détail. Néanmoins, le peu qu'on sait est tout de même instructif. ===Rappel sur les SDRAM : tampon de ligne et commandes=== Les mémoires SDRAM sont des mémoires à tampon de ligne. Elles font un accès mémoire en deux étapes. La première étape recopie une ligne de N * 64 bits dans un tampon interne à la SDRAM. La seconde étape sélectionne une colonne, à savoir une donnée de 64 bits, qui est soit envoyée sur le bus de données pour une lecture, soit modifiée par une écriture. [[File:Mémoire à tampon de ligne.png|centre|vignette|upright=2|Mémoire à tampon de ligne]] Les accès mémoire sont traduits par un séquenceur mémoire en une série de commandes mémoires, qui sont séparées par des délais mémoire de quelques cycles d'horloge. Les délais sont très précis, et sont à respecter à la lettre. Une lecture ou une écriture se fait en maximum trois commandes : une commande PRECHARGE qui ferme la ligne précédemment utilisée, une commande ACT qui précise l'adresse de la ligne, et une commande READ ou WRITE qui précise l'adresse de la colonne et éventuellement la donnée à écrire. : Pour être plus précis, la commande PRECHARGE précharge les lignes de bits du plan mémoire à une tension particulière, ce qui les vide de leur contenu. Mais c'est un détail sans importance pour ce qui va suivre. Les SDRAM permettent de se passer de la première étape si des accès consécutifs se font dans la même ligne. Une fois activée, la ligne reste ouverte et on peut accéder plusieurs fois de suite dedans. On a alors juste à préciser l'adresse de colonne dedans. Si un accès mémoire accède à une ligne déjà activée, on dit que c'est un '''succès de page'''. Si ce n'est pas le cas, on doit fermer la ligne courante, rouvrir la ligne vboulue, et préciser la colonne. C'est alors un '''défaut de page'''. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | bgcolor="#FFA0FF" | PRECHARGE || bgcolor="#FFA0FF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || bgcolor="#A0FFFF" | READ (2) || bgcolor="#A0FFFF" | READ (3) || || || bgcolor="#A0FFFF" | READ (4) || bgcolor="#A0FFFF" | READ (5) || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | Donnée READ 1 || ||bgcolor="#A0FFFF" | Donnée READ 2 | bgcolor="#A0FFFF" | Donnée READ 3 || || || bgcolor="#A0FFFF" | Donnée READ 4 || bgcolor="#A0FFFF" | Donnée READ 5 |} Les SDRAM sont parfois capables de démarrer une commande avant que la précédente soit terminée. Mais le respect des délais mémoire est très limitant, ce qui fait qu'on ne peut pas parler de réel pipeline, comme c'est le cas sur les processeurs. Parlons plutot de '''commandes anticipées'''. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | || bgcolor="#A0FFFF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || || || bgcolor="#FFA0FF" | READ (2) || || || || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 | bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 | |} Un dernier point est que les chips mémoires de SDRAM sont composés de plusieurs banques, chacune étant une sorte de mini-mémoire miniature. Chaque banque a son propre décodeur, son propre tampon de ligne, ses propres multiplexeurs de colonne, sa logique de rafraichissement mémoire, etc. C'est comme si une SDRAM regroupait plusieurs mémoires séparées dans un même circuit intégré. [[File:Arrangement vertical.jpg|centre|vignette|upright=2.5|Mémoire multi-banques.]] ===La mise en attente des accès mémoire=== Un contrôleur de SDRAM peut accepter plusieurs accès mémoire en même temps. Mais cela ne signifie pas que le contrôleur sera prêt à les traiter immédiatement. Pour éviter tout problème de disponibilité, le contrôleur met en attente les accès mémoire que le processeur lui envoie, pour les exécuter dès que possible. Les accès mémoire sont mis en attente dans une mémoire FIFO, histoire de les exécuter dans leur ordre d'arrivée, notamment pour renvoyer les lectures dans l'ordre demandé. Il y a aussi une mémoire FIFO pour les données à écrire et une FIFO pour les données lues. Cette dernière sert au cas où le cache ne soit pas disponible quand on lui envoie la donnée lue. [[File:Module d'interface avec la mémoire.png|centre|vignette|upright=2.5|Contrôleur mémoire avec mise en attente des requêtes processeur et autres optimisations.]] Des optimisations sont possibles dès la mémoire FIFO. Par exemple, on peut regrouper plusieurs accès à des données consécutives en un seul accès en rafale. Le contrôleur mémoire analyse les requêtes mises en attente et détecte si certaines se font à des adresses consécutives. Si c'est le cas, il fusionne ces requêtes en une lecture/écriture en rafale. Une telle optimisation est appelée la '''combinaison de lecture''' pour les lectures, et la '''combinaison d'écriture''' pour les écritures. L'optimisation précédente ne paraissent pas payer de mine, mais elles deviennent plus compréhensible quand on sait que certains contrôleurs mémoire peuvent mettre en attente beaucoup de données. Par exemple, l'''Intel E8870 - Scalable Memory Controller'' peut mettre en attente 64 lignes de cache dans la mémoire FIFO, soit 8 kibioctets de RAM ! Deux pages mémoire si la mémoire virtuelle utilise des pages de 4 kibioctets ! ===Les lectures anticipées : une forme d'OOO mémoire=== La mise en attente des accès mémoire est une optimisation intéressante, mais pas mirifique. Elle permet surtout de ne pas bloquer le processeur si le contrôleur mémoire a du travail sur la planche. Cependant, elle peut être optimisée quand on se rend compte d'une chose : le processeur ne voit que les lectures, pas les écritures. Pour les écritures, il les envoie au contrôleur mémoire et ce dernier fait le travail demandé, le processeur n'est pas prévenu quand une écriture se termine. Mais pour les lectures, c'est différent, car il reçoit la donnée lue. En conséquence, il est possible d'optimiser les écritures sans que le processeur ne voit quoique ce soit. Une optimisation possible est alors de changer l'ordre des accès mémoire, histoire de retarder les écritures ou au contraire de les faire en avance. La raison est que les mémoires SDRAM n'aiment pas quand on alterne lectures et écritures. Les délais mémoire, les fameux ''timings'' mémoire, sont clairs : faire une suite de lecture ou une suite d'écriture est plus performant que d'alterner entre lectures et écritures. L'idée est alors de changer l'ordre des écritures de manière à les faire en bloc, idem pour les lectures. L’optimisation est possible, mais à condition que le processeur n'y verra que du feu. Or, vous l'avez deviné, il y a des cas où ces réorganisations ne sont pas possibles, et vous avez sans doute trouvé que la situation était familière. L'optimisation en question est une forme d'exécution dans le désordre des accès mémoire, réalisée par le contrôleur mémoire lui-même ! Et qui dit exécution dans le désordre dit : problèmes liés aux dépendances de données. Changer l'ordre des écritures n'est pas toujours possible, notamment en raison des dépendances RAW, WAR et WAW. Pour que le processeur n'y voit que du feu, il faut respecter plusieurs critères. Premièrement, les lectures doivent être renvoyées au processeur dans l'ordre. Et quand je dis dans l'ordre, cela veut dire : dans l'ordre d'envoi des lectures au contrôleur mémoire. Les écritures aussi doivent se faire dans l'ordre, histoire qu'une série d'écriture donne le bon résultat final. Le troisième critère est qu'une lecture doit donner le résultat de la dernière écriture ''à la même adresse''. Pour le dire autrement, il faut juste détecter les dépendances RAW. Concrètement, si on a une série de lectures et d'écritures alternées, on peut regrouper les lectures et les écritures dans deux paquets séparés, à condition qu'aucune lecture ne lise une adresse écrite par une écriture. Mais si une lecture est dépendante d'une écriture, alors la lecture doit attendre que l'écriture se termine, idem pour les lectures suivantes. Une implémentation basique remplace la mémoire FIFO vue au-dessus, par deux mémoires FIFOs : une pour les lectures, une pour les écritures. Lorsqu'un accès mémoire arrive au séquenceur, il regarde si c'est une lecture ou une écriture et place l'accès mémoire dans la file adéquate. Le fait que ce soit des FIFOs garantit que les lectures se font dans l'ordre, idem pour les écritures. En sortie des deux FIFOs, le séquenceur mémoire détecte les dépendances RAW. Précisément, quand une lecture sort de la mémoire FIFO, il consulte la file d'attente des écriture pour voir si la lecture est dépendante d'une écriture en attente. La lecture attend si c'est le cas, et les écritures sont exécutées à la place. Séparer lectures et écriture est une source d'optimisation. Il est par exemple possible d'utiliser le réacheminement lecture sur écriture (''Store to load forwarding''). Il s'agit d'un équivalent de l’optimisation du même nom, utilisée dans le cadre de la désambiguïsation mémoire. Imaginez qu'une lecture accède à une donnée pas encore écrite, en attente dans le contrôleur mémoire. C'est une dépendance RAW assez claire. Dans ce cas, le contrôleur mémoire peut renvoyer la donnée directement depuis ses mémoires FIFOs, sans faire d'accès mémoire en lecture. Le contrôleur mémoire a tendance à privilégier les lectures, car celles-ci sont cruciales pour l'exécution dans le désordre. Plus elles se font tôt, plus vite le processeur recevra les données et pourra démarrer les instructions dépendantes de cette donnée. Le séquenceur a donc tendance à piocher en priorité dans la file de lecture, plutôt que dans la file d'écriture. A vrai dire, dans le cas idéal, les écritures ne sont faites que quand la file d'écriture est pleine ou quasi-pleine... Prenons l'exemple des coprocesseurs IO 81341 et 81342, qui étaient en réalité des ''chipsets'' intégrant un contrôleur SDRAM. Le ''chipset'' avait 5 ports : un pour les processeurs, un pour le pont sud (''southbridge'') et trois pour des canaux DMA. Pour le port processeur, il n'y avait pas de réordonnancement et ce port utilisait une seule mémoire FIFO. Les autres ports utilisaient l'optimisation qu'on vient de voir, et avaient vraisemblablement des files séparées pour les lectures et écritures. Le séquenceur mémoire vérifiait les dépendances mémoire de type RAW et autres. [[File:Double file d'attente pour les lectures et écritures.png|centre|vignette|upright=2.5|Double file d'attente pour les lectures et écritures]] ===Le ré-ordonnancement des commandes mémoires=== L'optimisation précédente est peu poussée, comparé aux formes d'exécution dans le désordre que les processeurs utilisent. Et on peut se demander si une exécution dans le désordre plus poussée est possible. La réponse est oui : un contrôleur mémoire peut faire des réorganisations bien plus poussées. Par contre, il faut faire une précision très importante : le contrôleur mémoire ne remet pas les accès mémoire dans l'ordre avant de les envoyer au processeur. C'est le processeur qui remet en ordre les accès mémoire, dans sa ''load queue''. En clair : le processeur voit les accès mémoire dans le désordre, c'est lui qui les remet en ordre. Pour cela, les requêtes mémoire sont "numérotées", à savoir qu'on leur attribue un identifiant binaire. Le contrôleur mémoire exécute les lectures/écritures dans le désordre, mais garde la trace de leur identifiant. Il sait dans quel ordre il exécute les accès mémoire et connait leur latence. Ce qui fait qu'il sait que la donnée lue à tel instant est associée à la requête mémoire numéro X. Pour les lectures, il renvoie la donnée lue avec l'identifiant. Le processeur sait alors à quelle lecture la donnée lue correspond et il se débrouille en interne pour gérer la situation. Maintenant que ces précisions sont faites, posons cette question : dans quelles situations est-il pertinent de faire des accès mémoire dans le désordre ? La réponse est : quand plusieurs accès à une même ligne ne sont pas consécutifs, qu'il y a des accès entre les deux. Après ré-ordonnancement, ces accès mémoire à une même ligne sont exécutés l'un à la suite de l'autre, ce qui est beaucoup plus rapide. Pour rendre le tout plus concret, voici un exemple. Imaginez que l'on sait les 3 accès mémoire suivants : * Une lecture ligne A ; * PRECHARGE + ACT ; * Une écriture ligne B ; * PRECHARGE + ACT ; * Une lecture ligne A. Sans réordonnancent, on doit émettre deux commandes PRECHARGE quand on passe d'une ligne à l'autre, et il faut ajouter les commandes ACT avec. pour éviter cela, le contrôleur mémoire peut retarder l'écriture, ou au contraire avancer la seconde lecture. le résultat est alors le suivant : * Lecture ligne A ; * Lecture ligne A * PRECHARGE + ACT ; * Une écriture ligne B. On a donc deux accès consécutifs à la même ligne, suivi par une écriture dans une autre ligne. La technique marche parce que l'on a un mix adéquat de lectures et d'écritures. Et encore une fois, il faut éviter d'intervertir lectures et écritures à une même adresse, sous peine de problèmes. Encore une fois, les dépendances RAW posent problème ! L'implémentation est assez simple : la ou les mémoires FIFOs précédentes sont remplacées par des mémoires similaires aux fenêtres d'instruction. Le contrôleur mémoire vérifie à chaque cycle les accès en attente, et vérifie à quelle ligne ils accèdent. Il priorise alors les accès qui tombent dans la ligne ouverte : ceux-là sont exécutés avant les autres. S'il n'y en a pas, il prend l'accès mémoire le plus ancien (ordre FIFO). Il teste aussi les dépendances mémoires RAW avant d'envoyer des commandes à la mémoire DDR/SDRAM. une telle solution est appelée l''''algorithme ''FR-FCFS''''' (''First Ready-First Come First Serve''). ===Les optimisations liées à la présence de plusieurs banques=== Les optimisations précédentes sont décuplées par la présence de plusieurs banques dans la mémoire SDRAM. Il est en effet possible de faire plusieurs accès mémoire en même temps, dans des banques différentes. Il est possible de lancer un accès mémoire dans deux banques en même temps, par exemple. La seule contrainte est que la SDRAM est limitée à 4 banques actives en même temps. {|class="wikitable" |+ Pipelining basique sur les SDRAM |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 |- ! Banque Numéro 1 | || || || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || || |- ! Banque Numéro 2 | || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || |- ! Banque Numéro 3 | || || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || |} Les optimisations précédentes peuvent s'appliquer par banque. Il est par exemple possible d'utiliser une mémoire FIFO par banque. Ainsi, les accès dans une même banque se feront en série, dans l'ordre d'arrivée, mais des accès à des banques différentes se feront dans le désordre. Cela ne pose pas de problème car les accès se font à des adresses différentes, ce qui fait qu'il n'y a pas de conflits majeurs. Il y a cependant un risque que les lectures se fassent dans le désordre si on n'y prend pas garde. [[File:Gestion parallèle des banques.png|centre|vignette|upright=2.5|Gestion parallèle des banques.]] Il est aussi possible d'utiliser une file séparée pour les lectures et les écriture, pour chaque banque. Les performances sont alors améliorées comparé à deux files globales pour toute la SDRAM. En idem avec la réorganisation des accès mémoire, pour regrouper les accès à une même ligne. Un problème avec ces optimisations est qu'il est rare que des accès mémoire se fassent dans des banques séparées. Les accès mémoire tendent à se faire avec une bonne localité spatiale, ce qui fait qu'ils tombent dans une même banque. Heureusement, les contrôleurs SDRAM/DDR modernes incorporent des optimisations pour corriger ce problème. Les optimisations en question sont une forme d'entrelacement adaptée aux mémoires SDRAM. L'entrelacement naïf ne marche pas à cause de la présence du tampon de ligne. Cependant, il peut y avoir un entrelacemententre banques SDRAM. Voyons ce que ca veut dire. L''''entrelacement de banques''' répartit deux lignes consécutives dans deux banques différentes. Pour comprendre l'idée, prenons un exemple. Imaginons une mémoire avec deux banques et 4 lignes. Imaginons qu'on parcoure/balaye la mémoire RAM en partant des adresses basses. Sans entrelacement de ligne, les accès se feront comme suit, de gauche à droite : {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Banque numéro 1 !! colspan="4" | Banque numéro 2 |- | Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 || Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 |} Avec l'entrelacement de banques, les accès se feront comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Banque n°1 !! Banque n°2 !! Banque n°1 !! Banque n°2 !! Banque n°1 !! Banque n°2 !! Banque n°1 !! Banque n°2 |- | Ligne 1 || Ligne 1 || Ligne 2 || Ligne 2 || Ligne 3 || Ligne 3 || Ligne 4 || Ligne 4 |} Avec N banques, la première ligne ira dans la première banque, la seconde ligne dans la seconde, et ainsi de suite jusqu'à la énième banque qui va dans la banque numéro N. Et la ligne N+1 repart dans la première banque. L'avantage est que passer d'une ligne à la suivante est assez rapide. Au lieu de devoir fermer la première ligne et ouvrir la suivante on ouvrira directement la ligne suivante dans une banque différente, ce qui sera plus rapide. La commande PRECHARGE pourra être envoyée à la seconde banque en avance. Pour gérer l'entrelacement, le contrôleur de SDRAM prend en entrée l'adresse envoyée par le processeur, et la découpe en plusieurs champs : un pour sélectionner la banque adéquate, un autre pour la ligne, un autre pour la colonne, un autre pour la rangée. Sans entrelacement, les adresses mémoire sont découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- | Adresse de banque || Adresse de ligne || Adresse de colonne |} Avec l'entrelacement de banques, l'adresse est découpée comme suit. {|class="wikitable" |+ Adresse mémoire |- | Adresse de ligne || Adresse de banque || Adresse de colonne (précise à l'octet) |} Il est aussi possible de faire la même chose, mais avec les rangées ou en utilisant du ''dual channel'', mais je vais passer cela sous silence. Toujours est-il que les méthodes d'entrelacement sont nombreuses et chaque contrôleur mémoire fait un peu à sa sauce. Ils permettent en général de choisir entre plusieurs entrelacements. Au minimum, ils permettent de désactiver l'entrelacement, d'activer un entrelacement de ligne, et d'autres entrelacements plus complexes, dont certains sont propriétaires et tenus secrets par le fabricant. L'entrelacement est géré juste avant le séquenceur mémoire, dans un '''circuit d'entrelacement'''. Ce dernier intervertit certains bits de l'adresse lors des accès mémoires. [[File:Controleur mémoire d'une SDRAM avec entrelacement.png|centre|vignette|upright=2.5|Contrôleur mémoire d'une SDRAM avec entrelacement.]] Les contrôleurs de SDRAM précédents sont assez basiques et ne représentent pas les contrôleurs les plus évolués. Ils étaient utilisés sur les anciens PC, à une époque où ils étaient encore sur la carte mère du processeur. Mais de nos jours, le contrôleur mémoire est intégré au processeur. Et il incorpore de nombreuses optimisations afin de gagner en performances. Malheureusement, ces optimisations sont elles-mêmes dépendantes des optimisations intégrées dans le processeur, ce qui fait qu'on ne peut pas en parler ici. Elles demandent que le contrôleur mémoire reçoive plusieurs accès mémoire simultanés, ce qui n'a aucun sens à ce stade du cours. Aussi, nous allons les passer sous silence pour le moment. Nous les verrons dans le chapitre sur le parallélisme mémoire. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=La désambiguïsation mémoire | prevText=La désambiguïsation mémoire | next=Les processeurs superscalaires | nextText=Les processeurs superscalaires }} </noinclude> 9ehjeaccmg4nen1qo9umnxlv4288jvp 764821 764820 2026-04-24T10:54:01Z Mewtow 31375 /* Les optimisations liées à la présence de plusieurs banques */ 764821 wikitext text/x-wiki Dans ce chapitre, nous allons voir les techniques qui permettent de gérer plusieurs accès mémoire simultanés directement au niveau du cache ou de la mémoire RAM. Par plusieurs accès mémoire simultanés, vous pensez sans doute à l'usage de cache multiports, voire de mémoires RAM multiport. C'est en effet une solution, mais ce n'est clairement pas la seule. Par exemple, il est possible de pipeliner l'accès au cache, voire à la mémoire RAM. Il existe de nombreuses techniques de '''parallélisme mémoire''', et nous allons les voir dans ce chapitre. Pour donner un exemple, il est possible de pipeliner les accès mémoire. Il est possible de pipeliner l'accès au cache et/ou l'accès à la mémoire, et nous verrons comment faire dans ce chapitre. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, qu'ils aient ou non un pipeline dynamique. Par contre, pipeliner la mémoire est une technique ancienne, aujourd'hui peu utilisée. Elle était utilisée sur des très vieux ordinateurs, qui avaient un pipeline mais pas de mémoire cache. Mais de nos jours, les mémoires SDRAM et DDR ne sont pas adaptées pour ça, il est très difficile de les pipeliner correctement. Le parallélisme mémoire est utile aussi bien sur des processeurs à émission dans l'ordre que dans le désordre. Par exemple, un processeur ''in-order'' avec des lectures non-bloquantes peut e, profiter. Il peut techniquement lancer une seconde lecture, après avoir rencontré une première lecture non-bloquante. En général, le nombre de lectures consécutives est cependant limité à moins d'une dizaine. À l'opposé, les processeurs sans lectures non-bloquantes profitent pas du parallélisme mémoire. À la rigueur, ils peuvent en profiter s'ils intègrent des techniques de préchargement. Pendant que le processeur exécute une lecture/écriture, il peut précharger une autre donnée en parallèle. Inutile de dire que sans pipeline processeur, le parallélisme mémoire ne sert pas à grand-chose. ==Le ''line fill buffer'' et les techniques associées== Lors d'un défaut de cache, le processeur doit attendre que toute la ligne de cache soit chargée avant d'être utilisable. Or, la taille d'une ligne de cache est supérieure à la largeur du bus mémoire, ce qui fait qu'une ligne de cache est chargée en plusieurs fois, morceaux par morceaux, mot mémoire par mot mémoire. Le chargement peut se faire directement dans le cache, mais ce n'est pas une solution très pratique. À la place, beaucoup de processeurs ajoutent une mémoire tampon entre la RAM et le cache, appelée le '''tampon de remplissage de ligne''' (''line-fill buffer'') dans les processeurs modernes. Lors d'un défaut de cache, le processeur charge la donnée de la RAM dans le tampon de remplissage de ligne, mot mémoire par mot mémoire. Une fois plein, le tampon de remplissage de ligne est recopié dans la ligne de cache. Le tampon de remplissage de ligne contient une ligne de cache, si ce n'est que certains bits de contrôle ne sont pas présents. Le tampon de remplissage de ligne contient un bit de validité pour chaque mot mémoire de la ligne de cache, qui indiquent si le mot mémoire a été chargé. Par exemple, prenons un processeur 64 bits, qui gère donc des mots mémoire de 8 octets, avec des lignes de cache 256 octets/32 mots mémoire. Le tampon de remplissage de ligne contiendra 32 bits de validité. Si le processeur a chargé les 6 premiers mots mémoire, les 6 premiers bits de validité seront mis à 1, les autres seront encore à zéro. Il faut noter que la disponibilité de la ligne complète se détermine assez facilement en faisant un ET logique entre tous les bits de validité. Le processeur sait ainsi quand la ligne de cache est disponible entièrement et donc quand la transférer dans le cache. La même chose existe avec une hiérarchie de cache, sauf que l'on trouve une mémoire tampon entre chaque niveau de cache. Il y a un tampon de remplissage de ligne entre le cache L1 et le cache L2, entre le cache L2 et le L3, etc. Si le cache ne gère qu'un seul défaut de cache à la fois, le ''fill line buffer'' est une mémoire très simple, qui ne mémorise qu'une seule ligne de cache. Et cela vaut aussi bien pour un cache bloquant que non-bloquant. Mais sur les caches capables de gérer plusieurs défauts simultanés, le tampon de remplissage de ligne est une mémoire de type FIFO ou LIFO, capable de mémoriser plusieurs lignes de cache. Notons que le tampon de remplissage de ligne est très utile pour implémenter certaines techniques, par exemple le contournement du cache. Nous avions vu dans le chapitre sur le cache que certains accès mémoire doivent contourner le cache, pour des raisons de cohérence des caches. C'est notamment nécessaire pour accéder aux périphériques, mais c'est aussi utile pour des raisons de performances dans des cas très spécifiques. Les accès qui contournent le cache se font directement dans le tampon de remplissage de ligne : le processeur écrit ou lit les données depuis ce tampon de remplissage de ligne, sans accéder au cache. ===L'''early restart'' et le ''critical word load''=== La présence du ''line buffer'' permet une optimisation assez intéressante, qui permet de réduire la latence des défauts de cache. L'optimisation consiste à lire un mot mémoire dans le tampon de remplissage de ligne, même si la ligne de cache complète n'a pas encore été chargée. Il existe deux manières de faire cela, qui portent les noms d'''early restart'' et de ''critical word load''. La première est la version la plus simple, la seconde est plus complexe mais plus performante. Avec la technique d''''''early restart''''', la ligne de cache est chargée normalement, en partant de son premier mot mémoire. Dès que le mot mémoire lu/écrit par le processeur est copié dans le ''line fill buffer'', il est envoyé au processeur immédiatement. Illustrons le tout par un exemple, où une ligne de cache fait 16 mots mémoire. Le processeur effectue une lecture, qui lit le 5ème mot mémoire. Sans ''early restart'', le processeur doit charger les 16 mots mémoire avant de faire la lecture dans le cache. Avec ''early restart'', le processeur reçoit la donnée dès que le 5ème mot mémoire est disponible. Le processeur doit attendre que les 4 premiers mots mémoire soient chargés, puis le 5ème mot mémoire arrive et est envoyé directement au processeur, il est lu directement depuis le ''line fill buffer''. Les 11 mots mémoire suivants sont ensuite chargés dans le cache pendant que le processeur fait des calculs dans son coin. Le ''critical word load'' est une optimisation de la technique précédente où le chargement de la ligne de cache commence directement à la donnée demandée par le processeur. Pour reprendre l'exemple précédent, où le processeur demande le 5ème mot mémoire sur 16, le ''critical word load'' charge le 5ème mot mémoire en premier et l'envoie au processeur, ce qui fait qu'il est chargé très rapidement. Pas besoin d'attendre que le processeur charge les 4 mots mémoire précédents comme avec l'''early restart''. Dans le détail, le processeur charge le 5ème mot mémoire en premier, puis charge les 11 suivants, et termine par les 4 mots mémoire du début. En clair, le ''critical word load'' commence par charger le mot mémoire lu, puis les blocs suivants, avant de revenir au début du bloc pour charger les blocs restants. Ainsi, la donnée demandée par le processeur sera la première disponible. Pour cela, l'organisation du tampon de remplissage de ligne est modifiée de manière à rendre cela possible. Il a une taille égale à une ligne de cache complète, qui contient elle-même plusieurs mots mémoire. Dans le ''line-fill buffer'', chaque mot mémoire est stocké avec un tag, qui indique l'adresse du mot mémoire stocké dans le ''line-fill buffer''. Le ''line-fill buffer'' est donc un cache un peu particulier, qui fonctionne comme un cache du point de vue du processeur, comme une mémoire FIFO pour les transferts avec le cache. Ainsi, un processeur qui veut lire dans le cache après un défaut peut accéder à la donnée directement depuis le tampon de remplissage de ligne, alors que la ligne de cache n'a pas encore été totalement recopiée en mémoire. ==Les caches pipelinés== Il est possible de pipeliner l'accès au cache, ce qui demande juste de rajouter des registres au bon endroits et quelques circuits de contrôle. Pipeliner l'accès au cache est une technique en vigueur dans les processeurs modernes, même ceux avec un pipeline dynamique. Et l'implémentation peut se faire de plusieurs manières différentes. Il faut dire que découper une mémoire cache en plusieurs étapes peut se faire de plusieurs manière. Commençons par la plus simple. Beaucoup de processeurs des années 2000 avaient une fréquence peu élevée comparée aux standards d'aujourd'hui, ce qui fait que leur cache avait bien un temps d'accès d'un cycle d'horloge. Mais l'accès au cache se faisait en deux cycles d'horloge : un cycle pour calculer une adresse et un cycle pour l'accès mémoire proprement dit. Et ces deux étapes étaient pipelinées, à savoir que deux micro-opérations mémoire peuvent s'exécuter en même temps : une dans l'étage de calcul d'adresse, une autre dans le cache. L'unité mémoire était donc pipelinée, alors que l'accès au cache ne l'était pas. Les processeurs qui implémentaient cette technique regroupent les micro-architectures K5 et K6 d'AMD, les processeurs Intel de micro-architecture P6 (Pentium 2 et 3) et quelques autres. Il est aussi possible de pipeliner l'accès au cache lui-même. Avec un cache pipeliné, l'accès au cache ne se fait pas en un seul cycle, mais en plusieurs. Cependant, on peut lancer un nouvel accès au cache à chaque cycle d'horloge, comme un processeur pipeliné lance une nouvelle instruction à chaque cycle. L'implémentation est assez simple : il suffit d'ajouter des registres dans le cache. Pour cela, on profite que le cache est composé de plusieurs composants séparés, qui échangent des données dans un ordre bien précis, d'un composant à un autre. De plus, le trajet des informations dans un cache est linéaire, ce qui les rend parfois pour l'usage d'un pipeline. ===Le ''pipelining'' des caches ''direct-mapped''=== Voici ce qui se passe avec un cache directement adressé. Pour rappel ce genre de cache est conçu en combinant une mémoire RAM, généralement une SRAM, avec quelques circuits de comparaison et un MUX. L'accès au cache se fait globalement en deux étapes : la première lit la donnée et le ''tag'' dans la SRAM, et on utilise les deux dans une seconde étape. Nous avions vu il y a quelques chapitre comment pipeliner des mémoires, dans le chapitre sur les mémoires évoluées. Et bien ces méthodes peuvent s'utiliser pour la mémoire RAM interne au cache !L'idée est d'insérer un registre entre la sortie de la RAM et la suite du cache, pour en faire un cache pipéliné, à deux étages. Le premier étage lit dans la SRAM, le second fait le reste. L'implémentation sur les caches associatifs à plusieurs voies est globalement la même à quelques détails près. [[File:Cache directement adressé pipeliné - deux étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - deux étages]] Avec le circuit précédent, il est possible d'aller plus loin, cette fois en pipelinant l'accès à la mémoire RAM interne au cache. Pour comprendre comment, rappelons qu'une mémoire SRAM est composée d'un plan mémoire et d'un décodeur. L'accès à la mémoire demande d'abord que le décodeur fasse son travail pour sélectionner la case mémoire adéquate, puis ensuite la lecture ou écriture a lieu dans cette case mémoire. L'accès se fait donc en deux étapes successives séparées, on a juste à mettre un registre entre les deux. Il suffit donc de mettre un gros registre entre le décodeur et le plan mémoire. [[File:Cache directement adressé pipeliné - trois étages.png|centre|vignette|upright=2|Cache directement adressé pipeliné - trois étages]] Et on peut aller encore plus loin en découpant le décodeur en deux circuits séparés. En effet, rappelez-vous le chapitre sur les circuits de sélection : nous avions vu qu'il est possible de créer des décodeurs en assemblant des décodeurs plus petits, contrôlés par un circuit de prédécodage. Et bien on peut encore une ajouter un registre entre ce circuit de prédécodage et les petits décodeurs. : Théoriquement, toute l'adresse est fournie d'un seul coup au cache, la quasi-totalité des processeurs présentent l'adresse complète à un cache pipeliné. Mais le Pentium 4 fait autrement. Il faut noter que les premiers étages manipulent l'indice dans la SRAM, qui est dans les bits de poids faible, alors que les étages ultérieurs manipulent le ''tag'' qui est dans les bits de poids fort. Les concepteurs du Pentium 4 ont alors décidé de présenter les bits de poids faible lors du premier cycle d'accès au cache, puis ceux de poids fort au second cycle. Pour cela, l'ALU fonctionnait à une fréquence double de celle du processeur, tout comme le cache L1. Il n'y avait pas de pipeline proprement dit, mais cela réduisait grandement la latence d'accès au cache. ===Le ''pipelining'' des caches splittés/sériels=== Il est aussi possible de pipeliner un cache dits splittés, aussi appelés à accès sériel. Pour rappel, les caches à accès sériel vérifient si il y a succès ou défaut de cache, avant d'accéder aux lignes de cache en cas de succès. Ils font donc différemment des autres caches, qui accèdent à une ligne de cache, avant de déterminer s'il y a succès ou défaut en lisant le tag de la ligne de cache. Les caches sériels disposent de deux SRAM : une pour les tags des lignes de cache et une pour les données. Ils accèdent à la SRAM pour les tags, avant d’accéder à la SRAM des données en cas de succès de cache. Vu que l'accès se fait en deux étapes, une vérification des tags suivie de la lecture/écriture des données, il est facile à pipeliner. Pipeliner le cache permet de régler le problème des accès au cache L1, et elle est tout le temps utilisé sur les processeurs modernes. Mais que faire en cas de défaut de cache ? ==Les caches non bloquants== Un '''cache bloquant''' est un cache auquel le processeur ne peut pas accéder pendant un défaut de cache. Il faut attendre que la lecture ou écriture en RAM soit terminée avant de pouvoir utiliser de nouveau le cache. Un '''cache non bloquant''' n'a pas ce problème : on peut l'utiliser pendant un défaut de cache. Les caches non bloquants permettent de démarrer une nouvelle lecture ou écriture alors qu'une autre est en cours, ce qui permet d'exécuter plusieurs lectures ou écritures en même temps. ===Les ''Miss Handling Status Registers''=== Lors d'un défaut de cache, la mémoire RAM est consultée pendant le défaut de cache, mais le cache est inutilisé. Un défaut de cache n'utilise pas le cache, ce n'est qu'un accès en mémoire RAM, sur le bus mémoire (ou un accès aux niveaux de cache inférieurs, peu importe). Le cache en lui-même est laissé libre, rien n’empêche d'y accéder, il est en réalité intrinsèquement non-bloquant. Les caches, bloquants comme non-bloquants, sont en réalité composés d'une mémoire cache proprement dite, entourée de circuits qui servent d'interface entre le processeur et le cache lui-même. Et parmi les circuits tout autour, certains gèrent l'accès au cache lors d'un défaut de cache. Ils sont regroupés sous le terme de '''''Miss Handling Architecture''''' (MHA). La différence entre un cache bloquant et un cache non-bloquant est en réalité liée à l'implémentation de la MHA. Les caches bloquants coupent volontairement l'accès au cache lors d'un défaut, car les défauts sont plus simples à gérer ainsi. Le défaut de cache rapatrie une donnée depuis la RAM, qui sera écrite dans le cache. Et il ne faut pas qu'une tentative d'accès à cette donnée ait lieu avant qu'elle ne soit chargée. Mais ce blocage est général et touche tout le cache, alors que seule une ligne de cache est concernée par le défaut de cache. L'idée derrière un cache non-bloquant est que seule la ligne de cache est bloquée, mais les autres sont accessibles. L'idée est alors de mémoriser les lignes de cache concernées par le défaut de cache, afin d'en bloquer l'accès. À chaque accès, on vérifie si la ligne de cache est déjà réservée par un défaut de cache. La lecture/écriture est alors bloquée si c'est le cas, mais elle accepte les accès sinon. Pour cela, la ''Miss Handling Architecture'' contient des registres qui mémorisent des informations sur les défauts de cache en cours. Ils portent le nom de '''''miss status handling registers''''', que j’appellerais dorénavant MSHR, qui sont aussi appelés des '''''miss buffer'''''. Le contenu des MSHR varie beaucoup suivant le processeur, mais ces derniers stockent au minimum les informations suivantes : * Le numéro de la ligne de cache dans laquelle les données sont chargées. * Un bit de validité qui indique si le MSHR est vide ou pas, qui est mis à 0 quand le défaut de cache est résolu. * Un ou plusieurs champs de lecture/écriture, qui contiennent des informations sur la lecture/écriture. ** Pour une lecture, elle contient des informations sur la destination de la donnée, à savoir qui prévenir quand le défaut de cache est terminé. C'est parfois un nom/numéro de registre (celui dans lequel charger la donnée), mais c'est souvent le numéro de l'entrée dans le ''load/store queue''. ** Pour les écritures, elle contient la donnée à écrire, ou éventuellement un numéro de ''load/store queue'' où se trouve la donnée à écrire. Il faut noter que le bus mémoire ne peut gérer qu'un seul défaut de cache à la fois. Aussi, il est intéressant de regarder ce qui se passe lorsqu'un second défaut de cache survient, pendant qu'un premier est en cours. Dans ce cas, il y a deux réponses qui correspondent à deux types de caches non-bloquants, qui portent les noms barbares de caches de type succès après défaut et défaut après défaut. Sur le premier type, il ne peut pas y avoir plusieurs défauts de cache simultanés. Dès qu'un second défaut de cache survient, le cache stoppe son activité et on ne peut plus démarrer de nouvelle lecture/écriture, tant que le premier défaut de cache n'est pas résolu. Le second type est plus souple et autorise la survenue de plusieurs défauts de cache simultanés. Du moins, jusqu'à une certaine limite, car le cache ne peut supporter qu'un nombre limité d'accès mémoires simultanés (pipelinés). ===Les accès simultanés à une même ligne de cache=== Il arrive que le processeur fasse plusieurs accès mémoire simultanés à la même ligne de cache. Si la ligne de cache en question n'a pas encore été chargée dans le cache, alors on a plusieurs défauts de cache consécutifs pour la même ligne de cache, et le cache non-bloquant doit gérer la situation. Pour la suite, il va falloir faire une petite distinction entre les défauts primaires et secondaires. Imaginons qu'un défaut de cache ait lieu et demande à charger une donnée dans la ligne de cache numéro N. Il s'agit du premier défaut impliquant cette ligne de cache précise, ce qui lui vaut le nom de '''défaut de cache primaire'''. Mais par la suite, d'autres accès mémoire à la même ligne de cache ont lieu, alors que la ligne de cache n'est pas encore disponible. Dans ce cas, il s'agit de '''défauts de cache secondaires'''. Pour l'unité d'accès mémoire, les défauts de cache primaire et secondaire sont différents (ils prennent tous une entrée dans la ''load/store queue''). Mais pour le cache, ils ne correspondent qu'à un seul accès au cache : celui qui demande de charger la ligne de cache demandée. Les défauts de cache primaire et secondaire à la même ligne de cache se voient attribuer un MSHR unique. La gestion des défauts de cache secondaires dépend du cache non-bloquant. La solution la plus simple ne permet pas les défauts de cache secondaires. Le cache ne permet pas deux défauts de cache simultanés pour la même ligne de cache. Les autres solutions le permettent, en fusionnant des accès simultanés à la même ligne de cache en un seul au niveau des MSHR. Dans tous les cas, détecter les défauts de cache secondaires sont un problème qu'il faut détecter. La ''Miss Handling Architecture'' doit détecter les défauts de cache secondaire. Pour cela, elle procède comme suit. Lors de chaque défaut de cache, la MHA récupère le numéro de la ligne de cache associé. Il vérifie alors chaque MSHR pour vérifier s'il contient le numéro en question. S'il n'y a aucune correspondance dans les MSHR, alors c'est signe que le défaut de cache est un défaut primaire. Mais s'il y en a une, alors c'est un défaut secondaire. Évidemment, cela signifie que lors d'un défaut de cache, le numéro de ligne de cache est envoyé à tous les MSHR, pour comparaison. Les MSHR sont donc regroupés dans une mémoire associative, une sorte de mini-cache, faciliter l'implémentation. Lors d'un défaut de cache primaire, l'accès à un cache non-bloquant se fait comme suit : le processeur envoie une adresse au cache, accède à celui-ci, et détecte la survenue d'un défaut de cache. Il en profite alors pour attribuer une ligne de cache dans laquelle sera chargée ce défaut. L'attribution est très simple dans le cas des caches ''direct mapped'', ou associatifs par voie, pour lesquels l'attribution se fait assez simplement. Il mémorise alors cette information dans les MSHR, après avoir vérifié que le défaut de cache n'était pas un défaut secondaire. Lors des accès ultérieurs à une adresse proche, censée être dans la même ligne de cache, le processeur va encore une fois rencontrer un défaut de cache. Il va alors déterminer le numéro de la ligne de cache associée à l'adresse, et comparer ce numéro avec les MSHRs. Si un MSHR contient ce numéro, c'est signe que le défaut de cache est un défaut secondaire. La MHA réagit alors différemment selon le processeur considéré. Une première solution n'autorise pas les défauts de cache secondaires. Si l'un d'entre eux survient, le processeur est gelé par un ''pipeline stall'', une bulle de pipeline. Une autre solution fusionne les défauts de cache secondaires avec le défaut de cache primaire : tout cela ne correspond qu'à un seul défaut de cache pour lui. ===Les MSHR simples=== Un cache non-bloquant à '''MSHR simple''' contient juste plusieurs MSHR qui mémorisent juste un numéro de ligne de cache, un bit de validité, le champ de lecture/écriture, et l'adresse exacte de lecture/écriture. Avec cette organisation, il est possible d'avoir plusieurs défauts de cache séparés, mais à la condition que chaque défaut accède à une ligne de cache différente. Deux accès simultanés à une même ligne de cache ne sont pas possibles, les défauts de cache secondaires ne sont pas autorisés. Ainsi, chaque défaut de cache se voit attribuer son propre MSHR, chacun contient un numéro de ligne de cache différent. Pour comprendre pourquoi c'est impossible de gérer les défauts secondaires, il faut regarder le champ de lecture/écriture. Si on veut effectuer plusieurs écritures consécutives à la même adresse, le MSHR n'aura pas de quoi mémoriser les deux données à écrire. Il pourra mémoriser la première donnée à écrire, pas la seconde. Même chose lors d'une lecture : le champ lecture/écriture peut mémorisr la destination de la première lecture, pas de la seconde. L'avantage est que la MHA n'a besoin que des MSHR et de quelques circuits annexes. Les autres solutions rajoutent des circuits annexes pour gérer les défauts de cache secondaires, qui utilisent beaucoup de circuits. Le cout en circuit est donc élevé, mais le gain en performance est là. Passons maintenant aux caches non-bloquants qui autorisent les défauts de cache secondaire. La solution la plus simple consiste à utiliser ===Les MSHR adressés implicitement=== Avec les '''MSHR adressés implicitement''', il est possible de fusionner plusieurs accès mémoire à une même ligne de cache, mais sous une condition très importante : ces accès lisent/écrivent des mots mémoire différents. Par exemple, imaginons qu'une ligne de cache contienne 8 mots mémoire de 64 bits. Si un premier accès mémoire lit le mot mémoire numéro 7 (dernier mot mémoire de la ligne), et le second accès le mot mémoire numéro 3, alors la fusion est possible. Mais si deux accès mémoire veulent lire/écrire le mot mémoire numéro 7, alors la fusion n'est pas possible et le processeur se bloque, un ''pipeline stall'' survient. Un MSHR adressé implicitement est un MSHR simple, qui contient naturellement un numéro de ligne de cache (tag) et un bit de validité, sauf que le champ de lecture/écriture est dupliqué. Les différents champs lecture/écriture d'un MSHR sont regroupés dans une mémoire RAM/cache qui contient autant d'entrées qu'il y a de mot mémoire dans une ligne de cache. Chaque entrée de la table est associée à un mot mémoire de la ligne de cache et stocke des informations sur celui-ci. Une entrée mémorise, au minimum : * Un champ de lecture/écriture qui contient soit la destination de la lecture, soit la donnée à écrire. * Un bit R/W permettant d'interpréter correctement le champ de lecture/écriture. * Un bit de validité pour chaque entrée de la table, qui dit si un défaut de cache antérieur accède déjà à ce mot mémoire. La fusion de deux défauts de cache est ainsi assez simple. Un défaut de cache primaire/secondaire configure l'entrée associée au mot mémoire lu/écrit. Si un défaut secondaire ultérieur lit/écrit un mot mémoire différent (dont le bit de validité de l'entrée dans la table est à zéro), pas de conflit : il configure une autre entrée, vide. Mais s'il lit/écrit un mot mémoire pour lequel l'entrée de la table est occupée, il y a conflit, le processeur est bloqué par un ''pipeline stall'', une bulle de pipeline. Avec cette organisation, le nombre de MSHR indique combien de lignes de cache peuvent être lues en même temps depuis la mémoire. Quant au nombre d'entrées par MSHR, il détermine combien d'accès mémoires qui ne se recouvrent pas peuvent avoir lieu en même temps. ===Les MSHR adressés explicitement=== Les '''MSHR adressés explicitement''' n'ont pas les contraintes des MSHR adressés implicitement. Avec eux, il est possible d'avoir plusieurs défauts de cache pointant vers la même ligne de cache, mais aussi vers le même mot mémoire. De tels défauts de cache apparaissent sur les processeurs à exécution dans le désordre, mais sont très rares, voire inexistants, sur les processeurs ''in-order''. L'idée est encore que chaque MSHR est associée à une table mémoire qui mémorise des entrées. Cette table n'est autre qu'une mémoire FIFO. Sauf que cette fois-ci, une entrée n'est pas associée à un mot mémoire. Une entrée est associée à un défaut de cache. Une entrée mémorise là encore la destination de la lecture ou la donnée de l'écriture suivi du bit R/W, ainsi que d'un bit de validité par entrée, mais aussi : la position du mot mémoire lu dans la ligne de cache. C'est cette dernière information qui n'était pas présente dans les MSHR adressés implicitement. De plus, le nombre d'entrée par MSHR n'est pas égal au nombre de mots mémoires dans une ligne de cache, mais peut être arbitrairement grand. L'avantage d'un tel cache est qu'il est capable de traité les défauts secondaires concernant un même mot mémoire, et ce, car les accès à la ligne de cache concernée se produisent dans l'ordre de génération des défauts (la table mémoire est une FIFO). ===Les MSHRs inversés=== Généralement, plus on veut supporter de défauts de cache, plus le nombre de MSHR et d'entrées augmente. Mais au-delà d'un certain nombre d'entrées et de MSHR, les MSHR adressés implicitement et explicitement ont tendance à bouffer un peu trop de circuits. Utiliser une organisation un peu moins gourmande en circuits est donc une nécessité. Cette organisation plus économe se base sur des MSHR inversés. Les MSHR inversés ne contiennent qu'une seule entrée, en quelque sorte : au lieu d’utiliser n MSHR de m entrées chacun, on va utiliser n × m MSHR inversés. La différence, c'est que plusieurs MSHR peuvent contenir un tag identique, contrairement aux MSHR adressés implicitement et explicitement. Lorsqu'un défaut de cache a lieu, chaque MSHR est vérifié. Si jamais aucun MSHR ne contient de tag identique à celui utilisé par le défaut, un MSHR vide est choisi pour stocker ce défaut, et une requête de lecture en mémoire est lancée. Dans le cas contraire, un MSHR est réservé au défaut de cache, mais la requête n'est pas lancée. Quand la donnée est disponible, les MSHR correspondant à la ligne qui vient d'être chargée vont être utilisés un par un pour résoudre les défauts de cache en attente. ===Les MSHR intégrés au cache=== Certains chercheurs ont remarqué que pendant qu'une ligne de cache est en train de subir un défaut de cache, celle-ci reste inutilisée, et son contenu est destiné à être perdu une fois le défaut de cache résolu. Ils se sont dit que, plutôt que d'utiliser des MSHR séparés, il vaudrait mieux utiliser la ligne de cache pour stocker les informations sur les défauts de cache en attente dans cette ligne de cache. Pour éviter tout problème, il faut rajouter un bit dans les bits de contrôle de la ligne de cache, qui sert à indiquer que la ligne de cache est occupée : un défaut de cache a eu lieu dans cette ligne, et elle stocke donc des informations pour résoudre les défauts de cache. ==L'entrelacement mémoire== Nous venons de voir comment une mémoire cache peut gérer plusieurs accès mémoire simultanés. Il se trouve que ce genre d'optimisation dépasse largement le cadre du cache. Les mémoires RAM ne sont pas en reste, elles aussi ! Il est possible de pipeliner l'accès à une mémoire RAM, en utilisant des techniques dites d''''entrelacement'''. Précisons cependant que cette optimisation n'est pas utilisée sur les mémoires SDRAM ou DDR modernes, du moins pas sans modifications majeures. Mais divers ordinateurs assez anciens, dont des superordinateurs, ont utilisé cette technique. La technique demande d'utiliser plusieurs mémoires séparées. Sur les anciens ordinateurs, les mémoires en question sont des chips mémoire, à savoir des circuits intégrés de DRAM. Pour faire plus général, nous allons utiliser le terme de '''banques''', ou encore de bancs mémoire. La différence est qu'il existe des mémoires multi-banque, qui regroupent plusieurs banques indépendantes dans un seul boitier, dans un seul chip mémoire. En clair, de telles mémoires regroupent plusieurs sous-mémoires dans un seul circuit intégré. Il est aussi possible d'utiliser plusieurs chips mémoire séparés, chacun ayant une banque. Reste à combiner ces banques pour former un pseudo-pipeline. ===Utiliser plusieurs banques sans entrelacement=== Sans optimisation particulière, les adresses sont réparties dans les banques comme indiqué ci-dessous. Il s'agit de ce qui s'appelle un arrangement horizontal, et nous avions vu celui-ci dans le chapiutre sur les mémoires SDRAM. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses sans entrelacement.]] Avec cet arrangement, les bits de poids fort de l'adresse sont utilisées pour sélectionner la banque adéquate, et le reste de l'adresse est envoyé sur le bus d'adresse. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Adresse de banque !! Adresse dans la banque |- | Quelques bits de poids fort || Reste de l'adresse |} L'entrelacement change cette répartition, afin d'accélérer les accès mémoire. L'entrelacement classique vise à améliorer les accès à des adresses mémoires consécutives, mais des formes plus évoluées d'entrelacement visent à optimiser des accès mémoire arbitraires. Voyons-les dans l'ordre, du plus simple au plus complexe. ===L'entrelacement classique=== Avec l''''entrelacement classique''', des adresses consécutives sont réparties dans des banques consécutives. Précisons que cette attribution des adresses n'implique absolument pas la mémoire virtuelle ou n'importe quel mécanisme dans le processeur. La répartition décide que telle adresse mémoire va dans telle banque, à telle adresse dans la banque. Elle est donc le fait du contrôleur mémoire, donc en dehors du processeur (les contrôleurs mémoires n’étaient pas encore intégrés dans le processeur à l'époque). [[File:Répartition des adresses dans une mémoire interleaved.png|centre|vignette|upright=1.5|Répartition des adresses dans une mémoire interleaved.]] L'entrelacement simple permet d'accélérer les accès mémoire qui se font à des adresses consécutives. Avec l'entrelacement, chaque accès mémoire tombe dans une banque mémoire différente, ce qui fait qu'on peut démarrer un nouvel accès mémoire à chaque cycle d'horloge. Pendant qu'une banque est occupée par un accès mémoire, on démarre le suivant dans une autre banque, et ainsi de suite. Le tout se termine soit quand on a épuisé toutes les banques libres, soit quand l'accès en rafales se termine. Pas besoin d'attendre que la mémoire ait fini sa lecture/écriture avant de démarrer la lecture/écriture suivante. [[File:Pipemining mémoire.png|centre|vignette|upright=2.5|Pipelining mémoire]] L'animation suivante illustre bien le processus, avec quatres banques. [[File:Interleaving.gif|centre|vignette|upright=2.5|Pipelining mémoire]] L'entrelacement simple utilise les bits de poids faible pour sélectionner la banque, et les bits de poids fort pour la case mémoire. [[File:Adresse mémoire d'une mémoire entrelacée.png|centre|vignette|upright=2|Adresse mémoire d'une mémoire entrelacée]] Précisons que le temps d'accès mémoire ne change pas beaucoup avec l'entrelacement. Par contre, on peut faire plus d'accès mémoire simultanés. On peut démarrer un accès mémoire par cycle d'horloge, mais l'accès en lui-même prend plusieurs cycles. Les mémoires à entrelacement ont donc un débit supérieur aux mémoires qui ne l'utilisent pas. L'entrelacement classique pourrait être utilisé pour accélérer les transferts entre mémoire RAM et cache, qui se font en blocs d'adresses consécutives. Mais de nos jours, il n'est pas utilisé dans cette optique. Il faut dire qu'il est redondant avec le mode rafale des mémoires DRAM. Les deux font globalement la même chose et on voit mal comment utiliser les deux en même temps. Le mode rafale étant plus simple à implémenter, et plus léger niveau utilisation du bus de commande mémoire, il est préféré à l'entrelacement. Par contre, l'entrelacement a eu son heure de gloire sur d'anciens processeurs qui n'avaient pas de mémoire cache, alors qu'ils disposaient d'un pipeline. L'exemple typique est celui des nombreux ''processeurs vectoriels'', que nous n'avons pas encore abordé à ce stade du cours. De tels processeurs avaient un pipeline, une unité de calcul fortement pipelinée, et parfois même de l'exécution dans le désordre et du renommage de registres ! ===L'entrelacement par décalage=== L'entrelacement simple est très utile pour les accès en rafle ou équivalents. Par contre, il existe de rares situations où il n'est pas optimal. Une de ces situations est celle des accès en enjambée (en ''stride''), où l'on accède en série à des adresses sont séparées par N mots mémoires. [[File:Accès par enjambées.png|centre|vignette|upright=2|Accès par enjambées.]] De tels accès surviennent quand un logiciel accède à des structures de données spécifiques, à savoir des tableaux de structures, des matrices, ou d'autres structures de données dans le genre. Un cas classique est celui du parcours d'une matrice colonne par colonne. Les matrices de nombres sont mémorisées ligne par ligne, ce qui fait que parcourir une colonne demande de passer d'une ligne à la suivante et de faire des grands sauts en mémoire RAM, mais tous espacés par la même distance. Avec l'entrelacement simple, les accès en enjambée sont moins performants que les accès à des adresses consécutives, mais cela ne fonctionne pas trop mal. Le pire cas est celui où l'on a N banques et où les données sont justement placées toutes les N adresses. Des accès consécutifs vont tous tomber dans la même banque, on ne peut plus accéder à des banques différentes en parallèle. Mais en dehors de ce cas, on voit une amélioration par rapport à la situation sans entrelacement. Pour obtenir des performances maximales pour les accès en enjambées, il faut répartir les mots mémoires dans la mémoire autrement. L'organisation idéale est la suivante. : Dans les explications qui vont suivre, la variable N représente le nombre de banques, qui sont numérotées de 0 à N-1. On commence par organiser les N premières adresses comme une mémoire entrelacée simple : l'adresse 0 correspond à la banque 0, l'adresse 1 à la banque 1, etc. Pour le bloc suivant, on décale tout d'une adresse, à savoir qu'on commence à remplir les banques à partir de la seconde, non de la première. Une fois la fin du bloc atteinte, on finit de remplir le bloc en repartant du début du bloc. Le troisième bloc d'adresse subit le même traitement, sauf qu'on commence à remplir à partir de la seconde banque. Et on poursuit l’assignation des adresses en décalant d'un cran en plus à chaque bloc. Ainsi, chaque bloc verra ses adresses décalées d'un cran en plus comparé au bloc précédent. Si jamais le décalage dépasse la fin d'un bloc, alors on reprend au début. [[File:Mémoire interleaved par décalage.png|centre|vignette|upright=1.5|Mémoire entrelacée par décalage.]] En faisant cela, on remarque que les banques situées à N adresses d'intervalle sont différentes. Dans l'exemple du dessus, nous avons ajouté un décalage de 1 à chaque nouveau bloc à remplir. Mais on aurait tout aussi bien pu prendre un décalage de 2, 3, etc. Dans tous les cas, on obtient un entrelacement par décalage. Ce décalage est appelé le pas d'entrelacement, noté P. Le calcul de l'adresse à envoyer à la banque, ainsi que la banque à sélectionner se fait en utilisant les formules suivantes : * adresse à envoyer à la banque = adresse totale / N ; * numéro de la banque = (adresse + décalage) modulo N ; * décalage = (adresse totale * P) mod N. Avec cet entrelacement par décalage, on peut prouver que la bande passante maximale est atteinte si le nombre de banques est un nombre premier. Seulement, utiliser un nombre de banques premier peut créer des trous dans la mémoire, des mots mémoires inadressables. Malgré ce défaut, la technique a été utilisée sur quelques ordinateurs, avec l'exemple notable du superordinateur ''Burroughs Scientific Processor''. Pour éviter cela, il y a plusieurs solutions. Par exemple, on peut faire en sorte que N et la taille d'une banque soient premiers entre eux : ils ne doivent pas avoir de diviseur commun. Mais en pratique, elles n'ont pas vraiment été implémentées dans une vraie machine, et sont restées à l'état de recherche, aussi je les passe sous silence. ===L'entrelacement pseudo-aléatoire=== Une dernière méthode de répartition consiste à répartir les adresses dans les banques de manière "pseudo-aléatoire". L'idée exacte est de faire passer l'adresse dans une '''fonction de hachage''', plus ou moins complexe. Pour rappel, une fonction de hachage prend une entrée de grande taille et fournit en sortie un résultat de petite taille. Idéalement, elles doivent donner des résultats aussi différents que possible pour des entrées similaires, histoire de simuler une sorte de pseudo-aléatoire. Ici, une adresse est transformée en un numéro de banque. Une fonction de hachage simple ne fait que permuter des bits de l'adresse pour obtenir son résultat. Elle ne fait qu'échanger des bits de place avant de couper l'adresse en deux morceaux : un pour la sélection de la banque, et un autre pour la sélection de l'adresse dans la banque. Cette permutation est fixe, et ne change pas suivant l'adresse. Des fonctions de hachage plus complexes font des XOR bit à bit entre certains bits de l'adresse. ==Les contrôleurs SDRAM/DDR avec "exécution dans le désordre"== Après avoir vu le cas des mémoires RAM, nous allons nous concentrer sur le cas particulier des mémoires SDRAM. Les mémoires SDRAM et DDR modernes sont capables de gérer plusieurs accès simultanés à la mémoire RAM, et leurs contrôleurs mémoire en font tout autant. C'est une différence majeure avec les mémoires asynchrones FPM/EDO, qui n'acceptaient qu'un seul accès mémoire à la fois. Leur contrôleur mémoire n'acceptait qu'un seul accès mémoire à la fois, c'était un contrôleur mémoire bloquant. Les contrôleurs mémoires des SDRAM sont eux non-bloquants et peuvent encaisser une dizaine d'accès mémoire à la fois. Peu de choses sont connues sur les contrôleurs de SDRAM/DDR modernes, les fabricants ne donnant que peu de détails dessus. Les rares simulateurs qui tentent de décrire leur fonctionnement, comme DRAM SIM I et II, sont particulièrement simples et ne vont pas dans le détail. Néanmoins, le peu qu'on sait est tout de même instructif. ===Rappel sur les SDRAM : tampon de ligne et commandes=== Les mémoires SDRAM sont des mémoires à tampon de ligne. Elles font un accès mémoire en deux étapes. La première étape recopie une ligne de N * 64 bits dans un tampon interne à la SDRAM. La seconde étape sélectionne une colonne, à savoir une donnée de 64 bits, qui est soit envoyée sur le bus de données pour une lecture, soit modifiée par une écriture. [[File:Mémoire à tampon de ligne.png|centre|vignette|upright=2|Mémoire à tampon de ligne]] Les accès mémoire sont traduits par un séquenceur mémoire en une série de commandes mémoires, qui sont séparées par des délais mémoire de quelques cycles d'horloge. Les délais sont très précis, et sont à respecter à la lettre. Une lecture ou une écriture se fait en maximum trois commandes : une commande PRECHARGE qui ferme la ligne précédemment utilisée, une commande ACT qui précise l'adresse de la ligne, et une commande READ ou WRITE qui précise l'adresse de la colonne et éventuellement la donnée à écrire. : Pour être plus précis, la commande PRECHARGE précharge les lignes de bits du plan mémoire à une tension particulière, ce qui les vide de leur contenu. Mais c'est un détail sans importance pour ce qui va suivre. Les SDRAM permettent de se passer de la première étape si des accès consécutifs se font dans la même ligne. Une fois activée, la ligne reste ouverte et on peut accéder plusieurs fois de suite dedans. On a alors juste à préciser l'adresse de colonne dedans. Si un accès mémoire accède à une ligne déjà activée, on dit que c'est un '''succès de page'''. Si ce n'est pas le cas, on doit fermer la ligne courante, rouvrir la ligne vboulue, et préciser la colonne. C'est alors un '''défaut de page'''. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | bgcolor="#FFA0FF" | PRECHARGE || bgcolor="#FFA0FF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || bgcolor="#A0FFFF" | READ (2) || bgcolor="#A0FFFF" | READ (3) || || || bgcolor="#A0FFFF" | READ (4) || bgcolor="#A0FFFF" | READ (5) || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | Donnée READ 1 || ||bgcolor="#A0FFFF" | Donnée READ 2 | bgcolor="#A0FFFF" | Donnée READ 3 || || || bgcolor="#A0FFFF" | Donnée READ 4 || bgcolor="#A0FFFF" | Donnée READ 5 |} Les SDRAM sont parfois capables de démarrer une commande avant que la précédente soit terminée. Mais le respect des délais mémoire est très limitant, ce qui fait qu'on ne peut pas parler de réel pipeline, comme c'est le cas sur les processeurs. Parlons plutot de '''commandes anticipées'''. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | || bgcolor="#A0FFFF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || || || bgcolor="#FFA0FF" | READ (2) || || || || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 | bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 | |} Un dernier point est que les chips mémoires de SDRAM sont composés de plusieurs banques, chacune étant une sorte de mini-mémoire miniature. Chaque banque a son propre décodeur, son propre tampon de ligne, ses propres multiplexeurs de colonne, sa logique de rafraichissement mémoire, etc. C'est comme si une SDRAM regroupait plusieurs mémoires séparées dans un même circuit intégré. [[File:Arrangement vertical.jpg|centre|vignette|upright=2.5|Mémoire multi-banques.]] ===La mise en attente des accès mémoire=== Un contrôleur de SDRAM peut accepter plusieurs accès mémoire en même temps. Mais cela ne signifie pas que le contrôleur sera prêt à les traiter immédiatement. Pour éviter tout problème de disponibilité, le contrôleur met en attente les accès mémoire que le processeur lui envoie, pour les exécuter dès que possible. Les accès mémoire sont mis en attente dans une mémoire FIFO, histoire de les exécuter dans leur ordre d'arrivée, notamment pour renvoyer les lectures dans l'ordre demandé. Il y a aussi une mémoire FIFO pour les données à écrire et une FIFO pour les données lues. Cette dernière sert au cas où le cache ne soit pas disponible quand on lui envoie la donnée lue. [[File:Module d'interface avec la mémoire.png|centre|vignette|upright=2.5|Contrôleur mémoire avec mise en attente des requêtes processeur et autres optimisations.]] Des optimisations sont possibles dès la mémoire FIFO. Par exemple, on peut regrouper plusieurs accès à des données consécutives en un seul accès en rafale. Le contrôleur mémoire analyse les requêtes mises en attente et détecte si certaines se font à des adresses consécutives. Si c'est le cas, il fusionne ces requêtes en une lecture/écriture en rafale. Une telle optimisation est appelée la '''combinaison de lecture''' pour les lectures, et la '''combinaison d'écriture''' pour les écritures. L'optimisation précédente ne paraissent pas payer de mine, mais elles deviennent plus compréhensible quand on sait que certains contrôleurs mémoire peuvent mettre en attente beaucoup de données. Par exemple, l'''Intel E8870 - Scalable Memory Controller'' peut mettre en attente 64 lignes de cache dans la mémoire FIFO, soit 8 kibioctets de RAM ! Deux pages mémoire si la mémoire virtuelle utilise des pages de 4 kibioctets ! ===Les lectures anticipées : une forme d'OOO mémoire=== La mise en attente des accès mémoire est une optimisation intéressante, mais pas mirifique. Elle permet surtout de ne pas bloquer le processeur si le contrôleur mémoire a du travail sur la planche. Cependant, elle peut être optimisée quand on se rend compte d'une chose : le processeur ne voit que les lectures, pas les écritures. Pour les écritures, il les envoie au contrôleur mémoire et ce dernier fait le travail demandé, le processeur n'est pas prévenu quand une écriture se termine. Mais pour les lectures, c'est différent, car il reçoit la donnée lue. En conséquence, il est possible d'optimiser les écritures sans que le processeur ne voit quoique ce soit. Une optimisation possible est alors de changer l'ordre des accès mémoire, histoire de retarder les écritures ou au contraire de les faire en avance. La raison est que les mémoires SDRAM n'aiment pas quand on alterne lectures et écritures. Les délais mémoire, les fameux ''timings'' mémoire, sont clairs : faire une suite de lecture ou une suite d'écriture est plus performant que d'alterner entre lectures et écritures. L'idée est alors de changer l'ordre des écritures de manière à les faire en bloc, idem pour les lectures. L’optimisation est possible, mais à condition que le processeur n'y verra que du feu. Or, vous l'avez deviné, il y a des cas où ces réorganisations ne sont pas possibles, et vous avez sans doute trouvé que la situation était familière. L'optimisation en question est une forme d'exécution dans le désordre des accès mémoire, réalisée par le contrôleur mémoire lui-même ! Et qui dit exécution dans le désordre dit : problèmes liés aux dépendances de données. Changer l'ordre des écritures n'est pas toujours possible, notamment en raison des dépendances RAW, WAR et WAW. Pour que le processeur n'y voit que du feu, il faut respecter plusieurs critères. Premièrement, les lectures doivent être renvoyées au processeur dans l'ordre. Et quand je dis dans l'ordre, cela veut dire : dans l'ordre d'envoi des lectures au contrôleur mémoire. Les écritures aussi doivent se faire dans l'ordre, histoire qu'une série d'écriture donne le bon résultat final. Le troisième critère est qu'une lecture doit donner le résultat de la dernière écriture ''à la même adresse''. Pour le dire autrement, il faut juste détecter les dépendances RAW. Concrètement, si on a une série de lectures et d'écritures alternées, on peut regrouper les lectures et les écritures dans deux paquets séparés, à condition qu'aucune lecture ne lise une adresse écrite par une écriture. Mais si une lecture est dépendante d'une écriture, alors la lecture doit attendre que l'écriture se termine, idem pour les lectures suivantes. Une implémentation basique remplace la mémoire FIFO vue au-dessus, par deux mémoires FIFOs : une pour les lectures, une pour les écritures. Lorsqu'un accès mémoire arrive au séquenceur, il regarde si c'est une lecture ou une écriture et place l'accès mémoire dans la file adéquate. Le fait que ce soit des FIFOs garantit que les lectures se font dans l'ordre, idem pour les écritures. En sortie des deux FIFOs, le séquenceur mémoire détecte les dépendances RAW. Précisément, quand une lecture sort de la mémoire FIFO, il consulte la file d'attente des écriture pour voir si la lecture est dépendante d'une écriture en attente. La lecture attend si c'est le cas, et les écritures sont exécutées à la place. Séparer lectures et écriture est une source d'optimisation. Il est par exemple possible d'utiliser le réacheminement lecture sur écriture (''Store to load forwarding''). Il s'agit d'un équivalent de l’optimisation du même nom, utilisée dans le cadre de la désambiguïsation mémoire. Imaginez qu'une lecture accède à une donnée pas encore écrite, en attente dans le contrôleur mémoire. C'est une dépendance RAW assez claire. Dans ce cas, le contrôleur mémoire peut renvoyer la donnée directement depuis ses mémoires FIFOs, sans faire d'accès mémoire en lecture. Le contrôleur mémoire a tendance à privilégier les lectures, car celles-ci sont cruciales pour l'exécution dans le désordre. Plus elles se font tôt, plus vite le processeur recevra les données et pourra démarrer les instructions dépendantes de cette donnée. Le séquenceur a donc tendance à piocher en priorité dans la file de lecture, plutôt que dans la file d'écriture. A vrai dire, dans le cas idéal, les écritures ne sont faites que quand la file d'écriture est pleine ou quasi-pleine... Prenons l'exemple des coprocesseurs IO 81341 et 81342, qui étaient en réalité des ''chipsets'' intégrant un contrôleur SDRAM. Le ''chipset'' avait 5 ports : un pour les processeurs, un pour le pont sud (''southbridge'') et trois pour des canaux DMA. Pour le port processeur, il n'y avait pas de réordonnancement et ce port utilisait une seule mémoire FIFO. Les autres ports utilisaient l'optimisation qu'on vient de voir, et avaient vraisemblablement des files séparées pour les lectures et écritures. Le séquenceur mémoire vérifiait les dépendances mémoire de type RAW et autres. [[File:Double file d'attente pour les lectures et écritures.png|centre|vignette|upright=2.5|Double file d'attente pour les lectures et écritures]] ===Le ré-ordonnancement des commandes mémoires=== L'optimisation précédente est peu poussée, comparé aux formes d'exécution dans le désordre que les processeurs utilisent. Et on peut se demander si une exécution dans le désordre plus poussée est possible. La réponse est oui : un contrôleur mémoire peut faire des réorganisations bien plus poussées. Par contre, il faut faire une précision très importante : le contrôleur mémoire ne remet pas les accès mémoire dans l'ordre avant de les envoyer au processeur. C'est le processeur qui remet en ordre les accès mémoire, dans sa ''load queue''. En clair : le processeur voit les accès mémoire dans le désordre, c'est lui qui les remet en ordre. Pour cela, les requêtes mémoire sont "numérotées", à savoir qu'on leur attribue un identifiant binaire. Le contrôleur mémoire exécute les lectures/écritures dans le désordre, mais garde la trace de leur identifiant. Il sait dans quel ordre il exécute les accès mémoire et connait leur latence. Ce qui fait qu'il sait que la donnée lue à tel instant est associée à la requête mémoire numéro X. Pour les lectures, il renvoie la donnée lue avec l'identifiant. Le processeur sait alors à quelle lecture la donnée lue correspond et il se débrouille en interne pour gérer la situation. Maintenant que ces précisions sont faites, posons cette question : dans quelles situations est-il pertinent de faire des accès mémoire dans le désordre ? La réponse est : quand plusieurs accès à une même ligne ne sont pas consécutifs, qu'il y a des accès entre les deux. Après ré-ordonnancement, ces accès mémoire à une même ligne sont exécutés l'un à la suite de l'autre, ce qui est beaucoup plus rapide. Pour rendre le tout plus concret, voici un exemple. Imaginez que l'on sait les 3 accès mémoire suivants : * Une lecture ligne A ; * PRECHARGE + ACT ; * Une écriture ligne B ; * PRECHARGE + ACT ; * Une lecture ligne A. Sans réordonnancent, on doit émettre deux commandes PRECHARGE quand on passe d'une ligne à l'autre, et il faut ajouter les commandes ACT avec. pour éviter cela, le contrôleur mémoire peut retarder l'écriture, ou au contraire avancer la seconde lecture. le résultat est alors le suivant : * Lecture ligne A ; * Lecture ligne A * PRECHARGE + ACT ; * Une écriture ligne B. On a donc deux accès consécutifs à la même ligne, suivi par une écriture dans une autre ligne. La technique marche parce que l'on a un mix adéquat de lectures et d'écritures. Et encore une fois, il faut éviter d'intervertir lectures et écritures à une même adresse, sous peine de problèmes. Encore une fois, les dépendances RAW posent problème ! L'implémentation est assez simple : la ou les mémoires FIFOs précédentes sont remplacées par des mémoires similaires aux fenêtres d'instruction. Le contrôleur mémoire vérifie à chaque cycle les accès en attente, et vérifie à quelle ligne ils accèdent. Il priorise alors les accès qui tombent dans la ligne ouverte : ceux-là sont exécutés avant les autres. S'il n'y en a pas, il prend l'accès mémoire le plus ancien (ordre FIFO). Il teste aussi les dépendances mémoires RAW avant d'envoyer des commandes à la mémoire DDR/SDRAM. une telle solution est appelée l''''algorithme ''FR-FCFS''''' (''First Ready-First Come First Serve''). ===Les optimisations liées à la présence de plusieurs banques=== Les optimisations précédentes sont décuplées par la présence de plusieurs banques dans la mémoire SDRAM. Il est en effet possible de faire plusieurs accès mémoire en même temps, dans des banques différentes. Il est possible de lancer un accès mémoire dans deux banques en même temps, par exemple. La seule contrainte est que la SDRAM est limitée à 4 banques actives en même temps. {|class="wikitable" |+ Pipelining basique sur les SDRAM |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 |- ! Banque Numéro 1 | || || || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || colspan="3" bgcolor="#FFFFA0" | Accès mémoire || || |- ! Banque Numéro 2 | || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || || colspan="3" bgcolor="#A0FFFF" | Accès mémoire || || |- ! Banque Numéro 3 | || || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || || colspan="3" bgcolor="#FFA0FF" | Accès mémoire || |} Les optimisations précédentes peuvent s'appliquer par banque. Il est par exemple possible d'utiliser une mémoire FIFO par banque. Ainsi, les accès dans une même banque se feront en série, dans l'ordre d'arrivée, mais des accès à des banques différentes se feront dans le désordre. Cela ne pose pas de problème car les accès se font à des adresses différentes, ce qui fait qu'il n'y a pas de conflits majeurs. Il y a cependant un risque que les lectures se fassent dans le désordre si on n'y prend pas garde. [[File:Gestion parallèle des banques.png|centre|vignette|upright=2.5|Gestion parallèle des banques.]] Il est aussi possible d'utiliser une file séparée pour les lectures et les écriture, pour chaque banque. Les performances sont alors améliorées comparé à deux files globales pour toute la SDRAM. En idem avec la réorganisation des accès mémoire, pour regrouper les accès à une même ligne. Un problème avec ces optimisations est qu'il est rare que des accès mémoire se fassent dans des banques séparées. Les accès mémoire tendent à se faire avec une bonne localité spatiale, ce qui fait qu'ils tombent dans une même banque. Heureusement, les contrôleurs SDRAM/DDR modernes incorporent des optimisations pour corriger ce problème. Les optimisations en question sont une forme d'entrelacement adaptée aux mémoires SDRAM. L'entrelacement naïf ne marche pas à cause de la présence du tampon de ligne. Cependant, il peut y avoir un entrelacemententre banques SDRAM. Voyons ce que ca veut dire. L''''entrelacement de banques''' répartit deux lignes consécutives dans deux banques différentes. Pour comprendre l'idée, prenons un exemple. Imaginons une mémoire avec deux banques et 4 lignes. Imaginons qu'on parcoure/balaye la mémoire RAM en partant des adresses basses. Sans entrelacement de ligne, les accès se feront comme suit, de gauche à droite : {|class="wikitable" |+ Adresse mémoire |- ! colspan="4" | Banque numéro 1 !! colspan="4" | Banque numéro 2 |- | Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 || Ligne 1 || Ligne 2 || Ligne 3 || Ligne 4 |} Avec l'entrelacement de banques, les accès se feront comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Banque n°1 !! Banque n°2 !! Banque n°1 !! Banque n°2 !! Banque n°1 !! Banque n°2 !! Banque n°1 !! Banque n°2 |- | Ligne 1 || Ligne 1 || Ligne 2 || Ligne 2 || Ligne 3 || Ligne 3 || Ligne 4 || Ligne 4 |} L'avantage est que passer d'une ligne à la suivante est plus rapide. Pas besoin de fermer la ligne pour passer à la suivante, on ouvre directement la ligne suivante dans une banque différente, ce qui sera plus rapide. La commande PRECHARGE pourra être envoyée à la seconde banque en avance. Pour gérer l'entrelacement, le contrôleur de SDRAM prend en entrée l'adresse envoyée par le processeur, et la découpe en plusieurs champs : un pour sélectionner la banque adéquate, un autre pour la ligne, un autre pour la colonne, un autre pour la rangée. Sans entrelacement, les adresses mémoire sont découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- | Adresse de banque || Adresse de ligne || Adresse de colonne |} Avec l'entrelacement de banques, l'adresse est découpée comme suit. {|class="wikitable" |+ Adresse mémoire |- | Adresse de ligne || Adresse de banque || Adresse de colonne (précise à l'octet) |} Il est aussi possible de faire la même chose, mais avec les rangées ou en utilisant du ''dual channel'', mais je vais passer cela sous silence. Toujours est-il que les méthodes d'entrelacement sont nombreuses et chaque contrôleur mémoire fait un peu à sa sauce. Ils permettent en général de choisir entre plusieurs entrelacements. Au minimum, ils permettent de désactiver l'entrelacement, d'activer un entrelacement de ligne, et d'autres entrelacements plus complexes, dont certains sont propriétaires et tenus secrets par le fabricant. L'entrelacement est géré juste avant le séquenceur mémoire, dans un '''circuit d'entrelacement'''. Ce dernier intervertit certains bits de l'adresse lors des accès mémoires. [[File:Controleur mémoire d'une SDRAM avec entrelacement.png|centre|vignette|upright=2.5|Contrôleur mémoire d'une SDRAM avec entrelacement.]] Les contrôleurs de SDRAM précédents sont assez basiques et ne représentent pas les contrôleurs les plus évolués. Ils étaient utilisés sur les anciens PC, à une époque où ils étaient encore sur la carte mère du processeur. Mais de nos jours, le contrôleur mémoire est intégré au processeur. Et il incorpore de nombreuses optimisations afin de gagner en performances. Malheureusement, ces optimisations sont elles-mêmes dépendantes des optimisations intégrées dans le processeur, ce qui fait qu'on ne peut pas en parler ici. Elles demandent que le contrôleur mémoire reçoive plusieurs accès mémoire simultanés, ce qui n'a aucun sens à ce stade du cours. Aussi, nous allons les passer sous silence pour le moment. Nous les verrons dans le chapitre sur le parallélisme mémoire. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=La désambiguïsation mémoire | prevText=La désambiguïsation mémoire | next=Les processeurs superscalaires | nextText=Les processeurs superscalaires }} </noinclude> r1svzifxzqq30mhkwst9vyas05ygvuc Fonctionnement d'un ordinateur/Les processeurs superscalaires 0 65956 764637 762284 2026-04-23T13:37:19Z Mewtow 31375 /* La micro-fusion des processeurs Intel et AMD */ 764637 wikitext text/x-wiki Les processeurs vus auparavant ne peuvent émettre au maximum qu'une instruction par cycle d'horloge : ce sont des processeurs à émission unique. 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. [[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. ==L'implémentation d'un processeur superscalaire== Sur un processeur à émission multiple, plusieurs micro-opérations sont émises en même temps, le nombre varie d'un processeur à l'autre. 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 modernes peuvent émettre 3, 4, 6, voire 8 micro-opérations simultanément. Aller au-delà ne sert pas à grand-chose. Un processeur superscalaire peut utiliser l'exécution dans le désordre et le renommage de registres, mais ce n'est pas une obligation. Il existe des processeurs superscalaires à émission dans l'ordre, sans optimisations avancées. 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. L'implémentation de la superscalarité est plus simple à expliquer avec émission dans l'ordre. Prenons un processeur simple, sans exécution dans le désordre, et essayons d'ajouter la superscalarité. Le processeur de base est illustré ci-contre. Il est très simple : une unité de chargement, un décodeur, une unité de calcul, un banc de registres et une unité mémoire. Nous omettons volontairement l'unité d'émission, qui n'est pas représentée sur les schémas qui vont suivre. [[File:Processeur non-superscalaire basique 01.png|centre|vignette|upright=1.5|Processeur non-superscalaire basique]] Reste à adapter ce processeur pour qu'il soit capable de : lire plusieurs instructions depuis la mémoire, les décoder, les exécuter. Intuitivement, on se fit qu'on doit dupliquer tous les circuits : les décodeurs, l'unité de chargement, les unités de calcul, etc. Dans les faits, tout n'est pas dupliqué, certains circuits sont simplement adaptés. ===L'impact de la superscalarité sur le ''front-end''=== Un processeur superscalaire doit pouvoir charger plusieurs instructions en même temps. Pour cela, dupliquer l'unité de chargement est parfaitement possible, à condition de dupliquer aussi les ports du cache d'instruction. Mais dans ce cas, le ''program counter'' doit générer deux adresses, au mieux consécutives, au pire prédites par l'unité de prédiction de branchement. L'implémentation est alors encore très complexe. Une autre solution se contente de doubler/tripler/quadrupler la taille du bus connecté au cache d'instruction. Par exemple, pour charger deux instructions de 64 bits chacune, il suffit de lire un bloc de 128 bits dans le cache d'instruction. Si les instructions sont de longueur fixe, cela charge deux instructions à la fois. Pour un processeur à triple émission, il faut tripler la taille du bus, quadrupler pour un processeur quadruple émission, etc. Tout n'est pas si simple, quelques subtilités font qu'on doit ajouter des circuits en plus pour corriger les défauts peu intuitifs de cette implémentation naïve. Par exemple, gérer les instructions de taille variable est un peu complexe avec cette solution. Mais l'implémentation est bien plus simple qu'en doublant les ports de lecture du cache d'instruction. Nous détaillerons les unités de chargement superscalaires dans la suite du chapitre, une section entière leur sera dédiée. Pour décoder plusieurs instructions en même temps, le décodeur d'instruction est lui dupliqué. Le cout en transistor est important, mais c'est la solution la plus simple qui soit. Il y a des subtilités sur les processeurs microcodés, mais nous verrons cela dans une section dédiée. Pour le moment, retenez juste que les décodeurs sont dupliqués. ===L'impact de la superscalarité sur le chemin de données=== Pour ce qui est des unités de calcul, elles sont dupliquées. Pour le moment, on va considérer que les unités de calcul sont doublées avec la double émission, triplées avec la triple émission, et ainsi de suite. En réalité, la duplication est souvent partielle, afin d'économiser des circuits, mais nous verrons cela plus tard. L'essentiel est que des unités de calcul sont ajoutées au processeur, comparé à ce qu'on aurait sans superscalarité. Et cela a des conséquences sur le reste du chemin de données. Pour le banc de registre, il n'est pas dupliqué directement, mais on lui rajoute des ports de lecture et d'écriture. On rajoute des ports de lecture pour alimenter les unités de calcul rajoutées en opérandes, des ports d'écritures pour qu'elles puissent enregistrer leurs résultats dans les registres. Par exemple, prenons un processeur à double émission, pour lequel on aurait doublé le nombre d'unités de calcul. Qui dit deux fois plus d'ALUs dit : lire deux fois plus d'opérandes, écrire deux résultats. Les ports du banc de registre sont donc doublés. [[File:Processeur non-superscalaire basique 03.png|centre|vignette|upright=2|Processeur non-superscalaire basique]] Pour l'unité mémoire, il y a plusieurs solutions. Avec la plus simple, l'unité mémoire n'est pas dupliquée et le processeur superscalaire ne peut pas faire deux accès mémoire simultanés. 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 deux instructions consécutives soient des instructions mémoire. Les situations où l'unité mémoire doit faire deux accès simultanés sont donc rares. Notons que le processeur peut exécuter une instruction mémoire en parallèle d'une autre. Le cas le plus fréquent est une instruction mémoire suivie ou précédée d'une instruction de calcul. Et le processeur peut parfaitement exécuter la première dans l'unité mémoire et la seconde dans une ALU. Seuls les accès mémoire simultanés sont impossibles. Par contre, dès qu'on passe à la quadruple émission ou au-delà, il est plus fréquent d'avoir des accès mémoire simultanés. Une autre solution double les ports de lecture/écriture du cache de données, ce qui permet plusieurs accès mémoire simultanés. L'unité mémoire est alors dupliquée, avec une unité mémoire par port du cache de données. Par exemple, un processeur double émission aura deux unités mémoire et un cache de données double port. Par contre, si les processeurs superscalaires ajoutent des ports au cache, ils se limitent à deux ou trois ports, rarement plus. Par exemple, il est possible d'avoir un processeur quadruple émission, avec seulement deux ports sur le cache. Le processeur peut donc émettre deux accès mémoire à la fois, mais pas trois ni quatre. Et là encore, ce n'est pas un gros problème, car il est peu fréquent d'avoir trois ou quatre accès mémoire consécutifs. [[File:Processeur non-superscalaire basique 02.png|centre|vignette|upright=2.5|Processeur non-superscalaire basique]] Quelques processeurs utilisaient des solutions intermédiaires, un exemple classique étant les Pentium 1 et 2 d'Intel. Mais nous en parlerons dans le chapitre suivant. ===L'impact de la superscalarité sur l'unité d'émission=== Il ne nous reste plus qu'à voir l'unité d'émission, qu'on a volontairement passé sous silence dans ce qui précédait. L'unité d'émission existe toujours, mais elle est adaptée pour prendre en entrée deux micro-opérations, au lieu d'une seule. Elle détecte les dépendances avec les micro-opérations en vol, comme avant. Mais elle doit aussi détecter les dépendances entre micro-opérations en entrée, celles tout juste décodées. Le ''scoreboard'' est donc modifié pour détecter les dépendances entre les instructions à émettre, ce qui demande de rajouter des comparateurs. Avec l'exécution dans le désordre, le ''scoreboard'' est remplacé par des fenêtres d'instruction, avec parfois une unité de renommage de registre en plus. Et ces circuits doivent être adaptés pour la superscalarité. Là encore, l'implémentation d'un processeur superscalaire demande de dupliquer plusieurs circuits et d'en adapter d'autres. Pour ce qui est de des fenêtres d'instruction ou des stations de réservation, il faut pouvoir insérer et émettre plusieurs instructions à la fois. Pour cela, il suffit de rajouter des ports de lecture et écriture, et de modifier la logique de sélection en conséquence. Le ROB et les autres structures doivent aussi être modifiées pour pouvoir émettre et terminer plusieurs instructions en même temps, là encore en ajoutant des ports de lecture/écriture. L'unité de renommage de registre n'est pas dupliquée, mais adaptée, pour 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, 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. ===L’implémentation réelle n'est pas forcément celle décrite plus haut=== Pour résumer le tout, voici ce que cela donne dans les grandes lignes. Les décodeurs et les unités de calcul sont dupliqués, ce qui a un cout en circuit pas négligeable. Le banc de registre et l'unité mémoire deviennent multiport, le nombre de ports double pour de la double émission, triple ou de la triple émission, et ainsi de suite. L'unité de chargement charge deux fois plus de données, mais n'est pas modifiée en profondeur. Les circuits d'émission ou d'exécution dans le désordre sont un peu modifiés, les fene^tres d'instruction voient leur nombre de ports doubler/tripler/quadrupler. {|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 |} Le cout en circuit est assez important. Pour de la double émission, cela double approximativement le nombre de circuits utilisés. Je dis approximativement, car c'est moins que ça en réalité, vu que tout n'est pas dupliqué. Mais surtout, diverses optimisations permettent de réduire le cout en circuit, sans trop impacter les performances. Nous avons parlé d'une de ces optimisations, plus haut, quand nous avons parlé de l'unité mémoire. Nous avions dit qu'il est possible de ne pas doubler/tripler les ports du cache, et de garder un cache simple port. Ou encore, d'augmenter le nombre de ports, mais pas au maximum permis par la triple/quadruple/octuple émission. Il s'agit là d'une économie qui réduit un peu les performances, mais avec un gain en matériel conséquent. Et bien sachez qu'il existe des optimisations similaires, pour les ports du banc de registre, pour l'unité d'émission, les décodeurs, et surtout : les unités de calcul. Par exemple, j'ai dit plus haut que les unités de calcul étaient dupliquées. Concrètement, si le processeur de base a une ALU entière, un ''barrel shifter'', un circuit multiplieur et une FPU ; ils sont tous dupliqués. L'avantage de faire ainsi est que le processeur n'a pas de contrainte quand il veut émettre deux instructions. Si le processeur veut émettre deux multiplications consécutives, il le peut. S'il veut émettre deux instructions flottantes, il le peut. Pour le dire autrement, toutes les paires d'instructions possibles sont compatibles avec la double émission. Le problème, c'est que le cout en circuit est conséquent ! Dupliquer la FPU ou les circuits multiplieurs bouffe du transistor. Pour économiser des transistors, il est possible de ne pas dupliquer toutes les ALU. Typiquement, les ALU entières simples sont dupliquées, mais pas la FPU, ni les circuits multiplieurs. Le cout en transistors est alors grandement réduit, au prix de l'apparition de dépendances structurelles. Par exemple, le CPU ne peut pas émettre deux multiplications consécutives sur un seul multiplieur. Ou encore, il ne peut pas émettre deux additions flottantes s'il y a un seul additionneur flottant. La conséquence est que les processeurs superscalaires ont des '''contraintes d'appariement''' sur les instructions à émettre en même temps. Si on prend un processeur ''dual-issue'', il y a des paires d'instructions autorisées et des paires interdites. Par exemple, l'exécution simultanée de deux branchements est interdite, les branchements sont exécutés l'un après l'autre. Mais il est possible d'émettre un branchement en même temps qu'une autre instruction, en espérant que la prédiction de branchement ait fait une bonne prédiction. La raison est qu'il n'y a qu'une seule unité de calcul pour les branchements dans un processeur. Pour résumer, tout n'est pas dupliqué à la perfection. Certaines structures matérielles ne le sont pas alors qu'elles le devraient, afin d'économiser du circuit. Dans la suite du chapitre, nous allons détailler ces optimisations. Et nous allons aussi détailler la conception de l'unité de chargement, de l'unité d'émission, des decodeurs superscalaires, et bien d'autres choses. ==Le séquenceur d'un processeur superscalaire== Le séquenceur d'un processeur superscalaire est modifié, afin de pouvoir charger et décoder plusieurs instructions à la fois. L'unité de chargement subit des modifications mineures. Quant au décodeur, la solution la plus fréquemment utilisée utiliser plusieurs décodeurs. De plus, les unités de renommage et d'émission doivent être modifiées afin de pouvoir renommer et émettre plusieurs instructions à la fois. Voyons quelles sont les modifications en question. ===L'étape de chargement superscalaire=== Pour charger plusieurs instructions, il suffit de doubler, tripler ou quadrupler le bus mémoire. Précisément, c'est le port de lecture du cache d’instruction qui est élargit, pour lire 2/3/4/... instructions. 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. Découper un bloc en instructions est trivial avec des instructions de longueur fixe, mais plus compliqué avec des instructions de taille variable. Il est cependant possible de s'en sortir avec deux solutions distinctes. La première solution utilise les techniques de prédécodage vues dans le chapitre sur les caches, à savoir que le découpage d'une ligne de cache est réalisé lors du chargement dans le cache d’instruction. Une autre solution améliore le circuit de détection des tailles d'instruction vu dans le chapitre sur l'unité de chargement. Avec la seconde solution, cela prend parfois un étage de pipeline entier, comme c'est le cas sur les processeurs Intel de microarchitecture P6. Un bloc de 8, 16, 32 octets contient plusieurs instructions, avec potentiellement des branchements dedans. 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 sont annulées, elles ne sont pas décodées ni exécutées. L'unité de chargement coupe le bloc chargé au niveau du premier branchement non-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. La majorité des processeurs superscalaires font ainsi, même de nos jours. [[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.]] Une solution plus performante 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 autoaligné.]] 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 ! Tout cela pour dire que rares sont les processeurs qui implémentent cette technique. [[File:Implémentation d'un cache d'instructions autoaligné.png|centre|vignette|upright=2|Implémentation d'un cache d'instructions autoaligné.]] ===Les décodeurs d'instructions superscalaires=== Un processeur superscalaire contient plusieurs décodeurs, chacun pouvant décoder une instruction en parallèle des autres. Prenons par exemple un processeur RISC dont toutes les instructions font 32 bits. Un processeur superscalaire de ce type charge des blocs de 128 bits, ce qui permet de charger 4 instructions d'un seul coup. Et pour les décoder, 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. 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 fusion des instructions se fait lors du décodage des instructions, grâce à la coopération des décodeurs. En théorie, d'autres utilisations de la macro-fusion sont possibles, certaines ont même été [https://www2.eecs.berkeley.edu/Pubs/TechRpts/2016/EECS-2016-130.pdf proposées pour le jeu d'instruction RISC-V], mais rien n'est encore implémenté (à ma connaissance). Décoder et émettre plusieurs instructions consécutives complexifie le ''front-end'', particulièrement sur les processeurs superscalaires modernes qui émettent de 5 à 10 instruction simultanées. Et le problème est encore aggravé quand on veut faire la macro-fusion : cela demande de tester un paquet de paires d'instructions et de vérifier que des paires fusionnables ne se recouvrent pas. Diverses solutions sont possibles pour amortir le problème. On peut par exemple limiter le nombre de décodeurs et compenser avec un cache de µops. Il est en théorie possible d'utiliser la technique du pré-décodage pour détecter les dépendances, mais le cout en circuits serait trop important. La microarchitecture Darkmont d'Intel a trouvé une solution alternative. Intel a trouvé qu'il y a environ un branchement tous les 4/5 instructions et a architecturé ses décodeurs en en tenant compte. Elle intègre 9 décodeurs, regroupés en trois groupes de 3. Un groupe de 3 décodeurs peut décoder trois instructions consécutives. De plus, l'unité de prédiction de branchement prédit deux branchements par cycle. L'idée est que le processeur décode trois blocs d'instructions séparés par des branchements. Le premier groupe décode 3 instructions consécutives, le second groupe décode les 3 instructions après le premier branchement, le troisième groupe décode les 3 instructions situées après le second branchement. Mais si le code est composé d'une suite d'instruction sans branchements, seul le premier groupe sera utilisé, limitant le décodage à trois instructions par cycle. ===L'unité de renommage superscalaire=== Sur un processeur à émission multiple, l'unité de renommage de registres doit renommer plusieurs instructions à la fois, mais aussi gérer les dépendances entre instructions. 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.]] ===L'unité d'émission superscalaire=== Pour émettre plusieurs instructions en même temps, l'unité d'émission doit être capable d'émettre plusieurs instructions par cycle. Et Pour cela, elle doit détecter les dépendances entre instructions. Il faut noter que la plupart des processeurs superscalaires utilisent le renommage de registre pour éliminer un maximum de dépendances inter-instructions. Les seules dépendances à détecter sont alors les dépendances RAW, qu'on peut détecter en comparant les registres de deux instructions consécutives. Sur les processeurs superscalaires à exécution dans l’ordre, il faut aussi gérer l'alignement des instructions dans la fenêtre d'instruction. Dans le cas le plus simple, les instructions sont chargées par blocs et on doit attendre que toutes les instructions du bloc soient émises pour charger un nouveau bloc. Avec la seconde méthode, La fenêtre d'instruction fonctionne comme une fenêtre glissante, qui se déplace de plusieurs crans à chaque cycle d'horloge. Mais au-delà de ça, le design de l'unité d'émission change. Avant, elle recevait une micro-opération sur son entrée, et fournissait une 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. Formellement, il est possible de faire une analogie avec une mémoire : l'unité d'émission dispose de ports d'écriture et de lecture. On envoie des micro-opérations décodées/renommées sur des ports d'écriture, et elle renvoie des micro-opérations émises sur le port de lecture. Dans ce qui suit, nous parlerons de '''ports de décodage''' et de '''ports d'émission'''. Intuitivement, on se dit qu'il y a autant de ports de décodage que de ports d'émission. Mais la présence d'un cache de µops découple le nombre d'instructions décodées par cycle avec le nombre de µops émises. Par exemple, le cache de µops peut alimenter une dizaine d'unités de calcul, alors que le processeur a seulement 5 décodeurs. Il y a ainsi une différence entre nombre d'instructions décodées par cycle d'horloge et nombre de µops émises par l'unité d'émission. Par contre, ce rythme idéal n'a lieu qu'en cas de succès dans le cache de µops. Le moindre défaut de cache entraine l'usage des décodeurs, pour alimenter les unités de calcul. Si l'unité d'émission est un vulgaire ''scoreboard'', il doit détecter les dépendances entre instructions émises simultanément, il doit détecter les paires d'instructions dépendantes et les sérialiser. Par contre, la détection des dépendances entre instructions consécutives est simplifiée avec une fenêtre d'instruction, il n'y a pour ainsi dire pas grand chose à faire, vu que les dépendances sont éliminées par le renommage de registre et que les signaux de réveil s'occupent de gérer les dépendances RAW. C'est la raison pour laquelle les processeurs superscalaires utilisent tous une fenêtre d'instruction centralisée ou décentralisée, et non des ''scoreboard''. ===Les fenêtres d'instruction et stations de réservation des CPU superscalaires=== Les processeurs superscalaires privilégient souvent des stations de réservations aux fenêtres d'instruction. Rappelons la terminologie utilisée dans ce cours. Les fenêtres d'instruction se contentent de mémoriser la micro-opération à émettre et quelques bits pour la disponibilité des opérandes. Par contre, les stations de réservations mémorisent aussi les opérandes des instructions. Les registres sont lus après émission avec une fenêtre d'instruction, avant avec des stations de réservation. Et cette différence a une influence sur le banc de registre, et précisément au nombre de ports de lecture. Supposons que toutes les instructions sont dyadiques, ou du moins qu'il n'existe pas de micro-opération à trois opérandes. Avec une fenêtre d'instruction, le nombre d'opérandes à lire simultanément est le double de la quantité d'instructions qu'on peut émettre en même temps. Sur un processeur ''dual issue'', qui peut émettre deux micro-opérations, cela fait deux micro-opérations à deux opérandes chacune, soit 4 opérandes. Le nombre de ports de lecture est donc de quatre. Avec une station de réservation, la lecture des opérandes a lieu avant l'émission, juste après le décodage/renommage. Le nombre d'opérande est le double du nombre de micro-opérations décodées/renommées, pas émises. Si le décodeur peut décoder/renommer 4 instructions par cycle, cela veut dire 4 micro-opérations émises par cycle. Et il se trouve que les deux situations ne sont pas évidentes. Avec une fenêtre d'instruction centralisée, le nombre d'instructions décodées et émises en même temps sont identiques. Mais dès qu'on utilise des fenêtres d'instruction décentralisées, les choses changent. Si le décodeur peut décoder/renommer 4 instructions par cycle, alors l'idéal est d'avoir 4 micro-opérations émises par cycle, ''par fenêtre d'instruction''. Imaginez que le décodeur décode 4 instructions entières : la fenêtre d'instruction entière doit pouvoir émettre 4 micro-opérations entières en même temps. Idem pour des instructions flottantes avec la fenêtre d'instruction flottantes. Vu qu'il y a deux fenêtres d'instruction, cela fait 4 micro-opérations entières + 4 micro-opérations flottantes = 8 ports de lecture. Non pas qu'ils soient tous simultanément utiles, mais il faut les câbler. ===Les conséquences sur le banc de registre=== Émettre plusieurs instructions en même temps signifie lire plusieurs opérandes à la fois : le nombre de ports du banc de registres doit être augmenté. De plus, il faut aussi écrire plusieurs résultats à la fois dans les registres, ce qui rajoute des ports d'écriture. 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. Et avec plusieurs dizaines d'unités de calcul différentes, le câblage est tout simplement ignoble. Mais diverses optimisations permettent de réduire le nombre de ports assez simplement. Un autre solution utilise un banc de registre unique, mais n'utilise pas autant de ports que le pire des cas le demanderait. Pour cela, le processeur doit détecter quand il n'y a pas assez de ports pour servir toutes les instructions. L'unité d'émission devra alors mettre en attente certaines instructions, le temps que les ports se libèrent. Cette détection est réalisée par un circuit d'arbitrage spécialisé, intégré à l'unité d'émission, l’'''arbitre du banc de registres''' (''register file arbiter''). Un autre détail est que le nombre de ports de lecture n'est pas le même selon que le processeur utilisé des stations de réservation ou une fenêtre d’instruction. Les fenêtres d'instruction impliquent plus de lectures d'opérandes, ce qui implique plus de ports de lecture sur le banc de registres. Les stations de réservation sont plus économes, elles vont bien avec un nombre modéré de ports de lecture. ==Les unités de calcul des processeurs superscalaires== Un processeur superscalaire émet/exécute plusieurs instructions simultanément dans plusieurs unités de calcul séparées. Intuitivement, on se dit qu'il faut dupliquer les unités de calcul à l'identique. Un processeur superscalaire contient alors N unités de calcul identiques, ce qui permet d'émettre N micro-opérations, tant qu'elles n'ont pas de dépendances. Manque de chance, ce cas est l'exception, pas la règle. Dupliquer toutes les ALU aurait un cout en circuit bien trop important. A la place, la duplication des unités de calcul n'est que partielle. Certaines ALU sont dupliquées, d'autres ne le sont pas. Il est même possible d'avoir un processeur superscalaire sans avoir à dupliquer la moindre ALU ! Il faut dire que les processeurs superscalaires usuels ont un pipeline dynamique, à savoir qu'ils incorporent plusieurs unités de calcul distinctes. Typiquement, une ALU pour les instructions entières, une FPU pour les instructions flottantes, une unité pour les accès mémoire (calcul d'adresse) et une unité pour les tests/branchements. Sans superscalarité, ces unités sont toutes reliées au même port d'émission. Avec superscalarité, ces unités de calcul existantes sont connectées à des ports d'émission différents. Voyons cela en détail. ===La double émission entière-flottante=== Prenons le cas d'un processeur avec une ALU entière et une FPU flottante, qu'on veut transformer en processeur superscalaire. La solution la plus simple est d'émettre une micro-opération entière en même temps qu'une micro-opération flottante. La micro-opération entière s'exécute dans l'ALU entière, la micro-opération flottante dans la FPU. Il suffit juste d'ajouter un port d'émission dédié sur la FPU. Le processeur a donc deux pipelines séparés : un pour les micro-opérations entières, un autre pour les micro-opérations flottantes. On parle alors de '''double émission entière-flottante'''. La mise en œuvre utilise assez peu de circuits, du moins sur les processeurs sans exécution dans le désordre. Concrétement, il suffit de scinder le décodeur en deux décodeurs séparés, de charger deux instructions à la fois, et d'ajouter quelques circuits pour la double émission. Le gain en performance qui est faible, mais en vaut la peine. La plupart des premiers processeurs superscalaires étaient de ce type. Les exemples les plus notables sont les processeurs POWER 1 et ses dérivés comme le ''RISC Single Chip''. Ils sont assez anciens et avaient un budget en transistors limité, ce qui fait qu'ils devaient se débrouiller avec peu de circuits dont ils disposaient. L'usage d'une double émission entière-flottante était assez naturelle. [[File:Double émission entière-flottante.png|centre|vignette|upright=2|Double émission entière-flottante]] ===L'émission multiple des micro-opérations flottantes=== La double émission entière-flottante ajoute un port d'émission pour la FPU, ce qui a un cout en circuits modeste, pour un gain en performance intéressant. Mais il faut savoir que les FPU regroupent un additionneur-soustracteur flottant et un multiplieur flottant, qui sont reliés au même port d'émission. Et il est possible d'ajouter des ports d'émission séparés pour l'additionneur flottant et le multiplieur flottant. Le processeur peut alors émettre une addition flottante en même temps qu'une multiplication flottante. Les autres circuits de calcul flottant sont répartis sur ces deux ports d'émission flottants. L'avantage est que cela se marie bien avec l'usage d'une fenêtre d'instruction séparée pour les opérations flottantes. La fenêtre d'instruction a alors deux ports séparés, au lieu d'un seul. Rajouter un second port d'émission flottant n'est pas trop un problème, car le cout lié à l'ajout d'un port n'est pas linéaire. Passer de un port à deux a un cout tolérable, bien plus que de passer de 3 ports à 4 ou de 4 à 5. Il est même possible de dupliquer l'additionneur et le multiplieur flottant, ce qui permet d'émettre deux additions et multiplications flottantes en même temps. C'est ce qui est fait sur les processeur AMD de architecture Zen 1 et 2. Ils ont deux additionneurs flottants par cœur, deux multiplieurs flottants, chacun avec leur propre port d'émission. Les performances en calcul flottant sont assez impressionnantes pour un processeur de l'époque. [[File:ZEN - émission multiple flottante.png|centre|vignette|upright=2.5|Microarchitecture Zen 1 d'AMD.]] ===L'émission multiple des micro-opérations entières=== Nous avons vu plus haut qu'il est possible d'ajouter plusieurs ports d'émission pour la FPU. Intuitivement, on se dit que la méthode peut aussi être appliquée pour les ALU entières. En effet, un processeur contient généralement plusieurs unités de calcul entières séparées, avec typiquement une ALU entière simple, un circuit multiplieur/diviseur, un ''barrel shifter'', parfois une unité de manipulation de bit. La présence de ces unités permet d'émettre jusqu'à 4 micro-opérations entières en même temps, à condition qu'on ajoute assez de ports d'émission. [[File:Emission multiple des opérations entières, implémentation naive.png|centre|vignette|upright=2|Émission multiple des opérations entières, implémentation naïve.]] Maintenant, posons-nous la question : est-ce que faire ainsi en vaut la peine ? Le processeur peut en théorie émettre une addition, une multiplication, un décalage et une opération de manipulation de bits en même temps. Mais une telle situation est rare, ce qui fait que les ports d'émission seront sous-utilisés. Pour réduire le nombre de ports d'émission sous-utilisés, il est possible de regrouper plusieurs unités de calcul sur le même port d'émission. Pour décider comment faire le regroupement, il faut se baser sur la fréquence des instructions. Les instructions arithmétiques sont plus fréquentes que les décalages et les opérations de manipulation de bit. Et elles sont souvent appariées, à savoir qu'une multiplication suit ou précède souvent une autre opération arithmétique, et vis versa. En conséquence, il est préférable d'avoir un port d'émission pour l'ALU entière, un autre pour le multiplieur. L'avantage est que cela permet d'exécuter des micro-opérations entières en parallèle d'une multiplication. Les multiplications étant des instructions à la fois multicycles et assez fréquentes, une telle situation n'est pas rare. Les unités restantes sont placées sur l'un des deux ports. [[File:Emission multiple des opérations entières, double émission.png|centre|vignette|upright=2|Émission multiple des opérations entières, double émission]] Typiquement, la plupart des programmes sont majoritairement remplis d'additions, avec des multiplications assez rares et des décalages qui le sont encore plus. En pratique, il n'est pas rare d'avoir une multiplication pour 4/5 additions. Si on veut profiter au maximum de l'émission multiple, il faut pouvoir émettre plusieurs additions/soustractions en même temps, ce qui demande de dupliquer les ALU simples et leur donner chacune son propre port d'émission. Le multiplieur n'est presque jamais dupliqué, car il est rare d'avoir plusieurs multiplications consécutives. Disposer de plusieurs circuits multiplieurs serait donc un cout en circuits qui ne servirait que rarement et n'en vaut pas la chandelle. Pour économiser des ports d'émission, les ALU entières dupliquées sont reliées à des ports d'émission existants. Par exemple, on peut ajouter la seconde ALU entière au port d'émission du multiplieur, la troisième ALU entière au port dédié au ''barrel shifter'', etc. Ainsi, les ports d'émission sont mieux utilisés : il est rare qu'on n'ait pas d'instruction à émettre sur un port. Le résultat est un gain en performance bien plus important qu'avec les techniques précédentes, pour un cout en transistor mineur. [[File:Emission multiple des opérations entières, implémentation courante.png|centre|vignette|upright=2|Emission multiple des opérations entières, implémentation courante]] ===L'émission multiple des accès mémoire=== Plus haut, nous avons parlé de la double émission entière-flottante. Et bien sachez qu'une optimisation similaire peut être appliquée aux accès mémoire. En théorie, les accès mémoire sont pris en charge par le pipeline pour les opérations entières, ce qui permet d'utiliser l'unité de calcul pour calculer des adresses. Mais il est aussi possible de relier l'unité mémoire à son propre port d'émission. Le processeur devient alors capable d’émettre une micro-opération mémoire en parallèle d'autres micro-opération entières/flottantes. On parle alors de '''triple émission entière-flottante-mémoire'''. La seule contrainte est que l'unité mémoire incorpore une unité de calcul d'adresse dédiée. Et comme pour pour les opérations flottantes et entières, il est possible d'avoir une '''émission multiple des accès mémoire''' ! Il est en effet possible 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. Pour cela, il faut idéalement que le cache soit multiport, afin de pouvoir servir plusieurs accès mémoire en même temps. 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 demande évidemment de dupliquer des circuits, mais pas l'unité mémoire complète. Pour rappel, l'unité mémoire s'interpose entre le cache et le reste du pipeline. Elle incorpore le cache de données L1, une file d'écriture, potentiellement des unités de calcul. Mais sur les processeurs superscalaires modernes, les unités de calcul sont placées en-dehors de l'unité mémoire. 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 il y a trois cas principaux qu'on va voir dans l'ordre. Le cas le plus simple est celui où l'unité mémoire est précédée par une file de µops mémoire unique, mais rares sont les processeurs à émission multiple qui sont dans ce cas. Les rares processeurs commerciaux à faire ainsi sont les processeurs AMD de micro-architecture K7 K8, à savoir les anciens AMD Athlon et AMD Athlon 64. 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. Par contre, la file de µops mémoire pouvait recevoir trois adresses par cycle, calculées par trois unités de calcul d'adresse distinctes. : La file de µops mémoire est appelée la ''Pre-Cache Queue'' dans les schéma qui suivent. La ''Post-Cache Queue'' est une structure servant à gérer les défauts de cache, 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. Sur la microarchitecture K7, les unités de calcul d'adresse sont alimentées par une fenêtre d'instruction qui gère à la fois les opérations entières et les calculs d'adresse. La triple émission était donc hybride : 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. [[File:AMD K7.png|centre|vignette|upright=2|AMD K7]] Sur la microarchitecture K7, il y a plusieurs fenêtre d'instruction, chacune gérant à la fois une ALU entière et une AGU de calcul d'adresse. La triple émission ne changeait pas, on avait toujours trois calculs d'adresse par cycle, mais un accès mémoire à la fois. [[File:AMD K8, microarchitecture.png|centre|vignette|upright=2|AMD K8]] Le cas avec deux files de micro-opération permet d'implémenter la double émission très simplement. Pour rappel, il s'agit du cas avec une file pour les lectures et une autre pour les écritures, appelées respectivement la file de µops LOAD et la file de µops STORE. Dans ce cas, il est possible d'émettre une lecture et une écriture lors du même cycle d'horloge. Il faut dire que l'implémentation est facilitée par le fait que l'unité mémoire est naturellement double port, avec un port de lecture et un port d'écriture. 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, bien qu'ils étaient antérieurs aux micro-architectures K7/K8 vues précédemment. [[File:Double émission avec le Load Ordering, Store Ordering.jpg|centre|vignette|upright=2.5|Double émission avec le Load Ordering, Store Ordering]] Il est possible d'adapter le tout pour la triple ou quadruple émission. Ajouter la possibilité d'émettre une seconde écriture est assez compliqué et demande d'utiliser une file d'écriture multiport, implémenter des lectures en plus est plus simple. Il faut pour cela rajouter un port de lecture au cache, à l'unité mémoire, et une file de µops LOAD. Les deux files de µops LOAD sont alimentées via un port d'émission chacune. Les processeurs avec une ''Load-store queue'' ne la dupliquent pas, mais la rende multi-ports afin de gérer plusieurs micro-opérations mémoire simultanées. Par exemple, les processeurs skylake ont une LSQ avec deux ports de lecture et un port d'écriture, ce qui permet de faire deux lectures en même temps qu'une écriture. Il faut cependant dupliquer les unités de calcul d'adresse reliées à la LSQ, chaque port d'émission mémoire ayant 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. [[File:Emissim multiple des µops mémoire.png|centre|vignette|upright=2.5|Emission multiple des µops mémoire.]] Un exemple est celui des processeurs Intel de microarchitecture Nehalem, qui pouvaient seulement émettre une lecture en même temps qu'une écriture. Ils avaient deux ports d'émission reliés à l'unité mémoire. Un port pour les lectures, un autre pour les écritures. Le premier port d'écriture recevait la donnée à écrire et s'occupait des calculs d'adresse, Le port de lecture faisait uniquement des calculs d'adresse. D'autres processeurs ont plusieurs ports d'émission pour les unités mémoire, mais qui peuvent faire indifféremment lecture comme écritures. Un exemple est celui du processeur Athlon 64, un processeur AMD sorti dans les années 2000. Il disposait d'une LSQ unique, reliée à un cache L1 de donnée double port. La LSQ était reliée à trois unités de calcul séparées de la LSQ. La LSQ avait des connexions avec les registres, pour gérer les lectures/écritures. ===L'interaction avec les fenêtres d'instruction=== Nous venons de voir qu'un processeur superscalaire peut avoir des ports d'émission reliés à plusieurs ALU. Pour le moment, nous avons vu le cas où le processeur dispose de fenêtres d'instruction séparées pour les opérations entières et flottantes. Un port d'émission est donc relié soit à des ALU entières, soit à des FPU. Mais il existe des processeurs où un même port d'émission alimente à la fois une ALU entière et une FPU. Par exemple, on peut relier un additionneur flottant sur le même port qu'une ALU entière. Il faut noter que cela implique une fenêtre d'instruction centralisée, capable de mettre en attente micro-opérations entières et flottantes. Un exemple est celui des processeurs Core 2 Duo. Ils disposent de 6 ports d'émission, dont 3 ports dédiés à l'unité mémoire. Les 3 ports restants alimentent chacun une ALU entière, un circuit de calcul flottant et une unité de calcul SSE (une unité de calcul SIMD, qu'on abordera dans quelques chapitres). * Le premier port alimente une ALU entière simple et un multiplieur/diviseur flottant. * Le second alimente une ALU entière, un multiplieur entier et un additionneur flottant. * Le troisième alimente une ALU entière, sans circuit flottant dédié. Une conséquence de partager les ports d'émission est l'apparition de dépendances structurelles. Par exemple, imaginez qu'on connecte un multiplieur entier et la FPU, sur le même port d'émission. Il est alors impossible d'émettre une multiplication et une opération flottante en même temps. Mais la situation ne se présente que pour certaines combinaisons de micro-opérations bien précises, qui sont idéalement assez rares. De telles dépendances structurelles n'apparaissent que sur des programmes qui entremêlent instructions flottantes et entières, ce qui est assez rare. Les dépendances structurelles doivent cependant être prises en compte par les unités d'émission. Dans le même genre, il est possible de partager un port d'émission entre l'unité mémoire et une ALU entière. Cela permet d'utiliser l'ALU entière pour les calculs d'adresse, ce qui évite d'avoir à utiliser une unité de calcul d'adresse distincte. Un exemple est celui du processeur superscalaire double émission Power PC 440. 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. L'organisation en question permet soit d'émettre un accès mémoire en même temps qu'une opération entière, soit d'émettre deux opérations entières simples, soit d’émettre une multiplication et une addition/soustraction/comparaison. Une organisation simple, mais très efficace ! [[File:PowerPC 440.png|centre|vignette|upright=2|Microarchitecture du PowerPC 440.]] ===Résumé=== Faisons un résumé rapide de cette section. Nous venons de voir que les différentes unités de calcul sont reliés à des ports d'émission, la répartition des ALU sur les ports d'émission étant très variable d'un processeur à l'autre. Entre les processeurs qui séparent les ports d'émission entier et flottant, ceux qui les mélangent, ceux qui séparent les ports d'émission mémoire des ports entiers et ceux qui les fusionnent, ceux qui autorisent l'émission multiple des micro-opérations mémoire ou flottante, il y a beaucoup de choix. Les divers choix possibles sont tous des compromis entre deux forces : réduire le nombre de ports d'émission d'un côté, garder de bonnes performances en limitant les dépendances structurelles de l'autre. Réduire le nombre de ports d'émission permet de garder des fenêtres d'instruction relativement simples. Plus elles ont de ports, plus elles consomment d'énergie, chauffent, sont lentes, et j'en passe. De plus, plus on émet de micro-opérations en même temps, plus la logique de détection des dépendances bouffe du circuit. Et cela a des conséquences sur la fréquence du processeur : à 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, regrouper plusieurs ALU sur un même port d'émission est à l'origine de dépendances structurelles. Impossible d'émettre deux micro-opérations sur deux ALU si elles sont sur le même port. Le nombre de ports peut être un facteur limitant pour la performance dans certaines situations de '''''port contention''''' où on a assez d'ALU pour exécuter N micro-opérations, mais où la répartition des ALUs sur les ports l’empêche. Suivant la répartition des ALU sur les ports, la perte de performance peut être légère ou importante, tout dépend des choix réalisés. Et les choix en question dépendent fortement de la répartition des instructions dans le programme exécuté. Le fait que certaines instructions sont plus fréquentes que d'autres, que certaines instructions sont rarement consécutives : tout cela guide ce choix de répartition des ALu sur les ports. ==Le contournement sur les processeurs superscalaires== 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]] ===Les problèmes du contournement sur les CPU avec beaucoup d'ALUs=== Avec plusieurs unités de calcul, 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 de circuits. 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 sur les processeurs modernes, qui ont facilement une dizaine d'unité de calcul. De plus, la complexité du réseau de contournement (l'ensemble des interconnexions entre ALU) a un cout en terme de rapidité du processeur. Plus il est complexe, plus les données contournées traversent de longs fils, plus leur temps de trajet est long, plus la fréquence du processeur en prend un coup. Diverses techniques permettent de limiter la casse, comme l'usage d'un bus de contournement, mais elle est assez impraticable avec beaucoup d'unités de calcul. Notez que cela vaut pour les processeurs superscalaires, mais aussi pour tout processeur avec beaucoup d'unités de calcul. Un simple CPU à exécution dans le désordre, non-superscalaire, a souvent pas mal d'unités de calcul et fait face au même problème. En théorie, un processeur sans exécution dans le désordre ou superscalarité pourrait avoir le problème. Mais 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. Généralement, cela arrive pour les unités de calcul entières, mais pas pour les unités flottantes. La raison est que les CPU ont souvent beaucoup d'unités de calcul entières, car les instructions entières sont légion, alors que les instructions flottantes sont plus rares et demandent au mieux une FPU simple. Évidemment, l'usage d'agglomérats fait que certaines possibilités de contournement sont perdues, avec la perte de performance qui va avec. Mais la perte en possibilités de contournement vaut bien le gain en fréquence et le 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. ===Les bancs de registre sont aussi adaptés pour le contournement=== L'usage d'agglomérats peut aussi prendre en compte les interconnexions entre unités de calcul et registres. C'est-à-dire que les registres peuvent être agglomérés. Et cela peut se faire de plusieurs façons différentes. Une première solution, déjà vue dans les chapitres sur la micro-architecture d'un processeur, consiste à découper le banc de registres en plusieurs bancs de registres plus petits. Il faut juste prévoir 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. Une autre solution duplique 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 lectures en RAM et les résultats des opérations sont envoyées à tous les bancs de registres, afin de garantir que leur contenu est identique. Le banc de registre est dupliqué en autant d'exemplaires qu'il y a d'agglomérats. Chaque exemplaire a exactement deux ports de lecture, une par opérande, au lieu de plusieurs dizaines. La conception du processeur est simplifiée, que ce soit au niveau du câblage, que de la conception des bancs de registres. ==Les optimisations de la pile d'appel : le ''stack engine''== 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. A 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 instruction ''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ée 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 microarchitectures 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 microarchitecture 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 microarchitecture 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=Exemples de microarchitectures CPU : le cas du x86 | nextText=Exemples de microarchitectures CPU : le cas du x86 }} </noinclude> 34lvtyjfj44amqrnxv0hv0oma60qdkl Fonctionnement d'un ordinateur/La cohérence des caches 0 65959 764829 763681 2026-04-24T11:39:32Z Mewtow 31375 /* La mise à jour et l'invalidation sur écriture */ 764829 wikitext text/x-wiki Il est possible d'utiliser des caches avec la mémoire partagée, mais aussi sur les architectures distribuées et les architectures NUMA. Néanmoins, la gestion des caches peut poser des problèmes dits de '''cohérence des caches''' quand une donnée est présente dans plusieurs caches distincts. [[File:Cohérence des caches.png|vignette|upright=1|Cohérence des caches]] Introduisons la cohérence des caches par un exemple. Prenons deux cœurs/processeurs qui ont chacun une copie d'une donnée dans leur cache. Si un processeur modifie sa copie de la donnée, l'autre ne sera pas mise à jour. L'autre processeur manipule donc une donnée périmée : il n'y a pas '''cohérence des caches''', sous-entendu cohérence entre le contenu des différents caches. [[File:Non Coherent.gif|centre|vignette|upright=2.5|Caches non-cohérents.]] Or, il faut éviter cela sous peine d'avoir des problèmes. Mais avoir des caches cohérents demande d'avoir une valeur à jour de la donnée dans l'ensemble de ses caches, ce qui implique que les écritures dans un cache doivent être propagées dans les caches des autres processeurs. Il est possible de rendre des caches cohérents avec diverses méthodes qu'on va voir dans ce chapitre. Les deux animations ci-dessous montrent l'exemple de caches non-cohérents et de caches cohérents. [[File:Coherent.gif|centre|vignette|upright=2.5|Caches cohérents.]] Les problèmes de cohérence des caches se manifestent sur toutes les architectures multiprocesseur/multicœur, mais ils peuvent même se manifester avec un seul processeur ! Ils surviennent dès qu'un tiers peut écrire dans la RAM, peu importe que ce soit un autre processeur, un périphérique, un contrôleur DMA, ... Nous avions déjà vu de tels problèmes avec les transferts DMA et les TLBs, mais sans en dire le nom. Par exemple, un transfert DMA modifie des données en RAM, mais pas leurs copies dans le cache. Pareil pour les TLBs : modifier une entrée de la table des pages ne modifie pas sa copie dans la TLB. Et jusqu'ici, ces problèmes de cohérences étaient réglés en invalidant le cache, quand les circonstances l'exigent. Elle est simple à implémenter, pour un cout en performance qui dépend de la fréquence des invalidations. Pour les TLBs, modifier le contenu d'une table des pages est peu fréquent, ce qui fait qu'elles sont des candidats parfaits pour la cohérence par invalidation. La seule difficulté est que toute modification de la table des pages doit entrainer une invalidation des TLBs de tous les processeurs. Pour cela, le système d'exploitation envoie une interruption inter-processeurs spécifique, dont la routine invalide les TLBs. L'interruption est distribuée à tous les processeurs, sans exception, même au processeur envoyeur. Mais pour les caches de données, invalider les caches de données à chaque écriture aurait un cout en performance trop important, vu qu'elles sont écrites très souvent. Pour complémenter l'invalidation des caches, les ingénieurs ont inventé des méthodes alternatives spécifiques aux caches de données. Elles permettent de détecter les données périmées et les mettre à jour. Le tout a été formalisé dans des '''protocoles de cohérence des caches'''. Les protocoles de cohérence des caches marquent les lignes de cache comme invalides, si elles ont une donnée périmée. Les lignes de cache invalides ne peuvent pas être lues ou écrites. Toute lecture/écriture d'une ligne de cache invalide entraine un défaut de cache automatique. Les lignes de caches sont alors mises à jour avec une donnée valide. Le rôle d'un protocole de cohérence des caches est de détecter les copies périmées et de les mettre à jour automatiquement. Les problèmes de cohérence des caches surviennent dès qu'on a plusieurs caches dans une architecture parallèle, sauf pour quelques exceptions. Tout protocole de cohérence des caches doit répondre à plusieurs problèmes. * Premièrement : comment identifier les données invalides ? * Deuxièmement : comment les autres caches sont-ils au courant qu'ils ont une donnée invalide ? * Troisièmement : comment mettre à jour les données invalides ? Les deux derniers problèmes impliquent une forme de communication entre les caches du processeur. Et pour cela, voyons par quel intermédiaire ils communiquent. Pour rappel, les processeurs/cœurs sont connectés entre eux soit avec un bus partagé, soit avec un réseau d'interconnexion assez complexe. Les deux situations n'utilisent pas les mêmes protocoles de cohérence des caches. Par contre, le premier problème implique d'associer un état valide/invalide à chaque ligne de cache, et c'est quelque chose d'indépendant de la communication entre processeurs. Voyons le premier problème avant de passer aux deux autres problèmes. ==Les états d'une ligne de cache : identifier les données invalides== La cohérence des caches détecte les lignes de cache invalides. Pour cela, le protocole de cohérence des caches attribue à chaque ligne de cache un état, qui indique si la ligne de cache contient une donnée périmée, une donnée valide, ou autre. Le '''protocole SI''' est le protocole de cohérence des caches le plus simple qui soit. Il ne gère que deux états : ligne de cache valide, ligne de cache invalide. L'état est encodé avec un '''''bit valid''''', un par ligne de cache, qui indique si la donnée est invalide ou non. Il est vérifié lors de chaque lecture. Voici décrit en image le fonctionnement de ce protocole. Un processeur garde une donnée valide tant qu'aucun autre processeur n'écrit dedans. Si cela arrive, la donnée devient invalide et toute lecture/écriture dedans se fait rejeter, un défaut de cache survient. Le processeur envoie alors une transaction sur le bus pour récupérer une donnée valide. [[File:Diagramme d'état du protocole SI.png|centre|vignette|upright=2|Diagramme d'état du protocole SI.]] Les protocoles plus élaborés ajoutent d'autres états pour des raisons d'optimisation. Les états en question sont encodés sur quelques bits, ajoutés à chaque ligne de cache, dans les bits de contrôle. Voyons ces protocoles plus élaborés dans ce qui suit. ===Le protocole MSI=== La cohérence des caches est très simple quand on a des caches ''write-trough'', mais ces derniers sont à l'origine de beaucoup d'écritures en mémoire qui saturent le bus. Aussi, on a inventé les caches ''Write-back'', où le contenu de la mémoire n'est pas cohérent avec le contenu du cache. Si on écrit dans la mémoire cache, le contenu de la mémoire RAM n'est pas mis à jour. On doit attendre que la donnée sorte du cache pour l'enregistrer en mémoire ou dans les niveaux de caches inférieurs (s'ils existent), ce qui évite de nombreuses écritures mémoires inutiles. Divers protocoles de cohérence des caches existent pour les caches ''Write Back''. Le plus simple d'entre eux est le protocole MSI. Pour simplifier, il permet à des cœurs de réserver en lecture et écriture des lignes de cache, bien que la réservation soit temporaire. Elles utilise pour cela trois états : ''Modified'', ''Shared'' et ''Invalid''. L'état ''Shared'' change et correspond maintenant à une donnée à jour, présente dans plusieurs caches. L'état ''Modified'' correspond à une donnée à jour, mais dont les copies des autres caches sont périmées. L'état ''Invalid'' correspond encore une fois au cas où la donnée présente dans le cache est périmée. Le protocole permet à un cœur de réserver une ligne de cache/donnée temporairement. Tout part d'une ligne de cache en état ''Shared'', c'est à dire accesible en lecture. Elle n'est pas réservée en écriture, mais elle est consultable par un ou plusieurs cœurs. Soit un seul coeur a chargé la donnée dans son cache, soit d'autres coeurs ont une copie de la donnée dans leur cache, peu importe. La seule contrainte est que l'on sait que tous les coeurs ont la même copie de la donnée, la cohérence des caches est donc respectée. Le protocole de cohérence doit faire en sorte de repasser dans l'état ''Shared'' après la moindre violation de cohérence des caches. Le seul évènement capable de violer la cohérence des caches est la survenue d'une ou de plusieurs écritures. Pour qu'une écriture ait lieu, il faut qu'un cœur réserve la donnée en écriture. Pour cela, le ou les cœurs effectuent une écriture, ils tentent d'écrire dans la donnée voulue. S'il est le seul cœur à vouloir écrire à ce moment, il réservera la ligne de cache automatiquement. Mais si d'autres cœurs veulent modifier la donnée en même temps, une compétition avec les autres cœurs pour la réservation et un seul des cœurs gagnera la course. Quoiqu'il en soit, un cœur réservera la donnée et sa copie de la ligne de cache passe de l'état ''Shared'' à l'état ''Modified''. Les autres cœurs voient leur ligne de cache passer de l'état ''Shared'' à l'état ''Invalid''. L'état ''Modified'' signifie que le coeur a réussît à réserver la ligne de cache aussi bien en écriture qu'en lecture. Seul lui peut lire ou écrire la donnée à sa guise. L'état ''Invalid'', quant à lui, sert à deux choses. Premièrement, il prévint qu'un autre cœur a réservé la donnée en écriture, ce qui bloque l'écriture dans la ligne de cache par tout autre cœur. Deuxièmement, il bloque aussi les lectures. Il prévint qu'un autre coeur a modifié la donnée, que la ligne contient actuellement une donnée périmée. Tout accès mémoire à une ligne de cache ''Invalid'' déclenche alors des mesures correctives afin de rétablir la cohérence des caches. Le contenu de la ligne de cache en état ''Modified'' est alors envoyé à tous les autres caches, la cohérence est rétablie, et les lignes de cache passent toutes en état ''Shared'', jusqu’à la prochaine écriture. [[File:Diagramme d'état informel du protocole MSI.png|centre|vignette|upright=2|Diagramme d'état informel du protocole MSI.]] Il est possible de modifier le fonctionnement précédent pour tenir compte d'un cas très spécifique : une écriture dans une ligne de cache marquée ''Invalid''. L'écriture est bloquée pour une ligne de cache en état ''Invalid'', car un autre cœur l'a réservée, elle est bloquée en attendant de recevoir la donnée valide. Mais vu qu'elle sera écrasée de toute manière, autant faire passer la ligne de cache directement en état ''Modified''. L'optimisation est intéressante, mais il faut tenir compte du fait que dans ce cas, on a deux écritures qui se suivent dans le temps, réalisées par deux cœurs, appelons-les cœur 1 et cœur 2. La première écriture, faite par cœur 1, a marqué la ligne de cache comme invalide pour les autres, en ''Modified'' pour lui. La seconde, réalisée par le cœur 2, met sa ligne de cache en état ''Modified'' pour lui, ''Invalid'' pour les autres. Des problèmes de ''race condition'' peuvent survenir, le protocole doit gérer ce genre de cas où deux écritures se suivent de près. Dans ce cas, la dernière écriture doit fournir la donnée valide. La donnée qui était en état ''Modified'' dans le cœur 1 doit passer en état invalide, il perd la réservation en écriture. Le protocole précédent doit donc être adapté de manière à ajouter deux transitions : une de M vers I si un autre processeur écrit la donnée, et I vers M si le processeur écrit la donnée lui-même. [[File:Modified-Shared-Invalid Protokoll.png|centre|vignette|upright=2|Diagramme du protocole MESI. Les abréviations PrRd et PrWr correspondent à des accès mémoire initiés par le processeur associé au cache, respectivement aux lectures et écritures. Les abréviations BusRd et BusRdx et Flush correspondent aux lectures, lectures exclusives ou écritures initiées par d'autres processeurs sur la ligne de cache.]] Notons les trois état M, S et I, pour faire simple. Avec ce système, les lectures sont possibles seulement pour les lignes de cache en état M et S. Toute lecture d'une ligne de cache en état I se termine avec un défaut de cache. Le processeur envoie alors une requête GetS pour récupérer la donnée valide dans les caches d'un autre cœur, une donnée en étant S. L'état I force donc un défaut de cache lors des lectures. Pour les écritures, c'est différent. Les écritures dans une ligne de cache en état M sont automatiquement des succès de cache. Mais les lignes de cache en état S ou I sont traitées autrement. Une telle écriture envoie un signal GetM qui demande à réserver la ligne de cache en écriture. Si la requête est acceptée, la ligne de cache passe en état M, l'écriture est un succès de cache, les autres caches invalident leur copie de la donnée. ===Le protocole MESI=== [[File:Diagrama MESI.GIF|vignette|Diagramme du protocole MESI. Les abréviations PrRd et PrWr correspondent à des accès mémoire initiés par le processeur associé au cache, respectivement aux lectures et écritures. Les abréviations BusRd et BusRdx et Flush correspondent aux lectures, lectures exclusives ou écritures initiées par d'autres processeurs sur la ligne de cache.]] Le protocole MSI n'est pas parfait. Un de ses défauts est que l'état '''Shared'' ne fait pas la différence entre une donnée présente dans un seul cache, et une donnée partagée par plusieurs cœurs. C'est un défaut car toute écriture déclenche des opérations correctives pour gérer la cohérence des caches, qui sont inutiles si un seul cœur a une copie de la donnée. Elles préviennent les autres caches pour rien en cas d'écriture dans une donnée non-partagée. Et les communications sur le bus ne sont pas gratuites. Pour régler ce problème, on peut scinder l'état ''Shared'' en deux états : ''Exclusive'' si les autres processeurs ne possèdent pas de copie de la donnée, ''Shared'' si la donnée est partagée sur plusieurs cœurs. Le '''protocole MESI''' ainsi créé est identique au protocole MSI, avec quelques ajouts. Par exemple, si une donnée est lue la première fois par un cœur, la ligne de cache lue passe soit en ''Exclusive'' (les autres caches n'ont pas de copie de la donnée), soit en ''Shared'' (les autres caches en possèdent déjà une copie). Une donnée marquée ''Exclusive'' peut devenir ''Shared'' si la donnée est chargée dans le cache d'un autre processeur. Comment le processeur fait-il pour savoir si les autres caches ont une copie de la donnée ? Pour cela, il faut ajouter un fil Shared sur le bus, qui sert à dire si un autre cache a une copie de la donnée. Lors de chaque lecture, l'adresse à lire sera envoyée à tous les caches, qui vérifieront s'ils possèdent une copie de la donnée. Une fois le résultat connu, chaque cache fournit un bit qui indique s'il a une copie de la donnée. Le bit Shared est obtenu en effectuant un OU logique entre toutes les versions du bit envoyé par les caches. ===Les protocoles MOSI et MOESI=== Les protocoles MESI et MSI ne permettent pas de transférer des données entre caches sans passer par la mémoire. Si le processeur demande la copie valide d'une donnée, tous les caches ayant la bonne version de la donnée répondent en même temps et la donnée est envoyée en plusieurs exemplaires ! Pour éviter ce problème, on doit rajouter un état supplémentaire : l'état ''Owned''. Si un processeur écrit dans son cache, il mettra sa donnée en Owned, mais les autres caches passeront leur donnée en version Modified, voire Shared une fois la mémoire mise à jour. Ainsi, un seul processeur pourra avoir une donnée dans l'état Owned et c'est lui qui est chargé de répondre aux demandes de mise à jour. [[File:MOSI Processor Transactions.png|vignette|Protocole MOSI, transactions initiées par le processeur associé à la ligne de cache.]] [[File:MOSI Bus Transactions.png|vignette|Protocole MOSI, transactions initiées par les autres processeurs.]] Divers protocoles de cohérences des caches utilisent cet état Owned. Le premier d’entre eux est le '''protocole MOSI''', une variante du MESI où l'état exclusif est remplacé par l'état O. Lors d'une lecture, le cache vérifie si la lecture envoyée sur le bus correspond à une de ses données. Mais cette vérification va prendre du temps, et le processeur va devoir attendre un certain temps. Si au bout d'un certain temps, aucun cache n'a répondu, le processeur postule qu'aucun cache n'a la donnée demandée et va lire la donnée en mémoire. Ce temps est parfois fixé une fois pour toute lors de la création des processeurs, mais il peut aussi être variable, qui est géré comme suit : * pour savoir si un cache contient une copie de la donnée demandée, chaque cache devra répondre en fournissant un bit ; * quand le cache a terminé la vérification, il envoie un 1 sur une sortie spécifique, et un 0 sinon ; * un ET logique est effectué entre tous les bits fournis par les différents caches, et le résultat final indique si tous les caches ont effectué leur vérification. On peut aussi citer le '''protocole MOESI''', un protocole MESI auquel on a jouté l'état O. ==La cohérence des caches par espionnage du bus== L''''espionnage du bus''' est la technique de cohérence du cache la plus simple à comprendre, du moins sur le principe. Aussi, nous allons la voir en premier. Avec elle, les caches sont reliés à bus partagé, qui communique avec les niveaux de cache inférieurs ou avec la RAM. Faisons d'abord un rappel sur ce qu'est ce bus partagé. ===Les bus partagés : rappels et implémentation de la cohérence des caches=== Le premier cas est celui où plusieurs processeurs/cœurs sont connectés à la mémoire RAM à travers un bus partagé. Les processeurs disposent d'une mémoire cache chacun et ils sont tous reliés à la mémoire RAM à travers le bus mémoire. Dans ce cas, tous les caches ont connectés au bus mémoire, qui sert de point de ralliement. Sans cohérence des caches, les communications se font dans le sens ''cache -> mémoire'' ou ''mémoire -> cache''. L'idée est alors de rajouter les communications ''cache- -> cache'' sur le bus mémoire. La mémoire ne répond pas forcément à de telles communications. [[File:Architecture multicoeurs à bus partagé.png|centre|vignette|upright=2|Architecture multicoeurs à bus partagé]] L'idée marche très bien, mais il faut l'adapter sur les processeurs multicœurs, qui ont une hiérarchie de cache assez complexe. Les caches partagés entre tous les cœurs ne posent aucun problème de cohérence car, avec eux, la donnée n'est présente qu'une seule fois dans tout le cache. Par contre, il faut gérer la cohérence entre caches dédiés. [[File:Shared cache coherency.png|centre|vignette|upright=2|Cohérence et caches partagés. Vous remarquerez que sur le schéma, la mémoire RAM contient encore une autre version de la donnée car on utilise un cache ''Write Back'').]] Le cas le plus simple est celui à deux niveaux de caches, avec des caches L1 dédiés et un cache L2 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. Sans cohérence des caches, les transferts sur ce bus se font des caches L1 vers le cache L2 partagé, ou dans l'autre sens. Là encore, l'idée est de faire communiquer les caches L1 via le bus partagé. [[File:Architecture multicoeurs à bus partagé entre caches L1 et L2.png|centre|vignette|upright=2|Architecture multicoeurs à bus partagé entre caches L1 et L2]] Dans ce qui va suivre, quand nous parlerons de bus partagé, cela voudra dire : soit on parle du bus entre le L1 et le L2 sur un processeur multicœur, soit on parle du bus mémoire sur une architecture multi-processeur/multicœurs avec des caches dédiés. L'idée est que ce bus partagé existe avec ou sans cohérence des caches. Sans cohérence, il permet d'échanger des données entre deux niveaux de la hiérarchie mémoire : cache L1 vers L2, caches vers mémoire. Avec cohérence, le bus partagé interconnecte les caches entre eux. ===L'espionnage du bus=== Les protocoles à '''espionnage du bus''' sont des protocoles où les transmissions entre caches se font sur le bus partagé. Le nom qui trahit l'idée qui se cache derrière cette technique : les caches interceptent les écritures sur le bus partagé, qu'elles proviennent ou non des autres processeurs. Quand un cœur/processeur écrit une donnée dans son cache L1, un signal est envoyé sur le bus partagé pour prévenir les autres caches, afin qu'ils invalident la ligne de cache concernée. De plus, il faut aussi mettre à jour les copies en question avec une donnée valide, ce qui passe là-encore par le bus partagé. L'implémentation la plus simple demande juste un bus partagé, avec des modifications des processeurs eux-mêmes. Les processeurs espionnent le bus, c'est précisément le contrôleur mémoire intégré au processeur qui s'en charge. Cependant, de nombreux processeurs multicœurs délèguent l'espionnage du bus au ''chipset'', ou au contrôleur mémoire. un exemple est celui de l'Intel E8870, où l'espionnage du bus est dispersé entre plusieurs circuits distincts. Le ''chipset'' peut relier 16 processeurs entre eux. Les processeurs sont regroupés en groupes de 4 processeurs, l'espionnage du bus se passe dans un groupe de 4 cœurs/processeurs. Un groupe de 4 processeur est relié à un ''Scalable Node Controller'' (SNC), qui est relié à la mémoire RAM. Précisément, chaque SNC contient un contrôleur SDRAM/DDR, on peut le voir comme un controleur mémoire amélioré, qui est capable de gérer l'espionnage du bus. Les 4 SNC sont reliés à un ''Scalability Port Switch'' (SPS), qui permet aux SNC de communiquer entre eux, notamment pour propager les invalidations. Les SPS permettent de distribuer l'espionnage du bus, en utilisant des mécanismes qu'on ne détaillera pas ici. [[File:Intel E8870.png|centre|vignette|upright=2|Intel E8870.]] Les SNC incorporent de nombreuses optimisations : ils sont capables de changer l'ordre des accès mémoire, peuvent mettre en attente des écritures pour 64 lignes de cache (jusqu'à 8 kibioctets d'écritures), les adresses mémoire sont entrelacées, etc. ===La mise à jour et l'invalidation sur écriture=== Voyons d'abord comment la mise à jour des copies se fait. La solution la plus simple pour cela est de propager les écritures dans les niveaux de cache inférieurs, jusqu'à la mémoire. L'écriture est alors transmise sur le bus partagé, les autres caches ont juste à récupérer la donnée et l'adresse écrite. Si l'adresse match une ligne de cache, la ligne de cache est mise à jour immédiatement avec la donnée envoyée sur le bus partagé. Le cache est alors mis à jour immédiatement, la ligne de cache n'a même pas le temps d'être invalidée. On parle alors de '''mise à jour sur écriture'''. Avec elle, les caches sont mis à jour automatiquement le plus tôt possible, il n'y a pas d'invalidation proprement dit. La solution la plus simple pour cela est d'utiliser des caches ''write through'', qui propagent toute écriture dans les niveaux de cache inférieurs, jusqu'à la mémoire. Toute écriture dans une ligne de cache déclenche alors une mise à jour. Mais l'impact sur le débit binaire est alors très important. Aussi, la plupart des processeurs préfèrent utiliser des caches ''write back'' pour gagner en performance. Dans ce cas, les écritures qui sont propagées dépendent de l'état de la ligne de cache écrite. Écrire dans une ligne de cache en état ''Exclusive'' ne déclenchera pas de mise à jour, par exemple. Seules les écritures dans des lignes de cache en état ''Modified'' et ''Shared'' le feront. Mais il est aussi possible de ne pas mettre à jour les lignes de cache à chaque écriture, et de préférer attendre. À la place, l'écriture est remplacée par un ''signal d'invalidation'' qui transmet uniquement l'adresse écrite et n'est pas pris en compte par le cache L2/L3 partagé. Il prévient les autres caches que telle ligne de cache a été modifiée et qu'il faut en invalider les copies. Le cache se contente de marquer la ligne de cache fautive comme invalide, mais ne la met pas à jour. Il y a alors '''invalidation sur écriture'''. La ligne de cache est mise à jour lors d'une lecture/écriture. Tout accès à une ligne de cache invalide entraine un défaut de cache et la donnée est chargée depuis la RAM et/ou depuis un autre cache. Les deux techniques précédentes différent sur un point : la première met à jour la ligne de cache immédiatement, la seconde attend que la ligne de cache soit lue/écrite avant de mettre à jour, elle le fait au dernier moment. Le temps d'accès à une donnée est donc plus long avec ces derniers. Avec la mise à jour sur écriture, la donnée est mise à jour tout de suite, les accès ultérieurs ne déclenchent pas de défaut de cache, la donnée est accessible directement. Le temps d'accès moyen est donc plus faible. Par contre, une partie des mises à jour sont inutiles, car les autres processeurs ne liront pas la donnée ou alors pas tout de suite. Avec l'invalidation, on met à jour les lignes de cache quand la donnée est lue, quand elle est réellement utilisée. Vu qu'une mise à jour est plus gourmande en énergie qu'une simple invalidation, on n'est pas forcément gagnant. Une mise à jour demande un accès complet au cache, avec écriture dans le plan mémoire. Alors d'une invalidation demande simplement de modifier les bits de contrôle d'une ligne de cache. De nos jours, les caches utilisent l'invalidation sur écriture pour des raisons de complexité d'implémentation. Les protocoles à mise à jour sur écriture sont plus complexes à implémenter pour de sombres raisons de consistance mémoire. ===La mise à jour en cas d'invalidation sur écriture=== Avec l'invalidation sur écriture, la mise à jour des lignes de cache se fait séparément de l'invalidation. Et dans ce cas, il faut trouver où se trouve la donnée valide. La donnée valide est présente soit dans le cache d'un autre processeur, soit dans la mémoire RAM. La donnée valide est copiée depuis cette source, c'est une simple transaction mémoire. Voyons ce qu'il en est. Le cas le plus simple est celui où plusieurs processeurs ont un cache chacun et sont reliés à une mémoire partagée. L'implémentation la plus simple lit la donnée valide depuis la RAM. Elle utilise alors des caches de type ''write-through'', où les écritures sont propagées la mémoire RAM. Il est possible de faire la même chose avec un cache ''write-back'', cependant. Mais il doit se comporter comme un cache ''write-through'' en propageant des écritures dans certaines situations. Dès qu'il écrit une ligne de cache en état ''Modified'' ou ''Shared'', il propagera l'écriture dans la RAM. Par contre, s'il a une ligne de cache en état ''Exclusive'', il n'a pas à propager les écritures dans le niveau de cache inférieur, il n'a pas à générer de signal d'invalidation du tout. Une autre solution fait une copie depuis le cache qui contient la donnée valide, sans passer par la mémoire RAM. Les copies entre caches passent par le bus mémoire, la différence étant que la mémoire ne répond pas forcément à des transferts. Les caches étant plus rapides que la RAM, les copies entre caches sont plus rapides qu'un accès en mémoire RAM, même si les deux utilisent le même bus. L'état ''Owned'' permet d'optimiser cette situation : c'est le cache en état ''Owned'' qui répond alors à la requête mémoire. [[File:Cohérence des caches write-through.png|centre|vignette|upright=3|Cohérence des caches ''write-through''.]] Sur les architectures multicœurs, le cache partagé prend la place de la mémoire RAM et le bus partagé celui du bus mémoire. En général, les caches L2 sont inclusifs, à savoir que toute donnée écrite dans les caches L1 est présente dans le cache L2. La donnée valide est donc généralement lue depuis le cache partagé L2/L3. Quand un cache a besoin d'une donnée valide, il envoie une '''requête de mise à jour''', qui demande aux autres caches s'ils ont une donnée valide. La donnée valide est en état ''Modified'' ou ''Owned''. Si un cache a une donnée dans cet état, il répond aux requêtes de mise à jour en envoyant la donnée voulue, éventuellement avec l'adresse. Le cache demandeur reçoit la donnée et met à jour sa ligne de cache. Les autres caches ne tiennent pas forcément compte de cette mise à jour. Du moins, pas avec "invalidation sur écriture" pure, mais certains caches sont un peu plus opportunistes et en profitent pour mettre à jour la ligne de cache au cas où. On peut les voir comme des intermédiaires entre invalidation et mise à jour sur écriture. ==La cohérence des caches à base de répertoires== La cohérence des caches à base de répertoire utilise un '''répertoire''', qui mémorise l'état de chaque ligne de cache, mais aussi quel processeur dispose de telle ou telle ligne de cache. Les processeurs envoient des requêtes au répertoire avant chaque accès au cache. Le répertoire va alors soit répondre favorablement et autoriser l'accès au cache, soit l'interdire. Une réponse favorable signifie que le processeur a une donnée valide, une réponse défavorable signifie que la ligne de cache accédée doit être mise à jour. La cohérence des caches est donc gérée par le répertoire, qui est centralisé. L'usage d'un répertoire est la norme sur les architectures NUMA, avec plusieurs ordinateurs reliés entre eux. Avec l'arrivée des processeurs multicœurs avec une hiérarchie de cache, les architectures à mémoire partagée se sont mises à s'inspirer des protocoles à répertoire pour gagner en performance. L'usage de caches de répertoire a permis d'utiliser la technique sur les processeurs multicœurs normaux, en complément de l'espionnage du bus. ===L'usage des répertoires sur les architectures NUMA=== Plusieurs architectures différentes utilisent une cohérence des caches par répertoire. Leur usage le plus intuitif est celui des architectures NUMA, avec plusieurs ordinateurs reliés entre eux via réseau. Sur les architectures NUMA, une donnée lue depuis la RAM d'un autre ordinateur peut être mise en mémoire cache. Et la moindre modification d'une copie doit être propagée via réseau sur les autres ordinateurs. Autant dire que la cohérence des caches est assez compliquée sur de telles architectures. Avec elles, chaque ordinateur a une copie du répertoire, pour gérer les données provenant des mémoires des autres ordinateurs. [[File:Cohérence des cache à répertoire.jpg|centre|vignette|upright=2.5|Cohérence des caches - Répertoire décentralisé.]] Les répertoires sont utilisés sur les architectures NUMA. Rappelons que sur de telles architectures, chaque processeur a une mémoire dédiée, mais qu'il a accès à toutes les mémoires de l'architecture à travers un réseau local. La mémoire dédiée au processeur est appelée la ''mémoire locale'', alors que celles des autres processeurs sont appelées les ''mémoires distantes''. Une partie de l'espace d'adressage est associé à la mémoire locale, mais le reste de l'espace d'adressage est associé aux mémoires distantes. Quand le processeur veut lire/écrire dans sa mémoire locale, le répertoire n'est pas consulté. Mais quand il veut lire/écrire en dehors, le répertoire est consulté pour gérer la cohérence des caches. [[File:Espace d'adressage d'une architecture NUMA.png|centre|vignette|upright=2|Espace d'adressage d'une architecture NUMA.]] Voyons maintenant comment le tout fonctionne. Nous allons prendre l'architecture illustrée ci-dessous. Elle contient plusieurs ordinateurs, chacun avec une mémoire locale reliée à un ou plusieurs processeurs multicœur aux hiérarchies de caches complexes. Nous n'allons pas nous intéresser aux caches L1/L2/L3, mais allons nous concentrer sur la mémoire cache L4, partagée entre plusieurs processeurs. Il s'agit d'une mémoire cache spécialisée dans les accès aux mémoires distantes. Elle contient des données provenant des mémoires distantes, qui ont été chargées lors d'accès antérieurs. Nous allons l'appeler le '''cache distant''' pour simplifier les explications. Les accès aux mémoires distantes se font via le réseau local d'interconnexion, mais le résultat des lectures est mémorisé dans le cache distant, ce qui évite de faire un accès réseau à chaque lecture/écriture. Le répertoire est consulté pour tout accès au cache distant, mais n'est pas consulté pour l'accès aux caches L1/L2/L3 gérés par espionnage de bus. Il l'est seulement quand un ordinateur veut accéder aux données d'une mémoire distante, lors d'un accès en dehors de l'espace d'adressage de la mémoire locale. [[File:Cc-NUMA System.svg|centre|vignette|upright=3|Architecture Ccc-NUMA.]] Pour comprendre comment le répertoire et le cache L4 sont utilisés, partons du principe qu'un processeur veuille lire une donnée située dans une mémoire distante. Le cache L4 ne contient pas la donnée en question, ce qui déclenche un défaut de cache. Une transaction réseau est alors démarrée, pour rapatrier la donnée dans le cache L4. Le rapatriement peut simplement lire la donnée dans la mémoire principale si elle n'a pas été cachée, mais elle peut aussi aller la chercher dans les caches du processeur distant si nécessaire (comme illustré ci-dessous). Les répertoires sont alors mis à jour sur tous les ordinateurs. Le répertoire permet de désigner quel processeur a une copie de la donnée voulue, et d'aller la chercher sans demander à tous les processeurs. [[File:Cc-NUMA Remote Memory Read.svg|centre|vignette|upright=2|Architecture Ccc-NUMA, lecture dans une mémoire distante.]] Intuitivement, on se dit que les futurs accès à cette donnée rapatriée se font dans le cache L4. Mais dans les faits, la donnée peut être copiée dans le cache L3/L2, voire L1 du processeur local. Maintenant, imaginons que le processeur local modifie cette donnée distante. Les protocoles de cohérence des caches par espionnage de bus propagent un signal d'invalidation jusque dans le cache L4. Il faut ensuite propager le signal d'invalidation aux autres ordinateurs qui manipulent cette donnée. Pour cela, le répertoire est consulté pour récupérer la liste des processeurs qui ont cette donnée dans leur cache. Le signal d'invalidation est ensuite transmis par le réseau d'interconnexion, et arrive aux destinataires, qui invalident la donnée dans leurs caches. [[File:Cc-NUMA Local Memory Read.svg|centre|vignette|upright=2|Architecture Ccc-NUMA, invalidation dans une mémoire distante.]] ===L'usage de répertoire sur les architectures multiprocesseurs et multicœurs=== L'espionnage de bus est simple à implémenter. Mais il a un défaut assez flagrant : les signaux d'invalidation et les requêtes de mise à jour passent par le bus partagé, idem pour les réponses à des signaux/requêtes. Le trafic sur le bus partagé est donc augmenté, assez fortement. Et au-delà de quelques processeurs, le trafic est trop important. L'usage d'état ''Owned'' et ''Exclusive'' améliore la situation, mais pas de quoi faire des miracles. Le problème de l'espionnage de bus est que les signaux et requêtes sont envoyés à tout le monde, grâce à l'usage d'un bus partagé. Et un bus partagé est une forme assez rudimentaire d'interconnexion, qui devient inefficace dès que le nombre de composants à connecter dessus est trop important. De nos jours, les processeurs multicœurs récents remplacent partiellement le bus partagé par un '''réseau d'interconnexion intra-processeur''' plus complexe qu'un simple bus. De même, les cartes mères multi-processeurs incorporent un '''réseau d'interconnexion inter-processeur''', placé sur la carte mère, pour connecter les processeurs, la mémoire et les autres entrées-sorties. Le tout est illustré ci-dessous et vous remarquerez que le tout ressemble un peu à une architecture NUMA mais sans mémoire RAM, la RAM étant accédée via le réseau d'interconnexion comme l'est le GPU ou un cœur/processeur. Mais cela ne fait pas grande différence, car l'essentiel est que les mémoires caches soient là, de même que le réseau d'interconnexion. Les systèmes multicœurs/multiprocesseurs utilisent l'espionnage du bus à l'intérieur d'un cœur/processeur, mais utilisent la cohérence basée sur un répertoire entre les processeurs/cœurs. [[File:Cohérence des caches avec un répertoire centralisé.png|centre|vignette|upright=2|Cohérence des caches avec un répertoire sur une architecture multicœurs.]] Dans ce qui suit, le réseau d'interconnexions entre processeurs/cœurs sera volontairement laissé vague, car il peut être absolument n'importe quoi : un bus partagé, un réseau en anneau, un réseau ''crossbar'', etc. Par contre, il doit donner l'illusion que chaque cache est connecté à tous les autres via un ensemble de liaisons point-à-point. Il n'y a pas une transmission à la fois, plusieurs transmissions entre processeurs/cœurs peuvent avoir lieu en même temps. Les signaux d'invalidation sont envoyés uniquement aux processeurs/cœurs qui ont une copie de la donnée, pas à tous. Idem pour les requêtes de mise à jour, envoyées seulement au cache qui a une copie valide de la donnée. De fait, les transmissions pour la cohérence peuvent se faire en même temps que d'autres lectures/écritures normales, ce qui fait meilleur usage du débit mémoire. Prenons comme exemple la situation du schéma précédent, où chaque cœur dispose d'un seul cache dédié, et voyons comment est gérée la cohérence des caches sur un tel processeur. Tout écriture dans le cache dédié entraine l'émission d'un signal d'invalidation pour préciser que la ligne de cache a été modifiée. L'envoi du signal d'invalidation est cependant géré par le répertoire, qui décide quels cœurs prévenir. Le répertoire configure alors le réseau d'interconnexion pour connecter entre eux les caches qui doivent l'être, pour propager les signaux d'invalidation et les requêtes de mise à jour. Les autres caches sont laissés libres et sont disponibles pour des lectures et écritures. On économise alors du débit binaire, au prix d'une perte en temps d'accès liée à l'interrogation du répertoire. Prenons ensuite le cas d'une lecture dans un cache dédié, illustré ci-dessous. Si un défaut de cache a lieu, alors la ligne de cache n'est pas disponible dans le cache dédié. Elle doit alors être rappatriée depuis la mémoire RAM dans le pire des cas, ou depuis un autre cache. Pour savoir dans quel cas il est, le cœur interroge le répertoire. Il sait si la ligne mémoire est cachée ou non, et dans quel cache elle se trouve si elle l'est. Le répertoire démarre alors une requête de lecture au cache adéquat, via une transaction réseau. Le cache adéquat répond à la transaction par une autre transaction réseau, à destination du cœur qui a déclenché le défaut de cache. Là encore, les trois requêtes sont envoyées uniquement aux cœurs/caches qui en ont besoin. [[File:Directory Scheme.png|centre|vignette|upright=2.5|Cohérence des caches avec un répertoire centralisé.]] ===Le contenu du répertoire : les implémentations à base de RAM et de caches=== Avant de poursuivre, un point de terminologie. Imaginez que la mémoire est découpée en blocs qui font la même taille qu'une ligne de cache, et qui sont alignés sur cette taille. Un bloc peut être copié dans le cache, éventuellement écrit par le processeur, et rapatrié en mémoire RAM une fois évincé du cache. Le bloc de mémoire sera appelé dans ce qui suit une '''ligne mémoire''', par analogie avec une ligne de cache. Un répertoire mémorise, pour chaque ligne mémoire, la liste des processeurs dont le cache qui en a une copie. Il peut aussi mémoriser l'état de la ligne de cache associée, mais ce n'est pas obligatoire, l'état peut être stocké dans la ligne de cache elle-même. ====Les répertoires basés sur une mémoire RAM==== L'implémentation la plus simple mémorise, pour chaque ligne mémoire, quel processeur l'a copié dans son cache. Pour cela, elle utilise un '''bit de présence''' par processeur, qui indique si le cache du processeur a une copie de la ligne mémoire : le énième bit de présence indique si le énième processeur a la ligne mémoire dans son cache. Elle a pour défaut de rapidement faire grossir le répertoire, dont la taille est proportionnelle au nombre de processeurs et de ligne mémoire. [[File:Full bit vector format diagram.jpg|centre|vignette|upright=2|Full bit vector format diagram]] Une autre solution mémorise la liste des processeurs autrement. Au lieu d'utiliser un bit par processeur, elle mémorise une '''liste de pointeurs''' vers ces processeurs. Elle attribue un numéro à chaque processeur et mémorise une liste de plusieurs numéros. L'avantage est que les numéros sont assez courts. Au lieu d'utiliser N bits pour N processeurs, chaque numéro ne fait que <math>\log_2{N}</math>. On gagne en mémoire si on autorise C copies, et que <math>C \times \log_2{N} \leq N</math>. Un exemple sera sans doute plus parlant. Prenons 256 processeurs. Un répertoire complet demandera 256 bits. Une liste de pointeurs encodera un numéro de processeur sur 8 bits, on est gagnant tant qu'on a moins de 256/8 = 32 processeurs par ligne de cache. Par contre, la technique n'autorise qu'un nombre maximal de numéros par ligne mémoire. Si ce nombre est dépassé, le répertoire doit gérer la situation. Et il y a plusieurs solutions possibles pour cela. La première n'autorise réellement que N copies d'une même ligne de cache et invalide les copies en trop si le nombre est dépassé. Le cout en performance est cependant élevé. Les deux autres solutions autorisent à dépasser le nombre maximal de numéros. La première se débrouille en repassant en mode ''broadcast'' une fois le nombre de numéro maximal dépassé. Le répertoire envoie alors toute invalidation de la ligne mémoire concernée à tous les processeurs, vu que le répertoire n'a pas les moyens de savoir qui a une copie valide ou non. Une autre solution déclenche une exception matérielle qui gère la situation en logiciel. {|class="wikitable" |+ Répertoire complet et à liste de pointeur |- ! ! Adresse ! État ! Liste des processeurs |- ! Représentation complète | rowspan="2" | 0xFFF1244 | rowspan="2" | ''Shared'' | 0001 1000 0001 1100 |- ! Liste de pointeurs | 3, 4, 5 12, 13 |} Les deux méthodes précédentes posent problème quand le nombre de processeur est élevé. Aussi, quelques optimisations permettent de limiter la casse. La première consiste à ne pas encoder toutes les informations nécessaires. Une idée possible est par exemple de regrouper les caches/processeurs par groupes de 2/3/4. Le répertoire mémorise alors quel groupe de cache/processeur contient une copie de la donnée, mais pas exactement quel cache dans ce groupe. Par exemple, avec des groupes de 2, il se peut qu'un processeur du groupe ait une copie de donnée, ou les deux, le répertoire ne fait pas la différence. Si la donnée est invalidée, il envoie des signaux d'invalidation aux deux processeurs du groupe. [[File:Coarse bit vector format diagram.jpg|centre|vignette|upright=2|Coarse bit vector format diagram]] Les répertoires vus plus haut sont basés sur une mémoire RAM. Elle contiennent une adresse par le ligne mémoire, l'adresse contient l'état de la ligne de cache et la liste des processeurs. La consultation du répertoire demande juste d'adresser le répertoire avec l'adresse de la ligne mémoire, ce qui peut être fait avant ou en parallèle de l'accès au cache. Mais le problème est que le répertoire est alors une mémoire très grosse. Elle est d'autant plus grosse qu'il y a de lignes mémoires et elle devient rapidement impraticable dès que la mémoire est un peu grosse. ====Les répertoires basés sur une mémoire cache==== Le répertoire est donc une structure assez grosse, ce qui est un problème. En pratique, les répertoires précédents sont tellement gros qu'on ne peut pas leur dédier une mémoire RAM. des tables qui sont mémorisées en mémoire RAM. Quelques processeurs ont réussi à le faire, notamment les premiers SGI Origin, qui avaient une banque de mémoire dédiée au répertoire. Mais la majeure partie des implémentations devaient placer le répertoire dans la mémoire RAM principale de l'ordinateur, un peu au même titre que la table des pages. Mais quoi bon avoir des caches si leur accès demande de consulter un répertoire en mémoire RAM ? Et pourtant, nous avons déjà vu une situation similaire. Il est possible de faire une analogie avec la table des pages, encore que celle-ci soit grandement limitée. La table des pages est là aussi une structure très grosse, censée être consultée à chaque accès mémoire, qui mémorise des informations pour chaque page mémoire. Le répertoire est une structure similaire : remplacez ligne mémoire par page mémoire et vous aurez l'idée. Et vous vous souvenez certainement de la solution utilisée, à savoir l'usage de caches de traduction d'adresse, les fameuses TLBs. Les caches en question sont appelés des '''caches de répertoire'''. Ils ont plusieurs entrées, mais moins qu'il n'y a de lignes mémoire. Une entrée peut être vide ou occupée. Une entrée occupée mémorise de quoi gérer la cohérence pour une ligne mémoire. Elle mémorise l'adresse de la ligne mémoire, l'état de la ligne et la liste des processeurs. Le cache de répertoire mémorise une partie du répertoire, celle en cours d'utilisation. L'implémentation la plus simple conserve un répertoire en mémoire RAM, complété par un cache de répertoire par processeur. Les caches de répertoire sont consultés à chaque écriture, ce qui n'est pas un problème vu qu'ils sont très petits et ont un temps d'accès minuscule. Il y a plusieurs caches de répertoire, avec plusieurs niveaux de cache. Typiquement, il y a un cache de répertoire L1 associé au cache L1 de données, un cache de répertoire L2 pour le cache de données L2, etc. Il faut cependant remarquer que le répertoire est une structure dont la majorité des entrées sont vides. En effet, les seules entrées occupées correspondent aux lignes mémoires présentes dans le cache du processeur. Il n'y a pas besoin de mémoriser autant d'entrées qu'il y a de lignes mémoires, seulement une entrée par ligne de cache. Cette simplification donne un répertoire très petit, dans lequel on a éliminé les entrées vides. Il s'agit d'une optimisation évident à laquelle vous aviez peut-être déjà pensé. Le tout est nommé avec le nom de ''inclusive directory cache'', que nous traduirons par '''cache de répertoire inclusif'''. Il existe deux implémentations possibles de ce cache de répertoire inclusif. La première place le répertoire dans un cache dédié, séparé des autres caches, associé au contrôleur mémoire. Le fonctionnement est alors le suivant. Pour toutes les lignes mémoires dans le cache, le cache de répertoire possède une entrée associée, qui mémorise une copie ''tag'' de la ligne de cache et la liste des processeurs. Le ''tag'' en question n'est autre que le ''tag'' utilisé dans le cache L1 (si on suppose que le L2 est partagé). Si jamais la ligne mémoire n'est pas trouvée dans ce cache, alors on suppose qu'elle est en état ''Invalid'', ou qu'elle n'a pas été chargée depuis la mémoire. Les actions correctives sont les mêmes dans les deux cas. Un défaut est que lorsqu'une ligne de cache est évincée du cache L1, le répertoire doit être prévenu, ce qui ajoute de la complexité. En théorie, le cache devrait être un cache associatif par voie, avec un grand nombre de voies pour gérer des accès simultannés. Une autre implémentation utilise des caches L1/L2 inclusifs. Ainsi, le répertoire a juste à mémoriser les lignes mémoires dans le cache partagé L2/L3. Mieux : il a juste à mémoriser la liste des processeurs dans la ligne de cache elle-même ! L'implémentation précédente recopiait les ''tag'' dans le répertoire, ce qui les dupliquait. Mais on n'avait pas le choix, car il fallait regrouper les ''tags'' des différents L1 dans un répertoire unique, on ne pouvait pas avoir un répertoire dispersé dans plusieurs caches L1. Mais avec des caches inclusifs, faire pareil avec les lignes de cache du L3 serait de la duplication inutile. Alors on fusionne le cache partagé L2/L3 avec le cache de répertoire. La difficulté est alors de maintenir des caches inclusifs, ce qui est plus compliqué que prévu. Une autre solution consiste à mémoriser la liste des copies dans les caches eux-mêmes. Le répertoire n'identifie, pour chaque ligne de cache, qu'un seul processeur : plus précisément, il identifie la ligne de cache du processeur qui contient la copie. À l'intérieur d'une ligne de cache, la copie suivante (si elle existe) est indiquée dans les bits de contrôle. On parle de répertoires à base de caches. [[File:Répertoire à base de caches.jpg|centre|vignette|upright=2|Répertoire à base de caches]] ==Les avantages et inconvénients des deux méthodes== L'avantage de l'espionnage du bus est qu'il utilise peu de circuits et qu'il est facile à implémenter, car il réutilise un bus partagé qui est déjà là. Par contre, son désavantage majeur est que les écritures dans un cache sont propagées sur le bus partagé, au moins partiellement. Soit les écritures sont réellement propagées sur le bus partagée, soit un message d'invalidation est envoyé sur le bus partagé, peu importe : un message est envoyé aux autres caches pour dire qu'une écriture a eu lieu et qu'il faut potentiellement invalider des données. Le débit binaire du bus partagé est donc partiellement grignoté par les communications entre caches. Et le désavantage est d'autant plus grand qu'il y a de coeurs/processeurs, qui se partagent le bus partagé. L'usage d'un répertoire résout ces problèmes. Le débit binaire du bus partagé n'est pas grignoté, car la liaison entre caches et répertoires est séparée. Par contre, le temps d'accès au cache est augmenté, car tout accès mémoire demande l'autorisation au répertoire. En soi, le problème est compensé par l'économie en débit binaire. Sur les architectures avec beaucoup de processeurs, le gain en débit binaire sur-compense la hausse du temps d'accès. Mais sur les architectures avec peu de cœurs, c'est l'inverse. En général, les architectures distribuées/NUMA utilisent des répertoires, alors que les architectures à mémoire partagée utilisent l'espionnage du bus. Le tout est résumé ci-dessous. {|class="wikitable" style="text-align:center;" |- ! ! Mémoire partagée ! Architectures NUMA ! Architecture distribuée |- ! Invalidation du cache | colspan="3" | Caches d'instruction et TLB |- ! Espionnage du bus | X | | |- ! Répertoire de cohérence | X | X | X |} Remarquez que l'espionnage du bus n'a de sens que sur les architectures à mémoire partagée, alors que l'usage de répertoire est plus générale. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les architectures à parallélisme de données | next=Les sections critiques et le modèle mémoire }} </noinclude> hsll5q0k4g7wzikume18u6bqckc231j 764830 764829 2026-04-24T11:56:01Z Mewtow 31375 /* L'espionnage du bus */ 764830 wikitext text/x-wiki Il est possible d'utiliser des caches avec la mémoire partagée, mais aussi sur les architectures distribuées et les architectures NUMA. Néanmoins, la gestion des caches peut poser des problèmes dits de '''cohérence des caches''' quand une donnée est présente dans plusieurs caches distincts. [[File:Cohérence des caches.png|vignette|upright=1|Cohérence des caches]] Introduisons la cohérence des caches par un exemple. Prenons deux cœurs/processeurs qui ont chacun une copie d'une donnée dans leur cache. Si un processeur modifie sa copie de la donnée, l'autre ne sera pas mise à jour. L'autre processeur manipule donc une donnée périmée : il n'y a pas '''cohérence des caches''', sous-entendu cohérence entre le contenu des différents caches. [[File:Non Coherent.gif|centre|vignette|upright=2.5|Caches non-cohérents.]] Or, il faut éviter cela sous peine d'avoir des problèmes. Mais avoir des caches cohérents demande d'avoir une valeur à jour de la donnée dans l'ensemble de ses caches, ce qui implique que les écritures dans un cache doivent être propagées dans les caches des autres processeurs. Il est possible de rendre des caches cohérents avec diverses méthodes qu'on va voir dans ce chapitre. Les deux animations ci-dessous montrent l'exemple de caches non-cohérents et de caches cohérents. [[File:Coherent.gif|centre|vignette|upright=2.5|Caches cohérents.]] Les problèmes de cohérence des caches se manifestent sur toutes les architectures multiprocesseur/multicœur, mais ils peuvent même se manifester avec un seul processeur ! Ils surviennent dès qu'un tiers peut écrire dans la RAM, peu importe que ce soit un autre processeur, un périphérique, un contrôleur DMA, ... Nous avions déjà vu de tels problèmes avec les transferts DMA et les TLBs, mais sans en dire le nom. Par exemple, un transfert DMA modifie des données en RAM, mais pas leurs copies dans le cache. Pareil pour les TLBs : modifier une entrée de la table des pages ne modifie pas sa copie dans la TLB. Et jusqu'ici, ces problèmes de cohérences étaient réglés en invalidant le cache, quand les circonstances l'exigent. Elle est simple à implémenter, pour un cout en performance qui dépend de la fréquence des invalidations. Pour les TLBs, modifier le contenu d'une table des pages est peu fréquent, ce qui fait qu'elles sont des candidats parfaits pour la cohérence par invalidation. La seule difficulté est que toute modification de la table des pages doit entrainer une invalidation des TLBs de tous les processeurs. Pour cela, le système d'exploitation envoie une interruption inter-processeurs spécifique, dont la routine invalide les TLBs. L'interruption est distribuée à tous les processeurs, sans exception, même au processeur envoyeur. Mais pour les caches de données, invalider les caches de données à chaque écriture aurait un cout en performance trop important, vu qu'elles sont écrites très souvent. Pour complémenter l'invalidation des caches, les ingénieurs ont inventé des méthodes alternatives spécifiques aux caches de données. Elles permettent de détecter les données périmées et les mettre à jour. Le tout a été formalisé dans des '''protocoles de cohérence des caches'''. Les protocoles de cohérence des caches marquent les lignes de cache comme invalides, si elles ont une donnée périmée. Les lignes de cache invalides ne peuvent pas être lues ou écrites. Toute lecture/écriture d'une ligne de cache invalide entraine un défaut de cache automatique. Les lignes de caches sont alors mises à jour avec une donnée valide. Le rôle d'un protocole de cohérence des caches est de détecter les copies périmées et de les mettre à jour automatiquement. Les problèmes de cohérence des caches surviennent dès qu'on a plusieurs caches dans une architecture parallèle, sauf pour quelques exceptions. Tout protocole de cohérence des caches doit répondre à plusieurs problèmes. * Premièrement : comment identifier les données invalides ? * Deuxièmement : comment les autres caches sont-ils au courant qu'ils ont une donnée invalide ? * Troisièmement : comment mettre à jour les données invalides ? Les deux derniers problèmes impliquent une forme de communication entre les caches du processeur. Et pour cela, voyons par quel intermédiaire ils communiquent. Pour rappel, les processeurs/cœurs sont connectés entre eux soit avec un bus partagé, soit avec un réseau d'interconnexion assez complexe. Les deux situations n'utilisent pas les mêmes protocoles de cohérence des caches. Par contre, le premier problème implique d'associer un état valide/invalide à chaque ligne de cache, et c'est quelque chose d'indépendant de la communication entre processeurs. Voyons le premier problème avant de passer aux deux autres problèmes. ==Les états d'une ligne de cache : identifier les données invalides== La cohérence des caches détecte les lignes de cache invalides. Pour cela, le protocole de cohérence des caches attribue à chaque ligne de cache un état, qui indique si la ligne de cache contient une donnée périmée, une donnée valide, ou autre. Le '''protocole SI''' est le protocole de cohérence des caches le plus simple qui soit. Il ne gère que deux états : ligne de cache valide, ligne de cache invalide. L'état est encodé avec un '''''bit valid''''', un par ligne de cache, qui indique si la donnée est invalide ou non. Il est vérifié lors de chaque lecture. Voici décrit en image le fonctionnement de ce protocole. Un processeur garde une donnée valide tant qu'aucun autre processeur n'écrit dedans. Si cela arrive, la donnée devient invalide et toute lecture/écriture dedans se fait rejeter, un défaut de cache survient. Le processeur envoie alors une transaction sur le bus pour récupérer une donnée valide. [[File:Diagramme d'état du protocole SI.png|centre|vignette|upright=2|Diagramme d'état du protocole SI.]] Les protocoles plus élaborés ajoutent d'autres états pour des raisons d'optimisation. Les états en question sont encodés sur quelques bits, ajoutés à chaque ligne de cache, dans les bits de contrôle. Voyons ces protocoles plus élaborés dans ce qui suit. ===Le protocole MSI=== La cohérence des caches est très simple quand on a des caches ''write-trough'', mais ces derniers sont à l'origine de beaucoup d'écritures en mémoire qui saturent le bus. Aussi, on a inventé les caches ''Write-back'', où le contenu de la mémoire n'est pas cohérent avec le contenu du cache. Si on écrit dans la mémoire cache, le contenu de la mémoire RAM n'est pas mis à jour. On doit attendre que la donnée sorte du cache pour l'enregistrer en mémoire ou dans les niveaux de caches inférieurs (s'ils existent), ce qui évite de nombreuses écritures mémoires inutiles. Divers protocoles de cohérence des caches existent pour les caches ''Write Back''. Le plus simple d'entre eux est le protocole MSI. Pour simplifier, il permet à des cœurs de réserver en lecture et écriture des lignes de cache, bien que la réservation soit temporaire. Elles utilise pour cela trois états : ''Modified'', ''Shared'' et ''Invalid''. L'état ''Shared'' change et correspond maintenant à une donnée à jour, présente dans plusieurs caches. L'état ''Modified'' correspond à une donnée à jour, mais dont les copies des autres caches sont périmées. L'état ''Invalid'' correspond encore une fois au cas où la donnée présente dans le cache est périmée. Le protocole permet à un cœur de réserver une ligne de cache/donnée temporairement. Tout part d'une ligne de cache en état ''Shared'', c'est à dire accesible en lecture. Elle n'est pas réservée en écriture, mais elle est consultable par un ou plusieurs cœurs. Soit un seul coeur a chargé la donnée dans son cache, soit d'autres coeurs ont une copie de la donnée dans leur cache, peu importe. La seule contrainte est que l'on sait que tous les coeurs ont la même copie de la donnée, la cohérence des caches est donc respectée. Le protocole de cohérence doit faire en sorte de repasser dans l'état ''Shared'' après la moindre violation de cohérence des caches. Le seul évènement capable de violer la cohérence des caches est la survenue d'une ou de plusieurs écritures. Pour qu'une écriture ait lieu, il faut qu'un cœur réserve la donnée en écriture. Pour cela, le ou les cœurs effectuent une écriture, ils tentent d'écrire dans la donnée voulue. S'il est le seul cœur à vouloir écrire à ce moment, il réservera la ligne de cache automatiquement. Mais si d'autres cœurs veulent modifier la donnée en même temps, une compétition avec les autres cœurs pour la réservation et un seul des cœurs gagnera la course. Quoiqu'il en soit, un cœur réservera la donnée et sa copie de la ligne de cache passe de l'état ''Shared'' à l'état ''Modified''. Les autres cœurs voient leur ligne de cache passer de l'état ''Shared'' à l'état ''Invalid''. L'état ''Modified'' signifie que le coeur a réussît à réserver la ligne de cache aussi bien en écriture qu'en lecture. Seul lui peut lire ou écrire la donnée à sa guise. L'état ''Invalid'', quant à lui, sert à deux choses. Premièrement, il prévint qu'un autre cœur a réservé la donnée en écriture, ce qui bloque l'écriture dans la ligne de cache par tout autre cœur. Deuxièmement, il bloque aussi les lectures. Il prévint qu'un autre coeur a modifié la donnée, que la ligne contient actuellement une donnée périmée. Tout accès mémoire à une ligne de cache ''Invalid'' déclenche alors des mesures correctives afin de rétablir la cohérence des caches. Le contenu de la ligne de cache en état ''Modified'' est alors envoyé à tous les autres caches, la cohérence est rétablie, et les lignes de cache passent toutes en état ''Shared'', jusqu’à la prochaine écriture. [[File:Diagramme d'état informel du protocole MSI.png|centre|vignette|upright=2|Diagramme d'état informel du protocole MSI.]] Il est possible de modifier le fonctionnement précédent pour tenir compte d'un cas très spécifique : une écriture dans une ligne de cache marquée ''Invalid''. L'écriture est bloquée pour une ligne de cache en état ''Invalid'', car un autre cœur l'a réservée, elle est bloquée en attendant de recevoir la donnée valide. Mais vu qu'elle sera écrasée de toute manière, autant faire passer la ligne de cache directement en état ''Modified''. L'optimisation est intéressante, mais il faut tenir compte du fait que dans ce cas, on a deux écritures qui se suivent dans le temps, réalisées par deux cœurs, appelons-les cœur 1 et cœur 2. La première écriture, faite par cœur 1, a marqué la ligne de cache comme invalide pour les autres, en ''Modified'' pour lui. La seconde, réalisée par le cœur 2, met sa ligne de cache en état ''Modified'' pour lui, ''Invalid'' pour les autres. Des problèmes de ''race condition'' peuvent survenir, le protocole doit gérer ce genre de cas où deux écritures se suivent de près. Dans ce cas, la dernière écriture doit fournir la donnée valide. La donnée qui était en état ''Modified'' dans le cœur 1 doit passer en état invalide, il perd la réservation en écriture. Le protocole précédent doit donc être adapté de manière à ajouter deux transitions : une de M vers I si un autre processeur écrit la donnée, et I vers M si le processeur écrit la donnée lui-même. [[File:Modified-Shared-Invalid Protokoll.png|centre|vignette|upright=2|Diagramme du protocole MESI. Les abréviations PrRd et PrWr correspondent à des accès mémoire initiés par le processeur associé au cache, respectivement aux lectures et écritures. Les abréviations BusRd et BusRdx et Flush correspondent aux lectures, lectures exclusives ou écritures initiées par d'autres processeurs sur la ligne de cache.]] Notons les trois état M, S et I, pour faire simple. Avec ce système, les lectures sont possibles seulement pour les lignes de cache en état M et S. Toute lecture d'une ligne de cache en état I se termine avec un défaut de cache. Le processeur envoie alors une requête GetS pour récupérer la donnée valide dans les caches d'un autre cœur, une donnée en étant S. L'état I force donc un défaut de cache lors des lectures. Pour les écritures, c'est différent. Les écritures dans une ligne de cache en état M sont automatiquement des succès de cache. Mais les lignes de cache en état S ou I sont traitées autrement. Une telle écriture envoie un signal GetM qui demande à réserver la ligne de cache en écriture. Si la requête est acceptée, la ligne de cache passe en état M, l'écriture est un succès de cache, les autres caches invalident leur copie de la donnée. ===Le protocole MESI=== [[File:Diagrama MESI.GIF|vignette|Diagramme du protocole MESI. Les abréviations PrRd et PrWr correspondent à des accès mémoire initiés par le processeur associé au cache, respectivement aux lectures et écritures. Les abréviations BusRd et BusRdx et Flush correspondent aux lectures, lectures exclusives ou écritures initiées par d'autres processeurs sur la ligne de cache.]] Le protocole MSI n'est pas parfait. Un de ses défauts est que l'état '''Shared'' ne fait pas la différence entre une donnée présente dans un seul cache, et une donnée partagée par plusieurs cœurs. C'est un défaut car toute écriture déclenche des opérations correctives pour gérer la cohérence des caches, qui sont inutiles si un seul cœur a une copie de la donnée. Elles préviennent les autres caches pour rien en cas d'écriture dans une donnée non-partagée. Et les communications sur le bus ne sont pas gratuites. Pour régler ce problème, on peut scinder l'état ''Shared'' en deux états : ''Exclusive'' si les autres processeurs ne possèdent pas de copie de la donnée, ''Shared'' si la donnée est partagée sur plusieurs cœurs. Le '''protocole MESI''' ainsi créé est identique au protocole MSI, avec quelques ajouts. Par exemple, si une donnée est lue la première fois par un cœur, la ligne de cache lue passe soit en ''Exclusive'' (les autres caches n'ont pas de copie de la donnée), soit en ''Shared'' (les autres caches en possèdent déjà une copie). Une donnée marquée ''Exclusive'' peut devenir ''Shared'' si la donnée est chargée dans le cache d'un autre processeur. Comment le processeur fait-il pour savoir si les autres caches ont une copie de la donnée ? Pour cela, il faut ajouter un fil Shared sur le bus, qui sert à dire si un autre cache a une copie de la donnée. Lors de chaque lecture, l'adresse à lire sera envoyée à tous les caches, qui vérifieront s'ils possèdent une copie de la donnée. Une fois le résultat connu, chaque cache fournit un bit qui indique s'il a une copie de la donnée. Le bit Shared est obtenu en effectuant un OU logique entre toutes les versions du bit envoyé par les caches. ===Les protocoles MOSI et MOESI=== Les protocoles MESI et MSI ne permettent pas de transférer des données entre caches sans passer par la mémoire. Si le processeur demande la copie valide d'une donnée, tous les caches ayant la bonne version de la donnée répondent en même temps et la donnée est envoyée en plusieurs exemplaires ! Pour éviter ce problème, on doit rajouter un état supplémentaire : l'état ''Owned''. Si un processeur écrit dans son cache, il mettra sa donnée en Owned, mais les autres caches passeront leur donnée en version Modified, voire Shared une fois la mémoire mise à jour. Ainsi, un seul processeur pourra avoir une donnée dans l'état Owned et c'est lui qui est chargé de répondre aux demandes de mise à jour. [[File:MOSI Processor Transactions.png|vignette|Protocole MOSI, transactions initiées par le processeur associé à la ligne de cache.]] [[File:MOSI Bus Transactions.png|vignette|Protocole MOSI, transactions initiées par les autres processeurs.]] Divers protocoles de cohérences des caches utilisent cet état Owned. Le premier d’entre eux est le '''protocole MOSI''', une variante du MESI où l'état exclusif est remplacé par l'état O. Lors d'une lecture, le cache vérifie si la lecture envoyée sur le bus correspond à une de ses données. Mais cette vérification va prendre du temps, et le processeur va devoir attendre un certain temps. Si au bout d'un certain temps, aucun cache n'a répondu, le processeur postule qu'aucun cache n'a la donnée demandée et va lire la donnée en mémoire. Ce temps est parfois fixé une fois pour toute lors de la création des processeurs, mais il peut aussi être variable, qui est géré comme suit : * pour savoir si un cache contient une copie de la donnée demandée, chaque cache devra répondre en fournissant un bit ; * quand le cache a terminé la vérification, il envoie un 1 sur une sortie spécifique, et un 0 sinon ; * un ET logique est effectué entre tous les bits fournis par les différents caches, et le résultat final indique si tous les caches ont effectué leur vérification. On peut aussi citer le '''protocole MOESI''', un protocole MESI auquel on a jouté l'état O. ==La cohérence des caches par espionnage du bus== L''''espionnage du bus''' est la technique de cohérence du cache la plus simple à comprendre, du moins sur le principe. Aussi, nous allons la voir en premier. Avec elle, les caches sont reliés à bus partagé, qui communique avec les niveaux de cache inférieurs ou avec la RAM. Faisons d'abord un rappel sur ce qu'est ce bus partagé. ===Les bus partagés : rappels et implémentation de la cohérence des caches=== Le premier cas est celui où plusieurs processeurs/cœurs sont connectés à la mémoire RAM à travers un bus partagé. Les processeurs disposent d'une mémoire cache chacun et ils sont tous reliés à la mémoire RAM à travers le bus mémoire. Dans ce cas, tous les caches ont connectés au bus mémoire, qui sert de point de ralliement. Sans cohérence des caches, les communications se font dans le sens ''cache -> mémoire'' ou ''mémoire -> cache''. L'idée est alors de rajouter les communications ''cache- -> cache'' sur le bus mémoire. La mémoire ne répond pas forcément à de telles communications. [[File:Architecture multicoeurs à bus partagé.png|centre|vignette|upright=2|Architecture multicoeurs à bus partagé]] L'idée marche très bien, mais il faut l'adapter sur les processeurs multicœurs, qui ont une hiérarchie de cache assez complexe. Les caches partagés entre tous les cœurs ne posent aucun problème de cohérence car, avec eux, la donnée n'est présente qu'une seule fois dans tout le cache. Par contre, il faut gérer la cohérence entre caches dédiés. [[File:Shared cache coherency.png|centre|vignette|upright=2|Cohérence et caches partagés. Vous remarquerez que sur le schéma, la mémoire RAM contient encore une autre version de la donnée car on utilise un cache ''Write Back'').]] Le cas le plus simple est celui à deux niveaux de caches, avec des caches L1 dédiés et un cache L2 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. Sans cohérence des caches, les transferts sur ce bus se font des caches L1 vers le cache L2 partagé, ou dans l'autre sens. Là encore, l'idée est de faire communiquer les caches L1 via le bus partagé. [[File:Architecture multicoeurs à bus partagé entre caches L1 et L2.png|centre|vignette|upright=2|Architecture multicoeurs à bus partagé entre caches L1 et L2]] Dans ce qui va suivre, quand nous parlerons de bus partagé, cela voudra dire : soit on parle du bus entre le L1 et le L2 sur un processeur multicœur, soit on parle du bus mémoire sur une architecture multi-processeur/multicœurs avec des caches dédiés. L'idée est que ce bus partagé existe avec ou sans cohérence des caches. Sans cohérence, il permet d'échanger des données entre deux niveaux de la hiérarchie mémoire : cache L1 vers L2, caches vers mémoire. Avec cohérence, le bus partagé interconnecte les caches entre eux. ===L'espionnage du bus=== Les protocoles à '''espionnage du bus''' sont des protocoles où les transmissions entre caches se font sur le bus partagé. Le nom qui trahit l'idée qui se cache derrière cette technique : les caches interceptent les écritures sur le bus partagé, qu'elles proviennent ou non des autres processeurs. Quand un cœur/processeur écrit une donnée dans son cache L1, un signal est envoyé sur le bus partagé pour prévenir les autres caches, afin qu'ils invalident la ligne de cache concernée. De plus, il faut aussi mettre à jour les copies en question avec une donnée valide, ce qui passe là-encore par le bus partagé. L'implémentation la plus simple demande juste un bus partagé, avec des modifications des processeurs eux-mêmes. Les processeurs espionnent le bus, c'est précisément le contrôleur mémoire intégré au processeur qui s'en charge. Cependant, de nombreux processeurs multicœurs délèguent l'espionnage du bus au ''chipset'', ou au contrôleur mémoire. Un exemple est celui de l'Intel E8870, où l'espionnage du bus est géré par un circuit séparé, qui n'est pas connecté directement au bus partagé. Le circuit d'espionnage du bus est appelé le ''Scalability Port Switch'' (SPS). Il y en a deux dans le ''chipset'', qui communiquent entre eux, mais passons sur ce détail. Avec cette organisation, les accès mémoire passent par le contrôleur mémoire, qui les envoie au SPS, puis à la RAM si besoin. Le SPS gère alors la cohérence des caches, et détecte quand des invalidations doivent être propagées. Le ''chipset'' peut relier 16 processeurs entre eux. Les processeurs sont regroupés en groupes de 4 processeurs, chaque groupe étant associé à un ''Scalable Node Controller'' (SNC), lui-même relié à la mémoire RAM. Un SNC est un contrôleur SDRAM/DDR amélioré. Les SNC incorporent de nombreuses optimisations : ils sont capables de changer l'ordre des accès mémoire, peuvent mettre en attente des écritures pour 64 lignes de cache (jusqu'à 8 kibioctets d'écritures), les adresses mémoire sont entrelacées, etc. [[File:Intel E8870.png|centre|vignette|upright=2|Intel E8870.]] Ce système permet d'implémenter des optimisations pour les lectures. Normalement, le processeur envoie la lecture sur le bus partagé, et il attend de voir si un autre cache répond. Si aucun cache ne répond, c'est un défaut de cache total, et le contrôleur mémoire lit la donnée en RAM. L'intel E8870 démarre l'accès en RAM avant qu'un autre cache ne réponde. La vérification des autres caches se passe donc en même temps que l'accès mémoire. Si aucun cache ne répond, l'accès aura été lancé en avance. Si un cache répond, il a juste à annuler la lecture entamée. Si celle-ci a été mise en attente dans le contrôleur mémoire, l'annulation n'entrainera pas de lecture sur le bus mémoire. Sinon, la lecture en RAM s'exécutera, mais la donnée lue sera juste ignorée. ===La mise à jour et l'invalidation sur écriture=== Voyons d'abord comment la mise à jour des copies se fait. La solution la plus simple pour cela est de propager les écritures dans les niveaux de cache inférieurs, jusqu'à la mémoire. L'écriture est alors transmise sur le bus partagé, les autres caches ont juste à récupérer la donnée et l'adresse écrite. Si l'adresse match une ligne de cache, la ligne de cache est mise à jour immédiatement avec la donnée envoyée sur le bus partagé. Le cache est alors mis à jour immédiatement, la ligne de cache n'a même pas le temps d'être invalidée. On parle alors de '''mise à jour sur écriture'''. Avec elle, les caches sont mis à jour automatiquement le plus tôt possible, il n'y a pas d'invalidation proprement dit. La solution la plus simple pour cela est d'utiliser des caches ''write through'', qui propagent toute écriture dans les niveaux de cache inférieurs, jusqu'à la mémoire. Toute écriture dans une ligne de cache déclenche alors une mise à jour. Mais l'impact sur le débit binaire est alors très important. Aussi, la plupart des processeurs préfèrent utiliser des caches ''write back'' pour gagner en performance. Dans ce cas, les écritures qui sont propagées dépendent de l'état de la ligne de cache écrite. Écrire dans une ligne de cache en état ''Exclusive'' ne déclenchera pas de mise à jour, par exemple. Seules les écritures dans des lignes de cache en état ''Modified'' et ''Shared'' le feront. Mais il est aussi possible de ne pas mettre à jour les lignes de cache à chaque écriture, et de préférer attendre. À la place, l'écriture est remplacée par un ''signal d'invalidation'' qui transmet uniquement l'adresse écrite et n'est pas pris en compte par le cache L2/L3 partagé. Il prévient les autres caches que telle ligne de cache a été modifiée et qu'il faut en invalider les copies. Le cache se contente de marquer la ligne de cache fautive comme invalide, mais ne la met pas à jour. Il y a alors '''invalidation sur écriture'''. La ligne de cache est mise à jour lors d'une lecture/écriture. Tout accès à une ligne de cache invalide entraine un défaut de cache et la donnée est chargée depuis la RAM et/ou depuis un autre cache. Les deux techniques précédentes différent sur un point : la première met à jour la ligne de cache immédiatement, la seconde attend que la ligne de cache soit lue/écrite avant de mettre à jour, elle le fait au dernier moment. Le temps d'accès à une donnée est donc plus long avec ces derniers. Avec la mise à jour sur écriture, la donnée est mise à jour tout de suite, les accès ultérieurs ne déclenchent pas de défaut de cache, la donnée est accessible directement. Le temps d'accès moyen est donc plus faible. Par contre, une partie des mises à jour sont inutiles, car les autres processeurs ne liront pas la donnée ou alors pas tout de suite. Avec l'invalidation, on met à jour les lignes de cache quand la donnée est lue, quand elle est réellement utilisée. Vu qu'une mise à jour est plus gourmande en énergie qu'une simple invalidation, on n'est pas forcément gagnant. Une mise à jour demande un accès complet au cache, avec écriture dans le plan mémoire. Alors d'une invalidation demande simplement de modifier les bits de contrôle d'une ligne de cache. De nos jours, les caches utilisent l'invalidation sur écriture pour des raisons de complexité d'implémentation. Les protocoles à mise à jour sur écriture sont plus complexes à implémenter pour de sombres raisons de consistance mémoire. ===La mise à jour en cas d'invalidation sur écriture=== Avec l'invalidation sur écriture, la mise à jour des lignes de cache se fait séparément de l'invalidation. Et dans ce cas, il faut trouver où se trouve la donnée valide. La donnée valide est présente soit dans le cache d'un autre processeur, soit dans la mémoire RAM. La donnée valide est copiée depuis cette source, c'est une simple transaction mémoire. Voyons ce qu'il en est. Le cas le plus simple est celui où plusieurs processeurs ont un cache chacun et sont reliés à une mémoire partagée. L'implémentation la plus simple lit la donnée valide depuis la RAM. Elle utilise alors des caches de type ''write-through'', où les écritures sont propagées la mémoire RAM. Il est possible de faire la même chose avec un cache ''write-back'', cependant. Mais il doit se comporter comme un cache ''write-through'' en propageant des écritures dans certaines situations. Dès qu'il écrit une ligne de cache en état ''Modified'' ou ''Shared'', il propagera l'écriture dans la RAM. Par contre, s'il a une ligne de cache en état ''Exclusive'', il n'a pas à propager les écritures dans le niveau de cache inférieur, il n'a pas à générer de signal d'invalidation du tout. Une autre solution fait une copie depuis le cache qui contient la donnée valide, sans passer par la mémoire RAM. Les copies entre caches passent par le bus mémoire, la différence étant que la mémoire ne répond pas forcément à des transferts. Les caches étant plus rapides que la RAM, les copies entre caches sont plus rapides qu'un accès en mémoire RAM, même si les deux utilisent le même bus. L'état ''Owned'' permet d'optimiser cette situation : c'est le cache en état ''Owned'' qui répond alors à la requête mémoire. [[File:Cohérence des caches write-through.png|centre|vignette|upright=3|Cohérence des caches ''write-through''.]] Sur les architectures multicœurs, le cache partagé prend la place de la mémoire RAM et le bus partagé celui du bus mémoire. En général, les caches L2 sont inclusifs, à savoir que toute donnée écrite dans les caches L1 est présente dans le cache L2. La donnée valide est donc généralement lue depuis le cache partagé L2/L3. Quand un cache a besoin d'une donnée valide, il envoie une '''requête de mise à jour''', qui demande aux autres caches s'ils ont une donnée valide. La donnée valide est en état ''Modified'' ou ''Owned''. Si un cache a une donnée dans cet état, il répond aux requêtes de mise à jour en envoyant la donnée voulue, éventuellement avec l'adresse. Le cache demandeur reçoit la donnée et met à jour sa ligne de cache. Les autres caches ne tiennent pas forcément compte de cette mise à jour. Du moins, pas avec "invalidation sur écriture" pure, mais certains caches sont un peu plus opportunistes et en profitent pour mettre à jour la ligne de cache au cas où. On peut les voir comme des intermédiaires entre invalidation et mise à jour sur écriture. ==La cohérence des caches à base de répertoires== La cohérence des caches à base de répertoire utilise un '''répertoire''', qui mémorise l'état de chaque ligne de cache, mais aussi quel processeur dispose de telle ou telle ligne de cache. Les processeurs envoient des requêtes au répertoire avant chaque accès au cache. Le répertoire va alors soit répondre favorablement et autoriser l'accès au cache, soit l'interdire. Une réponse favorable signifie que le processeur a une donnée valide, une réponse défavorable signifie que la ligne de cache accédée doit être mise à jour. La cohérence des caches est donc gérée par le répertoire, qui est centralisé. L'usage d'un répertoire est la norme sur les architectures NUMA, avec plusieurs ordinateurs reliés entre eux. Avec l'arrivée des processeurs multicœurs avec une hiérarchie de cache, les architectures à mémoire partagée se sont mises à s'inspirer des protocoles à répertoire pour gagner en performance. L'usage de caches de répertoire a permis d'utiliser la technique sur les processeurs multicœurs normaux, en complément de l'espionnage du bus. ===L'usage des répertoires sur les architectures NUMA=== Plusieurs architectures différentes utilisent une cohérence des caches par répertoire. Leur usage le plus intuitif est celui des architectures NUMA, avec plusieurs ordinateurs reliés entre eux via réseau. Sur les architectures NUMA, une donnée lue depuis la RAM d'un autre ordinateur peut être mise en mémoire cache. Et la moindre modification d'une copie doit être propagée via réseau sur les autres ordinateurs. Autant dire que la cohérence des caches est assez compliquée sur de telles architectures. Avec elles, chaque ordinateur a une copie du répertoire, pour gérer les données provenant des mémoires des autres ordinateurs. [[File:Cohérence des cache à répertoire.jpg|centre|vignette|upright=2.5|Cohérence des caches - Répertoire décentralisé.]] Les répertoires sont utilisés sur les architectures NUMA. Rappelons que sur de telles architectures, chaque processeur a une mémoire dédiée, mais qu'il a accès à toutes les mémoires de l'architecture à travers un réseau local. La mémoire dédiée au processeur est appelée la ''mémoire locale'', alors que celles des autres processeurs sont appelées les ''mémoires distantes''. Une partie de l'espace d'adressage est associé à la mémoire locale, mais le reste de l'espace d'adressage est associé aux mémoires distantes. Quand le processeur veut lire/écrire dans sa mémoire locale, le répertoire n'est pas consulté. Mais quand il veut lire/écrire en dehors, le répertoire est consulté pour gérer la cohérence des caches. [[File:Espace d'adressage d'une architecture NUMA.png|centre|vignette|upright=2|Espace d'adressage d'une architecture NUMA.]] Voyons maintenant comment le tout fonctionne. Nous allons prendre l'architecture illustrée ci-dessous. Elle contient plusieurs ordinateurs, chacun avec une mémoire locale reliée à un ou plusieurs processeurs multicœur aux hiérarchies de caches complexes. Nous n'allons pas nous intéresser aux caches L1/L2/L3, mais allons nous concentrer sur la mémoire cache L4, partagée entre plusieurs processeurs. Il s'agit d'une mémoire cache spécialisée dans les accès aux mémoires distantes. Elle contient des données provenant des mémoires distantes, qui ont été chargées lors d'accès antérieurs. Nous allons l'appeler le '''cache distant''' pour simplifier les explications. Les accès aux mémoires distantes se font via le réseau local d'interconnexion, mais le résultat des lectures est mémorisé dans le cache distant, ce qui évite de faire un accès réseau à chaque lecture/écriture. Le répertoire est consulté pour tout accès au cache distant, mais n'est pas consulté pour l'accès aux caches L1/L2/L3 gérés par espionnage de bus. Il l'est seulement quand un ordinateur veut accéder aux données d'une mémoire distante, lors d'un accès en dehors de l'espace d'adressage de la mémoire locale. [[File:Cc-NUMA System.svg|centre|vignette|upright=3|Architecture Ccc-NUMA.]] Pour comprendre comment le répertoire et le cache L4 sont utilisés, partons du principe qu'un processeur veuille lire une donnée située dans une mémoire distante. Le cache L4 ne contient pas la donnée en question, ce qui déclenche un défaut de cache. Une transaction réseau est alors démarrée, pour rapatrier la donnée dans le cache L4. Le rapatriement peut simplement lire la donnée dans la mémoire principale si elle n'a pas été cachée, mais elle peut aussi aller la chercher dans les caches du processeur distant si nécessaire (comme illustré ci-dessous). Les répertoires sont alors mis à jour sur tous les ordinateurs. Le répertoire permet de désigner quel processeur a une copie de la donnée voulue, et d'aller la chercher sans demander à tous les processeurs. [[File:Cc-NUMA Remote Memory Read.svg|centre|vignette|upright=2|Architecture Ccc-NUMA, lecture dans une mémoire distante.]] Intuitivement, on se dit que les futurs accès à cette donnée rapatriée se font dans le cache L4. Mais dans les faits, la donnée peut être copiée dans le cache L3/L2, voire L1 du processeur local. Maintenant, imaginons que le processeur local modifie cette donnée distante. Les protocoles de cohérence des caches par espionnage de bus propagent un signal d'invalidation jusque dans le cache L4. Il faut ensuite propager le signal d'invalidation aux autres ordinateurs qui manipulent cette donnée. Pour cela, le répertoire est consulté pour récupérer la liste des processeurs qui ont cette donnée dans leur cache. Le signal d'invalidation est ensuite transmis par le réseau d'interconnexion, et arrive aux destinataires, qui invalident la donnée dans leurs caches. [[File:Cc-NUMA Local Memory Read.svg|centre|vignette|upright=2|Architecture Ccc-NUMA, invalidation dans une mémoire distante.]] ===L'usage de répertoire sur les architectures multiprocesseurs et multicœurs=== L'espionnage de bus est simple à implémenter. Mais il a un défaut assez flagrant : les signaux d'invalidation et les requêtes de mise à jour passent par le bus partagé, idem pour les réponses à des signaux/requêtes. Le trafic sur le bus partagé est donc augmenté, assez fortement. Et au-delà de quelques processeurs, le trafic est trop important. L'usage d'état ''Owned'' et ''Exclusive'' améliore la situation, mais pas de quoi faire des miracles. Le problème de l'espionnage de bus est que les signaux et requêtes sont envoyés à tout le monde, grâce à l'usage d'un bus partagé. Et un bus partagé est une forme assez rudimentaire d'interconnexion, qui devient inefficace dès que le nombre de composants à connecter dessus est trop important. De nos jours, les processeurs multicœurs récents remplacent partiellement le bus partagé par un '''réseau d'interconnexion intra-processeur''' plus complexe qu'un simple bus. De même, les cartes mères multi-processeurs incorporent un '''réseau d'interconnexion inter-processeur''', placé sur la carte mère, pour connecter les processeurs, la mémoire et les autres entrées-sorties. Le tout est illustré ci-dessous et vous remarquerez que le tout ressemble un peu à une architecture NUMA mais sans mémoire RAM, la RAM étant accédée via le réseau d'interconnexion comme l'est le GPU ou un cœur/processeur. Mais cela ne fait pas grande différence, car l'essentiel est que les mémoires caches soient là, de même que le réseau d'interconnexion. Les systèmes multicœurs/multiprocesseurs utilisent l'espionnage du bus à l'intérieur d'un cœur/processeur, mais utilisent la cohérence basée sur un répertoire entre les processeurs/cœurs. [[File:Cohérence des caches avec un répertoire centralisé.png|centre|vignette|upright=2|Cohérence des caches avec un répertoire sur une architecture multicœurs.]] Dans ce qui suit, le réseau d'interconnexions entre processeurs/cœurs sera volontairement laissé vague, car il peut être absolument n'importe quoi : un bus partagé, un réseau en anneau, un réseau ''crossbar'', etc. Par contre, il doit donner l'illusion que chaque cache est connecté à tous les autres via un ensemble de liaisons point-à-point. Il n'y a pas une transmission à la fois, plusieurs transmissions entre processeurs/cœurs peuvent avoir lieu en même temps. Les signaux d'invalidation sont envoyés uniquement aux processeurs/cœurs qui ont une copie de la donnée, pas à tous. Idem pour les requêtes de mise à jour, envoyées seulement au cache qui a une copie valide de la donnée. De fait, les transmissions pour la cohérence peuvent se faire en même temps que d'autres lectures/écritures normales, ce qui fait meilleur usage du débit mémoire. Prenons comme exemple la situation du schéma précédent, où chaque cœur dispose d'un seul cache dédié, et voyons comment est gérée la cohérence des caches sur un tel processeur. Tout écriture dans le cache dédié entraine l'émission d'un signal d'invalidation pour préciser que la ligne de cache a été modifiée. L'envoi du signal d'invalidation est cependant géré par le répertoire, qui décide quels cœurs prévenir. Le répertoire configure alors le réseau d'interconnexion pour connecter entre eux les caches qui doivent l'être, pour propager les signaux d'invalidation et les requêtes de mise à jour. Les autres caches sont laissés libres et sont disponibles pour des lectures et écritures. On économise alors du débit binaire, au prix d'une perte en temps d'accès liée à l'interrogation du répertoire. Prenons ensuite le cas d'une lecture dans un cache dédié, illustré ci-dessous. Si un défaut de cache a lieu, alors la ligne de cache n'est pas disponible dans le cache dédié. Elle doit alors être rappatriée depuis la mémoire RAM dans le pire des cas, ou depuis un autre cache. Pour savoir dans quel cas il est, le cœur interroge le répertoire. Il sait si la ligne mémoire est cachée ou non, et dans quel cache elle se trouve si elle l'est. Le répertoire démarre alors une requête de lecture au cache adéquat, via une transaction réseau. Le cache adéquat répond à la transaction par une autre transaction réseau, à destination du cœur qui a déclenché le défaut de cache. Là encore, les trois requêtes sont envoyées uniquement aux cœurs/caches qui en ont besoin. [[File:Directory Scheme.png|centre|vignette|upright=2.5|Cohérence des caches avec un répertoire centralisé.]] ===Le contenu du répertoire : les implémentations à base de RAM et de caches=== Avant de poursuivre, un point de terminologie. Imaginez que la mémoire est découpée en blocs qui font la même taille qu'une ligne de cache, et qui sont alignés sur cette taille. Un bloc peut être copié dans le cache, éventuellement écrit par le processeur, et rapatrié en mémoire RAM une fois évincé du cache. Le bloc de mémoire sera appelé dans ce qui suit une '''ligne mémoire''', par analogie avec une ligne de cache. Un répertoire mémorise, pour chaque ligne mémoire, la liste des processeurs dont le cache qui en a une copie. Il peut aussi mémoriser l'état de la ligne de cache associée, mais ce n'est pas obligatoire, l'état peut être stocké dans la ligne de cache elle-même. ====Les répertoires basés sur une mémoire RAM==== L'implémentation la plus simple mémorise, pour chaque ligne mémoire, quel processeur l'a copié dans son cache. Pour cela, elle utilise un '''bit de présence''' par processeur, qui indique si le cache du processeur a une copie de la ligne mémoire : le énième bit de présence indique si le énième processeur a la ligne mémoire dans son cache. Elle a pour défaut de rapidement faire grossir le répertoire, dont la taille est proportionnelle au nombre de processeurs et de ligne mémoire. [[File:Full bit vector format diagram.jpg|centre|vignette|upright=2|Full bit vector format diagram]] Une autre solution mémorise la liste des processeurs autrement. Au lieu d'utiliser un bit par processeur, elle mémorise une '''liste de pointeurs''' vers ces processeurs. Elle attribue un numéro à chaque processeur et mémorise une liste de plusieurs numéros. L'avantage est que les numéros sont assez courts. Au lieu d'utiliser N bits pour N processeurs, chaque numéro ne fait que <math>\log_2{N}</math>. On gagne en mémoire si on autorise C copies, et que <math>C \times \log_2{N} \leq N</math>. Un exemple sera sans doute plus parlant. Prenons 256 processeurs. Un répertoire complet demandera 256 bits. Une liste de pointeurs encodera un numéro de processeur sur 8 bits, on est gagnant tant qu'on a moins de 256/8 = 32 processeurs par ligne de cache. Par contre, la technique n'autorise qu'un nombre maximal de numéros par ligne mémoire. Si ce nombre est dépassé, le répertoire doit gérer la situation. Et il y a plusieurs solutions possibles pour cela. La première n'autorise réellement que N copies d'une même ligne de cache et invalide les copies en trop si le nombre est dépassé. Le cout en performance est cependant élevé. Les deux autres solutions autorisent à dépasser le nombre maximal de numéros. La première se débrouille en repassant en mode ''broadcast'' une fois le nombre de numéro maximal dépassé. Le répertoire envoie alors toute invalidation de la ligne mémoire concernée à tous les processeurs, vu que le répertoire n'a pas les moyens de savoir qui a une copie valide ou non. Une autre solution déclenche une exception matérielle qui gère la situation en logiciel. {|class="wikitable" |+ Répertoire complet et à liste de pointeur |- ! ! Adresse ! État ! Liste des processeurs |- ! Représentation complète | rowspan="2" | 0xFFF1244 | rowspan="2" | ''Shared'' | 0001 1000 0001 1100 |- ! Liste de pointeurs | 3, 4, 5 12, 13 |} Les deux méthodes précédentes posent problème quand le nombre de processeur est élevé. Aussi, quelques optimisations permettent de limiter la casse. La première consiste à ne pas encoder toutes les informations nécessaires. Une idée possible est par exemple de regrouper les caches/processeurs par groupes de 2/3/4. Le répertoire mémorise alors quel groupe de cache/processeur contient une copie de la donnée, mais pas exactement quel cache dans ce groupe. Par exemple, avec des groupes de 2, il se peut qu'un processeur du groupe ait une copie de donnée, ou les deux, le répertoire ne fait pas la différence. Si la donnée est invalidée, il envoie des signaux d'invalidation aux deux processeurs du groupe. [[File:Coarse bit vector format diagram.jpg|centre|vignette|upright=2|Coarse bit vector format diagram]] Les répertoires vus plus haut sont basés sur une mémoire RAM. Elle contiennent une adresse par le ligne mémoire, l'adresse contient l'état de la ligne de cache et la liste des processeurs. La consultation du répertoire demande juste d'adresser le répertoire avec l'adresse de la ligne mémoire, ce qui peut être fait avant ou en parallèle de l'accès au cache. Mais le problème est que le répertoire est alors une mémoire très grosse. Elle est d'autant plus grosse qu'il y a de lignes mémoires et elle devient rapidement impraticable dès que la mémoire est un peu grosse. ====Les répertoires basés sur une mémoire cache==== Le répertoire est donc une structure assez grosse, ce qui est un problème. En pratique, les répertoires précédents sont tellement gros qu'on ne peut pas leur dédier une mémoire RAM. des tables qui sont mémorisées en mémoire RAM. Quelques processeurs ont réussi à le faire, notamment les premiers SGI Origin, qui avaient une banque de mémoire dédiée au répertoire. Mais la majeure partie des implémentations devaient placer le répertoire dans la mémoire RAM principale de l'ordinateur, un peu au même titre que la table des pages. Mais quoi bon avoir des caches si leur accès demande de consulter un répertoire en mémoire RAM ? Et pourtant, nous avons déjà vu une situation similaire. Il est possible de faire une analogie avec la table des pages, encore que celle-ci soit grandement limitée. La table des pages est là aussi une structure très grosse, censée être consultée à chaque accès mémoire, qui mémorise des informations pour chaque page mémoire. Le répertoire est une structure similaire : remplacez ligne mémoire par page mémoire et vous aurez l'idée. Et vous vous souvenez certainement de la solution utilisée, à savoir l'usage de caches de traduction d'adresse, les fameuses TLBs. Les caches en question sont appelés des '''caches de répertoire'''. Ils ont plusieurs entrées, mais moins qu'il n'y a de lignes mémoire. Une entrée peut être vide ou occupée. Une entrée occupée mémorise de quoi gérer la cohérence pour une ligne mémoire. Elle mémorise l'adresse de la ligne mémoire, l'état de la ligne et la liste des processeurs. Le cache de répertoire mémorise une partie du répertoire, celle en cours d'utilisation. L'implémentation la plus simple conserve un répertoire en mémoire RAM, complété par un cache de répertoire par processeur. Les caches de répertoire sont consultés à chaque écriture, ce qui n'est pas un problème vu qu'ils sont très petits et ont un temps d'accès minuscule. Il y a plusieurs caches de répertoire, avec plusieurs niveaux de cache. Typiquement, il y a un cache de répertoire L1 associé au cache L1 de données, un cache de répertoire L2 pour le cache de données L2, etc. Il faut cependant remarquer que le répertoire est une structure dont la majorité des entrées sont vides. En effet, les seules entrées occupées correspondent aux lignes mémoires présentes dans le cache du processeur. Il n'y a pas besoin de mémoriser autant d'entrées qu'il y a de lignes mémoires, seulement une entrée par ligne de cache. Cette simplification donne un répertoire très petit, dans lequel on a éliminé les entrées vides. Il s'agit d'une optimisation évident à laquelle vous aviez peut-être déjà pensé. Le tout est nommé avec le nom de ''inclusive directory cache'', que nous traduirons par '''cache de répertoire inclusif'''. Il existe deux implémentations possibles de ce cache de répertoire inclusif. La première place le répertoire dans un cache dédié, séparé des autres caches, associé au contrôleur mémoire. Le fonctionnement est alors le suivant. Pour toutes les lignes mémoires dans le cache, le cache de répertoire possède une entrée associée, qui mémorise une copie ''tag'' de la ligne de cache et la liste des processeurs. Le ''tag'' en question n'est autre que le ''tag'' utilisé dans le cache L1 (si on suppose que le L2 est partagé). Si jamais la ligne mémoire n'est pas trouvée dans ce cache, alors on suppose qu'elle est en état ''Invalid'', ou qu'elle n'a pas été chargée depuis la mémoire. Les actions correctives sont les mêmes dans les deux cas. Un défaut est que lorsqu'une ligne de cache est évincée du cache L1, le répertoire doit être prévenu, ce qui ajoute de la complexité. En théorie, le cache devrait être un cache associatif par voie, avec un grand nombre de voies pour gérer des accès simultannés. Une autre implémentation utilise des caches L1/L2 inclusifs. Ainsi, le répertoire a juste à mémoriser les lignes mémoires dans le cache partagé L2/L3. Mieux : il a juste à mémoriser la liste des processeurs dans la ligne de cache elle-même ! L'implémentation précédente recopiait les ''tag'' dans le répertoire, ce qui les dupliquait. Mais on n'avait pas le choix, car il fallait regrouper les ''tags'' des différents L1 dans un répertoire unique, on ne pouvait pas avoir un répertoire dispersé dans plusieurs caches L1. Mais avec des caches inclusifs, faire pareil avec les lignes de cache du L3 serait de la duplication inutile. Alors on fusionne le cache partagé L2/L3 avec le cache de répertoire. La difficulté est alors de maintenir des caches inclusifs, ce qui est plus compliqué que prévu. Une autre solution consiste à mémoriser la liste des copies dans les caches eux-mêmes. Le répertoire n'identifie, pour chaque ligne de cache, qu'un seul processeur : plus précisément, il identifie la ligne de cache du processeur qui contient la copie. À l'intérieur d'une ligne de cache, la copie suivante (si elle existe) est indiquée dans les bits de contrôle. On parle de répertoires à base de caches. [[File:Répertoire à base de caches.jpg|centre|vignette|upright=2|Répertoire à base de caches]] ==Les avantages et inconvénients des deux méthodes== L'avantage de l'espionnage du bus est qu'il utilise peu de circuits et qu'il est facile à implémenter, car il réutilise un bus partagé qui est déjà là. Par contre, son désavantage majeur est que les écritures dans un cache sont propagées sur le bus partagé, au moins partiellement. Soit les écritures sont réellement propagées sur le bus partagée, soit un message d'invalidation est envoyé sur le bus partagé, peu importe : un message est envoyé aux autres caches pour dire qu'une écriture a eu lieu et qu'il faut potentiellement invalider des données. Le débit binaire du bus partagé est donc partiellement grignoté par les communications entre caches. Et le désavantage est d'autant plus grand qu'il y a de coeurs/processeurs, qui se partagent le bus partagé. L'usage d'un répertoire résout ces problèmes. Le débit binaire du bus partagé n'est pas grignoté, car la liaison entre caches et répertoires est séparée. Par contre, le temps d'accès au cache est augmenté, car tout accès mémoire demande l'autorisation au répertoire. En soi, le problème est compensé par l'économie en débit binaire. Sur les architectures avec beaucoup de processeurs, le gain en débit binaire sur-compense la hausse du temps d'accès. Mais sur les architectures avec peu de cœurs, c'est l'inverse. En général, les architectures distribuées/NUMA utilisent des répertoires, alors que les architectures à mémoire partagée utilisent l'espionnage du bus. Le tout est résumé ci-dessous. {|class="wikitable" style="text-align:center;" |- ! ! Mémoire partagée ! Architectures NUMA ! Architecture distribuée |- ! Invalidation du cache | colspan="3" | Caches d'instruction et TLB |- ! Espionnage du bus | X | | |- ! Répertoire de cohérence | X | X | X |} Remarquez que l'espionnage du bus n'a de sens que sur les architectures à mémoire partagée, alors que l'usage de répertoire est plus générale. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les architectures à parallélisme de données | next=Les sections critiques et le modèle mémoire }} </noinclude> 3pjcgh3uhdy8ssvd2w0z5jz63tcnlfd 764831 764830 2026-04-24T11:57:54Z Mewtow 31375 /* L'espionnage du bus */ 764831 wikitext text/x-wiki Il est possible d'utiliser des caches avec la mémoire partagée, mais aussi sur les architectures distribuées et les architectures NUMA. Néanmoins, la gestion des caches peut poser des problèmes dits de '''cohérence des caches''' quand une donnée est présente dans plusieurs caches distincts. [[File:Cohérence des caches.png|vignette|upright=1|Cohérence des caches]] Introduisons la cohérence des caches par un exemple. Prenons deux cœurs/processeurs qui ont chacun une copie d'une donnée dans leur cache. Si un processeur modifie sa copie de la donnée, l'autre ne sera pas mise à jour. L'autre processeur manipule donc une donnée périmée : il n'y a pas '''cohérence des caches''', sous-entendu cohérence entre le contenu des différents caches. [[File:Non Coherent.gif|centre|vignette|upright=2.5|Caches non-cohérents.]] Or, il faut éviter cela sous peine d'avoir des problèmes. Mais avoir des caches cohérents demande d'avoir une valeur à jour de la donnée dans l'ensemble de ses caches, ce qui implique que les écritures dans un cache doivent être propagées dans les caches des autres processeurs. Il est possible de rendre des caches cohérents avec diverses méthodes qu'on va voir dans ce chapitre. Les deux animations ci-dessous montrent l'exemple de caches non-cohérents et de caches cohérents. [[File:Coherent.gif|centre|vignette|upright=2.5|Caches cohérents.]] Les problèmes de cohérence des caches se manifestent sur toutes les architectures multiprocesseur/multicœur, mais ils peuvent même se manifester avec un seul processeur ! Ils surviennent dès qu'un tiers peut écrire dans la RAM, peu importe que ce soit un autre processeur, un périphérique, un contrôleur DMA, ... Nous avions déjà vu de tels problèmes avec les transferts DMA et les TLBs, mais sans en dire le nom. Par exemple, un transfert DMA modifie des données en RAM, mais pas leurs copies dans le cache. Pareil pour les TLBs : modifier une entrée de la table des pages ne modifie pas sa copie dans la TLB. Et jusqu'ici, ces problèmes de cohérences étaient réglés en invalidant le cache, quand les circonstances l'exigent. Elle est simple à implémenter, pour un cout en performance qui dépend de la fréquence des invalidations. Pour les TLBs, modifier le contenu d'une table des pages est peu fréquent, ce qui fait qu'elles sont des candidats parfaits pour la cohérence par invalidation. La seule difficulté est que toute modification de la table des pages doit entrainer une invalidation des TLBs de tous les processeurs. Pour cela, le système d'exploitation envoie une interruption inter-processeurs spécifique, dont la routine invalide les TLBs. L'interruption est distribuée à tous les processeurs, sans exception, même au processeur envoyeur. Mais pour les caches de données, invalider les caches de données à chaque écriture aurait un cout en performance trop important, vu qu'elles sont écrites très souvent. Pour complémenter l'invalidation des caches, les ingénieurs ont inventé des méthodes alternatives spécifiques aux caches de données. Elles permettent de détecter les données périmées et les mettre à jour. Le tout a été formalisé dans des '''protocoles de cohérence des caches'''. Les protocoles de cohérence des caches marquent les lignes de cache comme invalides, si elles ont une donnée périmée. Les lignes de cache invalides ne peuvent pas être lues ou écrites. Toute lecture/écriture d'une ligne de cache invalide entraine un défaut de cache automatique. Les lignes de caches sont alors mises à jour avec une donnée valide. Le rôle d'un protocole de cohérence des caches est de détecter les copies périmées et de les mettre à jour automatiquement. Les problèmes de cohérence des caches surviennent dès qu'on a plusieurs caches dans une architecture parallèle, sauf pour quelques exceptions. Tout protocole de cohérence des caches doit répondre à plusieurs problèmes. * Premièrement : comment identifier les données invalides ? * Deuxièmement : comment les autres caches sont-ils au courant qu'ils ont une donnée invalide ? * Troisièmement : comment mettre à jour les données invalides ? Les deux derniers problèmes impliquent une forme de communication entre les caches du processeur. Et pour cela, voyons par quel intermédiaire ils communiquent. Pour rappel, les processeurs/cœurs sont connectés entre eux soit avec un bus partagé, soit avec un réseau d'interconnexion assez complexe. Les deux situations n'utilisent pas les mêmes protocoles de cohérence des caches. Par contre, le premier problème implique d'associer un état valide/invalide à chaque ligne de cache, et c'est quelque chose d'indépendant de la communication entre processeurs. Voyons le premier problème avant de passer aux deux autres problèmes. ==Les états d'une ligne de cache : identifier les données invalides== La cohérence des caches détecte les lignes de cache invalides. Pour cela, le protocole de cohérence des caches attribue à chaque ligne de cache un état, qui indique si la ligne de cache contient une donnée périmée, une donnée valide, ou autre. Le '''protocole SI''' est le protocole de cohérence des caches le plus simple qui soit. Il ne gère que deux états : ligne de cache valide, ligne de cache invalide. L'état est encodé avec un '''''bit valid''''', un par ligne de cache, qui indique si la donnée est invalide ou non. Il est vérifié lors de chaque lecture. Voici décrit en image le fonctionnement de ce protocole. Un processeur garde une donnée valide tant qu'aucun autre processeur n'écrit dedans. Si cela arrive, la donnée devient invalide et toute lecture/écriture dedans se fait rejeter, un défaut de cache survient. Le processeur envoie alors une transaction sur le bus pour récupérer une donnée valide. [[File:Diagramme d'état du protocole SI.png|centre|vignette|upright=2|Diagramme d'état du protocole SI.]] Les protocoles plus élaborés ajoutent d'autres états pour des raisons d'optimisation. Les états en question sont encodés sur quelques bits, ajoutés à chaque ligne de cache, dans les bits de contrôle. Voyons ces protocoles plus élaborés dans ce qui suit. ===Le protocole MSI=== La cohérence des caches est très simple quand on a des caches ''write-trough'', mais ces derniers sont à l'origine de beaucoup d'écritures en mémoire qui saturent le bus. Aussi, on a inventé les caches ''Write-back'', où le contenu de la mémoire n'est pas cohérent avec le contenu du cache. Si on écrit dans la mémoire cache, le contenu de la mémoire RAM n'est pas mis à jour. On doit attendre que la donnée sorte du cache pour l'enregistrer en mémoire ou dans les niveaux de caches inférieurs (s'ils existent), ce qui évite de nombreuses écritures mémoires inutiles. Divers protocoles de cohérence des caches existent pour les caches ''Write Back''. Le plus simple d'entre eux est le protocole MSI. Pour simplifier, il permet à des cœurs de réserver en lecture et écriture des lignes de cache, bien que la réservation soit temporaire. Elles utilise pour cela trois états : ''Modified'', ''Shared'' et ''Invalid''. L'état ''Shared'' change et correspond maintenant à une donnée à jour, présente dans plusieurs caches. L'état ''Modified'' correspond à une donnée à jour, mais dont les copies des autres caches sont périmées. L'état ''Invalid'' correspond encore une fois au cas où la donnée présente dans le cache est périmée. Le protocole permet à un cœur de réserver une ligne de cache/donnée temporairement. Tout part d'une ligne de cache en état ''Shared'', c'est à dire accesible en lecture. Elle n'est pas réservée en écriture, mais elle est consultable par un ou plusieurs cœurs. Soit un seul coeur a chargé la donnée dans son cache, soit d'autres coeurs ont une copie de la donnée dans leur cache, peu importe. La seule contrainte est que l'on sait que tous les coeurs ont la même copie de la donnée, la cohérence des caches est donc respectée. Le protocole de cohérence doit faire en sorte de repasser dans l'état ''Shared'' après la moindre violation de cohérence des caches. Le seul évènement capable de violer la cohérence des caches est la survenue d'une ou de plusieurs écritures. Pour qu'une écriture ait lieu, il faut qu'un cœur réserve la donnée en écriture. Pour cela, le ou les cœurs effectuent une écriture, ils tentent d'écrire dans la donnée voulue. S'il est le seul cœur à vouloir écrire à ce moment, il réservera la ligne de cache automatiquement. Mais si d'autres cœurs veulent modifier la donnée en même temps, une compétition avec les autres cœurs pour la réservation et un seul des cœurs gagnera la course. Quoiqu'il en soit, un cœur réservera la donnée et sa copie de la ligne de cache passe de l'état ''Shared'' à l'état ''Modified''. Les autres cœurs voient leur ligne de cache passer de l'état ''Shared'' à l'état ''Invalid''. L'état ''Modified'' signifie que le coeur a réussît à réserver la ligne de cache aussi bien en écriture qu'en lecture. Seul lui peut lire ou écrire la donnée à sa guise. L'état ''Invalid'', quant à lui, sert à deux choses. Premièrement, il prévint qu'un autre cœur a réservé la donnée en écriture, ce qui bloque l'écriture dans la ligne de cache par tout autre cœur. Deuxièmement, il bloque aussi les lectures. Il prévint qu'un autre coeur a modifié la donnée, que la ligne contient actuellement une donnée périmée. Tout accès mémoire à une ligne de cache ''Invalid'' déclenche alors des mesures correctives afin de rétablir la cohérence des caches. Le contenu de la ligne de cache en état ''Modified'' est alors envoyé à tous les autres caches, la cohérence est rétablie, et les lignes de cache passent toutes en état ''Shared'', jusqu’à la prochaine écriture. [[File:Diagramme d'état informel du protocole MSI.png|centre|vignette|upright=2|Diagramme d'état informel du protocole MSI.]] Il est possible de modifier le fonctionnement précédent pour tenir compte d'un cas très spécifique : une écriture dans une ligne de cache marquée ''Invalid''. L'écriture est bloquée pour une ligne de cache en état ''Invalid'', car un autre cœur l'a réservée, elle est bloquée en attendant de recevoir la donnée valide. Mais vu qu'elle sera écrasée de toute manière, autant faire passer la ligne de cache directement en état ''Modified''. L'optimisation est intéressante, mais il faut tenir compte du fait que dans ce cas, on a deux écritures qui se suivent dans le temps, réalisées par deux cœurs, appelons-les cœur 1 et cœur 2. La première écriture, faite par cœur 1, a marqué la ligne de cache comme invalide pour les autres, en ''Modified'' pour lui. La seconde, réalisée par le cœur 2, met sa ligne de cache en état ''Modified'' pour lui, ''Invalid'' pour les autres. Des problèmes de ''race condition'' peuvent survenir, le protocole doit gérer ce genre de cas où deux écritures se suivent de près. Dans ce cas, la dernière écriture doit fournir la donnée valide. La donnée qui était en état ''Modified'' dans le cœur 1 doit passer en état invalide, il perd la réservation en écriture. Le protocole précédent doit donc être adapté de manière à ajouter deux transitions : une de M vers I si un autre processeur écrit la donnée, et I vers M si le processeur écrit la donnée lui-même. [[File:Modified-Shared-Invalid Protokoll.png|centre|vignette|upright=2|Diagramme du protocole MESI. Les abréviations PrRd et PrWr correspondent à des accès mémoire initiés par le processeur associé au cache, respectivement aux lectures et écritures. Les abréviations BusRd et BusRdx et Flush correspondent aux lectures, lectures exclusives ou écritures initiées par d'autres processeurs sur la ligne de cache.]] Notons les trois état M, S et I, pour faire simple. Avec ce système, les lectures sont possibles seulement pour les lignes de cache en état M et S. Toute lecture d'une ligne de cache en état I se termine avec un défaut de cache. Le processeur envoie alors une requête GetS pour récupérer la donnée valide dans les caches d'un autre cœur, une donnée en étant S. L'état I force donc un défaut de cache lors des lectures. Pour les écritures, c'est différent. Les écritures dans une ligne de cache en état M sont automatiquement des succès de cache. Mais les lignes de cache en état S ou I sont traitées autrement. Une telle écriture envoie un signal GetM qui demande à réserver la ligne de cache en écriture. Si la requête est acceptée, la ligne de cache passe en état M, l'écriture est un succès de cache, les autres caches invalident leur copie de la donnée. ===Le protocole MESI=== [[File:Diagrama MESI.GIF|vignette|Diagramme du protocole MESI. Les abréviations PrRd et PrWr correspondent à des accès mémoire initiés par le processeur associé au cache, respectivement aux lectures et écritures. Les abréviations BusRd et BusRdx et Flush correspondent aux lectures, lectures exclusives ou écritures initiées par d'autres processeurs sur la ligne de cache.]] Le protocole MSI n'est pas parfait. Un de ses défauts est que l'état '''Shared'' ne fait pas la différence entre une donnée présente dans un seul cache, et une donnée partagée par plusieurs cœurs. C'est un défaut car toute écriture déclenche des opérations correctives pour gérer la cohérence des caches, qui sont inutiles si un seul cœur a une copie de la donnée. Elles préviennent les autres caches pour rien en cas d'écriture dans une donnée non-partagée. Et les communications sur le bus ne sont pas gratuites. Pour régler ce problème, on peut scinder l'état ''Shared'' en deux états : ''Exclusive'' si les autres processeurs ne possèdent pas de copie de la donnée, ''Shared'' si la donnée est partagée sur plusieurs cœurs. Le '''protocole MESI''' ainsi créé est identique au protocole MSI, avec quelques ajouts. Par exemple, si une donnée est lue la première fois par un cœur, la ligne de cache lue passe soit en ''Exclusive'' (les autres caches n'ont pas de copie de la donnée), soit en ''Shared'' (les autres caches en possèdent déjà une copie). Une donnée marquée ''Exclusive'' peut devenir ''Shared'' si la donnée est chargée dans le cache d'un autre processeur. Comment le processeur fait-il pour savoir si les autres caches ont une copie de la donnée ? Pour cela, il faut ajouter un fil Shared sur le bus, qui sert à dire si un autre cache a une copie de la donnée. Lors de chaque lecture, l'adresse à lire sera envoyée à tous les caches, qui vérifieront s'ils possèdent une copie de la donnée. Une fois le résultat connu, chaque cache fournit un bit qui indique s'il a une copie de la donnée. Le bit Shared est obtenu en effectuant un OU logique entre toutes les versions du bit envoyé par les caches. ===Les protocoles MOSI et MOESI=== Les protocoles MESI et MSI ne permettent pas de transférer des données entre caches sans passer par la mémoire. Si le processeur demande la copie valide d'une donnée, tous les caches ayant la bonne version de la donnée répondent en même temps et la donnée est envoyée en plusieurs exemplaires ! Pour éviter ce problème, on doit rajouter un état supplémentaire : l'état ''Owned''. Si un processeur écrit dans son cache, il mettra sa donnée en Owned, mais les autres caches passeront leur donnée en version Modified, voire Shared une fois la mémoire mise à jour. Ainsi, un seul processeur pourra avoir une donnée dans l'état Owned et c'est lui qui est chargé de répondre aux demandes de mise à jour. [[File:MOSI Processor Transactions.png|vignette|Protocole MOSI, transactions initiées par le processeur associé à la ligne de cache.]] [[File:MOSI Bus Transactions.png|vignette|Protocole MOSI, transactions initiées par les autres processeurs.]] Divers protocoles de cohérences des caches utilisent cet état Owned. Le premier d’entre eux est le '''protocole MOSI''', une variante du MESI où l'état exclusif est remplacé par l'état O. Lors d'une lecture, le cache vérifie si la lecture envoyée sur le bus correspond à une de ses données. Mais cette vérification va prendre du temps, et le processeur va devoir attendre un certain temps. Si au bout d'un certain temps, aucun cache n'a répondu, le processeur postule qu'aucun cache n'a la donnée demandée et va lire la donnée en mémoire. Ce temps est parfois fixé une fois pour toute lors de la création des processeurs, mais il peut aussi être variable, qui est géré comme suit : * pour savoir si un cache contient une copie de la donnée demandée, chaque cache devra répondre en fournissant un bit ; * quand le cache a terminé la vérification, il envoie un 1 sur une sortie spécifique, et un 0 sinon ; * un ET logique est effectué entre tous les bits fournis par les différents caches, et le résultat final indique si tous les caches ont effectué leur vérification. On peut aussi citer le '''protocole MOESI''', un protocole MESI auquel on a jouté l'état O. ==La cohérence des caches par espionnage du bus== L''''espionnage du bus''' est la technique de cohérence du cache la plus simple à comprendre, du moins sur le principe. Aussi, nous allons la voir en premier. Avec elle, les caches sont reliés à bus partagé, qui communique avec les niveaux de cache inférieurs ou avec la RAM. Faisons d'abord un rappel sur ce qu'est ce bus partagé. ===Les bus partagés : rappels et implémentation de la cohérence des caches=== Le premier cas est celui où plusieurs processeurs/cœurs sont connectés à la mémoire RAM à travers un bus partagé. Les processeurs disposent d'une mémoire cache chacun et ils sont tous reliés à la mémoire RAM à travers le bus mémoire. Dans ce cas, tous les caches ont connectés au bus mémoire, qui sert de point de ralliement. Sans cohérence des caches, les communications se font dans le sens ''cache -> mémoire'' ou ''mémoire -> cache''. L'idée est alors de rajouter les communications ''cache- -> cache'' sur le bus mémoire. La mémoire ne répond pas forcément à de telles communications. [[File:Architecture multicoeurs à bus partagé.png|centre|vignette|upright=2|Architecture multicoeurs à bus partagé]] L'idée marche très bien, mais il faut l'adapter sur les processeurs multicœurs, qui ont une hiérarchie de cache assez complexe. Les caches partagés entre tous les cœurs ne posent aucun problème de cohérence car, avec eux, la donnée n'est présente qu'une seule fois dans tout le cache. Par contre, il faut gérer la cohérence entre caches dédiés. [[File:Shared cache coherency.png|centre|vignette|upright=2|Cohérence et caches partagés. Vous remarquerez que sur le schéma, la mémoire RAM contient encore une autre version de la donnée car on utilise un cache ''Write Back'').]] Le cas le plus simple est celui à deux niveaux de caches, avec des caches L1 dédiés et un cache L2 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. Sans cohérence des caches, les transferts sur ce bus se font des caches L1 vers le cache L2 partagé, ou dans l'autre sens. Là encore, l'idée est de faire communiquer les caches L1 via le bus partagé. [[File:Architecture multicoeurs à bus partagé entre caches L1 et L2.png|centre|vignette|upright=2|Architecture multicoeurs à bus partagé entre caches L1 et L2]] Dans ce qui va suivre, quand nous parlerons de bus partagé, cela voudra dire : soit on parle du bus entre le L1 et le L2 sur un processeur multicœur, soit on parle du bus mémoire sur une architecture multi-processeur/multicœurs avec des caches dédiés. L'idée est que ce bus partagé existe avec ou sans cohérence des caches. Sans cohérence, il permet d'échanger des données entre deux niveaux de la hiérarchie mémoire : cache L1 vers L2, caches vers mémoire. Avec cohérence, le bus partagé interconnecte les caches entre eux. ===L'espionnage du bus=== Les protocoles à '''espionnage du bus''' sont des protocoles où les transmissions entre caches se font sur le bus partagé. Le nom qui trahit l'idée qui se cache derrière cette technique : les caches interceptent les écritures sur le bus partagé, qu'elles proviennent ou non des autres processeurs. Quand un cœur/processeur écrit une donnée dans son cache L1, un signal est envoyé sur le bus partagé pour prévenir les autres caches, afin qu'ils invalident la ligne de cache concernée. De plus, il faut aussi mettre à jour les copies en question avec une donnée valide, ce qui passe là-encore par le bus partagé. L'implémentation la plus simple demande juste un bus partagé, avec des modifications des processeurs eux-mêmes. Les processeurs espionnent le bus, c'est précisément le contrôleur mémoire intégré au processeur qui s'en charge. Cependant, de nombreux processeurs multicœurs délèguent l'espionnage du bus au ''chipset'', ou au contrôleur mémoire. Un exemple est celui de l'Intel E8870, où l'espionnage du bus est géré par un circuit séparé, qui n'est pas connecté directement au bus partagé. Le circuit d'espionnage du bus est appelé le ''Scalability Port Switch'' (SPS). Il y en a deux dans le ''chipset'', qui communiquent entre eux, mais passons sur ce détail. Avec cette organisation, les accès mémoire passent par le contrôleur mémoire, qui les envoie au SPS, puis à la RAM si besoin. Le SPS gère alors la cohérence des caches, et détecte quand des invalidations doivent être propagées. Le ''chipset'' peut relier 16 processeurs entre eux. Les processeurs sont regroupés en groupes de 4 processeurs, avec un contrôleur mémoire partagé par groupe. Les contrôleurs mémoire sont appelés des ''Scalable Node Controller'' (SNC). Les SNC incorporent de nombreuses optimisations : ils sont capables de changer l'ordre des accès mémoire, peuvent mettre en attente des écritures pour 64 lignes de cache (jusqu'à 8 kibioctets d'écritures), les adresses mémoire sont entrelacées, etc. [[File:Intel E8870.png|centre|vignette|upright=2|Intel E8870.]] Ce système permet d'implémenter des optimisations pour les lectures. Normalement, le processeur envoie la lecture sur le bus partagé, et il attend de voir si un autre cache répond. Si aucun cache ne répond, c'est un défaut de cache total, et le contrôleur mémoire lit la donnée en RAM. L'intel E8870 démarre l'accès en RAM avant qu'un autre cache ne réponde. La vérification des autres caches se passe donc en même temps que l'accès mémoire. Si aucun cache ne répond, l'accès aura été lancé en avance. Si un cache répond, il a juste à annuler la lecture entamée. Si celle-ci a été mise en attente dans le contrôleur mémoire, l'annulation n'entrainera pas de lecture sur le bus mémoire. Sinon, la lecture en RAM s'exécutera, mais la donnée lue sera juste ignorée. ===La mise à jour et l'invalidation sur écriture=== Voyons d'abord comment la mise à jour des copies se fait. La solution la plus simple pour cela est de propager les écritures dans les niveaux de cache inférieurs, jusqu'à la mémoire. L'écriture est alors transmise sur le bus partagé, les autres caches ont juste à récupérer la donnée et l'adresse écrite. Si l'adresse match une ligne de cache, la ligne de cache est mise à jour immédiatement avec la donnée envoyée sur le bus partagé. Le cache est alors mis à jour immédiatement, la ligne de cache n'a même pas le temps d'être invalidée. On parle alors de '''mise à jour sur écriture'''. Avec elle, les caches sont mis à jour automatiquement le plus tôt possible, il n'y a pas d'invalidation proprement dit. La solution la plus simple pour cela est d'utiliser des caches ''write through'', qui propagent toute écriture dans les niveaux de cache inférieurs, jusqu'à la mémoire. Toute écriture dans une ligne de cache déclenche alors une mise à jour. Mais l'impact sur le débit binaire est alors très important. Aussi, la plupart des processeurs préfèrent utiliser des caches ''write back'' pour gagner en performance. Dans ce cas, les écritures qui sont propagées dépendent de l'état de la ligne de cache écrite. Écrire dans une ligne de cache en état ''Exclusive'' ne déclenchera pas de mise à jour, par exemple. Seules les écritures dans des lignes de cache en état ''Modified'' et ''Shared'' le feront. Mais il est aussi possible de ne pas mettre à jour les lignes de cache à chaque écriture, et de préférer attendre. À la place, l'écriture est remplacée par un ''signal d'invalidation'' qui transmet uniquement l'adresse écrite et n'est pas pris en compte par le cache L2/L3 partagé. Il prévient les autres caches que telle ligne de cache a été modifiée et qu'il faut en invalider les copies. Le cache se contente de marquer la ligne de cache fautive comme invalide, mais ne la met pas à jour. Il y a alors '''invalidation sur écriture'''. La ligne de cache est mise à jour lors d'une lecture/écriture. Tout accès à une ligne de cache invalide entraine un défaut de cache et la donnée est chargée depuis la RAM et/ou depuis un autre cache. Les deux techniques précédentes différent sur un point : la première met à jour la ligne de cache immédiatement, la seconde attend que la ligne de cache soit lue/écrite avant de mettre à jour, elle le fait au dernier moment. Le temps d'accès à une donnée est donc plus long avec ces derniers. Avec la mise à jour sur écriture, la donnée est mise à jour tout de suite, les accès ultérieurs ne déclenchent pas de défaut de cache, la donnée est accessible directement. Le temps d'accès moyen est donc plus faible. Par contre, une partie des mises à jour sont inutiles, car les autres processeurs ne liront pas la donnée ou alors pas tout de suite. Avec l'invalidation, on met à jour les lignes de cache quand la donnée est lue, quand elle est réellement utilisée. Vu qu'une mise à jour est plus gourmande en énergie qu'une simple invalidation, on n'est pas forcément gagnant. Une mise à jour demande un accès complet au cache, avec écriture dans le plan mémoire. Alors d'une invalidation demande simplement de modifier les bits de contrôle d'une ligne de cache. De nos jours, les caches utilisent l'invalidation sur écriture pour des raisons de complexité d'implémentation. Les protocoles à mise à jour sur écriture sont plus complexes à implémenter pour de sombres raisons de consistance mémoire. ===La mise à jour en cas d'invalidation sur écriture=== Avec l'invalidation sur écriture, la mise à jour des lignes de cache se fait séparément de l'invalidation. Et dans ce cas, il faut trouver où se trouve la donnée valide. La donnée valide est présente soit dans le cache d'un autre processeur, soit dans la mémoire RAM. La donnée valide est copiée depuis cette source, c'est une simple transaction mémoire. Voyons ce qu'il en est. Le cas le plus simple est celui où plusieurs processeurs ont un cache chacun et sont reliés à une mémoire partagée. L'implémentation la plus simple lit la donnée valide depuis la RAM. Elle utilise alors des caches de type ''write-through'', où les écritures sont propagées la mémoire RAM. Il est possible de faire la même chose avec un cache ''write-back'', cependant. Mais il doit se comporter comme un cache ''write-through'' en propageant des écritures dans certaines situations. Dès qu'il écrit une ligne de cache en état ''Modified'' ou ''Shared'', il propagera l'écriture dans la RAM. Par contre, s'il a une ligne de cache en état ''Exclusive'', il n'a pas à propager les écritures dans le niveau de cache inférieur, il n'a pas à générer de signal d'invalidation du tout. Une autre solution fait une copie depuis le cache qui contient la donnée valide, sans passer par la mémoire RAM. Les copies entre caches passent par le bus mémoire, la différence étant que la mémoire ne répond pas forcément à des transferts. Les caches étant plus rapides que la RAM, les copies entre caches sont plus rapides qu'un accès en mémoire RAM, même si les deux utilisent le même bus. L'état ''Owned'' permet d'optimiser cette situation : c'est le cache en état ''Owned'' qui répond alors à la requête mémoire. [[File:Cohérence des caches write-through.png|centre|vignette|upright=3|Cohérence des caches ''write-through''.]] Sur les architectures multicœurs, le cache partagé prend la place de la mémoire RAM et le bus partagé celui du bus mémoire. En général, les caches L2 sont inclusifs, à savoir que toute donnée écrite dans les caches L1 est présente dans le cache L2. La donnée valide est donc généralement lue depuis le cache partagé L2/L3. Quand un cache a besoin d'une donnée valide, il envoie une '''requête de mise à jour''', qui demande aux autres caches s'ils ont une donnée valide. La donnée valide est en état ''Modified'' ou ''Owned''. Si un cache a une donnée dans cet état, il répond aux requêtes de mise à jour en envoyant la donnée voulue, éventuellement avec l'adresse. Le cache demandeur reçoit la donnée et met à jour sa ligne de cache. Les autres caches ne tiennent pas forcément compte de cette mise à jour. Du moins, pas avec "invalidation sur écriture" pure, mais certains caches sont un peu plus opportunistes et en profitent pour mettre à jour la ligne de cache au cas où. On peut les voir comme des intermédiaires entre invalidation et mise à jour sur écriture. ==La cohérence des caches à base de répertoires== La cohérence des caches à base de répertoire utilise un '''répertoire''', qui mémorise l'état de chaque ligne de cache, mais aussi quel processeur dispose de telle ou telle ligne de cache. Les processeurs envoient des requêtes au répertoire avant chaque accès au cache. Le répertoire va alors soit répondre favorablement et autoriser l'accès au cache, soit l'interdire. Une réponse favorable signifie que le processeur a une donnée valide, une réponse défavorable signifie que la ligne de cache accédée doit être mise à jour. La cohérence des caches est donc gérée par le répertoire, qui est centralisé. L'usage d'un répertoire est la norme sur les architectures NUMA, avec plusieurs ordinateurs reliés entre eux. Avec l'arrivée des processeurs multicœurs avec une hiérarchie de cache, les architectures à mémoire partagée se sont mises à s'inspirer des protocoles à répertoire pour gagner en performance. L'usage de caches de répertoire a permis d'utiliser la technique sur les processeurs multicœurs normaux, en complément de l'espionnage du bus. ===L'usage des répertoires sur les architectures NUMA=== Plusieurs architectures différentes utilisent une cohérence des caches par répertoire. Leur usage le plus intuitif est celui des architectures NUMA, avec plusieurs ordinateurs reliés entre eux via réseau. Sur les architectures NUMA, une donnée lue depuis la RAM d'un autre ordinateur peut être mise en mémoire cache. Et la moindre modification d'une copie doit être propagée via réseau sur les autres ordinateurs. Autant dire que la cohérence des caches est assez compliquée sur de telles architectures. Avec elles, chaque ordinateur a une copie du répertoire, pour gérer les données provenant des mémoires des autres ordinateurs. [[File:Cohérence des cache à répertoire.jpg|centre|vignette|upright=2.5|Cohérence des caches - Répertoire décentralisé.]] Les répertoires sont utilisés sur les architectures NUMA. Rappelons que sur de telles architectures, chaque processeur a une mémoire dédiée, mais qu'il a accès à toutes les mémoires de l'architecture à travers un réseau local. La mémoire dédiée au processeur est appelée la ''mémoire locale'', alors que celles des autres processeurs sont appelées les ''mémoires distantes''. Une partie de l'espace d'adressage est associé à la mémoire locale, mais le reste de l'espace d'adressage est associé aux mémoires distantes. Quand le processeur veut lire/écrire dans sa mémoire locale, le répertoire n'est pas consulté. Mais quand il veut lire/écrire en dehors, le répertoire est consulté pour gérer la cohérence des caches. [[File:Espace d'adressage d'une architecture NUMA.png|centre|vignette|upright=2|Espace d'adressage d'une architecture NUMA.]] Voyons maintenant comment le tout fonctionne. Nous allons prendre l'architecture illustrée ci-dessous. Elle contient plusieurs ordinateurs, chacun avec une mémoire locale reliée à un ou plusieurs processeurs multicœur aux hiérarchies de caches complexes. Nous n'allons pas nous intéresser aux caches L1/L2/L3, mais allons nous concentrer sur la mémoire cache L4, partagée entre plusieurs processeurs. Il s'agit d'une mémoire cache spécialisée dans les accès aux mémoires distantes. Elle contient des données provenant des mémoires distantes, qui ont été chargées lors d'accès antérieurs. Nous allons l'appeler le '''cache distant''' pour simplifier les explications. Les accès aux mémoires distantes se font via le réseau local d'interconnexion, mais le résultat des lectures est mémorisé dans le cache distant, ce qui évite de faire un accès réseau à chaque lecture/écriture. Le répertoire est consulté pour tout accès au cache distant, mais n'est pas consulté pour l'accès aux caches L1/L2/L3 gérés par espionnage de bus. Il l'est seulement quand un ordinateur veut accéder aux données d'une mémoire distante, lors d'un accès en dehors de l'espace d'adressage de la mémoire locale. [[File:Cc-NUMA System.svg|centre|vignette|upright=3|Architecture Ccc-NUMA.]] Pour comprendre comment le répertoire et le cache L4 sont utilisés, partons du principe qu'un processeur veuille lire une donnée située dans une mémoire distante. Le cache L4 ne contient pas la donnée en question, ce qui déclenche un défaut de cache. Une transaction réseau est alors démarrée, pour rapatrier la donnée dans le cache L4. Le rapatriement peut simplement lire la donnée dans la mémoire principale si elle n'a pas été cachée, mais elle peut aussi aller la chercher dans les caches du processeur distant si nécessaire (comme illustré ci-dessous). Les répertoires sont alors mis à jour sur tous les ordinateurs. Le répertoire permet de désigner quel processeur a une copie de la donnée voulue, et d'aller la chercher sans demander à tous les processeurs. [[File:Cc-NUMA Remote Memory Read.svg|centre|vignette|upright=2|Architecture Ccc-NUMA, lecture dans une mémoire distante.]] Intuitivement, on se dit que les futurs accès à cette donnée rapatriée se font dans le cache L4. Mais dans les faits, la donnée peut être copiée dans le cache L3/L2, voire L1 du processeur local. Maintenant, imaginons que le processeur local modifie cette donnée distante. Les protocoles de cohérence des caches par espionnage de bus propagent un signal d'invalidation jusque dans le cache L4. Il faut ensuite propager le signal d'invalidation aux autres ordinateurs qui manipulent cette donnée. Pour cela, le répertoire est consulté pour récupérer la liste des processeurs qui ont cette donnée dans leur cache. Le signal d'invalidation est ensuite transmis par le réseau d'interconnexion, et arrive aux destinataires, qui invalident la donnée dans leurs caches. [[File:Cc-NUMA Local Memory Read.svg|centre|vignette|upright=2|Architecture Ccc-NUMA, invalidation dans une mémoire distante.]] ===L'usage de répertoire sur les architectures multiprocesseurs et multicœurs=== L'espionnage de bus est simple à implémenter. Mais il a un défaut assez flagrant : les signaux d'invalidation et les requêtes de mise à jour passent par le bus partagé, idem pour les réponses à des signaux/requêtes. Le trafic sur le bus partagé est donc augmenté, assez fortement. Et au-delà de quelques processeurs, le trafic est trop important. L'usage d'état ''Owned'' et ''Exclusive'' améliore la situation, mais pas de quoi faire des miracles. Le problème de l'espionnage de bus est que les signaux et requêtes sont envoyés à tout le monde, grâce à l'usage d'un bus partagé. Et un bus partagé est une forme assez rudimentaire d'interconnexion, qui devient inefficace dès que le nombre de composants à connecter dessus est trop important. De nos jours, les processeurs multicœurs récents remplacent partiellement le bus partagé par un '''réseau d'interconnexion intra-processeur''' plus complexe qu'un simple bus. De même, les cartes mères multi-processeurs incorporent un '''réseau d'interconnexion inter-processeur''', placé sur la carte mère, pour connecter les processeurs, la mémoire et les autres entrées-sorties. Le tout est illustré ci-dessous et vous remarquerez que le tout ressemble un peu à une architecture NUMA mais sans mémoire RAM, la RAM étant accédée via le réseau d'interconnexion comme l'est le GPU ou un cœur/processeur. Mais cela ne fait pas grande différence, car l'essentiel est que les mémoires caches soient là, de même que le réseau d'interconnexion. Les systèmes multicœurs/multiprocesseurs utilisent l'espionnage du bus à l'intérieur d'un cœur/processeur, mais utilisent la cohérence basée sur un répertoire entre les processeurs/cœurs. [[File:Cohérence des caches avec un répertoire centralisé.png|centre|vignette|upright=2|Cohérence des caches avec un répertoire sur une architecture multicœurs.]] Dans ce qui suit, le réseau d'interconnexions entre processeurs/cœurs sera volontairement laissé vague, car il peut être absolument n'importe quoi : un bus partagé, un réseau en anneau, un réseau ''crossbar'', etc. Par contre, il doit donner l'illusion que chaque cache est connecté à tous les autres via un ensemble de liaisons point-à-point. Il n'y a pas une transmission à la fois, plusieurs transmissions entre processeurs/cœurs peuvent avoir lieu en même temps. Les signaux d'invalidation sont envoyés uniquement aux processeurs/cœurs qui ont une copie de la donnée, pas à tous. Idem pour les requêtes de mise à jour, envoyées seulement au cache qui a une copie valide de la donnée. De fait, les transmissions pour la cohérence peuvent se faire en même temps que d'autres lectures/écritures normales, ce qui fait meilleur usage du débit mémoire. Prenons comme exemple la situation du schéma précédent, où chaque cœur dispose d'un seul cache dédié, et voyons comment est gérée la cohérence des caches sur un tel processeur. Tout écriture dans le cache dédié entraine l'émission d'un signal d'invalidation pour préciser que la ligne de cache a été modifiée. L'envoi du signal d'invalidation est cependant géré par le répertoire, qui décide quels cœurs prévenir. Le répertoire configure alors le réseau d'interconnexion pour connecter entre eux les caches qui doivent l'être, pour propager les signaux d'invalidation et les requêtes de mise à jour. Les autres caches sont laissés libres et sont disponibles pour des lectures et écritures. On économise alors du débit binaire, au prix d'une perte en temps d'accès liée à l'interrogation du répertoire. Prenons ensuite le cas d'une lecture dans un cache dédié, illustré ci-dessous. Si un défaut de cache a lieu, alors la ligne de cache n'est pas disponible dans le cache dédié. Elle doit alors être rappatriée depuis la mémoire RAM dans le pire des cas, ou depuis un autre cache. Pour savoir dans quel cas il est, le cœur interroge le répertoire. Il sait si la ligne mémoire est cachée ou non, et dans quel cache elle se trouve si elle l'est. Le répertoire démarre alors une requête de lecture au cache adéquat, via une transaction réseau. Le cache adéquat répond à la transaction par une autre transaction réseau, à destination du cœur qui a déclenché le défaut de cache. Là encore, les trois requêtes sont envoyées uniquement aux cœurs/caches qui en ont besoin. [[File:Directory Scheme.png|centre|vignette|upright=2.5|Cohérence des caches avec un répertoire centralisé.]] ===Le contenu du répertoire : les implémentations à base de RAM et de caches=== Avant de poursuivre, un point de terminologie. Imaginez que la mémoire est découpée en blocs qui font la même taille qu'une ligne de cache, et qui sont alignés sur cette taille. Un bloc peut être copié dans le cache, éventuellement écrit par le processeur, et rapatrié en mémoire RAM une fois évincé du cache. Le bloc de mémoire sera appelé dans ce qui suit une '''ligne mémoire''', par analogie avec une ligne de cache. Un répertoire mémorise, pour chaque ligne mémoire, la liste des processeurs dont le cache qui en a une copie. Il peut aussi mémoriser l'état de la ligne de cache associée, mais ce n'est pas obligatoire, l'état peut être stocké dans la ligne de cache elle-même. ====Les répertoires basés sur une mémoire RAM==== L'implémentation la plus simple mémorise, pour chaque ligne mémoire, quel processeur l'a copié dans son cache. Pour cela, elle utilise un '''bit de présence''' par processeur, qui indique si le cache du processeur a une copie de la ligne mémoire : le énième bit de présence indique si le énième processeur a la ligne mémoire dans son cache. Elle a pour défaut de rapidement faire grossir le répertoire, dont la taille est proportionnelle au nombre de processeurs et de ligne mémoire. [[File:Full bit vector format diagram.jpg|centre|vignette|upright=2|Full bit vector format diagram]] Une autre solution mémorise la liste des processeurs autrement. Au lieu d'utiliser un bit par processeur, elle mémorise une '''liste de pointeurs''' vers ces processeurs. Elle attribue un numéro à chaque processeur et mémorise une liste de plusieurs numéros. L'avantage est que les numéros sont assez courts. Au lieu d'utiliser N bits pour N processeurs, chaque numéro ne fait que <math>\log_2{N}</math>. On gagne en mémoire si on autorise C copies, et que <math>C \times \log_2{N} \leq N</math>. Un exemple sera sans doute plus parlant. Prenons 256 processeurs. Un répertoire complet demandera 256 bits. Une liste de pointeurs encodera un numéro de processeur sur 8 bits, on est gagnant tant qu'on a moins de 256/8 = 32 processeurs par ligne de cache. Par contre, la technique n'autorise qu'un nombre maximal de numéros par ligne mémoire. Si ce nombre est dépassé, le répertoire doit gérer la situation. Et il y a plusieurs solutions possibles pour cela. La première n'autorise réellement que N copies d'une même ligne de cache et invalide les copies en trop si le nombre est dépassé. Le cout en performance est cependant élevé. Les deux autres solutions autorisent à dépasser le nombre maximal de numéros. La première se débrouille en repassant en mode ''broadcast'' une fois le nombre de numéro maximal dépassé. Le répertoire envoie alors toute invalidation de la ligne mémoire concernée à tous les processeurs, vu que le répertoire n'a pas les moyens de savoir qui a une copie valide ou non. Une autre solution déclenche une exception matérielle qui gère la situation en logiciel. {|class="wikitable" |+ Répertoire complet et à liste de pointeur |- ! ! Adresse ! État ! Liste des processeurs |- ! Représentation complète | rowspan="2" | 0xFFF1244 | rowspan="2" | ''Shared'' | 0001 1000 0001 1100 |- ! Liste de pointeurs | 3, 4, 5 12, 13 |} Les deux méthodes précédentes posent problème quand le nombre de processeur est élevé. Aussi, quelques optimisations permettent de limiter la casse. La première consiste à ne pas encoder toutes les informations nécessaires. Une idée possible est par exemple de regrouper les caches/processeurs par groupes de 2/3/4. Le répertoire mémorise alors quel groupe de cache/processeur contient une copie de la donnée, mais pas exactement quel cache dans ce groupe. Par exemple, avec des groupes de 2, il se peut qu'un processeur du groupe ait une copie de donnée, ou les deux, le répertoire ne fait pas la différence. Si la donnée est invalidée, il envoie des signaux d'invalidation aux deux processeurs du groupe. [[File:Coarse bit vector format diagram.jpg|centre|vignette|upright=2|Coarse bit vector format diagram]] Les répertoires vus plus haut sont basés sur une mémoire RAM. Elle contiennent une adresse par le ligne mémoire, l'adresse contient l'état de la ligne de cache et la liste des processeurs. La consultation du répertoire demande juste d'adresser le répertoire avec l'adresse de la ligne mémoire, ce qui peut être fait avant ou en parallèle de l'accès au cache. Mais le problème est que le répertoire est alors une mémoire très grosse. Elle est d'autant plus grosse qu'il y a de lignes mémoires et elle devient rapidement impraticable dès que la mémoire est un peu grosse. ====Les répertoires basés sur une mémoire cache==== Le répertoire est donc une structure assez grosse, ce qui est un problème. En pratique, les répertoires précédents sont tellement gros qu'on ne peut pas leur dédier une mémoire RAM. des tables qui sont mémorisées en mémoire RAM. Quelques processeurs ont réussi à le faire, notamment les premiers SGI Origin, qui avaient une banque de mémoire dédiée au répertoire. Mais la majeure partie des implémentations devaient placer le répertoire dans la mémoire RAM principale de l'ordinateur, un peu au même titre que la table des pages. Mais quoi bon avoir des caches si leur accès demande de consulter un répertoire en mémoire RAM ? Et pourtant, nous avons déjà vu une situation similaire. Il est possible de faire une analogie avec la table des pages, encore que celle-ci soit grandement limitée. La table des pages est là aussi une structure très grosse, censée être consultée à chaque accès mémoire, qui mémorise des informations pour chaque page mémoire. Le répertoire est une structure similaire : remplacez ligne mémoire par page mémoire et vous aurez l'idée. Et vous vous souvenez certainement de la solution utilisée, à savoir l'usage de caches de traduction d'adresse, les fameuses TLBs. Les caches en question sont appelés des '''caches de répertoire'''. Ils ont plusieurs entrées, mais moins qu'il n'y a de lignes mémoire. Une entrée peut être vide ou occupée. Une entrée occupée mémorise de quoi gérer la cohérence pour une ligne mémoire. Elle mémorise l'adresse de la ligne mémoire, l'état de la ligne et la liste des processeurs. Le cache de répertoire mémorise une partie du répertoire, celle en cours d'utilisation. L'implémentation la plus simple conserve un répertoire en mémoire RAM, complété par un cache de répertoire par processeur. Les caches de répertoire sont consultés à chaque écriture, ce qui n'est pas un problème vu qu'ils sont très petits et ont un temps d'accès minuscule. Il y a plusieurs caches de répertoire, avec plusieurs niveaux de cache. Typiquement, il y a un cache de répertoire L1 associé au cache L1 de données, un cache de répertoire L2 pour le cache de données L2, etc. Il faut cependant remarquer que le répertoire est une structure dont la majorité des entrées sont vides. En effet, les seules entrées occupées correspondent aux lignes mémoires présentes dans le cache du processeur. Il n'y a pas besoin de mémoriser autant d'entrées qu'il y a de lignes mémoires, seulement une entrée par ligne de cache. Cette simplification donne un répertoire très petit, dans lequel on a éliminé les entrées vides. Il s'agit d'une optimisation évident à laquelle vous aviez peut-être déjà pensé. Le tout est nommé avec le nom de ''inclusive directory cache'', que nous traduirons par '''cache de répertoire inclusif'''. Il existe deux implémentations possibles de ce cache de répertoire inclusif. La première place le répertoire dans un cache dédié, séparé des autres caches, associé au contrôleur mémoire. Le fonctionnement est alors le suivant. Pour toutes les lignes mémoires dans le cache, le cache de répertoire possède une entrée associée, qui mémorise une copie ''tag'' de la ligne de cache et la liste des processeurs. Le ''tag'' en question n'est autre que le ''tag'' utilisé dans le cache L1 (si on suppose que le L2 est partagé). Si jamais la ligne mémoire n'est pas trouvée dans ce cache, alors on suppose qu'elle est en état ''Invalid'', ou qu'elle n'a pas été chargée depuis la mémoire. Les actions correctives sont les mêmes dans les deux cas. Un défaut est que lorsqu'une ligne de cache est évincée du cache L1, le répertoire doit être prévenu, ce qui ajoute de la complexité. En théorie, le cache devrait être un cache associatif par voie, avec un grand nombre de voies pour gérer des accès simultannés. Une autre implémentation utilise des caches L1/L2 inclusifs. Ainsi, le répertoire a juste à mémoriser les lignes mémoires dans le cache partagé L2/L3. Mieux : il a juste à mémoriser la liste des processeurs dans la ligne de cache elle-même ! L'implémentation précédente recopiait les ''tag'' dans le répertoire, ce qui les dupliquait. Mais on n'avait pas le choix, car il fallait regrouper les ''tags'' des différents L1 dans un répertoire unique, on ne pouvait pas avoir un répertoire dispersé dans plusieurs caches L1. Mais avec des caches inclusifs, faire pareil avec les lignes de cache du L3 serait de la duplication inutile. Alors on fusionne le cache partagé L2/L3 avec le cache de répertoire. La difficulté est alors de maintenir des caches inclusifs, ce qui est plus compliqué que prévu. Une autre solution consiste à mémoriser la liste des copies dans les caches eux-mêmes. Le répertoire n'identifie, pour chaque ligne de cache, qu'un seul processeur : plus précisément, il identifie la ligne de cache du processeur qui contient la copie. À l'intérieur d'une ligne de cache, la copie suivante (si elle existe) est indiquée dans les bits de contrôle. On parle de répertoires à base de caches. [[File:Répertoire à base de caches.jpg|centre|vignette|upright=2|Répertoire à base de caches]] ==Les avantages et inconvénients des deux méthodes== L'avantage de l'espionnage du bus est qu'il utilise peu de circuits et qu'il est facile à implémenter, car il réutilise un bus partagé qui est déjà là. Par contre, son désavantage majeur est que les écritures dans un cache sont propagées sur le bus partagé, au moins partiellement. Soit les écritures sont réellement propagées sur le bus partagée, soit un message d'invalidation est envoyé sur le bus partagé, peu importe : un message est envoyé aux autres caches pour dire qu'une écriture a eu lieu et qu'il faut potentiellement invalider des données. Le débit binaire du bus partagé est donc partiellement grignoté par les communications entre caches. Et le désavantage est d'autant plus grand qu'il y a de coeurs/processeurs, qui se partagent le bus partagé. L'usage d'un répertoire résout ces problèmes. Le débit binaire du bus partagé n'est pas grignoté, car la liaison entre caches et répertoires est séparée. Par contre, le temps d'accès au cache est augmenté, car tout accès mémoire demande l'autorisation au répertoire. En soi, le problème est compensé par l'économie en débit binaire. Sur les architectures avec beaucoup de processeurs, le gain en débit binaire sur-compense la hausse du temps d'accès. Mais sur les architectures avec peu de cœurs, c'est l'inverse. En général, les architectures distribuées/NUMA utilisent des répertoires, alors que les architectures à mémoire partagée utilisent l'espionnage du bus. Le tout est résumé ci-dessous. {|class="wikitable" style="text-align:center;" |- ! ! Mémoire partagée ! Architectures NUMA ! Architecture distribuée |- ! Invalidation du cache | colspan="3" | Caches d'instruction et TLB |- ! Espionnage du bus | X | | |- ! Répertoire de cohérence | X | X | X |} Remarquez que l'espionnage du bus n'a de sens que sur les architectures à mémoire partagée, alors que l'usage de répertoire est plus générale. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les architectures à parallélisme de données | next=Les sections critiques et le modèle mémoire }} </noinclude> d7o0erbpmxtcbsee9dc2zcxzj3q5qe9 Mathc initiation/Sommaire 0 69534 764825 762203 2026-04-24T11:02:29Z Xhungab 23827 764825 wikitext text/x-wiki __NOTOC__ [[Catégorie:Mathc initiation (livre)]] : * [[Mathc initiation/Introduction|Introduction]] : {{Partie{{{type|}}}|fond={{{fond|}}}|prefixTable=I - |prefix1=Première Partie : | '''Le cours'''}} : {{Partie{{{type|}}}|[[Mathc initiation/a00| Les bases du C et de gnuplot]]}} : : {{Partie{{{type|}}}|fond={{{fond|}}}|prefixTable=I - |prefix1=Deuxième Partie : | '''Application'''}} : Vous pouvez maintenant travailler quelques applications numérique ci-dessous. : Je vous propose comme cours de référence les quatre livres de '''openstax''' en accès libre. Vous pouvez les lire en ligne ou télécharger les PDF. * [https://openstax.org/details/books/algebra-and-trigonometry-2e '''Algebra and Trigonometry 2e'''], ... '''[https://www.youtube.com/playlist?list=PLZBnAG03572riK3KtGrTnpGNGn2fi-zDm Playlist]''' . * [https://openstax.org/details/books/calculus-volume-1 '''Calculus 1'''], * [https://openstax.org/details/books/statistics '''Statistics'''] * [https://openstax.org/details/books/calculus-volume-2 '''Calculus 2'''] * [https://openstax.org/details/books/calculus-volume-3 '''Calculus 3'''] . * [https://openstax.org/details/books/university-physics-volume-1 Physics 1] * [https://openstax.org/details/books/university-physics-volume-2 Physics 2] * [https://openstax.org/details/books/university-physics-volume-3 Physics 3] . * [https://openstax.org/details/books/chemistry-2e Chemistry] * [https://openstax.org/details/books/organic-chemistry Organic Chemistry] . * [https://openstax.org/details/books/biology-ap-courses Biology] * [https://openstax.org/details/books/anatomy-and-physiology-2e Anatomy, Physiology] . * [https://openstax.org/details/books/introduction-computer-science introduction computer science] : {{Partie{{{type|}}}|[[Mathc initiation/a79| Algèbre]]}} : {{Partie{{{type|}}}|[[Mathc initiation/Fichiers h : c44a4| Analyse]]}} : {{Partie{{{type|}}}|[[Mathc initiation/Fichiers h : x_17a| La théorie des ensembles]]}} : {{Partie{{{type|}}}|[[Mathc matrices| Algèbre Linéaire dans ℝ]]}} : {{Partie{{{type|}}}|[[Mathc complexes| Algèbre Linéaire dans ℂ]]}} : : {{Partie{{{type|}}}|fond={{{fond|}}}|prefixTable=I - |prefix1=Troisième Partie :| '''Vérifier quelques propriétés mathématiques'''}} : '''Retrouver''' de '''mémoire''' ou par '''raisonnement''' le contenu de ce chapitre. {{Partie{{{type|}}}|[[Mathc initiation/a00q| Algèbre :]]}} {{Partie{{{type|}}}|[[Mathc initiation/a0097| Analyse :]]}} : {{Partie{{{type|}}}|fond={{{fond|}}}|prefixTable=I - |prefix1=Quatrième Partie : | '''Deux petits jeux graphique'''}} : {{Partie{{{type|}}}|[[Mathc initiation/a586|Géométrie de la tortue standard]]}} : {{Partie{{{type|}}}|[[Mathc initiation/a587|Géométrie de la tortue vectorielle]]}} : {{Partie{{{type|}}}|fond={{{fond|}}}|prefixTable=I - |prefix1= | '''S'entraîner au calcul mental'''}} : {{Partie{{{type|}}}|[[Mathc initiation/Fichiers h : c15|* Travailler sur des entiers]]}} : {{Partie{{{type|}}}|[[Mathc initiation/Fichiers h : c16|* Travailler sur des fractions]]}} : {{Partie{{{type|}}}|fond={{{fond|}}}|prefixTable=I - |prefix1= | '''Glate : L'anglais à l'oral, et à l'écrit'''}} : {{Partie{{{type|}}}|[[Mathc initiation/a00b|'''Glate''' : L'anglais à l'oral, et à l'écrit]]}} : {{Partie{{{type|}}}|[[Mathc initiation/a0070|'''Glate''' : Les dictées en Français]]}} : : {{Partie{{{type|}}}|fond={{{fond|}}}|prefixTable=I - |prefix1= |'''Quelques logiciels utiles'''}} : {{Partie{{{type|}}}|[[Mathc initiation/a611|Gnuplot, Octave]]}} : . : {{Lien modifier|Mathc initiation/Sommaire|modifier le sommaire}} {{AutoCat}} 0mqfddn6fgigkkqy9tqjy0rdkfioysg Fonctionnement d'un ordinateur/Sommaire 0 69596 764639 763771 2026-04-23T13:37:41Z Mewtow 31375 /* L’exécution dans le désordre */ 764639 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]] ===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 circuits compteurs et décompteurs|Les circuits compteurs et décompteurs]] * [[Fonctionnement d'un ordinateur/Les timers et diviseurs de fréquence|Les timers et diviseurs de fréquence]] ===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]] * [[Fonctionnement d'un ordinateur/Les circuits de conversion analogique-numérique|Les circuits de conversion analogique-numérique]] ===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 spécialisés 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]] * [[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]] ===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]] ===L’exécution dans le désordre=== * [[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/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]] * [[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|Exemples de CPU superscalaires: le cas du x86]] * [[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== ===Annexes sur les nombres flottants et les FPUs=== * [[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]] ===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/Les ISA optimisés pour la compilation/interprétation|Les ISA optimisés pour la compilation/interprétation]] * [[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 architectures systoliques|Les architectures systoliques]] * [[Fonctionnement d'un ordinateur/Les architectures neuromorphiques|Les réseaux de neurones matériels]] * [[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}} c0bhchsgqvgqghgjanjjh0ye6tlz01a 764640 764639 2026-04-23T13:38:15Z Mewtow 31375 /* L’exécution dans le désordre */ 764640 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]] ===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 circuits compteurs et décompteurs|Les circuits compteurs et décompteurs]] * [[Fonctionnement d'un ordinateur/Les timers et diviseurs de fréquence|Les timers et diviseurs de fréquence]] ===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]] * [[Fonctionnement d'un ordinateur/Les circuits de conversion analogique-numérique|Les circuits de conversion analogique-numérique]] ===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 spécialisés 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]] * [[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]] ===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]] ===L’exécution dans le désordre=== * [[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/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|Exemples de CPU superscalaires: le cas du x86]] * [[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== ===Annexes sur les nombres flottants et les FPUs=== * [[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]] ===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/Les ISA optimisés pour la compilation/interprétation|Les ISA optimisés pour la compilation/interprétation]] * [[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 architectures systoliques|Les architectures systoliques]] * [[Fonctionnement d'un ordinateur/Les architectures neuromorphiques|Les réseaux de neurones matériels]] * [[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}} rkail5slblfoo75b7g5d3y5l3tdd1ww Fonctionnement d'un ordinateur/Les mémoires RAM dynamiques (DRAM) 0 71327 764644 764589 2026-04-23T13:57:30Z Mewtow 31375 /* L'arrangement vertical : cumuler des adresses mémoire */ 764644 wikitext text/x-wiki Après avoir vu les mémoires statiques (SRAM), il est temps de passer aux mémoires RAM dynamiques, aussi appelées mémoires DRAM. Pour rappel, les DRAM dynamiques ont pour défaut que leurss données s'effacent après un certain temps, en quelques millièmes ou centièmes de secondes . En conséquence, il faut réécrire chaque bit de la mémoire régulièrement pour éviter qu'il ne s'efface. On dit qu'on doit effectuer régulièrement un '''rafraîchissement mémoire'''. Et celui-ci rend les DRAM très différentes des SRAM. Les DRAM des PC ont beaucoup évolués dans le temps. Les toutes premières mémoires DRAM étaient des mémoires asynchrones, mais elles ont été remplacées par des modèles synchrones. Les modèles asynchrones ont été très nombreux. Après l'apparition des premières DRAM, les mémoires ''Fast Page Mode'' sont apparues, suivies par les mémoires ''Extended Data Out'', puis les EDO en mode rafale. Elles ont été utilisées jusque dans la moitié des années 90, pour ensuite être supplantées par les mémoires SDRAM. Les mémoires DDR actuelles sont des améliorations des mémoires SDRAM actuelles. Le fait est que les DRAM sont des mémoires électroniques comme les autres, qui se présentent sous la forme de circuits intégrés, à savoir que ce sont des petits boitiers noirs avec des broches. Il est possible de souder ces boitiers sur une cartre mère, et c'est ce qui est fait sur nombre d'ordinateurs portables. Mais il est aussi possible de regrouper plusieurs boitiers sur une barrette de RAM séparée. Dans ce qui suit, nous les appellerons des '''chips mémoire''', ou encore, des puces mémoires. [[File:Canyon CN-WF514 - EtronTech EM638325TS-6-4022.jpg|centre|vignette|upright=2|Exemple de chip mémoire.]] Dans ce qui suit, nous allons étudier ces chips de DRAM, avant de voir comment ils sont regroupés sur une barrette de RAM. Puis, nous allons voire chaque type de mémoire, FPM, EDO, SDRAM, DDR, ... ; un par un. ==L'interface des DRAM et le contrôleur mémoire== L'interface d'une mémoire DRAM est plus compliquée que l'interface d'une SRAM basique. Et c'est suffisant pour qu'on ait besoin d'un intermédiaire pour faire la conversion entre processeur et DRAM. Les DRAM modernes ne sont pas connectées directement au processeur, mais le sont par l'intermédiaire d'un '''contrôleur mémoire externe'''. Il ne faut pas le confondre avec le contrôleur mémoire interne, placé dans la mémoire RAM, et qui contient notamment le décodeur. Les deux sont totalement différents, bien que leur nom soit similaire. Pour éviter toute confusion, j'utiliserais le terme de '''contrôleur de DRAM''', plus parlant. ===Le bus d'adresse des DRAM est multiplexé=== Un point important pour le contrôleur de DRAM est de transformer les adresses mémoires fournies par le processeur, en adresses utilisables par la DRAM. Car les DRAM ont une interface assez spécifique. Les DRAM ont ce qui s'appelle un '''bus d'adresse multiplexé'''. Avec de tels bus, l'adresse est envoyée en deux fois. Les bits de poids fort sont envoyés avant les bits de poids faible. On peut ainsi envoyer une adresse de 32 bits sur un bus d'adresse de 16 bits, par exemple. Le bus d'adresse contient alors environ moitié moins de fils que la normale. Pour rappel, l'avantage de cette méthode est qu'elle permet de limiter le nombre de fils du bus d'adresse, ce qui très intéressant sur les mémoires de grande capacité. Les mémoires DRAM étant utilisées comme mémoire principale d'un ordinateur, elles devaient avoir une grande capacité. Cependant, avoir un petit nombre de broches sur les barrettes de mémoire est clairement important, ce qui impose d'utiliser des stratagèmes. Envoyer l'adresse en deux fois répond parfaitement à ce problème : cela permet d'avoir des adresses larges et donc des mémoires de forte capacité, avec une performance acceptable et peu de fils sur le bus d'adresse. Les bus multiplexés se marient bien avec le fait que les DRAM sont des mémoires à adressage par coïncidence ou à tampon de ligne. Sur ces mémoires, l'adresse est découpée en deux : une adresse haute pour sélectionner la ligne, et une adresse basse qui sélectionne la colonne. L'adresse est envoyée en deux fois : la ligne, puis la colonne. Pour savoir si une donnée envoyée sur le bus d'adresse est une adresse de ligne ou de colonne, le bus de commande de ces mémoires contenait deux fils bien particuliers : les RAS et le CAS. Pour simplifier, le signal RAS permettait de sélectionner une ligne, et le signal CAS permettait de sélectionner une colonne. [[File:Signaux RAS et CAS.png|centre|vignette|upright=2|Signaux RAS et CAS.]] Si on a deux bits RAS et CAS, c'est parce que la mémoire prend en compte les signaux RAS et CAS quand ils passent de 1 à 0. C'est à ce moment là que la ligne ou colonne dont l'adresse est sur le bus sera sélectionnée. Tant que des signaux sont à zéro, la ligne ou colonne reste sélectionnée : on peut changer l'adresse sur le bus, cela ne désélectionnera pas la ligne ou la colonne et la valeur présente lors du front descendant est conservée. [[File:L'intérieur d'une FPM.png|centre|vignette|upright=2|L'intérieur d'une FPM.]] ===Le rafraichissement mémoire=== La spécificité des DRAM est qu'elles doivent être rafraichies régulièrement, sans quoi leurs cellules perdent leurs données. Le rafraichissement est basiquement une lecture camouflée. Elle lit les cellules mémoires, mais n'envoie pas le contenu lu sur le bus de données. Rappelons que la lecture sur une DRAM est destructive, à savoir qu'elle vide la cellule mémoire, mais que le système d'amplification de lecture régénère le contenu de la cellule automatiquement. La cellule est donc rafraichie automatiquement lors d'une lecture. La quasi-totalité des DRAM supporte des commandes de rafraichissement, séparées des lectures et écritures classiques. Une commande de rafraichissement ordonne de rafraichir une adresse, voire une ligne complète. Les commandes de rafraichissement sont générées par le contrôleur de DRAM, dans la grosse majorité des cas. Il est aussi possible que ce soit le processeur qui les génère, mais c'est beaucoup plus rare. Il est aussi possible d'envoyer des commandes de rafraichissement vides, qui ne précisent ni adresse ni numéro de ligne. Pour les gérer, la mémoire contient un compteur, qui pointe sur la prochaine ligne à rafraichir, qui est incrémenté à chaque commande de rafraichissement. Une commande de rafraichissement indique à la mémoire d'utiliser l'adresse dans ce compteur pour savoir quelle adresse/ligne rafraichir. [[File:Rafraichissement mémoire automatique.png|centre|vignette|upright=2|Rafraichissement mémoire automatique.]] Il existe des mémoires qui sont des intermédiaires entre les mémoires SRAM et DRAM. Il s'agit des '''mémoires pseudo-statiques''', qui sont techniquement des mémoires DRAM, utilisant des transistors et des condensateurs, mais qui gèrent leur rafraichissement mémoire toutes seules. Le rafraichissement mémoire est alors totalement automatique, ni le processeur, ni le contrôleur de DRAM ne devant s'en charger. Le rafraichissement est purement le fait des circuits de la mémoire RAM et devient une simple opération de maintenance interne, gérée par la RAM elle-même. L'envoi des commandes de rafraichissement peuvent se faire de deux manières : soit on les envoie toutes en même temps, soit on les disperse le plus possible. Le premier cas est un '''rafraichissement en rafale''', le second un '''rafraichissement étalé'''. Le rafraichissement en rafale n'est pas utilisé dans les PC, car il bloque la mémoire pendant un temps assez long. Mais les anciennes consoles de jeu gagnaient parfois à utiliser eu rafraichissement en rafale. En effet, la mémoire était souvent effacée entre l'affichage de deux images, pour éviter certains problèmes dont on ne parlera pas ici. Le rafraichissement de la mémoire était effectué à ce moment là : l'effacement rafraichissait la mémoire. Le temps mis pour rafraichir la mémoire est le temps mis pour parcourir toute la mémoire. Il s'agit du temps de balayage vu dans le chapitre sur les performances d'un ordinateur. Pour les mémoires FPM et EDO, il est défini en divisant la capacité de la mémoire par son débit binaire. C'est le temps nécessaire pour lire ou réécrire tout le contenu de la mémoire. Sur les SDRAM, les choses sont un peu différentes, pour une raison qu'on expliquera plus bas. ===Le contrôleur de DRAM=== Le contrôleur de DRAM gère le bus mémoire et tout ce qui est envoyé dessus. Il envoie des commandes aux barrettes de mémoire, commandes qui peuvent être des lectures, des écritures, ou des demandes de rafraichissement, parfois d'autres commandes. La mémoire répond à ces commandes par l'action adéquate : lire la donnée et la placer sur le bus de données pour une commande de lecture, par exemple. Le rôle du contrôleur de DRAM varie grandement suivant le contrôleur en question, ainsi que selon le type de DRAM. Les anciens contrôleurs de DRAM étaient des composants séparés du processeur, du ''chipset'' ou du reste de la carte mère. Par exemple, les contrôleur de DRAM Intel 8202, Intel 8203 et Intel 8207 étaient vendus dans des boitiers DIP et étaient soudés sur la carte mère. Par la suite, ils ont été intégré au ''chipset'' de la carte mère pendant les décennies 90-2000. Après les années 2000, ils ont été intégrés dans les processeurs. Il est possible de connecter plusieurs barrettes sur le même bus mémoire, ou alors celles-ci sont connectées au contrôleur de DRAM avec un bus par barrette/boitier. C'est ce qui permet de placer plusieurs barrettes de mémoire sur la même carte mère : toutes les barrettes sont connectées au contrôleur de DRAM d'une manière ou d'une autre. ==Les rangées : l'arrangement horizontal et vertical== Il est rare d'utiliser un chip mémoire seul, car ceux-ci n'ont pas une capacité suffisante. Pour donner quelques chiffres, à l'heure où j'écris ces lignes, la norme pour un ordinateur est d'avoir entre 8 et 64 gibioctets de RAM. Mais les chips mémoire font entre 1 et 4 gibioctets, rarement plus. La raison est que les ordinateurs combinent ensemble plusieurs chips mémoire pour additionner leurs capacités. La concaténation de plusieurs chips mémoire peut se faire de deux manières différentes, appelées l'arrangement horizontal et l'arrangement vertical. Les deux additionnent la capacité des chips mémoire, mais se distinguent sur un point : ce qui arrive respectivement au bus de données, et au nombre d'adresses. Intuitivement, on se dit que doubler la capacité mémoire implique de doubler le nombre d'adresses mémoire. C'est effectivement ce qui se passe avec l'arrangement vertical. Mais avec l'arrangement horizontal, le nombre d'adresse ne varie pas. Voyons cela en détail, et commençons par le cas le plus simple, celui de l'arrangement vertical seul. ===L'arrangement vertical : cumuler des adresses mémoire=== Introduisons l'arrangement vertical par un exemple. Imaginez que je souhaite obtenir de 4 mébioctets de RAM, en combinant 4 chips mémoires de 1 mébioctet chacun. L'idée est que le premier mébioctet est placé dans le premier chip mémoire, le second mébioctet dans le second chip, etc. Des adresses consécutives se trouvent ainsi dans le même chip mémoire, sauf pour quelques adresses où on passe d'un chip à l'autre. Avec cette organisation, le bus de donnée fait un octet, et les chips mémoire ont aussi un bus de données d'un octet. Je peux alors combiner les capacités de plusieurs chips mémoire, sans toucher au bus de données. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses avec arrangement vertical.]] Avec cet arrangement, les bits de poids fort de l'adresse sont utilisées pour sélectionner la banque adéquate, et le reste de l'adresse est envoyé sur le bus d'adresse. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Adresse de banque !! Adresse dans la banque |- | Quelques bits de poids fort || Reste de l'adresse |} Pour cela, il faut que chaque mémoire dispose d'une entrée ''Chip Select'', qui permet de l'activer ou de la désactiver. L'idée est que selon l'adresse demandée, on active le chip mémoire associé à cette adresse. Les signaux ''Chip Select'' sont générés par le contrôleur de DRAM, à partir de l'adresse. On dit qu'il y a un '''décodage d'adresse'''. Les bits de poids fort sont utilisés pour sélectionner le chip mémoire adéquat. Par exemple, avec 4 chips mémoire, les deux bits de poids fort de l'adresse sont utilisés pour sélectionner le chip mémoire adéquat. ===L'arrangement horizontal : élargir le bus de données=== L'arrangement horizontal permet lui aussi d'additionner les capacités mémoire de plusieurs chips mémoire. Cependant, il les combine d'une autre manière. Le nombre d'adresses mémoire n'est pas changé en utilisant plusieurs chips, mais le bus de données est élargi. Le mieux pour comprendre l'idée est de partir d'un exemple, et nous allons prendre celui d'une mémoire SDRAM. Les ordinateurs actuels ont un bus de données de 64 bits (on met de côté le cas du double ou triple canal). Cependant, il n'existe pas de chip mémoire avec un bus aussi large. Les puces de SDRAM/DDR ont un bus de 4, 8 ou 16 bits, ce sont les tailles les plus courantes. L'arrangement horizontal résout ce problème en combinant plusieurs chips mémoire de manière à ce que leurs bus de données s'"additionnent", se concatènent. Par exemple, on peut regrouper 8 chips mémoires de 8 bits, obtenir un bus mémoire de 64 bits. Il est aussi possible d'obtenir ces 64 bits avec des puces de 16 chips mémoire de 4 bits, ou 4 chips mémoire de 16 bits. [[File:Arrangement horizontal SDRAM - un Rank.png|centre|vignette|upright=2|Arrangement horizontal SDRAM.]] Avec cette organisation, on accède à tous les bancs en parallèle à chaque accès, avec la même adresse. Vu que les chips mémoires contiennent tous une partie de la donnée demandée, ils doivent tous être activés en même temps. Pour cela, l'adresse à lire est envoyée à tous les chips mémoire d'un même ''rank'', idem pour les signaux de commande. Un ensemble de N chips reliés de cette manière forme une '''rangée''' (le terme anglais est ''rank''). [[File:Arrangement horizontal.jpg|centre|vignette|upright=2|Arrangement horizontal.]] ===L'arrangement horizontal et vertical combinés=== Nous venons de voir l'arrangement vertical et horizontal, pour ce qui est des barrettes de mémoire. Précisons que ce qui vient d'être dit marche aussi bien pour les barrettes de RAM que pour la mémoire soudée sur la carte mère. Du moment qu'on combine plusieurs chips mémoire ensemble, ces concepts restent valides. Et il en est de même pour la suite, encore que ce soit nettement moins fréquent avec de la mémoire soudée. Il est possible de combiner à la fois l'arrangement vertical et l'arrangement horizontal. Rien de plus simple : il suffit d'utiliser un arrangement vertical entre plusieurs rangées, chacun composée de plusieurs chips mémoire. C'est surtout utilisé sur les barrettes de mémoire SDRAM, qui contiennent 1, 2, 4 ou 8 rangées, rarement plus. Par exemple, une SDRAM peut combiner 16 chips de DRAM de 8 bits chacun, dans deux rangées de 64 bits chacun, chaque rangée regroupant 8 chips. Le choix entre la première ou la seconde rangée se fait en configurant les bits ''Chip Select'' de chaque rangée. [[File:SDRAM avec 4 ranks.png|centre|vignette|upright=2|SDRAM avec 4 ranks]] Il faut noter que les bits de ''Chip Select'' sont générés par le contrôleur mémoire, et envoyés sur le bus de commande. Le contrôleur de DRAM peut adresser un certain nombre de rangées, dispersés sur plusieurs barrettes. La limite maximale dépend du contrôleur de DRAM, elle est souvent proche de 8 ou 16 rangées. Si on combine plusieurs barrettes de mémoire, il est possible de dépasser cette limite. Par exemple, prenez un contrôleur de DRAM supportant maximum 8 rangées. Avec 4 barrettes contenant 4 rangées chacune, la limite est dépassée. : Il faut noter que tout ce qui vient d'être dit vaut aussi pour les mémoires ROM et SRAM. Mais en pratique, les arrangements verticaux et horizontaux sont surtout utilisés sur les mémoires DRAM. Il faut dire que de tels arrangements servent à augmenter la capacité mémoire, ce qui colle plus avec des DRAM que des SRAM ou des ROM. ==Les barrettes de mémoire DRAM== [[File:Ram-module.svg|droite|vignette|upright=0.5|Barrette de mémoire RAM.]] Il est possible de souder plusieurs boitiers de DRAM sur une cartre mère, et c'est ce qui est fait sur nombre d'ordinateurs portables. Mais dans les PC fixes, les puces de DRAM sont regroupées sur des ''barrettes mémoires'''. Les barrettes de mémoire se fixent à la carte mère sur un connecteur standardisé, appelé '''slot mémoire'''. Le dessin ci-contre montre une barrette de mémoire, celui-ci ci-dessous est celui d'un ''slot'' mémoire. [[File:Dual channel slots.jpg|centre|vignette|Slots mémoires.]] Sur le schéma de droite, on remarque facilement les boitiers de DRAM, rectangulaires, de couleur sombre. Chaque barrette combine ces puces de manière à additionner leurs capacités : on peut ainsi créer une mémoire de 8 gibioctets à partir de 8 puces d'un gibioctet, par exemple. Ils sont soudés sur un PCB en plastique vert sur lequel sont gravés des connexions métalliques. Les trucs dorés situés en bas des barrettes de mémoire sont des broches qui connectent la barrette au bus mémoire. Les barrettes des mémoires FPM/EDO/SDRAM/DDR n'ont pas le même nombre de broches, pour des raisons de compatibilité. {|class="wikitable" |- !Type de barrette !Type de mémoire !Nombre de broches |- | rowspan="2" | SIMM | rowspan="2" | FPM/EDO |30 |- |72 |- | rowspan="4" | DIMM |SDRAM |168 |- |DDR |184 |- |DDR2 |214, 240 ou 244, suivant la barrette ou la carte mère. |- |DDR3 |204 ou 240, suivant la barrette ou la carte mère. |} ===Le format des barrettes de mémoire=== Certaines barrettes ont des puces mémoire d'un seul côté alors que d'autres en ont sur les deux faces. Cela permet de distinguer les barrettes SIMM et DIMM. * Les '''barrettes SIMM''' ont des puces sur une seule face de la barrette. Elles étaient utilisées pour les mémoires FPM et EDO-RAM. * Les '''barrettes DIMM''' ont des puces sur les deux côtés. Elles sont utilisées sur les SDRAM et les DDR. {| class="flexible" |+ '''Barrette SIMM''' |- |[[File:SIMM FPM 4 MB - C0448721-7229.jpg|vignette|SIMM recto.]] |[[File:SIMM FPM 4 MB - C0448721-7230.jpg|vignette|SIMM verso.]] |} : Les modules DIMM tendent à avoir plus de rangées que les modules SIMM, mais ce n'est pas systématique. Il est souvent dit que les barrettes DIMM ont deux rangées, contre seulement 1 pour les SIMM, mais les contre-exemples sont nombreux. Les barrettes '''SO-DIMM''', pour ordinateurs portables, sont différentes des barrettes DIMM normales des DDR/SDRAM. La raison est qu'il n'y a pas beaucoup de place à l'intérieur d'un PC portable, ce qui demande de diminuer la taille des barrettes. {| |- |[[File:Desktop DDR Memory Comparison.svg|centre|vignette|upright=1.5|Barrettes de DDR pour PC de bureau.]] |[[File:Laptop SODIMM DDR Memory Comparison V2.svg|centre|vignette|upright=1.5|Barrettes de DDR pour PC portables.]] |} Les barrettes de Rambus ont parfois été appelées des '''barrettes RB-DIMM''', mais ce sont en réalité des DIMM comme les autres. La différence principale est que la position des broches n'était pas la même que celle des formats DIMM normaux, sans compter que le connecteur Rambus n'était pas compatible avec les connecteurs SDR/DDR normaux. ===Les interconnexions à l'intérieur d'une barrette de mémoire=== Les boîtiers de DRAM noirs sont connectés au bus par le biais de connexions métalliques. Toutes les puces d'une même rangée sont connectées aux bus d'adresse et de commande. Et les chips d'une même rangée reçoivent exactement les mêmes signaux de commande/adresses, ce qui permet d'envoyer la même adresse/commande à toutes les puces en même temps. La manière dont ces puces sont reliées au bus de commande dépend selon la mémoire utilisée. Les DDR1 et 2 utilisent ce qu'on appelle une '''topologie en T''', illustrée ci-dessous. On voit que le bus de commande forme une sorte d'arbre, dont chaque extrémité est connectée à une puce. La topologie en T permet d'égaliser le délai de transmission des commandes à travers le bus : la commande transmise arrive en même temps sur toutes les puces. Mais elle a de nombreux défauts, à savoir : elle fonctionne mal à haute fréquence, elle est difficile à router en raisons des embranchements. [[File:Organisation des bus de commandes sur les DDR1-2.png|centre|vignette|upright=3.0|Organisation des bus de commandes sur les DDR1-2, nommée topologie en T.]] En comparaison, les DDR3 utilisent une '''topologie ''fly-by''''', où les puces sont connectées en série sur le bus de commande/adresse. La topologie ''fly-by'' n'a pas les problèmes de la topologie en T : elle est simple à router et fonctionne très bien à haute fréquence. [[File:Organisation des bus de commandes sur les DDR3 - topologie fly-by.png|centre|vignette|upright=3.0|Organisation des bus de commandes sur les DDR3 - topologie ''fly-by'']] ===Les barrettes tamponnées (à registres)=== Certaines barrettes intègrent un registre tampon, qui fait l'interface entre le bus et la barrette de RAM. L'utilité est d'améliorer la transmission du signal sur le bus mémoire. Sans ce registre, les signaux électriques doivent traverser le bus, puis traverser les connexions à l'intérieur de la barrette, jusqu'aux puces de mémoire. Avec un registre tampon, les signaux traversent le bus, sont mémorisés dans le registre et c'est tout. Le registre envoie les commandes/données jusqu'aux puces mémoire, mais le signal a été régénéré par le registre. Le signal transmis est donc de meilleure qualité, ce qui augmente la fiabilité du système mémoire. Le défaut est que la présence de ce registre fait que les barrettes ont un temps de latence est plus important que celui des barrettes normales, du fait de la latence du registre. Les barrettes de ce genre sont appelées des '''barrettes RIMM'''. Il en existe deux types : * Avec les '''barrettes RDIMM''', le registre fait l'interface pour le bus d'adresse et le bus de commande, mais pas pour le bus de données. * Avec les '''barrettes LRDIMM''' (''Load Reduced DIMMs''), le registre fait tampon pour tous les bus, y compris le bus de données. [[File:Organisation des bus de commandes sur les RDIMM.png|centre|vignette|upright=3.0|Organisation des bus de commandes sur les RDIMM.]] ===Le ''Serial Presence Detect''=== [[File:SPD SDRAM.jpg|vignette|Localisation du SPD sur une barrette de SDRAM.]] Toute barrette de mémoire assez récente contient une petite mémoire ROM qui stocke les différentes informations sur la mémoire : délais mémoire, capacité, marque, etc. Cette mémoire s'appelle le '''''Serial Presence Detect''''', aussi communément appelé le SPD. Ce SPD contient non seulement les timings de la mémoire RAM, mais aussi diverses informations, comme le numéro de série de la barrette, sa marque, et diverses informations. Le SPD est lu au démarrage de l'ordinateur par le BIOS, afin de pourvoir configurer ce qu'il faut. Le contenu de ce fameux SPD est standardisé par un organisme nommé le JEDEC, qui s'est chargé de standardiser le contenu de cette mémoire, ainsi que les fréquences, timings, tensions et autres paramètres des mémoires SDRAM et DDR. Pour les curieux, vous pouvez lire la page wikipédia sur le SPD, qui donne son contenu pour les mémoires SDR et DDR : [https://en.wikipedia.org/wiki/Serial_presence_detect Serial Presence Detect]. ==Les mémoires asynchrones à RAS/CAS : FPM et EDO-RAM== Avant l'invention des mémoires SDRAM et DDR, il exista un grand nombre de mémoires différentes, les plus connues étant les mémoires fast page mode et EDO-RAM. Ces mémoires n'étaient pas synchronisées par un signal d'horloge, c'était des '''mémoires asynchrones'''. Quand ces mémoires ont été créées, cela ne posait aucun problème : les accès mémoire étaient très rapides et le processeur était certain que la mémoire aurait déjà fini sa lecture ou écriture au cycle suivant. Les mémoires asynchrones les plus connues étaient les '''mémoires FPM''' et '''mémoires EDO'''. Pour ce qui est de leur interface, il faut signaler qu'elles n'ont pas d'entrée ''Chip Select'' ou d'entrée ''Output Enable''. Les signaux RAS et CAS remplacent en quelque sorte ces deux signaux. Le bit RAS fait office de ''Chip Select'', le bit CAS fait office d'''Output Enable''. ===Les mémoires FPM=== Les '''mémoires FPM (''Fast Page Mode'')''' possédaient une petite amélioration, qui rendait l'adressage plus simple. Avec elles, il n'y a pas besoin de préciser deux fois la ligne si celle-ci ne changeait pas lors de deux accès consécutifs : on pouvait garder la ligne sélectionnée durant plusieurs accès. Par contre, il faut quand même préciser les adresses de colonnes à chaque changement d'adresse. Il existe une petite différence entre les mémoire ''Page Mode'' et les mémoires ''Fast-Page Mode'' proprement dit. Sur les premières, le signal CAS est censé passer à 0 avant qu'on fournisse l'adresse de colonne. Avec les ''Fast-Page Mode'', l'adresse de colonne pouvait être fournie avant que l'on configure le signal CAS. Cela faisait gagner un petit peu de temps, en réduisant quelque peu le temps d'accès total. [[File:Sélection d'une ligne sur une mémoire FPM ou EDO.png|centre|vignette|upright=2|Sélection d'une ligne sur une mémoire FPM ou EDO.]] Avec les '''mémoires en mode quartet''', il est possible de lire quatre octets consécutifs sans avoir à préciser la ligne ou la colonne à chaque accès. On envoie l'adresse de ligne et l'adresse de colonne pour le premier accès, mais les accès suivants sont fait automatiquement. La seule contrainte est que l'on doit générer un front descendant sur le signal CAS pour passer à l'adresse suivante. Vous aurez noté la ressemblance avec le mode rafale vu il y a quelques chapitres, mais il y a une différence notable : le mode rafale vrai n'aurait pas besoin qu'on précise quand passer à l'adresse suivante avec le signal CAS. [[File:Mode quartet.png|centre|vignette|upright=3|Mode quartet.]] Les '''mémoires FPM à colonne statique''' se passent même du signal CAS. Le changement de l'adresse de colonne est détecté automatiquement par la mémoire et suffit pour passer à la colonne suivante. Dans ces conditions, un délai supplémentaire a fait son apparition : le temps minimum entre deux sélections de deux colonnes différentes, appelé tCAS-to-CAS. [[File:Accès en colonne statique.jpg|centre|vignette|upright=2.5|Accès en colonne statique.]] ===Les mémoires EDO-RAM=== L''''EDO-RAM''' a été inventée quelques années après la mémoire FPM. Elle a été déclinée en deux versions : la EDO simple, et la EDO en rafale. L'EDO simple ajoutait une entrée ''Ouput Enable'' à une mémoire FPM. Pour rappel, l'entrée ''Ouput Enable'' permet de connecter/déconnecter la DRAM du bus de données. S'il est mis à 0, les lectures et écritures sont empêchées. Pour ajouter cette entrée, il a fallu rajouter un registre sur la sortie de donnée, celle qui sert pour les lectures. Et l'ajout de ce registre a introduit une capacité dite de ''pipelining'', sur le même modèle que pour les mémoires SRAM synchrones. La donnée pouvait être maintenue sur le bus de données durant un certain temps, même après la remontée du signal CAS. Le registre de sortie maintenait la donnée lu tant que le signal RAS restait à 0, et tant qu'un nouveau signal CAS n'a pas été envoyé. Faire remonter le signal CAS à 1 n'invalidait pas la donnée en sortie. La conséquence est qu'on pouvait démarrer une nouvelle lecture alors que la donnée de l'accès précédent était encore présent sur le bus de données. Le pipeline obtenu avait deux étages : un où on présentait l'adresse et sélectionnait la colonne, un autre où la donnée était lue depuis le registre de sortie. Les mémoires EDO étaient donc plus rapides. [[File:EDO RAM.png|centre|vignette|upright=3|EDO RAM]] Cependant, cela marchait surtout pour les lectures, pas pour les écritures. Une écriture ne démarre que quand la lecture ou écriture précédente est totalement terminée. De même, on ne peut pas démarrer un nouvel accès mémoire tant qu'une écriture est en cours. ===Les mémoires EDO-RAM avec mode rafale=== Les '''EDO en rafale''' effectuent les accès à 4 octets consécutifs automatiquement : il suffit d'adresser le premier octet à lire. Les 4 octets étaient envoyés sur le bus les uns après les autres, au rythme d'un par cycle d’horloge : ce genre d'accès mémoire s'appelle un accès en rafale. [[File:Accès en rafale.png|centre|vignette|upright=2|Accès en rafale sur une DRAM EDO.]] Implémenter cette technique nécessite d'ajouter un compteur, capable de faire passer d'une colonne à une autre quand on lui demande, et quelques circuits annexes pour commander le tout. [[File:Modifications du contrôleur mémoire liées aux accès en rafale.png|centre|vignette|upright=2|Modifications du contrôleur de DRAM liées aux accès en rafale.]] ===Le rafraichissement mémoire=== Les mémoires FPM et EDO doivent être rafraichies régulièrement. Au début, le rafraichissement se faisait ligne par ligne. Le rafraichissement avait lieu quand le RAS passait à l'état haut, alors que le CAS restait à l'état bas. Le processeur, ou le contrôleur mémoire, sélectionnait la ligne à rafraichir en fournissant son adresse mémoire. D'où le nom de '''rafraichissement par adresse''' qui est donné à cette méthode de commande du rafraichissement mémoire. Divers processeurs implémentaient de quoi faciliter le rafraichissement par adresse. Par exemple, le Zilog Z80 contenait un compteur de ligne, un registre qui contenait le numéro de la prochaine ligne à rafraichir. Il était incrémenté à chaque rafraichissement mémoire, automatiquement, par le processeur lui-même. Un ''timer'' interne permettait de savoir quand rafraichir la mémoire : quand ce ''timer'' atteignait 0, une commande de rafraichissement était envoyée à la mémoire, et le ''timer'' était ''reset''. [[File:Rafraichissement mémoire manuel.png|centre|vignette|upright=2|Rafraichissement mémoire manuel.]] Par la suite, certaines mémoires ont implémenté un compteur interne d'adresse, pour déterminer la prochaine adresse à rafraichir sans la préciser sur le bus d'adresse. Le déclenchement du rafraichissement se faisait toujours par une commande externe, provenant du contrôleur de DRAM ou du processeur. Cette commande faisait passer le CAS à 0 avant le RAS. Cette méthode de rafraichissement se nomme '''rafraichissement interne'''. [[File:Rafraichissement sur CAS précoce.png|centre|vignette|upright=2|Rafraichissement sur CAS précoce.]] On peut noter qu'il est possible de déclencher plusieurs rafraichissements à la suite en laissant le signal CAS dans le même état. Ce genre de choses pouvait avoir lieu après une lecture : on pouvait profiter du fait que le CAS soit mis à zéro par la lecture ou l'écriture pour ensuite effectuer des rafraichissements en touchant au signal RAS. Dans cette situation, la donnée lue était maintenue sur la sortie durant les différents rafraichissements. [[File:Rafraichissements multiples sur CAS précoce.png|centre|vignette|upright=2|Rafraichissements multiples sur CAS précoce.]] ==Les mémoires SDRAM== Dans les années 90, les mémoires asynchrones ont laissé la place aux '''mémoires SDRAM''', qui sont synchronisées avec le bus par une horloge. L'utilisation d'une horloge a comme avantage des temps d'accès fixes : le processeur sait qu'un accès mémoire prendra un nombre déterminé de cycles d'horloge. Avec les mémoires asynchrones, le processeur ne pouvait pas prévoir quand la donnée serait disponible et ne faisait rien tant que la mémoire n'avait pas répondu : il exécutait ce qu'on appelle des ''wait states'' en attendant que la mémoire ait fini. Les mémoires SDRAM sont standardisées par un organisme international, le JEDEC. Le standard SDRAM impose des spécifications électriques bien précise pour les barrettes de mémoire et le bus mémoire, décrit le protocole utilisé pour communiquer avec les barrettes de mémoire, et bien d'autres choses encore. Les SDRAM ont été déclinées en versions de performances différentes, décrites dans le tableau ci-dessous : {| class="wikitable" ! Nom standard ! Fréquence ! Bande passante |- | PC66 | 66 mhz | 528 Mio/s |- | PC66 | 100 mhz | 800 Mio/s |- | PC66 | 133 mhz | 1064 Mio/s |- | PC66 | 150 mhz | 1200 Mio/s |} ===Les banques internes aux chips mémoires SDRAM=== L'intérieur d'une mémoire SDRAM contient plusieurs '''banques''', aussi appelées des banc mémoire. Concrètement, une banque est... une mémoire. Ou plutôt, une sorte de mini-mémoire miniature. Chaque banque a son propre tampon de ligne, ses propres multiplexeurs de colonne et ses propres décodeurs. C'est comme si une SDRAM regroupait plusieurs mémoires séparées dans un même circuit intégré. [[File:Arrangement vertical.jpg|centre|vignette|upright=2.5|Mémoire multi-banques.]] Un point important est que chaque banque a son propre tampon de ligne. Il est donc possible d'ouvrir plusieurs lignes en même temps, chacune dans une banque différente. Par exemple, on peut ouvrir une ligne dans la banque numéro 1, et une autre ligne dans la banque numéro 2. Et c'est une source d'optimisations très intéressantes. La première optimisation est liée au rafraichissement mémoire. Au lieu de rafraichir chaque adresse une par une, il est possible de rafraichir des banques indépendantes en même temps, ce qui divise le temps de rafraichissement par le nombre de banques. C'est ce que je sous-entendais plus haut quand je disais que le temps de rafraichissement n'est pas égal au temps de balayage sur les SDRAM, alors que c'est le cas sur les DRAM FPM et EDO. De plus, et sans rentrer dans les détails, cela permet de faire plusieurs accès mémoire en même temps, dans des banques différentes. La possibilité est limitée, mais elle existe et elle améliore grandement la performance de la SDRAM. Mais nous en reparlerons dans un chapitre ultérieur, car cette histoire d'accès simultanés a plus sa place dans le chapitre sur le parallélisme mémoire. Pour le moment, nous ne pouvons pas expliquer pourquoi ni comment un processeur pourrait émettre plusieurs accès mémoire simultanés. Un processeur est censé travailler une instruction à la fois, à ce stade du cours, il ne peut pas en faire plusieurs en même temps. Mais nous allons cependant mentionner un cas où cette possibilité est intéressante : une mémoire SDRAM partagée entre un processeur et une carte graphique. Les deux accèdent à des données séparées, qui sont dans des banques différentes. On suppose que la carte graphique accède plus fréquemment à la mémoire que le processeur. Le contrôleur mémoire reçoit les accès mémoire du CPU et du GPU et il tente d'intercaler des accès CPU entre deux accès de la carte graphique. Vu qu'ils tombent dans des banques différentes, un accès CPU et un accès carte graphique peuvent se faire presque en même temps. La seule contrainte est que si on lance plusieurs accès mémoire simultanés, ceux-ci ne peuvent pas utiliser le bus de données en même temps. {|class="wikitable" |+ Pipelining basique sur les SDRAM |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 |- ! Banque Numéro 1 | || || || || || || || || || || |- ! Banque Numéro 2 | || colspan="3" bgcolor="#A0FFFF" | Accès CPU || || || colspan="3" bgcolor="#A0FFFF" | Accès CPU || || |- ! Banque Numéro 3 | || || || || || || || || || || |- ! Banque Numéro 4 | || || || colspan="3" bgcolor="#FFA0FF" | Accès carte graphique || || colspan="3" bgcolor="#FFA0FF" | Accès carte graphique || |} ===Le mode rafale des SDRAM=== Un point important est que les SDRAM reprennent les optimisations des mémoires FPM et EDO. Elles utilisent aussi un tampon de ligne, avec la possibilité de lire plusieurs colonnes à la suite sans avoir à préciser l'adresse de ligne à chaque fois. Mais surtout, elles gèrent nativement le mode rafale. les paramètres qui ont trait au mode rafale sont configurables. Il est possible de configurer la SDRAM pour activer les accès sans rafale, ou les désactiver. Il y a aussi la possibilité de configurer le nombre d'octets consécutifs à lire ou écrire en mode rafale. On peut ainsi accéder à 1, 2, 4, ou 8 octets en une seule fois, alors que les EDO ne permettaient que des accès à 4 octets consécutifs. Enfin, on peut décider s'il faut faire un accès en mode linéaire ou entrelacé. La configuration de la SDRAM est mémorisée dans un registre de 10 bits, le '''registre de mode'''. Il faisait 10 bits sur les mémoires SDRAM, mais a été étendu à 13 bits sur la DDR2. Voici les 10 bits originels de ce registre : {|class="wikitable" |+ Signification des bits du registre de mode des SDRAM |- ! Bit n°9 | Type d'accès : en rafale ou normal |- ! Bit n°8 et 7 | Doivent valoir 00, sont réservés pour une utilisation ultérieur dans de futurs standards. |- ! Bit n°6, 5, et 4 | Latence CAS (voir plus bas) |- ! Bit n°3 | Type de rafale : linéaire ou entrelacée |- ! Bit n°2, 3, et 0 | Longueur de la rafale : indique le nombre d'octets à lire/écrire lors d'une rafale. |} ===L'interface d'une mémoire SDRAM=== Le bus de commandes d'une SDRAM contient au moins 18 fils, dont celui pour le signal d'horloge. L'interface d'une SDRAM contient tous les bits présents sur une mémoire DRAM classique : une entrée RAS, une entrée CAS, une entrée R/W, et un bus d'adresse. A cela, il faut cependant ajouter une entrée ''Chip Select'' (CS), qui permet d'activer/désactiver la mémoire SDRAM. Je rappelle que le bit CS a été introduit sur les mémoires SDRAM, il n'était pas présent sur les mémoires FPM/EDO. Deux autres bits de commande sont vraiment spécifiques des mémoires SDRAM. Il s'agit des bits CKE et DQM. Le '''bit CKE''' est l'abréviation de ''Clock Enable'', qui qui trahit sa fonction. Lorsque ce signal est à 0, le chip de SDRAM voit son signal d'horloge gelè. S'il est à 0, le contrôleur de DRAM peut envoyer ce qu'il veut sur le bus de commande ou d'adresse, la SDRAM ne réagira pas du tout, il ne se passera rien. Le '''bit DQM''' est une sorte de bit ''Output Enable'', avec une nuance importante. Le terme DQM est l'abréviation de ''Data Mask'', ce qui trahit encore une fois sa fonction. Il y a un bit DQM pour chaque octet du bus de données. Une SDRAM ayant un bus de 64 bits, cela fait 8 bits DQM par mémoire SDRAM. Lorsque le bit DQM est à 1, l'octet en question n'est simplement pas lu ou écrit, le bus de donnée est déconnecté pour cet octet. Le bus d'adresse est particulier, car il tient compte de la présence de ''banques mémoires''. Le bus d'adresse est découpé en deux portions : une portion pour sélectionner la banque, une autre pour sélectionner l'adresse à l'intérieur d'une banque. L'interface de la SDRAM fait ainsi la différence entre une '''adresse de banque''' et une '''adresse intra-banque'''. L'adresse de banque est codée sur quelques bits, généralement deux ou trois suivant la SDRAM. Le reste de l'adresse est codé sur 11 bits sur les SDRAM, mais cela a augmenté avec les DDR 1, 2, 3, 4, 5. Le bus de données d'une SDRAM fait 4, 8, ou 16 bits. Je précise bien qu'il s'agit là des puces de SDRAM, les barrettes de SDRAM combinent plusieurs puces SDRAM avec un arrangement horizontal, qui peut combiner plusieurs puces de 8 bits pour alimenter un bus de données de 64 bits. La taille des puces utilisées souvent indiquée sur la barrette de RAM, avec une mention x4, x8 ou x16. Les puces de SDRAM les plus courantes ont une interface de 8 bits pour les données. Les SDRAM de 4 bits sont surtout utilisées pour les serveurs, c'est lié au support de l'ECC. les puces x16 sont moins utilisées car elles ont généralement moins de banques que les autres. ===Les commandes SDRAM=== Le bus de commande permet d'envoyer des commandes à la mémoire, chaque commande étant précisée par une combinaison précise des bits CS, RAS, CAS, R/W, et autres. Les commandes en question sont des demandes de lecture, d'écriture, de préchargement et autres. Elles sont codées par une valeur bien précise qui est envoyée sur les 18 fils du bus de commande. Ces commandes sont nommées READ, READA, WRITE, WRITEA, PRECHARGE, ACT, ... Les plus importantes sont les commandes PRECHARGE, ACT et READ/WRITE. La commande ACT sélectionne une ligne : elle met le bit RAS à zéro et présente une adresse de ligne. Les commandes READ et WRITE sélectionnent une colonne, et déclenchent respectivement une lecture ou une écriture. Elles précisent une adresse de colonne, mettent le bit CAS à 0 et le bit RAS à 1, et précise la valeur du bit R/W. Les commandes READ et WRITE ne peuvent se faire qu'une fois que la banque a été activée par une commande ACT. Il est possible d'envoyer plusieurs commandes READ ou WRITE successives à des colonnes différentes, ce qui permet d'implémenter les optimisations des mémoires FPM. La commande PRECHARGE ferme la ligne courante et prépare l'ouverture de la suivante. Elle précharge les lignes de bit de la RAM, d'où son nom. Il est nécessaire d'en envoyer une avant d'envoyer une commande ACT. Notons que la commande PRECHARGE agit sur une banque, dont l'adresse est indiquée dans la commande PRECHARGE. Il existe une commande PRECHARGE ALL, qui agit sur toutes les banques de la SDRAM à la fois. Les commandes READA et WRITEA fusionnent une commande READ/WRITE avec une commande PRECHARGE. Elles permettent d'éviter d'avoir à envoyer une commande PRECHARGE pour fermer la ligne courante. Au lieu d'envoyer une commande READ ou WRITE, puis une commande PRECHARGE pour fermer la ligne, on envoie une seule commande READA/WRITEA. Il s'agit d'une petite optimisation, qui permet de réduire le nombre de commandes envoyées sur le bus. Les commandes sont encodées comme indiquées dans ce tableau. Une commande est tout simplement encodée en précisant une adresse si nécessaire, et une combinaison des bits CS, RAS, CAS et R/W. La seule subtilité est que le bit numéro 10 du bus d'adresse sert à commander les opérations de PRECHARGE, y compris celles implicites dans les opérations READA et WRITEA. {| class="wikitable" style="text-align:center" ! Bit CS || Bit RAS || Bit CAS || Bit WE || Bits de sélection de banque (2 bits) || Bit du bus d'adresse A10 || Reste du bus d'adresse || Nom de la commande : Description |- | 1 | colspan="6" | X | Absence de commandes. |- | 0 || 1 || 1 || 1 || colspan="3" | X || No Operation : Pas d'opération |- | 0 || 1 || 1 || 0 || colspan="3" | X || Burst Terminante : Stoppe un accès en rafale (en cours). |- | 0 || 1 || 0 || 1 || Adresse de la banque || 0 || Adresse de la colonne || READ : lit une donnée depuis la ligne active. |- | 0 || 1 || 0 || 1 || Adresse de la banque || 1 || Adresse de la colonne || READA : lit une donnée depuis la ligne active, puis ferme la ligne. |- | 0 || 1 || 0 || 0 || Adresse de la banque || 0 || Adresse de la colonne || WRITE : écrit une donnée dans la ligne active. |- | 0 || 1 || 0 || 0 || Adresse de la banque || 1 || Adresse de la colonne || WRITEA : écrit une donnée dans la ligne active, puis ferme la ligne. |- | 0 || 0 || 1 || 1 || Adresse de la banque || colspan="2" | Adresse de la ligne || ACT : charge une ligne dans le tampon de ligne. |- | 0 || 0 || 1 || 0 || Adresse de la banque || 0 || X || PRECHARGE : précharge le tampon de ligne dans la banque voulue. |- | 0 || 0 || 1 || 0 || Adresse de la X || 1 || X || PRECHARGE ALL : précharge le tampon de ligne dans toutes les banques. |- | 0 || 0 || 0 || 1 || colspan="3" | X || Auto refresh : Demande de rafraichissement, gérée par la SDRAM. |- | 0 || 0 || 0 || 0 || 00 || colspan="2" | Nouveau contenu du registre de mode || LOAD MODE REGISTER : configure le registre de mode. |} Les commandes ACT se font à partir de l'état de repos, l'état où toutes les banques sont préchargées. Par contre, les commandes MODE REGISTER SET et AUTO REFRESH ne peuvent se faire que si toutes les banques sont désactivées. Le fonctionnement simplifié d'une SDRAM peut se résumer dans ce diagramme : [[File:Fonctionnement simplifié d'une SDRAM.jpg|centre|vignette|upright=2|Fonctionnement simplifié d'une SDRAM.]] ===Les délais mémoires=== Les mémoires SDRAM n'étant pas infiniment rapides, il y a toujours un certain délais à respecter entre deux commandes. Par exemple, quand on envoie une commande ACT pour activer une ligne, on ne peut pas envoyer une commande READ/WRITE au cycle suivant. La plupart des SDRAM ne sont pas assez rapides pour ça. Il faut respecter un délai de quelques cycles, qui dépend de la mémoire. Et il n'y a pas que ce délai entre une commande ACT et la commande suivante. Une SDRAM doit gérer d'autres temps d'attente, appelés des '''délais mémoires''', ou encore des ''timings'' mémoire. Les délais mémoire le plus importants sont résumés ci-dessous : {|class="wikitable" |- !Timing!!Description |- | colspan="2" | |- ! colspan="2" | Délais primaires |- ||tRP|| Temps entre une commande PRECHARGE et une commande ACT |- ||tRCD|| Temps entre une commande ACT et une commande READ/WRITE. |- ||tCL|| Temps entre une commande READ et l'envoi de la donnée lue sur le bus de données. |- ||tDQSS|| Temps entre une commande WRITE et l'écriture de la donnée. |- ||tCAS-to-CAS|| Temps minimum entre deux commandes READ. |- ! colspan="2" | Délais secondaires |- ||tWTR|| Temps entre une lecture et une écriture consécutives. |- ||tRAS || Temps entre une commande ACT et une commande PRECHARGE. |} La façon de mesurer ces délais varie : sur les mémoires FPM et EDO, on les mesure en unités de temps (secondes, millisecondes, micro-secondes, etc.), tandis qu'on les mesure en cycles d'horloge sur les mémoires SDRAM. Les délais/timings mémoire ne sont pas les mêmes suivant la barrette de mémoire que vous achetez. Certaines mémoires sont ainsi conçues pour avoir des timings assez bas et sont donc plus rapides, et surtout : beaucoup plus chères que les autres. Le gain en performances dépend beaucoup du processeur utilisé et est assez minime comparé au prix de ces barrettes. Les délais mémoires d'une barrette sont mémorisés dans le ''Serial Presence Detect'' de la barrette et sont lus par le BIOS au démarrage de l'ordinateur, et sont ensuite utilisés pour configurer le contrôleur de DRAM. ==Les mémoires DDR== Les mémoires SDRAM récentes sont des mémoires de type ''dual data rate'', ce qui fait qu'elles portent le nom de mémoires DDR. Pour rappel, les mémoires ''dual data rate'' ont un plan mémoire deux fois plus large que le bus mémoire, avec un bus mémoire allant à une fréquence double. Par double, on veut dire que les transferts sur le bus mémoire ont lieu sur les fronts montants et descendants de l'horloge. Il y a donc deux transferts de données sur le bus pour chaque cycle d'horloge, ce qui permet de doubler le débit sans toucher à la fréquence du plan mémoire lui-même. Les mémoires DDR sont standardisées par un organisme international, le JEDEC, et ont été déclinées en plusieurs générations : DDR1, DDR2, DDR3, et DDR4. La différence entre ces modèles sont très nombreuses, mais les plus évidentes sont la fréquence de la mémoire et du bus mémoire. D'autres différences mineures existent entre les SDRAM et les mémoires DDR. Par exemple, la tension d'alimentation des mémoires DDR est plus faible que pour les SDRAM. ET elle a diminué dans le temps, d'une génération de DDR à l'autre. Avec les mémoires DDR2,la tension d'alimentation est passée de 2,5/2,6 Volts à 1,8 Volts. Avec les mémoires DDR3, la tension d'alimentation est notamment passée à 1,5 Volts. ===Les performances des mémoires DDR=== Les mémoires SDRAM ont évolué dans le temps, mais leur temps d'accès/fréquence n'a pas beaucoup changé. Il valait environ 10 nanosecondes pour les SDRAM, approximativement 5 ns pour la DDR-400, il a peu évolué pendant la génération DDR et DDR3, avant d'augmenter pendant les générations DDR4 et de stagner à nouveau pour la génération DDR5. L'usage du DDR, puis du QDR, visait à augmenter les performances malgré la stagnation des temps d'accès. En conséquence, la fréquence du bus a augmenté plus vite que celle des puces mémoire pour compenser. {|class="wikitable" |- ! Année ! Type de mémoire ! Fréquence de la mémoire (haut de gamme) ! Fréquence du bus ! Coefficient multiplicateur entre les deux fréquences |- | 1998 | DDR 1 | 100 - 200 MHz | 200 - 400 MHz | 2 |- | 2003 | DDR 2 | 100 - 266 MHz | 400 - 1066 MHz | 4 |- | 2007 | DDR 3 | 100 - 266 MHz | 800 - 2133 MHz | 8 |- | 2014 | DDR 4 | 200 - 400 MHz | 1600 - 3200 MHz | 8 |- | 2020 | DDR 5 | 200 - 450 MHz | 3200 - 7200 MHz | 8 à 16 |} Une conséquence est que la latence CAS, exprimée en nombre de cycles, a augmenté avec le temps. Si vous comparez des mémoires DDR2 avec une DDR4, par exemple, vous allez voir que la latence CAS est plus élevée pour la DDR4. Mais c'est parce que la latence est exprimée en nombre de cycles d'horloge, et que la fréquence a augmentée. En comparant les temps d'accès exprimés en secondes, on voit une amélioration. ===Les commandes des mémoires DDR=== Les commandes des mémoires DDR sont globalement les mêmes que celles des mémoires SDRAM, vues plus haut. Les modifications entre SDRAM, DDR1, DDR2, DDR3, DDR4, et DDR5 sont assez mineures. Les seules différences sont l'addition de bits pour la transmission des adresses, des bits en plus pour la sélection des banques, etc. En clair, une simple augmentation quantitative. Le registre de mode a été un peu modifié. Il est passé de 10 bits pour les SDRAM et DDR1, à 13 bits sur la DDR 2 et les suivantes. Les DDR ont aussi ajouté le support de plusieurs registres de mode, qui sont sélectionnés en réutilisant l'adresse de banque. Dans une commande LOAD MODE REGISTER, l'adresse de banque indique quel registre de mode il faut altérer. Avant la DDR4, les modifications des commandes sont mineures. La DDR2 supprime la commande ''Burst Terminate'', la DDR3 et la DDR4 utilisent le bit A12 pour préciser s'il faut faire une rafale complète, ou une rafale de moitié moins de données. Une optimisation des DDR2 et 3 est celle des '''CAS postés'''. L'idée est que le contrôleur de DRAM peut envoyer une commande ACT et une commande READ/WRITE sans se soucier des ''timings'' nécessaires entre les deux. En théorie, les deux commandes doivent être séparées par quelques cycles, sur une SDRAM ou une DDR1. Mais avec la DDR2, le contrôleur de DRAM peut envoyer les deux l'une après l'autre, au cycle suivant. C'est la mémoire qui mettra en attente la commande READ/WRITE pour respecter les ''timings'' mémoire. Cela complexifie le fonctionnement interne de la DDR, mais simplifie grandement le travail du contrôleur de DRAM. Mais avec la DDR4, les choses changent, notamment au niveau de la commande ACT. Avec l'augmentation de la capacité des barrettes mémoires, la taille des adresses est devenue trop importante. Pour éviter de rajouter des bits d'adresses, les concepteurs du standard DDR4 ont décidé de ruser. Lors d'une commande ACT, les bits RAS, CAS et WE sont utilisés comme bits d'adresse, alors qu'ils ont leur signification normale pour les autres commandes. Pour éviter toute confusion, un nouveau bit ACT est ajouté pour indiquer la présence d'une commande ACT : il est à 1 pour une commande ACT, 0 pour les autres commandes. {| class="wikitable" style="text-align:center" |+ Commandes d'une mémoire DDR4, seule la commande colorée change par rapport aux SDRAM ! Bit CS || style="background: #CCFFCC" | Bit ACT || Bit RAS || Bit CAS || Bit WE || Bits de sélection de banque (4 bits) || Bit du bas d'adresse A10 || Reste du bus d'adresse || Nom de la commande : Description |- | 1 | colspan="6" | X | Absence de commandes. |- | 0 || style="background: #CCFFCC" | 0 || 1 || 1 || 1 || colspan="3" | X || No Operation : Pas d'opération |- | 0 || style="background: #CCFFCC" | 0 || 1 || 1 || 0 || colspan="3" | X || Burst Terminante : Arrêt d'un accès en rafale en cours. |- | 0 || style="background: #CCFFCC" | 0 || 1 || 0 || 1 || Adresse de la banque || 0 || Adresse de la colonne || READ : lire une donnée depuis la ligne active. |- | 0 || style="background: #CCFFCC" | 0 || 1 || 0 || 1 || Adresse de la banque || 1 || Adresse de la colonne || READA : lire une donnée depuis la ligne active, avec rafraichissement automatique de la ligne. |- | 0 || style="background: #CCFFCC" | 0 || 1 || 0 || 0 || Adresse de la banque || 0 || Adresse de la colonne || WRITE : écrire une donnée depuis la ligne active. |- | 0 || style="background: #CCFFCC" | 0 || 1 || 0 || 0 || Adresse de la banque || 1 || Adresse de la colonne || WRITEA : écrire une donnée depuis la ligne active, avec rafraichissement automatique de la ligne. |- style="background: #CCFFCC" | 0 || style="background: #CCFFCC" | 1 || colspan="3" | Adresse de la ligne (bits de poids forts) || Adresse de la banque || colspan="2" | Adresse de la ligne (bits de poids faible) || ACT : charge une ligne dans le tampon de ligne. |- | 0 || style="background: #CCFFCC" | 0 || 0 || 1 || 0 || Adresse de la banque || 0 || X || PRECHARGE : précharge le tampon de ligne dans la banque voulue. |- | 0 || style="background: #CCFFCC" | 0 || 0 || 1 || 0 || Adresse de la X || 1 || X || PRECHARGE ALL : précharge le tampon de ligne' dans toutes les banques. |- | 0 || style="background: #CCFFCC" | 0 || 0 || 0 || 1 || colspan="3" | X || Auto refresh : Demande de rafraichissement, gérée par la SDRAM. |- | 0 || style="background: #CCFFCC" | 0 || 0 || 0 || 0 || Numéro de registre de mode || colspan="2" | Nouveau contenu du registre de mode || LOAD MODE REGISTER : configure le registre de mode. |} ==Les VRAM des cartes vidéo== Les cartes graphiques ont des besoins légèrement différents des DRAM des processeurs, ce qui fait qu'il existe des mémoires DRAM qui leur sont dédiées. Elles sont appelés des '''''Graphics RAM''''' (GRAM). La plupart incorporent des fonctionnalités utiles uniquement pour les mémoires vidéos, comme des fonctionnalités de masquage (appliquer un masque aux données lue ou à écrire), ou le remplissage d'un bloc de mémoire avec une donnée unique. Les anciennes cartes graphiques et les anciennes consoles utilisaient de la DRAM normale, faute de mieux. La première GRAM utilisée était la NEC μPD481850, qui a été utilisée sur la console de jeu PlayStation, à partir de son modèle SCPH-5000. D'autres modèles de GRAM ont rapidement suivi. Les anciennes consoles de jeu, mais aussi des cartes graphiquesn utilisaient des GRAM spécifiques. ===Les mémoires vidéo double port=== Sur les premières consoles de jeu et les premières cartes graphiques, le ''framebuffer'' était mémorisé dans une mémoire vidéo spécialisée appelée une '''mémoire vidéo double port'''. Le premier port était connecté au processeur ou à la carte graphique, alors que le second port était connecté à un écran CRT. Aussi, nous appellerons ces deux port le ''port CPU/GPU'' et l'autre sera appelé le ''port CRT''. Le premier port était utilisé pour enregistrer l'image à calculer et faire les calculs, alors que le second port était utilisé pour envoyer à l'écran l'image à afficher. Le port CPU/GPU est tout ce qu'il y a de plus normal : on peut lire ou écrire des données, en précisant l'adresse mémoire de la donnée, rien de compliqué. Le port CRT est assez original : il permet d'envoyer un paquet de données bit par bit. De telles mémoires étaient des mémoires à tampon de ligne, dont le support de mémorisation était organisé en ligne et colonnes. Une ligne à l'intérieur de la mémoire correspond à une ligne de pixel à l'écran, ce qui se marie bien avec le fait que les anciens écrans CRT affichaient les images ligne par ligne. L'envoi d'une ligne à l'écran se fait bit par bit, sur un câble assez simple comme un câble VGA ou autre. Le second port permettait de faire cela automatiquement, en permettant de lire une ligne bit par bit, les bits étant envoyés l'un après l'autre automatiquement. Pour cela, les mémoires vidéo double port incorporaient un tampon de ligne spécialisé pour le port lié à l'écran. Ce tampon de ligne n'était autre qu'un registre à décalage, contrairement au tampon de ligne normal. Lors de l'accès au second port, la carte graphique fournissait un numéro de ligne et la ligne était chargée dans le tampon de ligne associé à l'écran. La carte graphique envoyait un signal d'horloge de même fréquence que l'écran, qui commandait le tampon de ligne à décalage : un bit sortait à chaque cycle d'écran et les bits étaient envoyé dans le bon ordre. ===Les mémoires SGRAM et GDDR=== De nos jours, les cartes graphiques n'utilisent plus de mémoires double port, mais des mémoires simple port. Les mémoires graphiques actuelles sont des SDRAM modifiées pour fonctionner en tant que ''Graphic RAM''. Les plus connues sont les '''mémoires GDDR''', pour ''graphics double data rate'', utilisées presque exclusivement sur les cartes graphiques. Il en existe plusieurs types pendant que j'écris ce tutoriel : GDDR, GDDR2, GDDR3, GDDR4, et GDDR5. Mais attention, il y a des différences avec les DDR normales. Par exemple, les GDDR ont une fréquence plus élevée que les DDR normales, avec des temps d'accès plus élevés (sauf pour le tCAS). De plus, elles sont capables de laisser ouvertes deux lignes en même temps. Par contre, ce sont des mémoires simple port. ==Les mémoires SLDRAM, RDRAM et associées== Les mémoires précédentes sont généralement associées à des bus larges. Les mémoires SDRAM et DDR modernes ont des bus de données de 64 bits de large, avec des d'adresse et de commande de largeur similaire. Le nombre de fils du bus mémoire dépasse facilement la centaine de fils, avec autant de broches sur les barrettes de mémoire. Largeur de ces bus pose de problèmes problèmes électriques, dont la résolution n'est pas triviale. En conséquence, la fréquence du bus mémoire est généralement moins performantes comparé à ce qu'on aurait avec un bus moins large. Mais d'autres mémoires DRAM ont exploré une solution alternative : avoir un bus peu large mais de haute fréquence, sur lequel on envoie les commandes/données en plusieurs fois. Elles sont regroupées sous le nom de '''DRAM à commutation par paquets'''. Elles utilisent des bus spéciaux, où les commandes/adresses/données sont transmises par paquets, par trames, en plusieurs fois. En théorie, ce qu'on a dit sur le codage des trames dans le chapitre sur le bus devrait s'appliquer à de telles mémoires. En pratique, les protocoles de transmission sur le bus mémoire sont simplifiés, pour gérer le fonctionnement à haute fréquence. Le processeur envoie des paquets de commandes, les mémoires répondent avec des paquets de données ou des accusés de réception. Les mémoires à commutation par paquets sont peu nombreuses. Les plus connues sont les mémoires conçues par la société Rambus, à savoir la ''RDRAM'' (''Rambus DRAM'') et ses deux successeurs ''XDR RAM'' et ''XDR RAM 2''. La ''Synchronous-link DRAM'' (''SLDRAM'') est un format concurrent conçu par un consortium de plusieurs concepteurs de mémoire. ===La SLDRAM (''Synchronous-link DRAM'')=== Les '''mémoires SLDRAM''' avaient un bus de données de 64 bits allant à 200-400 Hz, avec technologie DDR, ce qui était dans la norme de l'époque pour la fréquence (début des années 2000). Elle utilisait un bus de commande de 11 bits, qui était utilisé pour transmettre des commandes de 40 bits, transmises en quatre cycles d'horloge consécutifs (en réalité, quatre fronts d'horloge donc deux cycles en DDR). Le bus de données était de 18 bits, mais les transferts de donnée se faisaient par paquets de 4 à 8 octets (32-65 bits). Pour résumer, données et commandes sont chacunes transmises en plusieurs cycles consécutifs, sur un bus de commande/données plus court que les données/commandes elle-mêmes. Là où les SDRAM sélectionnent la bonne barrette grâce à des signaux de commande dédiés, ce n'est pas le cas avec la SLDRAM. A la place, chaque barrette de mémoire reçoit un identifiant, un numéro codé sur 7-8 bits. Les commandes de lecture/écriture précisent l'identifiant dans la commande. Toutes les barrettes reçoivent la commande, elles vérifient si l'identifiant de la commande est le leur, et elles la prennent en compte seulement si c'est le cas. Voici le format d'une commande SLDRAM. Elle contient l'adresse, qui regroupe le numéro de banque, le numéro de ligne et le numéro de colonne. On trouve aussi un code commande qui indique s'il faut faire une lecture ou une écriture, et qui configure l'accès mémoire. Il configure notamment le mode rafale, en indiquant s'il faut lire/écrire 4 ou 8 octets. Enfin, il indique s'il faut fermer la ligne accédée une fois l'accès terminé, ou s'il faut la laisser ouverte. Le code commande peut aussi préciser que la commande est un rafraichissement ou non, effectuer des opérations de configuration, etc. L'identifiant de barrette mémoire est envoyé en premier, histoire que les barrettes sachent précocement si l'accès les concerne ou non. {|class="wikitable" style="text-align:center" |+SLDRAM Read, write or row op request packet ! FLAG || CA9 || CA8 || CA7 || CA6 || CA5 || CA4 || CA3 || CA2 || CA1 || CA0 |- ! 1 | colspan=9 bgcolor=#ffcccc| Identifiant de barrette mémoire|| bgcolor=#ccffcc| Code de commande |- ! 0 | colspan=5 bgcolor=#ccffcc| Code de commande ||colspan=3 bgcolor=#ff88ff| Banque||colspan=2 bgcolor=#ffffcc| Ligne |- ! 0 | colspan=9 bgcolor=#ffffcc| Ligne || 0 |- ! 0 | 0 || 0 || 0 ||colspan=7 bgcolor=#ccffff| Colonne |} ===Les mémoires Rambus=== Les mémoires conçues par la société Rambus regroupent la '''RDRAM''' (''Rambus DRAM'') et ses deux successeurs '''XDR RAM''' et '''XDR RAM 2'''. Les toutes premières étaient les '''mémoires RDRAM''', où le bus permettait de transmettre soit des commandes (adresse inclue), soit des données, avec un multiplexage total. Le processeur envoie un paquet contenant commandes et adresse à la mémoire, qui répond avec un paquet d'acquittement. Lors d'une lecture, le paquet d'acquittement contient la donnée lue. Lors d'une écriture, le paquet d'acquittement est réduit au strict minimum. Le bus de commandes est réduit au strict minimum, à savoir l'horloge et quelques bits absolument essentiels, les bits RW est transmis dans un paquet et n'ont pas de ligne dédiée, pareil pour le bit OE. Toutes les barrettes de mémoire doivent vérifier toutes les transmissions et déterminer si elles sont concernées en analysant l'adresse transmise dans la trame. Elles ont été utilisées dans des PC ou d'anciennes consoles de jeu. Par exemple, la Nintendo 64 incorporait 4 mébioctets de mémoire RDRAM en tant que mémoire principale. La RDRAM de la Nintendo 64 était cadencée à 500 MHz, utilisait un bus de 9 bits, et avait un débit binaire maximal théorique de 500 MB/s. La Playstation 2 contenait quant à elle 32 mébioctets de RDRAM en ''dual-channel'', pour un débit binaire de 3.2 Gibioctets par seconde. Les processeurs Pentium 3 pouvaient être associés à de la RDRAM sur certaines mères. Les Pentium 4 étaient eux aussi associés à la de RDRAM, mais les cartes mères ne géraient que ce genre de mémoire. La Playstation 3 contenait quant à elle de la XDR RAM. ==Les eDRAM : des DRAM adaptées aux ''chiplets''== Les '''mémoires eDRAM''', pour ''embedded DRAM'', sont des mémoires RAM qui sont destinées à être intégrée au processeur. Pour comparer, les DRAM normales sont placées sur des barrettes de RAM ou soudées à la carte mère. Dans la quasi-totalité des cas, l'eDRAM est utilisée pour implémenter une mémoire cache, elle ne sert pas de mémoire principale (cache L4, le plus proche de la mémoire sur ces puces). De ce fait, elles sont conçues pour être très rapides, avoir une grande bande passante, au détriment de leur capacité mémoire. Pour être plus précis, l'eDRAM est une puce de DRAM conçue pour être intégrée dans un ''chiplet'', , à savoir des circuits imprimés qui regroupent plusieurs puces électroniques distinctes, regroupées sur le même PCB. Typiquement, un processeur de type ''chiplet'' avec de l'eDRAM comprend deux puces séparées : une pour le processeur, une autre pour une puce de communication avec la RAM. Avec la mémoire eDRAM, les deux puces sont complétées par une troisième puce spécialisée qui incorpore l'eDRAM. Elle a été utilisée sur quelques processeurs, mais aussi dans des consoles de jeu vidéo, pour la carte graphique des consoles suivantes : la PlayStation 2, la PlayStation Portable, la GameCube, la Wii, la Wii U, et la XBOX 360. Sur ces consoles, la RAM de la carte graphique était intégrée avec le processeur graphique dans le même circuit. La fameuse mémoire vidéo et le GPU n'étaient qu'une seule et même puce électronique, un seul circuit intégré. Ce n'est pas le cas sur une carte graphique moderne : regardez votre carte graphique avec attention et vous verrez que le GPU est une puce carrée située sous les ventilateurs, alors que les puces mémoires sont situées juste autour et soudées sur le PCB de la carte. Les processeurs Intel Core de microarchitecture Broadwell disposaient d'un cache L4 de 128 mébioctets, intégralement implémenté avec de la mémoire eDRAM. Quelques processeurs de la microarchitecture précédente (Haswell), disposaient aussi de ce cache. Le cache L4 eDRAM était implémenté sur un chiplet à part, à savoir que le processeur était composé de trois puces séparées : une pour le processeur, une autre pour la gestion des entrées-sorties, et une autre pour le cache L4. La puce pour le cache L4 était appelée ''Crystal Well''. La puce ''Crystal Well'' était une puce gravée en 22nm, ce qui était une finesse de gravure plus élevée que celle des processeurs associés. ''Crystal Well'' était très optimisé pour l'époque. Par exemple, elle disposait de bus séparées pour la lecture et l'écriture, chose qu'on retrouve fréquemment sur les SRAM mais qui est absent sur les mémoires DRAM actuelles. Pour le reste, elle ressemblait beaucoup aux mémoires DDR de l'époque (système de ''double data rate'', entres autres), mais elle allait à une fréquence plus élevée que les DRAM de l'époque et avait un débit bien plus élevé, pour une consommation moindre. ''Crystal Well'' consommait entre 1 à 5 watts (1 watt en veille, 5 à pleine utilisation), pour un débit binaire de 102 GB/s et fonctionnait à 3.2 GHz. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les mémoires SRAM synchrones | prevText=Les mémoires SRAM synchrones | next=Contrôleur mémoire externe | nextText=Le contrôleur mémoire externe }}{{autocat}} </noinclude> frqvlmgp92mfq7racni2kjlo0ts79t3 764645 764644 2026-04-23T13:57:56Z Mewtow 31375 /* L'arrangement horizontal et vertical combinés */ 764645 wikitext text/x-wiki Après avoir vu les mémoires statiques (SRAM), il est temps de passer aux mémoires RAM dynamiques, aussi appelées mémoires DRAM. Pour rappel, les DRAM dynamiques ont pour défaut que leurss données s'effacent après un certain temps, en quelques millièmes ou centièmes de secondes . En conséquence, il faut réécrire chaque bit de la mémoire régulièrement pour éviter qu'il ne s'efface. On dit qu'on doit effectuer régulièrement un '''rafraîchissement mémoire'''. Et celui-ci rend les DRAM très différentes des SRAM. Les DRAM des PC ont beaucoup évolués dans le temps. Les toutes premières mémoires DRAM étaient des mémoires asynchrones, mais elles ont été remplacées par des modèles synchrones. Les modèles asynchrones ont été très nombreux. Après l'apparition des premières DRAM, les mémoires ''Fast Page Mode'' sont apparues, suivies par les mémoires ''Extended Data Out'', puis les EDO en mode rafale. Elles ont été utilisées jusque dans la moitié des années 90, pour ensuite être supplantées par les mémoires SDRAM. Les mémoires DDR actuelles sont des améliorations des mémoires SDRAM actuelles. Le fait est que les DRAM sont des mémoires électroniques comme les autres, qui se présentent sous la forme de circuits intégrés, à savoir que ce sont des petits boitiers noirs avec des broches. Il est possible de souder ces boitiers sur une cartre mère, et c'est ce qui est fait sur nombre d'ordinateurs portables. Mais il est aussi possible de regrouper plusieurs boitiers sur une barrette de RAM séparée. Dans ce qui suit, nous les appellerons des '''chips mémoire''', ou encore, des puces mémoires. [[File:Canyon CN-WF514 - EtronTech EM638325TS-6-4022.jpg|centre|vignette|upright=2|Exemple de chip mémoire.]] Dans ce qui suit, nous allons étudier ces chips de DRAM, avant de voir comment ils sont regroupés sur une barrette de RAM. Puis, nous allons voire chaque type de mémoire, FPM, EDO, SDRAM, DDR, ... ; un par un. ==L'interface des DRAM et le contrôleur mémoire== L'interface d'une mémoire DRAM est plus compliquée que l'interface d'une SRAM basique. Et c'est suffisant pour qu'on ait besoin d'un intermédiaire pour faire la conversion entre processeur et DRAM. Les DRAM modernes ne sont pas connectées directement au processeur, mais le sont par l'intermédiaire d'un '''contrôleur mémoire externe'''. Il ne faut pas le confondre avec le contrôleur mémoire interne, placé dans la mémoire RAM, et qui contient notamment le décodeur. Les deux sont totalement différents, bien que leur nom soit similaire. Pour éviter toute confusion, j'utiliserais le terme de '''contrôleur de DRAM''', plus parlant. ===Le bus d'adresse des DRAM est multiplexé=== Un point important pour le contrôleur de DRAM est de transformer les adresses mémoires fournies par le processeur, en adresses utilisables par la DRAM. Car les DRAM ont une interface assez spécifique. Les DRAM ont ce qui s'appelle un '''bus d'adresse multiplexé'''. Avec de tels bus, l'adresse est envoyée en deux fois. Les bits de poids fort sont envoyés avant les bits de poids faible. On peut ainsi envoyer une adresse de 32 bits sur un bus d'adresse de 16 bits, par exemple. Le bus d'adresse contient alors environ moitié moins de fils que la normale. Pour rappel, l'avantage de cette méthode est qu'elle permet de limiter le nombre de fils du bus d'adresse, ce qui très intéressant sur les mémoires de grande capacité. Les mémoires DRAM étant utilisées comme mémoire principale d'un ordinateur, elles devaient avoir une grande capacité. Cependant, avoir un petit nombre de broches sur les barrettes de mémoire est clairement important, ce qui impose d'utiliser des stratagèmes. Envoyer l'adresse en deux fois répond parfaitement à ce problème : cela permet d'avoir des adresses larges et donc des mémoires de forte capacité, avec une performance acceptable et peu de fils sur le bus d'adresse. Les bus multiplexés se marient bien avec le fait que les DRAM sont des mémoires à adressage par coïncidence ou à tampon de ligne. Sur ces mémoires, l'adresse est découpée en deux : une adresse haute pour sélectionner la ligne, et une adresse basse qui sélectionne la colonne. L'adresse est envoyée en deux fois : la ligne, puis la colonne. Pour savoir si une donnée envoyée sur le bus d'adresse est une adresse de ligne ou de colonne, le bus de commande de ces mémoires contenait deux fils bien particuliers : les RAS et le CAS. Pour simplifier, le signal RAS permettait de sélectionner une ligne, et le signal CAS permettait de sélectionner une colonne. [[File:Signaux RAS et CAS.png|centre|vignette|upright=2|Signaux RAS et CAS.]] Si on a deux bits RAS et CAS, c'est parce que la mémoire prend en compte les signaux RAS et CAS quand ils passent de 1 à 0. C'est à ce moment là que la ligne ou colonne dont l'adresse est sur le bus sera sélectionnée. Tant que des signaux sont à zéro, la ligne ou colonne reste sélectionnée : on peut changer l'adresse sur le bus, cela ne désélectionnera pas la ligne ou la colonne et la valeur présente lors du front descendant est conservée. [[File:L'intérieur d'une FPM.png|centre|vignette|upright=2|L'intérieur d'une FPM.]] ===Le rafraichissement mémoire=== La spécificité des DRAM est qu'elles doivent être rafraichies régulièrement, sans quoi leurs cellules perdent leurs données. Le rafraichissement est basiquement une lecture camouflée. Elle lit les cellules mémoires, mais n'envoie pas le contenu lu sur le bus de données. Rappelons que la lecture sur une DRAM est destructive, à savoir qu'elle vide la cellule mémoire, mais que le système d'amplification de lecture régénère le contenu de la cellule automatiquement. La cellule est donc rafraichie automatiquement lors d'une lecture. La quasi-totalité des DRAM supporte des commandes de rafraichissement, séparées des lectures et écritures classiques. Une commande de rafraichissement ordonne de rafraichir une adresse, voire une ligne complète. Les commandes de rafraichissement sont générées par le contrôleur de DRAM, dans la grosse majorité des cas. Il est aussi possible que ce soit le processeur qui les génère, mais c'est beaucoup plus rare. Il est aussi possible d'envoyer des commandes de rafraichissement vides, qui ne précisent ni adresse ni numéro de ligne. Pour les gérer, la mémoire contient un compteur, qui pointe sur la prochaine ligne à rafraichir, qui est incrémenté à chaque commande de rafraichissement. Une commande de rafraichissement indique à la mémoire d'utiliser l'adresse dans ce compteur pour savoir quelle adresse/ligne rafraichir. [[File:Rafraichissement mémoire automatique.png|centre|vignette|upright=2|Rafraichissement mémoire automatique.]] Il existe des mémoires qui sont des intermédiaires entre les mémoires SRAM et DRAM. Il s'agit des '''mémoires pseudo-statiques''', qui sont techniquement des mémoires DRAM, utilisant des transistors et des condensateurs, mais qui gèrent leur rafraichissement mémoire toutes seules. Le rafraichissement mémoire est alors totalement automatique, ni le processeur, ni le contrôleur de DRAM ne devant s'en charger. Le rafraichissement est purement le fait des circuits de la mémoire RAM et devient une simple opération de maintenance interne, gérée par la RAM elle-même. L'envoi des commandes de rafraichissement peuvent se faire de deux manières : soit on les envoie toutes en même temps, soit on les disperse le plus possible. Le premier cas est un '''rafraichissement en rafale''', le second un '''rafraichissement étalé'''. Le rafraichissement en rafale n'est pas utilisé dans les PC, car il bloque la mémoire pendant un temps assez long. Mais les anciennes consoles de jeu gagnaient parfois à utiliser eu rafraichissement en rafale. En effet, la mémoire était souvent effacée entre l'affichage de deux images, pour éviter certains problèmes dont on ne parlera pas ici. Le rafraichissement de la mémoire était effectué à ce moment là : l'effacement rafraichissait la mémoire. Le temps mis pour rafraichir la mémoire est le temps mis pour parcourir toute la mémoire. Il s'agit du temps de balayage vu dans le chapitre sur les performances d'un ordinateur. Pour les mémoires FPM et EDO, il est défini en divisant la capacité de la mémoire par son débit binaire. C'est le temps nécessaire pour lire ou réécrire tout le contenu de la mémoire. Sur les SDRAM, les choses sont un peu différentes, pour une raison qu'on expliquera plus bas. ===Le contrôleur de DRAM=== Le contrôleur de DRAM gère le bus mémoire et tout ce qui est envoyé dessus. Il envoie des commandes aux barrettes de mémoire, commandes qui peuvent être des lectures, des écritures, ou des demandes de rafraichissement, parfois d'autres commandes. La mémoire répond à ces commandes par l'action adéquate : lire la donnée et la placer sur le bus de données pour une commande de lecture, par exemple. Le rôle du contrôleur de DRAM varie grandement suivant le contrôleur en question, ainsi que selon le type de DRAM. Les anciens contrôleurs de DRAM étaient des composants séparés du processeur, du ''chipset'' ou du reste de la carte mère. Par exemple, les contrôleur de DRAM Intel 8202, Intel 8203 et Intel 8207 étaient vendus dans des boitiers DIP et étaient soudés sur la carte mère. Par la suite, ils ont été intégré au ''chipset'' de la carte mère pendant les décennies 90-2000. Après les années 2000, ils ont été intégrés dans les processeurs. Il est possible de connecter plusieurs barrettes sur le même bus mémoire, ou alors celles-ci sont connectées au contrôleur de DRAM avec un bus par barrette/boitier. C'est ce qui permet de placer plusieurs barrettes de mémoire sur la même carte mère : toutes les barrettes sont connectées au contrôleur de DRAM d'une manière ou d'une autre. ==Les rangées : l'arrangement horizontal et vertical== Il est rare d'utiliser un chip mémoire seul, car ceux-ci n'ont pas une capacité suffisante. Pour donner quelques chiffres, à l'heure où j'écris ces lignes, la norme pour un ordinateur est d'avoir entre 8 et 64 gibioctets de RAM. Mais les chips mémoire font entre 1 et 4 gibioctets, rarement plus. La raison est que les ordinateurs combinent ensemble plusieurs chips mémoire pour additionner leurs capacités. La concaténation de plusieurs chips mémoire peut se faire de deux manières différentes, appelées l'arrangement horizontal et l'arrangement vertical. Les deux additionnent la capacité des chips mémoire, mais se distinguent sur un point : ce qui arrive respectivement au bus de données, et au nombre d'adresses. Intuitivement, on se dit que doubler la capacité mémoire implique de doubler le nombre d'adresses mémoire. C'est effectivement ce qui se passe avec l'arrangement vertical. Mais avec l'arrangement horizontal, le nombre d'adresse ne varie pas. Voyons cela en détail, et commençons par le cas le plus simple, celui de l'arrangement vertical seul. ===L'arrangement vertical : cumuler des adresses mémoire=== Introduisons l'arrangement vertical par un exemple. Imaginez que je souhaite obtenir de 4 mébioctets de RAM, en combinant 4 chips mémoires de 1 mébioctet chacun. L'idée est que le premier mébioctet est placé dans le premier chip mémoire, le second mébioctet dans le second chip, etc. Des adresses consécutives se trouvent ainsi dans le même chip mémoire, sauf pour quelques adresses où on passe d'un chip à l'autre. Avec cette organisation, le bus de donnée fait un octet, et les chips mémoire ont aussi un bus de données d'un octet. Je peux alors combiner les capacités de plusieurs chips mémoire, sans toucher au bus de données. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses avec arrangement vertical.]] Avec cet arrangement, les bits de poids fort de l'adresse sont utilisées pour sélectionner la banque adéquate, et le reste de l'adresse est envoyé sur le bus d'adresse. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Adresse de banque !! Adresse dans la banque |- | Quelques bits de poids fort || Reste de l'adresse |} Pour cela, il faut que chaque mémoire dispose d'une entrée ''Chip Select'', qui permet de l'activer ou de la désactiver. L'idée est que selon l'adresse demandée, on active le chip mémoire associé à cette adresse. Les signaux ''Chip Select'' sont générés par le contrôleur de DRAM, à partir de l'adresse. On dit qu'il y a un '''décodage d'adresse'''. Les bits de poids fort sont utilisés pour sélectionner le chip mémoire adéquat. Par exemple, avec 4 chips mémoire, les deux bits de poids fort de l'adresse sont utilisés pour sélectionner le chip mémoire adéquat. ===L'arrangement horizontal : élargir le bus de données=== L'arrangement horizontal permet lui aussi d'additionner les capacités mémoire de plusieurs chips mémoire. Cependant, il les combine d'une autre manière. Le nombre d'adresses mémoire n'est pas changé en utilisant plusieurs chips, mais le bus de données est élargi. Le mieux pour comprendre l'idée est de partir d'un exemple, et nous allons prendre celui d'une mémoire SDRAM. Les ordinateurs actuels ont un bus de données de 64 bits (on met de côté le cas du double ou triple canal). Cependant, il n'existe pas de chip mémoire avec un bus aussi large. Les puces de SDRAM/DDR ont un bus de 4, 8 ou 16 bits, ce sont les tailles les plus courantes. L'arrangement horizontal résout ce problème en combinant plusieurs chips mémoire de manière à ce que leurs bus de données s'"additionnent", se concatènent. Par exemple, on peut regrouper 8 chips mémoires de 8 bits, obtenir un bus mémoire de 64 bits. Il est aussi possible d'obtenir ces 64 bits avec des puces de 16 chips mémoire de 4 bits, ou 4 chips mémoire de 16 bits. [[File:Arrangement horizontal SDRAM - un Rank.png|centre|vignette|upright=2|Arrangement horizontal SDRAM.]] Avec cette organisation, on accède à tous les bancs en parallèle à chaque accès, avec la même adresse. Vu que les chips mémoires contiennent tous une partie de la donnée demandée, ils doivent tous être activés en même temps. Pour cela, l'adresse à lire est envoyée à tous les chips mémoire d'un même ''rank'', idem pour les signaux de commande. Un ensemble de N chips reliés de cette manière forme une '''rangée''' (le terme anglais est ''rank''). [[File:Arrangement horizontal.jpg|centre|vignette|upright=2|Arrangement horizontal.]] ===L'arrangement horizontal et vertical combinés=== Nous venons de voir l'arrangement vertical et horizontal, pour ce qui est des barrettes de mémoire. Précisons que ce qui vient d'être dit marche aussi bien pour les barrettes de RAM que pour la mémoire soudée sur la carte mère. Du moment qu'on combine plusieurs chips mémoire ensemble, ces concepts restent valides. Et il en est de même pour la suite, encore que ce soit nettement moins fréquent avec de la mémoire soudée. Il est possible de combiner à la fois l'arrangement vertical et l'arrangement horizontal. Rien de plus simple : il suffit d'utiliser un arrangement vertical entre plusieurs rangées, chacun composée de plusieurs chips mémoire. C'est surtout utilisé sur les barrettes de mémoire SDRAM, qui contiennent 1, 2, 4 ou 8 rangées, rarement plus. Par exemple, une SDRAM peut combiner 16 chips de DRAM de 8 bits chacun, dans deux rangées de 64 bits chacun, chaque rangée regroupant 8 chips. [[File:SDRAM avec 4 ranks.png|centre|vignette|upright=2|SDRAM avec 4 ranks]] Le choix entre la première ou la seconde rangée se fait en configurant les bits ''Chip Select'' de chaque rangée. Il faut noter que les bits de ''Chip Select'' sont générés par le contrôleur mémoire, et envoyés sur le bus de commande. [[File:Td6bfig3.png|centre|vignette|upright=2|Comparaison entre arrangement horizontal (à gauche) et arrangement vertical (à droite).]] Le contrôleur de DRAM peut adresser un certain nombre de rangées, dispersés sur plusieurs barrettes. La limite maximale dépend du contrôleur de DRAM, elle est souvent proche de 8 ou 16 rangées. Si on combine plusieurs barrettes de mémoire, il est possible de dépasser cette limite. Par exemple, prenez un contrôleur de DRAM supportant maximum 8 rangées. Avec 4 barrettes contenant 4 rangées chacune, la limite est dépassée. : Il faut noter que tout ce qui vient d'être dit vaut aussi pour les mémoires ROM et SRAM. Mais en pratique, les arrangements verticaux et horizontaux sont surtout utilisés sur les mémoires DRAM. Il faut dire que de tels arrangements servent à augmenter la capacité mémoire, ce qui colle plus avec des DRAM que des SRAM ou des ROM. ==Les barrettes de mémoire DRAM== [[File:Ram-module.svg|droite|vignette|upright=0.5|Barrette de mémoire RAM.]] Il est possible de souder plusieurs boitiers de DRAM sur une cartre mère, et c'est ce qui est fait sur nombre d'ordinateurs portables. Mais dans les PC fixes, les puces de DRAM sont regroupées sur des ''barrettes mémoires'''. Les barrettes de mémoire se fixent à la carte mère sur un connecteur standardisé, appelé '''slot mémoire'''. Le dessin ci-contre montre une barrette de mémoire, celui-ci ci-dessous est celui d'un ''slot'' mémoire. [[File:Dual channel slots.jpg|centre|vignette|Slots mémoires.]] Sur le schéma de droite, on remarque facilement les boitiers de DRAM, rectangulaires, de couleur sombre. Chaque barrette combine ces puces de manière à additionner leurs capacités : on peut ainsi créer une mémoire de 8 gibioctets à partir de 8 puces d'un gibioctet, par exemple. Ils sont soudés sur un PCB en plastique vert sur lequel sont gravés des connexions métalliques. Les trucs dorés situés en bas des barrettes de mémoire sont des broches qui connectent la barrette au bus mémoire. Les barrettes des mémoires FPM/EDO/SDRAM/DDR n'ont pas le même nombre de broches, pour des raisons de compatibilité. {|class="wikitable" |- !Type de barrette !Type de mémoire !Nombre de broches |- | rowspan="2" | SIMM | rowspan="2" | FPM/EDO |30 |- |72 |- | rowspan="4" | DIMM |SDRAM |168 |- |DDR |184 |- |DDR2 |214, 240 ou 244, suivant la barrette ou la carte mère. |- |DDR3 |204 ou 240, suivant la barrette ou la carte mère. |} ===Le format des barrettes de mémoire=== Certaines barrettes ont des puces mémoire d'un seul côté alors que d'autres en ont sur les deux faces. Cela permet de distinguer les barrettes SIMM et DIMM. * Les '''barrettes SIMM''' ont des puces sur une seule face de la barrette. Elles étaient utilisées pour les mémoires FPM et EDO-RAM. * Les '''barrettes DIMM''' ont des puces sur les deux côtés. Elles sont utilisées sur les SDRAM et les DDR. {| class="flexible" |+ '''Barrette SIMM''' |- |[[File:SIMM FPM 4 MB - C0448721-7229.jpg|vignette|SIMM recto.]] |[[File:SIMM FPM 4 MB - C0448721-7230.jpg|vignette|SIMM verso.]] |} : Les modules DIMM tendent à avoir plus de rangées que les modules SIMM, mais ce n'est pas systématique. Il est souvent dit que les barrettes DIMM ont deux rangées, contre seulement 1 pour les SIMM, mais les contre-exemples sont nombreux. Les barrettes '''SO-DIMM''', pour ordinateurs portables, sont différentes des barrettes DIMM normales des DDR/SDRAM. La raison est qu'il n'y a pas beaucoup de place à l'intérieur d'un PC portable, ce qui demande de diminuer la taille des barrettes. {| |- |[[File:Desktop DDR Memory Comparison.svg|centre|vignette|upright=1.5|Barrettes de DDR pour PC de bureau.]] |[[File:Laptop SODIMM DDR Memory Comparison V2.svg|centre|vignette|upright=1.5|Barrettes de DDR pour PC portables.]] |} Les barrettes de Rambus ont parfois été appelées des '''barrettes RB-DIMM''', mais ce sont en réalité des DIMM comme les autres. La différence principale est que la position des broches n'était pas la même que celle des formats DIMM normaux, sans compter que le connecteur Rambus n'était pas compatible avec les connecteurs SDR/DDR normaux. ===Les interconnexions à l'intérieur d'une barrette de mémoire=== Les boîtiers de DRAM noirs sont connectés au bus par le biais de connexions métalliques. Toutes les puces d'une même rangée sont connectées aux bus d'adresse et de commande. Et les chips d'une même rangée reçoivent exactement les mêmes signaux de commande/adresses, ce qui permet d'envoyer la même adresse/commande à toutes les puces en même temps. La manière dont ces puces sont reliées au bus de commande dépend selon la mémoire utilisée. Les DDR1 et 2 utilisent ce qu'on appelle une '''topologie en T''', illustrée ci-dessous. On voit que le bus de commande forme une sorte d'arbre, dont chaque extrémité est connectée à une puce. La topologie en T permet d'égaliser le délai de transmission des commandes à travers le bus : la commande transmise arrive en même temps sur toutes les puces. Mais elle a de nombreux défauts, à savoir : elle fonctionne mal à haute fréquence, elle est difficile à router en raisons des embranchements. [[File:Organisation des bus de commandes sur les DDR1-2.png|centre|vignette|upright=3.0|Organisation des bus de commandes sur les DDR1-2, nommée topologie en T.]] En comparaison, les DDR3 utilisent une '''topologie ''fly-by''''', où les puces sont connectées en série sur le bus de commande/adresse. La topologie ''fly-by'' n'a pas les problèmes de la topologie en T : elle est simple à router et fonctionne très bien à haute fréquence. [[File:Organisation des bus de commandes sur les DDR3 - topologie fly-by.png|centre|vignette|upright=3.0|Organisation des bus de commandes sur les DDR3 - topologie ''fly-by'']] ===Les barrettes tamponnées (à registres)=== Certaines barrettes intègrent un registre tampon, qui fait l'interface entre le bus et la barrette de RAM. L'utilité est d'améliorer la transmission du signal sur le bus mémoire. Sans ce registre, les signaux électriques doivent traverser le bus, puis traverser les connexions à l'intérieur de la barrette, jusqu'aux puces de mémoire. Avec un registre tampon, les signaux traversent le bus, sont mémorisés dans le registre et c'est tout. Le registre envoie les commandes/données jusqu'aux puces mémoire, mais le signal a été régénéré par le registre. Le signal transmis est donc de meilleure qualité, ce qui augmente la fiabilité du système mémoire. Le défaut est que la présence de ce registre fait que les barrettes ont un temps de latence est plus important que celui des barrettes normales, du fait de la latence du registre. Les barrettes de ce genre sont appelées des '''barrettes RIMM'''. Il en existe deux types : * Avec les '''barrettes RDIMM''', le registre fait l'interface pour le bus d'adresse et le bus de commande, mais pas pour le bus de données. * Avec les '''barrettes LRDIMM''' (''Load Reduced DIMMs''), le registre fait tampon pour tous les bus, y compris le bus de données. [[File:Organisation des bus de commandes sur les RDIMM.png|centre|vignette|upright=3.0|Organisation des bus de commandes sur les RDIMM.]] ===Le ''Serial Presence Detect''=== [[File:SPD SDRAM.jpg|vignette|Localisation du SPD sur une barrette de SDRAM.]] Toute barrette de mémoire assez récente contient une petite mémoire ROM qui stocke les différentes informations sur la mémoire : délais mémoire, capacité, marque, etc. Cette mémoire s'appelle le '''''Serial Presence Detect''''', aussi communément appelé le SPD. Ce SPD contient non seulement les timings de la mémoire RAM, mais aussi diverses informations, comme le numéro de série de la barrette, sa marque, et diverses informations. Le SPD est lu au démarrage de l'ordinateur par le BIOS, afin de pourvoir configurer ce qu'il faut. Le contenu de ce fameux SPD est standardisé par un organisme nommé le JEDEC, qui s'est chargé de standardiser le contenu de cette mémoire, ainsi que les fréquences, timings, tensions et autres paramètres des mémoires SDRAM et DDR. Pour les curieux, vous pouvez lire la page wikipédia sur le SPD, qui donne son contenu pour les mémoires SDR et DDR : [https://en.wikipedia.org/wiki/Serial_presence_detect Serial Presence Detect]. ==Les mémoires asynchrones à RAS/CAS : FPM et EDO-RAM== Avant l'invention des mémoires SDRAM et DDR, il exista un grand nombre de mémoires différentes, les plus connues étant les mémoires fast page mode et EDO-RAM. Ces mémoires n'étaient pas synchronisées par un signal d'horloge, c'était des '''mémoires asynchrones'''. Quand ces mémoires ont été créées, cela ne posait aucun problème : les accès mémoire étaient très rapides et le processeur était certain que la mémoire aurait déjà fini sa lecture ou écriture au cycle suivant. Les mémoires asynchrones les plus connues étaient les '''mémoires FPM''' et '''mémoires EDO'''. Pour ce qui est de leur interface, il faut signaler qu'elles n'ont pas d'entrée ''Chip Select'' ou d'entrée ''Output Enable''. Les signaux RAS et CAS remplacent en quelque sorte ces deux signaux. Le bit RAS fait office de ''Chip Select'', le bit CAS fait office d'''Output Enable''. ===Les mémoires FPM=== Les '''mémoires FPM (''Fast Page Mode'')''' possédaient une petite amélioration, qui rendait l'adressage plus simple. Avec elles, il n'y a pas besoin de préciser deux fois la ligne si celle-ci ne changeait pas lors de deux accès consécutifs : on pouvait garder la ligne sélectionnée durant plusieurs accès. Par contre, il faut quand même préciser les adresses de colonnes à chaque changement d'adresse. Il existe une petite différence entre les mémoire ''Page Mode'' et les mémoires ''Fast-Page Mode'' proprement dit. Sur les premières, le signal CAS est censé passer à 0 avant qu'on fournisse l'adresse de colonne. Avec les ''Fast-Page Mode'', l'adresse de colonne pouvait être fournie avant que l'on configure le signal CAS. Cela faisait gagner un petit peu de temps, en réduisant quelque peu le temps d'accès total. [[File:Sélection d'une ligne sur une mémoire FPM ou EDO.png|centre|vignette|upright=2|Sélection d'une ligne sur une mémoire FPM ou EDO.]] Avec les '''mémoires en mode quartet''', il est possible de lire quatre octets consécutifs sans avoir à préciser la ligne ou la colonne à chaque accès. On envoie l'adresse de ligne et l'adresse de colonne pour le premier accès, mais les accès suivants sont fait automatiquement. La seule contrainte est que l'on doit générer un front descendant sur le signal CAS pour passer à l'adresse suivante. Vous aurez noté la ressemblance avec le mode rafale vu il y a quelques chapitres, mais il y a une différence notable : le mode rafale vrai n'aurait pas besoin qu'on précise quand passer à l'adresse suivante avec le signal CAS. [[File:Mode quartet.png|centre|vignette|upright=3|Mode quartet.]] Les '''mémoires FPM à colonne statique''' se passent même du signal CAS. Le changement de l'adresse de colonne est détecté automatiquement par la mémoire et suffit pour passer à la colonne suivante. Dans ces conditions, un délai supplémentaire a fait son apparition : le temps minimum entre deux sélections de deux colonnes différentes, appelé tCAS-to-CAS. [[File:Accès en colonne statique.jpg|centre|vignette|upright=2.5|Accès en colonne statique.]] ===Les mémoires EDO-RAM=== L''''EDO-RAM''' a été inventée quelques années après la mémoire FPM. Elle a été déclinée en deux versions : la EDO simple, et la EDO en rafale. L'EDO simple ajoutait une entrée ''Ouput Enable'' à une mémoire FPM. Pour rappel, l'entrée ''Ouput Enable'' permet de connecter/déconnecter la DRAM du bus de données. S'il est mis à 0, les lectures et écritures sont empêchées. Pour ajouter cette entrée, il a fallu rajouter un registre sur la sortie de donnée, celle qui sert pour les lectures. Et l'ajout de ce registre a introduit une capacité dite de ''pipelining'', sur le même modèle que pour les mémoires SRAM synchrones. La donnée pouvait être maintenue sur le bus de données durant un certain temps, même après la remontée du signal CAS. Le registre de sortie maintenait la donnée lu tant que le signal RAS restait à 0, et tant qu'un nouveau signal CAS n'a pas été envoyé. Faire remonter le signal CAS à 1 n'invalidait pas la donnée en sortie. La conséquence est qu'on pouvait démarrer une nouvelle lecture alors que la donnée de l'accès précédent était encore présent sur le bus de données. Le pipeline obtenu avait deux étages : un où on présentait l'adresse et sélectionnait la colonne, un autre où la donnée était lue depuis le registre de sortie. Les mémoires EDO étaient donc plus rapides. [[File:EDO RAM.png|centre|vignette|upright=3|EDO RAM]] Cependant, cela marchait surtout pour les lectures, pas pour les écritures. Une écriture ne démarre que quand la lecture ou écriture précédente est totalement terminée. De même, on ne peut pas démarrer un nouvel accès mémoire tant qu'une écriture est en cours. ===Les mémoires EDO-RAM avec mode rafale=== Les '''EDO en rafale''' effectuent les accès à 4 octets consécutifs automatiquement : il suffit d'adresser le premier octet à lire. Les 4 octets étaient envoyés sur le bus les uns après les autres, au rythme d'un par cycle d’horloge : ce genre d'accès mémoire s'appelle un accès en rafale. [[File:Accès en rafale.png|centre|vignette|upright=2|Accès en rafale sur une DRAM EDO.]] Implémenter cette technique nécessite d'ajouter un compteur, capable de faire passer d'une colonne à une autre quand on lui demande, et quelques circuits annexes pour commander le tout. [[File:Modifications du contrôleur mémoire liées aux accès en rafale.png|centre|vignette|upright=2|Modifications du contrôleur de DRAM liées aux accès en rafale.]] ===Le rafraichissement mémoire=== Les mémoires FPM et EDO doivent être rafraichies régulièrement. Au début, le rafraichissement se faisait ligne par ligne. Le rafraichissement avait lieu quand le RAS passait à l'état haut, alors que le CAS restait à l'état bas. Le processeur, ou le contrôleur mémoire, sélectionnait la ligne à rafraichir en fournissant son adresse mémoire. D'où le nom de '''rafraichissement par adresse''' qui est donné à cette méthode de commande du rafraichissement mémoire. Divers processeurs implémentaient de quoi faciliter le rafraichissement par adresse. Par exemple, le Zilog Z80 contenait un compteur de ligne, un registre qui contenait le numéro de la prochaine ligne à rafraichir. Il était incrémenté à chaque rafraichissement mémoire, automatiquement, par le processeur lui-même. Un ''timer'' interne permettait de savoir quand rafraichir la mémoire : quand ce ''timer'' atteignait 0, une commande de rafraichissement était envoyée à la mémoire, et le ''timer'' était ''reset''. [[File:Rafraichissement mémoire manuel.png|centre|vignette|upright=2|Rafraichissement mémoire manuel.]] Par la suite, certaines mémoires ont implémenté un compteur interne d'adresse, pour déterminer la prochaine adresse à rafraichir sans la préciser sur le bus d'adresse. Le déclenchement du rafraichissement se faisait toujours par une commande externe, provenant du contrôleur de DRAM ou du processeur. Cette commande faisait passer le CAS à 0 avant le RAS. Cette méthode de rafraichissement se nomme '''rafraichissement interne'''. [[File:Rafraichissement sur CAS précoce.png|centre|vignette|upright=2|Rafraichissement sur CAS précoce.]] On peut noter qu'il est possible de déclencher plusieurs rafraichissements à la suite en laissant le signal CAS dans le même état. Ce genre de choses pouvait avoir lieu après une lecture : on pouvait profiter du fait que le CAS soit mis à zéro par la lecture ou l'écriture pour ensuite effectuer des rafraichissements en touchant au signal RAS. Dans cette situation, la donnée lue était maintenue sur la sortie durant les différents rafraichissements. [[File:Rafraichissements multiples sur CAS précoce.png|centre|vignette|upright=2|Rafraichissements multiples sur CAS précoce.]] ==Les mémoires SDRAM== Dans les années 90, les mémoires asynchrones ont laissé la place aux '''mémoires SDRAM''', qui sont synchronisées avec le bus par une horloge. L'utilisation d'une horloge a comme avantage des temps d'accès fixes : le processeur sait qu'un accès mémoire prendra un nombre déterminé de cycles d'horloge. Avec les mémoires asynchrones, le processeur ne pouvait pas prévoir quand la donnée serait disponible et ne faisait rien tant que la mémoire n'avait pas répondu : il exécutait ce qu'on appelle des ''wait states'' en attendant que la mémoire ait fini. Les mémoires SDRAM sont standardisées par un organisme international, le JEDEC. Le standard SDRAM impose des spécifications électriques bien précise pour les barrettes de mémoire et le bus mémoire, décrit le protocole utilisé pour communiquer avec les barrettes de mémoire, et bien d'autres choses encore. Les SDRAM ont été déclinées en versions de performances différentes, décrites dans le tableau ci-dessous : {| class="wikitable" ! Nom standard ! Fréquence ! Bande passante |- | PC66 | 66 mhz | 528 Mio/s |- | PC66 | 100 mhz | 800 Mio/s |- | PC66 | 133 mhz | 1064 Mio/s |- | PC66 | 150 mhz | 1200 Mio/s |} ===Les banques internes aux chips mémoires SDRAM=== L'intérieur d'une mémoire SDRAM contient plusieurs '''banques''', aussi appelées des banc mémoire. Concrètement, une banque est... une mémoire. Ou plutôt, une sorte de mini-mémoire miniature. Chaque banque a son propre tampon de ligne, ses propres multiplexeurs de colonne et ses propres décodeurs. C'est comme si une SDRAM regroupait plusieurs mémoires séparées dans un même circuit intégré. [[File:Arrangement vertical.jpg|centre|vignette|upright=2.5|Mémoire multi-banques.]] Un point important est que chaque banque a son propre tampon de ligne. Il est donc possible d'ouvrir plusieurs lignes en même temps, chacune dans une banque différente. Par exemple, on peut ouvrir une ligne dans la banque numéro 1, et une autre ligne dans la banque numéro 2. Et c'est une source d'optimisations très intéressantes. La première optimisation est liée au rafraichissement mémoire. Au lieu de rafraichir chaque adresse une par une, il est possible de rafraichir des banques indépendantes en même temps, ce qui divise le temps de rafraichissement par le nombre de banques. C'est ce que je sous-entendais plus haut quand je disais que le temps de rafraichissement n'est pas égal au temps de balayage sur les SDRAM, alors que c'est le cas sur les DRAM FPM et EDO. De plus, et sans rentrer dans les détails, cela permet de faire plusieurs accès mémoire en même temps, dans des banques différentes. La possibilité est limitée, mais elle existe et elle améliore grandement la performance de la SDRAM. Mais nous en reparlerons dans un chapitre ultérieur, car cette histoire d'accès simultanés a plus sa place dans le chapitre sur le parallélisme mémoire. Pour le moment, nous ne pouvons pas expliquer pourquoi ni comment un processeur pourrait émettre plusieurs accès mémoire simultanés. Un processeur est censé travailler une instruction à la fois, à ce stade du cours, il ne peut pas en faire plusieurs en même temps. Mais nous allons cependant mentionner un cas où cette possibilité est intéressante : une mémoire SDRAM partagée entre un processeur et une carte graphique. Les deux accèdent à des données séparées, qui sont dans des banques différentes. On suppose que la carte graphique accède plus fréquemment à la mémoire que le processeur. Le contrôleur mémoire reçoit les accès mémoire du CPU et du GPU et il tente d'intercaler des accès CPU entre deux accès de la carte graphique. Vu qu'ils tombent dans des banques différentes, un accès CPU et un accès carte graphique peuvent se faire presque en même temps. La seule contrainte est que si on lance plusieurs accès mémoire simultanés, ceux-ci ne peuvent pas utiliser le bus de données en même temps. {|class="wikitable" |+ Pipelining basique sur les SDRAM |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 |- ! Banque Numéro 1 | || || || || || || || || || || |- ! Banque Numéro 2 | || colspan="3" bgcolor="#A0FFFF" | Accès CPU || || || colspan="3" bgcolor="#A0FFFF" | Accès CPU || || |- ! Banque Numéro 3 | || || || || || || || || || || |- ! Banque Numéro 4 | || || || colspan="3" bgcolor="#FFA0FF" | Accès carte graphique || || colspan="3" bgcolor="#FFA0FF" | Accès carte graphique || |} ===Le mode rafale des SDRAM=== Un point important est que les SDRAM reprennent les optimisations des mémoires FPM et EDO. Elles utilisent aussi un tampon de ligne, avec la possibilité de lire plusieurs colonnes à la suite sans avoir à préciser l'adresse de ligne à chaque fois. Mais surtout, elles gèrent nativement le mode rafale. les paramètres qui ont trait au mode rafale sont configurables. Il est possible de configurer la SDRAM pour activer les accès sans rafale, ou les désactiver. Il y a aussi la possibilité de configurer le nombre d'octets consécutifs à lire ou écrire en mode rafale. On peut ainsi accéder à 1, 2, 4, ou 8 octets en une seule fois, alors que les EDO ne permettaient que des accès à 4 octets consécutifs. Enfin, on peut décider s'il faut faire un accès en mode linéaire ou entrelacé. La configuration de la SDRAM est mémorisée dans un registre de 10 bits, le '''registre de mode'''. Il faisait 10 bits sur les mémoires SDRAM, mais a été étendu à 13 bits sur la DDR2. Voici les 10 bits originels de ce registre : {|class="wikitable" |+ Signification des bits du registre de mode des SDRAM |- ! Bit n°9 | Type d'accès : en rafale ou normal |- ! Bit n°8 et 7 | Doivent valoir 00, sont réservés pour une utilisation ultérieur dans de futurs standards. |- ! Bit n°6, 5, et 4 | Latence CAS (voir plus bas) |- ! Bit n°3 | Type de rafale : linéaire ou entrelacée |- ! Bit n°2, 3, et 0 | Longueur de la rafale : indique le nombre d'octets à lire/écrire lors d'une rafale. |} ===L'interface d'une mémoire SDRAM=== Le bus de commandes d'une SDRAM contient au moins 18 fils, dont celui pour le signal d'horloge. L'interface d'une SDRAM contient tous les bits présents sur une mémoire DRAM classique : une entrée RAS, une entrée CAS, une entrée R/W, et un bus d'adresse. A cela, il faut cependant ajouter une entrée ''Chip Select'' (CS), qui permet d'activer/désactiver la mémoire SDRAM. Je rappelle que le bit CS a été introduit sur les mémoires SDRAM, il n'était pas présent sur les mémoires FPM/EDO. Deux autres bits de commande sont vraiment spécifiques des mémoires SDRAM. Il s'agit des bits CKE et DQM. Le '''bit CKE''' est l'abréviation de ''Clock Enable'', qui qui trahit sa fonction. Lorsque ce signal est à 0, le chip de SDRAM voit son signal d'horloge gelè. S'il est à 0, le contrôleur de DRAM peut envoyer ce qu'il veut sur le bus de commande ou d'adresse, la SDRAM ne réagira pas du tout, il ne se passera rien. Le '''bit DQM''' est une sorte de bit ''Output Enable'', avec une nuance importante. Le terme DQM est l'abréviation de ''Data Mask'', ce qui trahit encore une fois sa fonction. Il y a un bit DQM pour chaque octet du bus de données. Une SDRAM ayant un bus de 64 bits, cela fait 8 bits DQM par mémoire SDRAM. Lorsque le bit DQM est à 1, l'octet en question n'est simplement pas lu ou écrit, le bus de donnée est déconnecté pour cet octet. Le bus d'adresse est particulier, car il tient compte de la présence de ''banques mémoires''. Le bus d'adresse est découpé en deux portions : une portion pour sélectionner la banque, une autre pour sélectionner l'adresse à l'intérieur d'une banque. L'interface de la SDRAM fait ainsi la différence entre une '''adresse de banque''' et une '''adresse intra-banque'''. L'adresse de banque est codée sur quelques bits, généralement deux ou trois suivant la SDRAM. Le reste de l'adresse est codé sur 11 bits sur les SDRAM, mais cela a augmenté avec les DDR 1, 2, 3, 4, 5. Le bus de données d'une SDRAM fait 4, 8, ou 16 bits. Je précise bien qu'il s'agit là des puces de SDRAM, les barrettes de SDRAM combinent plusieurs puces SDRAM avec un arrangement horizontal, qui peut combiner plusieurs puces de 8 bits pour alimenter un bus de données de 64 bits. La taille des puces utilisées souvent indiquée sur la barrette de RAM, avec une mention x4, x8 ou x16. Les puces de SDRAM les plus courantes ont une interface de 8 bits pour les données. Les SDRAM de 4 bits sont surtout utilisées pour les serveurs, c'est lié au support de l'ECC. les puces x16 sont moins utilisées car elles ont généralement moins de banques que les autres. ===Les commandes SDRAM=== Le bus de commande permet d'envoyer des commandes à la mémoire, chaque commande étant précisée par une combinaison précise des bits CS, RAS, CAS, R/W, et autres. Les commandes en question sont des demandes de lecture, d'écriture, de préchargement et autres. Elles sont codées par une valeur bien précise qui est envoyée sur les 18 fils du bus de commande. Ces commandes sont nommées READ, READA, WRITE, WRITEA, PRECHARGE, ACT, ... Les plus importantes sont les commandes PRECHARGE, ACT et READ/WRITE. La commande ACT sélectionne une ligne : elle met le bit RAS à zéro et présente une adresse de ligne. Les commandes READ et WRITE sélectionnent une colonne, et déclenchent respectivement une lecture ou une écriture. Elles précisent une adresse de colonne, mettent le bit CAS à 0 et le bit RAS à 1, et précise la valeur du bit R/W. Les commandes READ et WRITE ne peuvent se faire qu'une fois que la banque a été activée par une commande ACT. Il est possible d'envoyer plusieurs commandes READ ou WRITE successives à des colonnes différentes, ce qui permet d'implémenter les optimisations des mémoires FPM. La commande PRECHARGE ferme la ligne courante et prépare l'ouverture de la suivante. Elle précharge les lignes de bit de la RAM, d'où son nom. Il est nécessaire d'en envoyer une avant d'envoyer une commande ACT. Notons que la commande PRECHARGE agit sur une banque, dont l'adresse est indiquée dans la commande PRECHARGE. Il existe une commande PRECHARGE ALL, qui agit sur toutes les banques de la SDRAM à la fois. Les commandes READA et WRITEA fusionnent une commande READ/WRITE avec une commande PRECHARGE. Elles permettent d'éviter d'avoir à envoyer une commande PRECHARGE pour fermer la ligne courante. Au lieu d'envoyer une commande READ ou WRITE, puis une commande PRECHARGE pour fermer la ligne, on envoie une seule commande READA/WRITEA. Il s'agit d'une petite optimisation, qui permet de réduire le nombre de commandes envoyées sur le bus. Les commandes sont encodées comme indiquées dans ce tableau. Une commande est tout simplement encodée en précisant une adresse si nécessaire, et une combinaison des bits CS, RAS, CAS et R/W. La seule subtilité est que le bit numéro 10 du bus d'adresse sert à commander les opérations de PRECHARGE, y compris celles implicites dans les opérations READA et WRITEA. {| class="wikitable" style="text-align:center" ! Bit CS || Bit RAS || Bit CAS || Bit WE || Bits de sélection de banque (2 bits) || Bit du bus d'adresse A10 || Reste du bus d'adresse || Nom de la commande : Description |- | 1 | colspan="6" | X | Absence de commandes. |- | 0 || 1 || 1 || 1 || colspan="3" | X || No Operation : Pas d'opération |- | 0 || 1 || 1 || 0 || colspan="3" | X || Burst Terminante : Stoppe un accès en rafale (en cours). |- | 0 || 1 || 0 || 1 || Adresse de la banque || 0 || Adresse de la colonne || READ : lit une donnée depuis la ligne active. |- | 0 || 1 || 0 || 1 || Adresse de la banque || 1 || Adresse de la colonne || READA : lit une donnée depuis la ligne active, puis ferme la ligne. |- | 0 || 1 || 0 || 0 || Adresse de la banque || 0 || Adresse de la colonne || WRITE : écrit une donnée dans la ligne active. |- | 0 || 1 || 0 || 0 || Adresse de la banque || 1 || Adresse de la colonne || WRITEA : écrit une donnée dans la ligne active, puis ferme la ligne. |- | 0 || 0 || 1 || 1 || Adresse de la banque || colspan="2" | Adresse de la ligne || ACT : charge une ligne dans le tampon de ligne. |- | 0 || 0 || 1 || 0 || Adresse de la banque || 0 || X || PRECHARGE : précharge le tampon de ligne dans la banque voulue. |- | 0 || 0 || 1 || 0 || Adresse de la X || 1 || X || PRECHARGE ALL : précharge le tampon de ligne dans toutes les banques. |- | 0 || 0 || 0 || 1 || colspan="3" | X || Auto refresh : Demande de rafraichissement, gérée par la SDRAM. |- | 0 || 0 || 0 || 0 || 00 || colspan="2" | Nouveau contenu du registre de mode || LOAD MODE REGISTER : configure le registre de mode. |} Les commandes ACT se font à partir de l'état de repos, l'état où toutes les banques sont préchargées. Par contre, les commandes MODE REGISTER SET et AUTO REFRESH ne peuvent se faire que si toutes les banques sont désactivées. Le fonctionnement simplifié d'une SDRAM peut se résumer dans ce diagramme : [[File:Fonctionnement simplifié d'une SDRAM.jpg|centre|vignette|upright=2|Fonctionnement simplifié d'une SDRAM.]] ===Les délais mémoires=== Les mémoires SDRAM n'étant pas infiniment rapides, il y a toujours un certain délais à respecter entre deux commandes. Par exemple, quand on envoie une commande ACT pour activer une ligne, on ne peut pas envoyer une commande READ/WRITE au cycle suivant. La plupart des SDRAM ne sont pas assez rapides pour ça. Il faut respecter un délai de quelques cycles, qui dépend de la mémoire. Et il n'y a pas que ce délai entre une commande ACT et la commande suivante. Une SDRAM doit gérer d'autres temps d'attente, appelés des '''délais mémoires''', ou encore des ''timings'' mémoire. Les délais mémoire le plus importants sont résumés ci-dessous : {|class="wikitable" |- !Timing!!Description |- | colspan="2" | |- ! colspan="2" | Délais primaires |- ||tRP|| Temps entre une commande PRECHARGE et une commande ACT |- ||tRCD|| Temps entre une commande ACT et une commande READ/WRITE. |- ||tCL|| Temps entre une commande READ et l'envoi de la donnée lue sur le bus de données. |- ||tDQSS|| Temps entre une commande WRITE et l'écriture de la donnée. |- ||tCAS-to-CAS|| Temps minimum entre deux commandes READ. |- ! colspan="2" | Délais secondaires |- ||tWTR|| Temps entre une lecture et une écriture consécutives. |- ||tRAS || Temps entre une commande ACT et une commande PRECHARGE. |} La façon de mesurer ces délais varie : sur les mémoires FPM et EDO, on les mesure en unités de temps (secondes, millisecondes, micro-secondes, etc.), tandis qu'on les mesure en cycles d'horloge sur les mémoires SDRAM. Les délais/timings mémoire ne sont pas les mêmes suivant la barrette de mémoire que vous achetez. Certaines mémoires sont ainsi conçues pour avoir des timings assez bas et sont donc plus rapides, et surtout : beaucoup plus chères que les autres. Le gain en performances dépend beaucoup du processeur utilisé et est assez minime comparé au prix de ces barrettes. Les délais mémoires d'une barrette sont mémorisés dans le ''Serial Presence Detect'' de la barrette et sont lus par le BIOS au démarrage de l'ordinateur, et sont ensuite utilisés pour configurer le contrôleur de DRAM. ==Les mémoires DDR== Les mémoires SDRAM récentes sont des mémoires de type ''dual data rate'', ce qui fait qu'elles portent le nom de mémoires DDR. Pour rappel, les mémoires ''dual data rate'' ont un plan mémoire deux fois plus large que le bus mémoire, avec un bus mémoire allant à une fréquence double. Par double, on veut dire que les transferts sur le bus mémoire ont lieu sur les fronts montants et descendants de l'horloge. Il y a donc deux transferts de données sur le bus pour chaque cycle d'horloge, ce qui permet de doubler le débit sans toucher à la fréquence du plan mémoire lui-même. Les mémoires DDR sont standardisées par un organisme international, le JEDEC, et ont été déclinées en plusieurs générations : DDR1, DDR2, DDR3, et DDR4. La différence entre ces modèles sont très nombreuses, mais les plus évidentes sont la fréquence de la mémoire et du bus mémoire. D'autres différences mineures existent entre les SDRAM et les mémoires DDR. Par exemple, la tension d'alimentation des mémoires DDR est plus faible que pour les SDRAM. ET elle a diminué dans le temps, d'une génération de DDR à l'autre. Avec les mémoires DDR2,la tension d'alimentation est passée de 2,5/2,6 Volts à 1,8 Volts. Avec les mémoires DDR3, la tension d'alimentation est notamment passée à 1,5 Volts. ===Les performances des mémoires DDR=== Les mémoires SDRAM ont évolué dans le temps, mais leur temps d'accès/fréquence n'a pas beaucoup changé. Il valait environ 10 nanosecondes pour les SDRAM, approximativement 5 ns pour la DDR-400, il a peu évolué pendant la génération DDR et DDR3, avant d'augmenter pendant les générations DDR4 et de stagner à nouveau pour la génération DDR5. L'usage du DDR, puis du QDR, visait à augmenter les performances malgré la stagnation des temps d'accès. En conséquence, la fréquence du bus a augmenté plus vite que celle des puces mémoire pour compenser. {|class="wikitable" |- ! Année ! Type de mémoire ! Fréquence de la mémoire (haut de gamme) ! Fréquence du bus ! Coefficient multiplicateur entre les deux fréquences |- | 1998 | DDR 1 | 100 - 200 MHz | 200 - 400 MHz | 2 |- | 2003 | DDR 2 | 100 - 266 MHz | 400 - 1066 MHz | 4 |- | 2007 | DDR 3 | 100 - 266 MHz | 800 - 2133 MHz | 8 |- | 2014 | DDR 4 | 200 - 400 MHz | 1600 - 3200 MHz | 8 |- | 2020 | DDR 5 | 200 - 450 MHz | 3200 - 7200 MHz | 8 à 16 |} Une conséquence est que la latence CAS, exprimée en nombre de cycles, a augmenté avec le temps. Si vous comparez des mémoires DDR2 avec une DDR4, par exemple, vous allez voir que la latence CAS est plus élevée pour la DDR4. Mais c'est parce que la latence est exprimée en nombre de cycles d'horloge, et que la fréquence a augmentée. En comparant les temps d'accès exprimés en secondes, on voit une amélioration. ===Les commandes des mémoires DDR=== Les commandes des mémoires DDR sont globalement les mêmes que celles des mémoires SDRAM, vues plus haut. Les modifications entre SDRAM, DDR1, DDR2, DDR3, DDR4, et DDR5 sont assez mineures. Les seules différences sont l'addition de bits pour la transmission des adresses, des bits en plus pour la sélection des banques, etc. En clair, une simple augmentation quantitative. Le registre de mode a été un peu modifié. Il est passé de 10 bits pour les SDRAM et DDR1, à 13 bits sur la DDR 2 et les suivantes. Les DDR ont aussi ajouté le support de plusieurs registres de mode, qui sont sélectionnés en réutilisant l'adresse de banque. Dans une commande LOAD MODE REGISTER, l'adresse de banque indique quel registre de mode il faut altérer. Avant la DDR4, les modifications des commandes sont mineures. La DDR2 supprime la commande ''Burst Terminate'', la DDR3 et la DDR4 utilisent le bit A12 pour préciser s'il faut faire une rafale complète, ou une rafale de moitié moins de données. Une optimisation des DDR2 et 3 est celle des '''CAS postés'''. L'idée est que le contrôleur de DRAM peut envoyer une commande ACT et une commande READ/WRITE sans se soucier des ''timings'' nécessaires entre les deux. En théorie, les deux commandes doivent être séparées par quelques cycles, sur une SDRAM ou une DDR1. Mais avec la DDR2, le contrôleur de DRAM peut envoyer les deux l'une après l'autre, au cycle suivant. C'est la mémoire qui mettra en attente la commande READ/WRITE pour respecter les ''timings'' mémoire. Cela complexifie le fonctionnement interne de la DDR, mais simplifie grandement le travail du contrôleur de DRAM. Mais avec la DDR4, les choses changent, notamment au niveau de la commande ACT. Avec l'augmentation de la capacité des barrettes mémoires, la taille des adresses est devenue trop importante. Pour éviter de rajouter des bits d'adresses, les concepteurs du standard DDR4 ont décidé de ruser. Lors d'une commande ACT, les bits RAS, CAS et WE sont utilisés comme bits d'adresse, alors qu'ils ont leur signification normale pour les autres commandes. Pour éviter toute confusion, un nouveau bit ACT est ajouté pour indiquer la présence d'une commande ACT : il est à 1 pour une commande ACT, 0 pour les autres commandes. {| class="wikitable" style="text-align:center" |+ Commandes d'une mémoire DDR4, seule la commande colorée change par rapport aux SDRAM ! Bit CS || style="background: #CCFFCC" | Bit ACT || Bit RAS || Bit CAS || Bit WE || Bits de sélection de banque (4 bits) || Bit du bas d'adresse A10 || Reste du bus d'adresse || Nom de la commande : Description |- | 1 | colspan="6" | X | Absence de commandes. |- | 0 || style="background: #CCFFCC" | 0 || 1 || 1 || 1 || colspan="3" | X || No Operation : Pas d'opération |- | 0 || style="background: #CCFFCC" | 0 || 1 || 1 || 0 || colspan="3" | X || Burst Terminante : Arrêt d'un accès en rafale en cours. |- | 0 || style="background: #CCFFCC" | 0 || 1 || 0 || 1 || Adresse de la banque || 0 || Adresse de la colonne || READ : lire une donnée depuis la ligne active. |- | 0 || style="background: #CCFFCC" | 0 || 1 || 0 || 1 || Adresse de la banque || 1 || Adresse de la colonne || READA : lire une donnée depuis la ligne active, avec rafraichissement automatique de la ligne. |- | 0 || style="background: #CCFFCC" | 0 || 1 || 0 || 0 || Adresse de la banque || 0 || Adresse de la colonne || WRITE : écrire une donnée depuis la ligne active. |- | 0 || style="background: #CCFFCC" | 0 || 1 || 0 || 0 || Adresse de la banque || 1 || Adresse de la colonne || WRITEA : écrire une donnée depuis la ligne active, avec rafraichissement automatique de la ligne. |- style="background: #CCFFCC" | 0 || style="background: #CCFFCC" | 1 || colspan="3" | Adresse de la ligne (bits de poids forts) || Adresse de la banque || colspan="2" | Adresse de la ligne (bits de poids faible) || ACT : charge une ligne dans le tampon de ligne. |- | 0 || style="background: #CCFFCC" | 0 || 0 || 1 || 0 || Adresse de la banque || 0 || X || PRECHARGE : précharge le tampon de ligne dans la banque voulue. |- | 0 || style="background: #CCFFCC" | 0 || 0 || 1 || 0 || Adresse de la X || 1 || X || PRECHARGE ALL : précharge le tampon de ligne' dans toutes les banques. |- | 0 || style="background: #CCFFCC" | 0 || 0 || 0 || 1 || colspan="3" | X || Auto refresh : Demande de rafraichissement, gérée par la SDRAM. |- | 0 || style="background: #CCFFCC" | 0 || 0 || 0 || 0 || Numéro de registre de mode || colspan="2" | Nouveau contenu du registre de mode || LOAD MODE REGISTER : configure le registre de mode. |} ==Les VRAM des cartes vidéo== Les cartes graphiques ont des besoins légèrement différents des DRAM des processeurs, ce qui fait qu'il existe des mémoires DRAM qui leur sont dédiées. Elles sont appelés des '''''Graphics RAM''''' (GRAM). La plupart incorporent des fonctionnalités utiles uniquement pour les mémoires vidéos, comme des fonctionnalités de masquage (appliquer un masque aux données lue ou à écrire), ou le remplissage d'un bloc de mémoire avec une donnée unique. Les anciennes cartes graphiques et les anciennes consoles utilisaient de la DRAM normale, faute de mieux. La première GRAM utilisée était la NEC μPD481850, qui a été utilisée sur la console de jeu PlayStation, à partir de son modèle SCPH-5000. D'autres modèles de GRAM ont rapidement suivi. Les anciennes consoles de jeu, mais aussi des cartes graphiquesn utilisaient des GRAM spécifiques. ===Les mémoires vidéo double port=== Sur les premières consoles de jeu et les premières cartes graphiques, le ''framebuffer'' était mémorisé dans une mémoire vidéo spécialisée appelée une '''mémoire vidéo double port'''. Le premier port était connecté au processeur ou à la carte graphique, alors que le second port était connecté à un écran CRT. Aussi, nous appellerons ces deux port le ''port CPU/GPU'' et l'autre sera appelé le ''port CRT''. Le premier port était utilisé pour enregistrer l'image à calculer et faire les calculs, alors que le second port était utilisé pour envoyer à l'écran l'image à afficher. Le port CPU/GPU est tout ce qu'il y a de plus normal : on peut lire ou écrire des données, en précisant l'adresse mémoire de la donnée, rien de compliqué. Le port CRT est assez original : il permet d'envoyer un paquet de données bit par bit. De telles mémoires étaient des mémoires à tampon de ligne, dont le support de mémorisation était organisé en ligne et colonnes. Une ligne à l'intérieur de la mémoire correspond à une ligne de pixel à l'écran, ce qui se marie bien avec le fait que les anciens écrans CRT affichaient les images ligne par ligne. L'envoi d'une ligne à l'écran se fait bit par bit, sur un câble assez simple comme un câble VGA ou autre. Le second port permettait de faire cela automatiquement, en permettant de lire une ligne bit par bit, les bits étant envoyés l'un après l'autre automatiquement. Pour cela, les mémoires vidéo double port incorporaient un tampon de ligne spécialisé pour le port lié à l'écran. Ce tampon de ligne n'était autre qu'un registre à décalage, contrairement au tampon de ligne normal. Lors de l'accès au second port, la carte graphique fournissait un numéro de ligne et la ligne était chargée dans le tampon de ligne associé à l'écran. La carte graphique envoyait un signal d'horloge de même fréquence que l'écran, qui commandait le tampon de ligne à décalage : un bit sortait à chaque cycle d'écran et les bits étaient envoyé dans le bon ordre. ===Les mémoires SGRAM et GDDR=== De nos jours, les cartes graphiques n'utilisent plus de mémoires double port, mais des mémoires simple port. Les mémoires graphiques actuelles sont des SDRAM modifiées pour fonctionner en tant que ''Graphic RAM''. Les plus connues sont les '''mémoires GDDR''', pour ''graphics double data rate'', utilisées presque exclusivement sur les cartes graphiques. Il en existe plusieurs types pendant que j'écris ce tutoriel : GDDR, GDDR2, GDDR3, GDDR4, et GDDR5. Mais attention, il y a des différences avec les DDR normales. Par exemple, les GDDR ont une fréquence plus élevée que les DDR normales, avec des temps d'accès plus élevés (sauf pour le tCAS). De plus, elles sont capables de laisser ouvertes deux lignes en même temps. Par contre, ce sont des mémoires simple port. ==Les mémoires SLDRAM, RDRAM et associées== Les mémoires précédentes sont généralement associées à des bus larges. Les mémoires SDRAM et DDR modernes ont des bus de données de 64 bits de large, avec des d'adresse et de commande de largeur similaire. Le nombre de fils du bus mémoire dépasse facilement la centaine de fils, avec autant de broches sur les barrettes de mémoire. Largeur de ces bus pose de problèmes problèmes électriques, dont la résolution n'est pas triviale. En conséquence, la fréquence du bus mémoire est généralement moins performantes comparé à ce qu'on aurait avec un bus moins large. Mais d'autres mémoires DRAM ont exploré une solution alternative : avoir un bus peu large mais de haute fréquence, sur lequel on envoie les commandes/données en plusieurs fois. Elles sont regroupées sous le nom de '''DRAM à commutation par paquets'''. Elles utilisent des bus spéciaux, où les commandes/adresses/données sont transmises par paquets, par trames, en plusieurs fois. En théorie, ce qu'on a dit sur le codage des trames dans le chapitre sur le bus devrait s'appliquer à de telles mémoires. En pratique, les protocoles de transmission sur le bus mémoire sont simplifiés, pour gérer le fonctionnement à haute fréquence. Le processeur envoie des paquets de commandes, les mémoires répondent avec des paquets de données ou des accusés de réception. Les mémoires à commutation par paquets sont peu nombreuses. Les plus connues sont les mémoires conçues par la société Rambus, à savoir la ''RDRAM'' (''Rambus DRAM'') et ses deux successeurs ''XDR RAM'' et ''XDR RAM 2''. La ''Synchronous-link DRAM'' (''SLDRAM'') est un format concurrent conçu par un consortium de plusieurs concepteurs de mémoire. ===La SLDRAM (''Synchronous-link DRAM'')=== Les '''mémoires SLDRAM''' avaient un bus de données de 64 bits allant à 200-400 Hz, avec technologie DDR, ce qui était dans la norme de l'époque pour la fréquence (début des années 2000). Elle utilisait un bus de commande de 11 bits, qui était utilisé pour transmettre des commandes de 40 bits, transmises en quatre cycles d'horloge consécutifs (en réalité, quatre fronts d'horloge donc deux cycles en DDR). Le bus de données était de 18 bits, mais les transferts de donnée se faisaient par paquets de 4 à 8 octets (32-65 bits). Pour résumer, données et commandes sont chacunes transmises en plusieurs cycles consécutifs, sur un bus de commande/données plus court que les données/commandes elle-mêmes. Là où les SDRAM sélectionnent la bonne barrette grâce à des signaux de commande dédiés, ce n'est pas le cas avec la SLDRAM. A la place, chaque barrette de mémoire reçoit un identifiant, un numéro codé sur 7-8 bits. Les commandes de lecture/écriture précisent l'identifiant dans la commande. Toutes les barrettes reçoivent la commande, elles vérifient si l'identifiant de la commande est le leur, et elles la prennent en compte seulement si c'est le cas. Voici le format d'une commande SLDRAM. Elle contient l'adresse, qui regroupe le numéro de banque, le numéro de ligne et le numéro de colonne. On trouve aussi un code commande qui indique s'il faut faire une lecture ou une écriture, et qui configure l'accès mémoire. Il configure notamment le mode rafale, en indiquant s'il faut lire/écrire 4 ou 8 octets. Enfin, il indique s'il faut fermer la ligne accédée une fois l'accès terminé, ou s'il faut la laisser ouverte. Le code commande peut aussi préciser que la commande est un rafraichissement ou non, effectuer des opérations de configuration, etc. L'identifiant de barrette mémoire est envoyé en premier, histoire que les barrettes sachent précocement si l'accès les concerne ou non. {|class="wikitable" style="text-align:center" |+SLDRAM Read, write or row op request packet ! FLAG || CA9 || CA8 || CA7 || CA6 || CA5 || CA4 || CA3 || CA2 || CA1 || CA0 |- ! 1 | colspan=9 bgcolor=#ffcccc| Identifiant de barrette mémoire|| bgcolor=#ccffcc| Code de commande |- ! 0 | colspan=5 bgcolor=#ccffcc| Code de commande ||colspan=3 bgcolor=#ff88ff| Banque||colspan=2 bgcolor=#ffffcc| Ligne |- ! 0 | colspan=9 bgcolor=#ffffcc| Ligne || 0 |- ! 0 | 0 || 0 || 0 ||colspan=7 bgcolor=#ccffff| Colonne |} ===Les mémoires Rambus=== Les mémoires conçues par la société Rambus regroupent la '''RDRAM''' (''Rambus DRAM'') et ses deux successeurs '''XDR RAM''' et '''XDR RAM 2'''. Les toutes premières étaient les '''mémoires RDRAM''', où le bus permettait de transmettre soit des commandes (adresse inclue), soit des données, avec un multiplexage total. Le processeur envoie un paquet contenant commandes et adresse à la mémoire, qui répond avec un paquet d'acquittement. Lors d'une lecture, le paquet d'acquittement contient la donnée lue. Lors d'une écriture, le paquet d'acquittement est réduit au strict minimum. Le bus de commandes est réduit au strict minimum, à savoir l'horloge et quelques bits absolument essentiels, les bits RW est transmis dans un paquet et n'ont pas de ligne dédiée, pareil pour le bit OE. Toutes les barrettes de mémoire doivent vérifier toutes les transmissions et déterminer si elles sont concernées en analysant l'adresse transmise dans la trame. Elles ont été utilisées dans des PC ou d'anciennes consoles de jeu. Par exemple, la Nintendo 64 incorporait 4 mébioctets de mémoire RDRAM en tant que mémoire principale. La RDRAM de la Nintendo 64 était cadencée à 500 MHz, utilisait un bus de 9 bits, et avait un débit binaire maximal théorique de 500 MB/s. La Playstation 2 contenait quant à elle 32 mébioctets de RDRAM en ''dual-channel'', pour un débit binaire de 3.2 Gibioctets par seconde. Les processeurs Pentium 3 pouvaient être associés à de la RDRAM sur certaines mères. Les Pentium 4 étaient eux aussi associés à la de RDRAM, mais les cartes mères ne géraient que ce genre de mémoire. La Playstation 3 contenait quant à elle de la XDR RAM. ==Les eDRAM : des DRAM adaptées aux ''chiplets''== Les '''mémoires eDRAM''', pour ''embedded DRAM'', sont des mémoires RAM qui sont destinées à être intégrée au processeur. Pour comparer, les DRAM normales sont placées sur des barrettes de RAM ou soudées à la carte mère. Dans la quasi-totalité des cas, l'eDRAM est utilisée pour implémenter une mémoire cache, elle ne sert pas de mémoire principale (cache L4, le plus proche de la mémoire sur ces puces). De ce fait, elles sont conçues pour être très rapides, avoir une grande bande passante, au détriment de leur capacité mémoire. Pour être plus précis, l'eDRAM est une puce de DRAM conçue pour être intégrée dans un ''chiplet'', , à savoir des circuits imprimés qui regroupent plusieurs puces électroniques distinctes, regroupées sur le même PCB. Typiquement, un processeur de type ''chiplet'' avec de l'eDRAM comprend deux puces séparées : une pour le processeur, une autre pour une puce de communication avec la RAM. Avec la mémoire eDRAM, les deux puces sont complétées par une troisième puce spécialisée qui incorpore l'eDRAM. Elle a été utilisée sur quelques processeurs, mais aussi dans des consoles de jeu vidéo, pour la carte graphique des consoles suivantes : la PlayStation 2, la PlayStation Portable, la GameCube, la Wii, la Wii U, et la XBOX 360. Sur ces consoles, la RAM de la carte graphique était intégrée avec le processeur graphique dans le même circuit. La fameuse mémoire vidéo et le GPU n'étaient qu'une seule et même puce électronique, un seul circuit intégré. Ce n'est pas le cas sur une carte graphique moderne : regardez votre carte graphique avec attention et vous verrez que le GPU est une puce carrée située sous les ventilateurs, alors que les puces mémoires sont situées juste autour et soudées sur le PCB de la carte. Les processeurs Intel Core de microarchitecture Broadwell disposaient d'un cache L4 de 128 mébioctets, intégralement implémenté avec de la mémoire eDRAM. Quelques processeurs de la microarchitecture précédente (Haswell), disposaient aussi de ce cache. Le cache L4 eDRAM était implémenté sur un chiplet à part, à savoir que le processeur était composé de trois puces séparées : une pour le processeur, une autre pour la gestion des entrées-sorties, et une autre pour le cache L4. La puce pour le cache L4 était appelée ''Crystal Well''. La puce ''Crystal Well'' était une puce gravée en 22nm, ce qui était une finesse de gravure plus élevée que celle des processeurs associés. ''Crystal Well'' était très optimisé pour l'époque. Par exemple, elle disposait de bus séparées pour la lecture et l'écriture, chose qu'on retrouve fréquemment sur les SRAM mais qui est absent sur les mémoires DRAM actuelles. Pour le reste, elle ressemblait beaucoup aux mémoires DDR de l'époque (système de ''double data rate'', entres autres), mais elle allait à une fréquence plus élevée que les DRAM de l'époque et avait un débit bien plus élevé, pour une consommation moindre. ''Crystal Well'' consommait entre 1 à 5 watts (1 watt en veille, 5 à pleine utilisation), pour un débit binaire de 102 GB/s et fonctionnait à 3.2 GHz. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les mémoires SRAM synchrones | prevText=Les mémoires SRAM synchrones | next=Contrôleur mémoire externe | nextText=Le contrôleur mémoire externe }}{{autocat}} </noinclude> e5fec9zj7u6ngm3zgfs7durhzw9ew87 764646 764645 2026-04-23T13:58:49Z Mewtow 31375 /* L'arrangement vertical : cumuler des adresses mémoire */ 764646 wikitext text/x-wiki Après avoir vu les mémoires statiques (SRAM), il est temps de passer aux mémoires RAM dynamiques, aussi appelées mémoires DRAM. Pour rappel, les DRAM dynamiques ont pour défaut que leurss données s'effacent après un certain temps, en quelques millièmes ou centièmes de secondes . En conséquence, il faut réécrire chaque bit de la mémoire régulièrement pour éviter qu'il ne s'efface. On dit qu'on doit effectuer régulièrement un '''rafraîchissement mémoire'''. Et celui-ci rend les DRAM très différentes des SRAM. Les DRAM des PC ont beaucoup évolués dans le temps. Les toutes premières mémoires DRAM étaient des mémoires asynchrones, mais elles ont été remplacées par des modèles synchrones. Les modèles asynchrones ont été très nombreux. Après l'apparition des premières DRAM, les mémoires ''Fast Page Mode'' sont apparues, suivies par les mémoires ''Extended Data Out'', puis les EDO en mode rafale. Elles ont été utilisées jusque dans la moitié des années 90, pour ensuite être supplantées par les mémoires SDRAM. Les mémoires DDR actuelles sont des améliorations des mémoires SDRAM actuelles. Le fait est que les DRAM sont des mémoires électroniques comme les autres, qui se présentent sous la forme de circuits intégrés, à savoir que ce sont des petits boitiers noirs avec des broches. Il est possible de souder ces boitiers sur une cartre mère, et c'est ce qui est fait sur nombre d'ordinateurs portables. Mais il est aussi possible de regrouper plusieurs boitiers sur une barrette de RAM séparée. Dans ce qui suit, nous les appellerons des '''chips mémoire''', ou encore, des puces mémoires. [[File:Canyon CN-WF514 - EtronTech EM638325TS-6-4022.jpg|centre|vignette|upright=2|Exemple de chip mémoire.]] Dans ce qui suit, nous allons étudier ces chips de DRAM, avant de voir comment ils sont regroupés sur une barrette de RAM. Puis, nous allons voire chaque type de mémoire, FPM, EDO, SDRAM, DDR, ... ; un par un. ==L'interface des DRAM et le contrôleur mémoire== L'interface d'une mémoire DRAM est plus compliquée que l'interface d'une SRAM basique. Et c'est suffisant pour qu'on ait besoin d'un intermédiaire pour faire la conversion entre processeur et DRAM. Les DRAM modernes ne sont pas connectées directement au processeur, mais le sont par l'intermédiaire d'un '''contrôleur mémoire externe'''. Il ne faut pas le confondre avec le contrôleur mémoire interne, placé dans la mémoire RAM, et qui contient notamment le décodeur. Les deux sont totalement différents, bien que leur nom soit similaire. Pour éviter toute confusion, j'utiliserais le terme de '''contrôleur de DRAM''', plus parlant. ===Le bus d'adresse des DRAM est multiplexé=== Un point important pour le contrôleur de DRAM est de transformer les adresses mémoires fournies par le processeur, en adresses utilisables par la DRAM. Car les DRAM ont une interface assez spécifique. Les DRAM ont ce qui s'appelle un '''bus d'adresse multiplexé'''. Avec de tels bus, l'adresse est envoyée en deux fois. Les bits de poids fort sont envoyés avant les bits de poids faible. On peut ainsi envoyer une adresse de 32 bits sur un bus d'adresse de 16 bits, par exemple. Le bus d'adresse contient alors environ moitié moins de fils que la normale. Pour rappel, l'avantage de cette méthode est qu'elle permet de limiter le nombre de fils du bus d'adresse, ce qui très intéressant sur les mémoires de grande capacité. Les mémoires DRAM étant utilisées comme mémoire principale d'un ordinateur, elles devaient avoir une grande capacité. Cependant, avoir un petit nombre de broches sur les barrettes de mémoire est clairement important, ce qui impose d'utiliser des stratagèmes. Envoyer l'adresse en deux fois répond parfaitement à ce problème : cela permet d'avoir des adresses larges et donc des mémoires de forte capacité, avec une performance acceptable et peu de fils sur le bus d'adresse. Les bus multiplexés se marient bien avec le fait que les DRAM sont des mémoires à adressage par coïncidence ou à tampon de ligne. Sur ces mémoires, l'adresse est découpée en deux : une adresse haute pour sélectionner la ligne, et une adresse basse qui sélectionne la colonne. L'adresse est envoyée en deux fois : la ligne, puis la colonne. Pour savoir si une donnée envoyée sur le bus d'adresse est une adresse de ligne ou de colonne, le bus de commande de ces mémoires contenait deux fils bien particuliers : les RAS et le CAS. Pour simplifier, le signal RAS permettait de sélectionner une ligne, et le signal CAS permettait de sélectionner une colonne. [[File:Signaux RAS et CAS.png|centre|vignette|upright=2|Signaux RAS et CAS.]] Si on a deux bits RAS et CAS, c'est parce que la mémoire prend en compte les signaux RAS et CAS quand ils passent de 1 à 0. C'est à ce moment là que la ligne ou colonne dont l'adresse est sur le bus sera sélectionnée. Tant que des signaux sont à zéro, la ligne ou colonne reste sélectionnée : on peut changer l'adresse sur le bus, cela ne désélectionnera pas la ligne ou la colonne et la valeur présente lors du front descendant est conservée. [[File:L'intérieur d'une FPM.png|centre|vignette|upright=2|L'intérieur d'une FPM.]] ===Le rafraichissement mémoire=== La spécificité des DRAM est qu'elles doivent être rafraichies régulièrement, sans quoi leurs cellules perdent leurs données. Le rafraichissement est basiquement une lecture camouflée. Elle lit les cellules mémoires, mais n'envoie pas le contenu lu sur le bus de données. Rappelons que la lecture sur une DRAM est destructive, à savoir qu'elle vide la cellule mémoire, mais que le système d'amplification de lecture régénère le contenu de la cellule automatiquement. La cellule est donc rafraichie automatiquement lors d'une lecture. La quasi-totalité des DRAM supporte des commandes de rafraichissement, séparées des lectures et écritures classiques. Une commande de rafraichissement ordonne de rafraichir une adresse, voire une ligne complète. Les commandes de rafraichissement sont générées par le contrôleur de DRAM, dans la grosse majorité des cas. Il est aussi possible que ce soit le processeur qui les génère, mais c'est beaucoup plus rare. Il est aussi possible d'envoyer des commandes de rafraichissement vides, qui ne précisent ni adresse ni numéro de ligne. Pour les gérer, la mémoire contient un compteur, qui pointe sur la prochaine ligne à rafraichir, qui est incrémenté à chaque commande de rafraichissement. Une commande de rafraichissement indique à la mémoire d'utiliser l'adresse dans ce compteur pour savoir quelle adresse/ligne rafraichir. [[File:Rafraichissement mémoire automatique.png|centre|vignette|upright=2|Rafraichissement mémoire automatique.]] Il existe des mémoires qui sont des intermédiaires entre les mémoires SRAM et DRAM. Il s'agit des '''mémoires pseudo-statiques''', qui sont techniquement des mémoires DRAM, utilisant des transistors et des condensateurs, mais qui gèrent leur rafraichissement mémoire toutes seules. Le rafraichissement mémoire est alors totalement automatique, ni le processeur, ni le contrôleur de DRAM ne devant s'en charger. Le rafraichissement est purement le fait des circuits de la mémoire RAM et devient une simple opération de maintenance interne, gérée par la RAM elle-même. L'envoi des commandes de rafraichissement peuvent se faire de deux manières : soit on les envoie toutes en même temps, soit on les disperse le plus possible. Le premier cas est un '''rafraichissement en rafale''', le second un '''rafraichissement étalé'''. Le rafraichissement en rafale n'est pas utilisé dans les PC, car il bloque la mémoire pendant un temps assez long. Mais les anciennes consoles de jeu gagnaient parfois à utiliser eu rafraichissement en rafale. En effet, la mémoire était souvent effacée entre l'affichage de deux images, pour éviter certains problèmes dont on ne parlera pas ici. Le rafraichissement de la mémoire était effectué à ce moment là : l'effacement rafraichissait la mémoire. Le temps mis pour rafraichir la mémoire est le temps mis pour parcourir toute la mémoire. Il s'agit du temps de balayage vu dans le chapitre sur les performances d'un ordinateur. Pour les mémoires FPM et EDO, il est défini en divisant la capacité de la mémoire par son débit binaire. C'est le temps nécessaire pour lire ou réécrire tout le contenu de la mémoire. Sur les SDRAM, les choses sont un peu différentes, pour une raison qu'on expliquera plus bas. ===Le contrôleur de DRAM=== Le contrôleur de DRAM gère le bus mémoire et tout ce qui est envoyé dessus. Il envoie des commandes aux barrettes de mémoire, commandes qui peuvent être des lectures, des écritures, ou des demandes de rafraichissement, parfois d'autres commandes. La mémoire répond à ces commandes par l'action adéquate : lire la donnée et la placer sur le bus de données pour une commande de lecture, par exemple. Le rôle du contrôleur de DRAM varie grandement suivant le contrôleur en question, ainsi que selon le type de DRAM. Les anciens contrôleurs de DRAM étaient des composants séparés du processeur, du ''chipset'' ou du reste de la carte mère. Par exemple, les contrôleur de DRAM Intel 8202, Intel 8203 et Intel 8207 étaient vendus dans des boitiers DIP et étaient soudés sur la carte mère. Par la suite, ils ont été intégré au ''chipset'' de la carte mère pendant les décennies 90-2000. Après les années 2000, ils ont été intégrés dans les processeurs. Il est possible de connecter plusieurs barrettes sur le même bus mémoire, ou alors celles-ci sont connectées au contrôleur de DRAM avec un bus par barrette/boitier. C'est ce qui permet de placer plusieurs barrettes de mémoire sur la même carte mère : toutes les barrettes sont connectées au contrôleur de DRAM d'une manière ou d'une autre. ==Les rangées : l'arrangement horizontal et vertical== Il est rare d'utiliser un chip mémoire seul, car ceux-ci n'ont pas une capacité suffisante. Pour donner quelques chiffres, à l'heure où j'écris ces lignes, la norme pour un ordinateur est d'avoir entre 8 et 64 gibioctets de RAM. Mais les chips mémoire font entre 1 et 4 gibioctets, rarement plus. La raison est que les ordinateurs combinent ensemble plusieurs chips mémoire pour additionner leurs capacités. La concaténation de plusieurs chips mémoire peut se faire de deux manières différentes, appelées l'arrangement horizontal et l'arrangement vertical. Les deux additionnent la capacité des chips mémoire, mais se distinguent sur un point : ce qui arrive respectivement au bus de données, et au nombre d'adresses. Intuitivement, on se dit que doubler la capacité mémoire implique de doubler le nombre d'adresses mémoire. C'est effectivement ce qui se passe avec l'arrangement vertical. Mais avec l'arrangement horizontal, le nombre d'adresse ne varie pas. Voyons cela en détail, et commençons par le cas le plus simple, celui de l'arrangement vertical seul. ===L'arrangement vertical : cumuler des adresses mémoire=== Introduisons l'arrangement vertical par un exemple. Imaginez que je souhaite obtenir de 4 mébioctets de RAM, en combinant 4 chips mémoires de 1 mébioctet chacun. L'idée est que le premier mébioctet est placé dans le premier chip mémoire, le second mébioctet dans le second chip, etc. Des adresses consécutives se trouvent ainsi dans le même chip mémoire, sauf pour quelques adresses où on passe d'un chip à l'autre. Avec cette organisation, le bus de donnée fait un octet, et les chips mémoire ont aussi un bus de données d'un octet. Je peux alors combiner les capacités de plusieurs chips mémoire, sans toucher au bus de données. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses avec arrangement vertical.]] Pour sélectionner le chip mémoire adéquat, il faut que chaque chip mémoire dispose d'une entrée ''Chip Select'', qui permet de l'activer ou de le désactiver. L'idée est que selon l'adresse demandée, on active le chip mémoire associé à cette adresse. Les signaux ''Chip Select'' sont générés par le contrôleur de DRAM, à partir de l'adresse. On dit qu'il y a un '''décodage d'adresse'''. Avec cet arrangement, les bits de poids fort de l'adresse sont utilisées pour sélectionner la banque adéquate, et le reste de l'adresse est envoyé sur le bus d'adresse. Par exemple, avec 4 chips mémoire, les deux bits de poids fort de l'adresse sont utilisés pour sélectionner le chip mémoire adéquat. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Adresse de banque !! Adresse dans la banque |- | Quelques bits de poids fort || Reste de l'adresse |} ===L'arrangement horizontal : élargir le bus de données=== L'arrangement horizontal permet lui aussi d'additionner les capacités mémoire de plusieurs chips mémoire. Cependant, il les combine d'une autre manière. Le nombre d'adresses mémoire n'est pas changé en utilisant plusieurs chips, mais le bus de données est élargi. Le mieux pour comprendre l'idée est de partir d'un exemple, et nous allons prendre celui d'une mémoire SDRAM. Les ordinateurs actuels ont un bus de données de 64 bits (on met de côté le cas du double ou triple canal). Cependant, il n'existe pas de chip mémoire avec un bus aussi large. Les puces de SDRAM/DDR ont un bus de 4, 8 ou 16 bits, ce sont les tailles les plus courantes. L'arrangement horizontal résout ce problème en combinant plusieurs chips mémoire de manière à ce que leurs bus de données s'"additionnent", se concatènent. Par exemple, on peut regrouper 8 chips mémoires de 8 bits, obtenir un bus mémoire de 64 bits. Il est aussi possible d'obtenir ces 64 bits avec des puces de 16 chips mémoire de 4 bits, ou 4 chips mémoire de 16 bits. [[File:Arrangement horizontal SDRAM - un Rank.png|centre|vignette|upright=2|Arrangement horizontal SDRAM.]] Avec cette organisation, on accède à tous les bancs en parallèle à chaque accès, avec la même adresse. Vu que les chips mémoires contiennent tous une partie de la donnée demandée, ils doivent tous être activés en même temps. Pour cela, l'adresse à lire est envoyée à tous les chips mémoire d'un même ''rank'', idem pour les signaux de commande. Un ensemble de N chips reliés de cette manière forme une '''rangée''' (le terme anglais est ''rank''). [[File:Arrangement horizontal.jpg|centre|vignette|upright=2|Arrangement horizontal.]] ===L'arrangement horizontal et vertical combinés=== Nous venons de voir l'arrangement vertical et horizontal, pour ce qui est des barrettes de mémoire. Précisons que ce qui vient d'être dit marche aussi bien pour les barrettes de RAM que pour la mémoire soudée sur la carte mère. Du moment qu'on combine plusieurs chips mémoire ensemble, ces concepts restent valides. Et il en est de même pour la suite, encore que ce soit nettement moins fréquent avec de la mémoire soudée. Il est possible de combiner à la fois l'arrangement vertical et l'arrangement horizontal. Rien de plus simple : il suffit d'utiliser un arrangement vertical entre plusieurs rangées, chacun composée de plusieurs chips mémoire. C'est surtout utilisé sur les barrettes de mémoire SDRAM, qui contiennent 1, 2, 4 ou 8 rangées, rarement plus. Par exemple, une SDRAM peut combiner 16 chips de DRAM de 8 bits chacun, dans deux rangées de 64 bits chacun, chaque rangée regroupant 8 chips. [[File:SDRAM avec 4 ranks.png|centre|vignette|upright=2|SDRAM avec 4 ranks]] Le choix entre la première ou la seconde rangée se fait en configurant les bits ''Chip Select'' de chaque rangée. Il faut noter que les bits de ''Chip Select'' sont générés par le contrôleur mémoire, et envoyés sur le bus de commande. [[File:Td6bfig3.png|centre|vignette|upright=2|Comparaison entre arrangement horizontal (à gauche) et arrangement vertical (à droite).]] Le contrôleur de DRAM peut adresser un certain nombre de rangées, dispersés sur plusieurs barrettes. La limite maximale dépend du contrôleur de DRAM, elle est souvent proche de 8 ou 16 rangées. Si on combine plusieurs barrettes de mémoire, il est possible de dépasser cette limite. Par exemple, prenez un contrôleur de DRAM supportant maximum 8 rangées. Avec 4 barrettes contenant 4 rangées chacune, la limite est dépassée. : Il faut noter que tout ce qui vient d'être dit vaut aussi pour les mémoires ROM et SRAM. Mais en pratique, les arrangements verticaux et horizontaux sont surtout utilisés sur les mémoires DRAM. Il faut dire que de tels arrangements servent à augmenter la capacité mémoire, ce qui colle plus avec des DRAM que des SRAM ou des ROM. ==Les barrettes de mémoire DRAM== [[File:Ram-module.svg|droite|vignette|upright=0.5|Barrette de mémoire RAM.]] Il est possible de souder plusieurs boitiers de DRAM sur une cartre mère, et c'est ce qui est fait sur nombre d'ordinateurs portables. Mais dans les PC fixes, les puces de DRAM sont regroupées sur des ''barrettes mémoires'''. Les barrettes de mémoire se fixent à la carte mère sur un connecteur standardisé, appelé '''slot mémoire'''. Le dessin ci-contre montre une barrette de mémoire, celui-ci ci-dessous est celui d'un ''slot'' mémoire. [[File:Dual channel slots.jpg|centre|vignette|Slots mémoires.]] Sur le schéma de droite, on remarque facilement les boitiers de DRAM, rectangulaires, de couleur sombre. Chaque barrette combine ces puces de manière à additionner leurs capacités : on peut ainsi créer une mémoire de 8 gibioctets à partir de 8 puces d'un gibioctet, par exemple. Ils sont soudés sur un PCB en plastique vert sur lequel sont gravés des connexions métalliques. Les trucs dorés situés en bas des barrettes de mémoire sont des broches qui connectent la barrette au bus mémoire. Les barrettes des mémoires FPM/EDO/SDRAM/DDR n'ont pas le même nombre de broches, pour des raisons de compatibilité. {|class="wikitable" |- !Type de barrette !Type de mémoire !Nombre de broches |- | rowspan="2" | SIMM | rowspan="2" | FPM/EDO |30 |- |72 |- | rowspan="4" | DIMM |SDRAM |168 |- |DDR |184 |- |DDR2 |214, 240 ou 244, suivant la barrette ou la carte mère. |- |DDR3 |204 ou 240, suivant la barrette ou la carte mère. |} ===Le format des barrettes de mémoire=== Certaines barrettes ont des puces mémoire d'un seul côté alors que d'autres en ont sur les deux faces. Cela permet de distinguer les barrettes SIMM et DIMM. * Les '''barrettes SIMM''' ont des puces sur une seule face de la barrette. Elles étaient utilisées pour les mémoires FPM et EDO-RAM. * Les '''barrettes DIMM''' ont des puces sur les deux côtés. Elles sont utilisées sur les SDRAM et les DDR. {| class="flexible" |+ '''Barrette SIMM''' |- |[[File:SIMM FPM 4 MB - C0448721-7229.jpg|vignette|SIMM recto.]] |[[File:SIMM FPM 4 MB - C0448721-7230.jpg|vignette|SIMM verso.]] |} : Les modules DIMM tendent à avoir plus de rangées que les modules SIMM, mais ce n'est pas systématique. Il est souvent dit que les barrettes DIMM ont deux rangées, contre seulement 1 pour les SIMM, mais les contre-exemples sont nombreux. Les barrettes '''SO-DIMM''', pour ordinateurs portables, sont différentes des barrettes DIMM normales des DDR/SDRAM. La raison est qu'il n'y a pas beaucoup de place à l'intérieur d'un PC portable, ce qui demande de diminuer la taille des barrettes. {| |- |[[File:Desktop DDR Memory Comparison.svg|centre|vignette|upright=1.5|Barrettes de DDR pour PC de bureau.]] |[[File:Laptop SODIMM DDR Memory Comparison V2.svg|centre|vignette|upright=1.5|Barrettes de DDR pour PC portables.]] |} Les barrettes de Rambus ont parfois été appelées des '''barrettes RB-DIMM''', mais ce sont en réalité des DIMM comme les autres. La différence principale est que la position des broches n'était pas la même que celle des formats DIMM normaux, sans compter que le connecteur Rambus n'était pas compatible avec les connecteurs SDR/DDR normaux. ===Les interconnexions à l'intérieur d'une barrette de mémoire=== Les boîtiers de DRAM noirs sont connectés au bus par le biais de connexions métalliques. Toutes les puces d'une même rangée sont connectées aux bus d'adresse et de commande. Et les chips d'une même rangée reçoivent exactement les mêmes signaux de commande/adresses, ce qui permet d'envoyer la même adresse/commande à toutes les puces en même temps. La manière dont ces puces sont reliées au bus de commande dépend selon la mémoire utilisée. Les DDR1 et 2 utilisent ce qu'on appelle une '''topologie en T''', illustrée ci-dessous. On voit que le bus de commande forme une sorte d'arbre, dont chaque extrémité est connectée à une puce. La topologie en T permet d'égaliser le délai de transmission des commandes à travers le bus : la commande transmise arrive en même temps sur toutes les puces. Mais elle a de nombreux défauts, à savoir : elle fonctionne mal à haute fréquence, elle est difficile à router en raisons des embranchements. [[File:Organisation des bus de commandes sur les DDR1-2.png|centre|vignette|upright=3.0|Organisation des bus de commandes sur les DDR1-2, nommée topologie en T.]] En comparaison, les DDR3 utilisent une '''topologie ''fly-by''''', où les puces sont connectées en série sur le bus de commande/adresse. La topologie ''fly-by'' n'a pas les problèmes de la topologie en T : elle est simple à router et fonctionne très bien à haute fréquence. [[File:Organisation des bus de commandes sur les DDR3 - topologie fly-by.png|centre|vignette|upright=3.0|Organisation des bus de commandes sur les DDR3 - topologie ''fly-by'']] ===Les barrettes tamponnées (à registres)=== Certaines barrettes intègrent un registre tampon, qui fait l'interface entre le bus et la barrette de RAM. L'utilité est d'améliorer la transmission du signal sur le bus mémoire. Sans ce registre, les signaux électriques doivent traverser le bus, puis traverser les connexions à l'intérieur de la barrette, jusqu'aux puces de mémoire. Avec un registre tampon, les signaux traversent le bus, sont mémorisés dans le registre et c'est tout. Le registre envoie les commandes/données jusqu'aux puces mémoire, mais le signal a été régénéré par le registre. Le signal transmis est donc de meilleure qualité, ce qui augmente la fiabilité du système mémoire. Le défaut est que la présence de ce registre fait que les barrettes ont un temps de latence est plus important que celui des barrettes normales, du fait de la latence du registre. Les barrettes de ce genre sont appelées des '''barrettes RIMM'''. Il en existe deux types : * Avec les '''barrettes RDIMM''', le registre fait l'interface pour le bus d'adresse et le bus de commande, mais pas pour le bus de données. * Avec les '''barrettes LRDIMM''' (''Load Reduced DIMMs''), le registre fait tampon pour tous les bus, y compris le bus de données. [[File:Organisation des bus de commandes sur les RDIMM.png|centre|vignette|upright=3.0|Organisation des bus de commandes sur les RDIMM.]] ===Le ''Serial Presence Detect''=== [[File:SPD SDRAM.jpg|vignette|Localisation du SPD sur une barrette de SDRAM.]] Toute barrette de mémoire assez récente contient une petite mémoire ROM qui stocke les différentes informations sur la mémoire : délais mémoire, capacité, marque, etc. Cette mémoire s'appelle le '''''Serial Presence Detect''''', aussi communément appelé le SPD. Ce SPD contient non seulement les timings de la mémoire RAM, mais aussi diverses informations, comme le numéro de série de la barrette, sa marque, et diverses informations. Le SPD est lu au démarrage de l'ordinateur par le BIOS, afin de pourvoir configurer ce qu'il faut. Le contenu de ce fameux SPD est standardisé par un organisme nommé le JEDEC, qui s'est chargé de standardiser le contenu de cette mémoire, ainsi que les fréquences, timings, tensions et autres paramètres des mémoires SDRAM et DDR. Pour les curieux, vous pouvez lire la page wikipédia sur le SPD, qui donne son contenu pour les mémoires SDR et DDR : [https://en.wikipedia.org/wiki/Serial_presence_detect Serial Presence Detect]. ==Les mémoires asynchrones à RAS/CAS : FPM et EDO-RAM== Avant l'invention des mémoires SDRAM et DDR, il exista un grand nombre de mémoires différentes, les plus connues étant les mémoires fast page mode et EDO-RAM. Ces mémoires n'étaient pas synchronisées par un signal d'horloge, c'était des '''mémoires asynchrones'''. Quand ces mémoires ont été créées, cela ne posait aucun problème : les accès mémoire étaient très rapides et le processeur était certain que la mémoire aurait déjà fini sa lecture ou écriture au cycle suivant. Les mémoires asynchrones les plus connues étaient les '''mémoires FPM''' et '''mémoires EDO'''. Pour ce qui est de leur interface, il faut signaler qu'elles n'ont pas d'entrée ''Chip Select'' ou d'entrée ''Output Enable''. Les signaux RAS et CAS remplacent en quelque sorte ces deux signaux. Le bit RAS fait office de ''Chip Select'', le bit CAS fait office d'''Output Enable''. ===Les mémoires FPM=== Les '''mémoires FPM (''Fast Page Mode'')''' possédaient une petite amélioration, qui rendait l'adressage plus simple. Avec elles, il n'y a pas besoin de préciser deux fois la ligne si celle-ci ne changeait pas lors de deux accès consécutifs : on pouvait garder la ligne sélectionnée durant plusieurs accès. Par contre, il faut quand même préciser les adresses de colonnes à chaque changement d'adresse. Il existe une petite différence entre les mémoire ''Page Mode'' et les mémoires ''Fast-Page Mode'' proprement dit. Sur les premières, le signal CAS est censé passer à 0 avant qu'on fournisse l'adresse de colonne. Avec les ''Fast-Page Mode'', l'adresse de colonne pouvait être fournie avant que l'on configure le signal CAS. Cela faisait gagner un petit peu de temps, en réduisant quelque peu le temps d'accès total. [[File:Sélection d'une ligne sur une mémoire FPM ou EDO.png|centre|vignette|upright=2|Sélection d'une ligne sur une mémoire FPM ou EDO.]] Avec les '''mémoires en mode quartet''', il est possible de lire quatre octets consécutifs sans avoir à préciser la ligne ou la colonne à chaque accès. On envoie l'adresse de ligne et l'adresse de colonne pour le premier accès, mais les accès suivants sont fait automatiquement. La seule contrainte est que l'on doit générer un front descendant sur le signal CAS pour passer à l'adresse suivante. Vous aurez noté la ressemblance avec le mode rafale vu il y a quelques chapitres, mais il y a une différence notable : le mode rafale vrai n'aurait pas besoin qu'on précise quand passer à l'adresse suivante avec le signal CAS. [[File:Mode quartet.png|centre|vignette|upright=3|Mode quartet.]] Les '''mémoires FPM à colonne statique''' se passent même du signal CAS. Le changement de l'adresse de colonne est détecté automatiquement par la mémoire et suffit pour passer à la colonne suivante. Dans ces conditions, un délai supplémentaire a fait son apparition : le temps minimum entre deux sélections de deux colonnes différentes, appelé tCAS-to-CAS. [[File:Accès en colonne statique.jpg|centre|vignette|upright=2.5|Accès en colonne statique.]] ===Les mémoires EDO-RAM=== L''''EDO-RAM''' a été inventée quelques années après la mémoire FPM. Elle a été déclinée en deux versions : la EDO simple, et la EDO en rafale. L'EDO simple ajoutait une entrée ''Ouput Enable'' à une mémoire FPM. Pour rappel, l'entrée ''Ouput Enable'' permet de connecter/déconnecter la DRAM du bus de données. S'il est mis à 0, les lectures et écritures sont empêchées. Pour ajouter cette entrée, il a fallu rajouter un registre sur la sortie de donnée, celle qui sert pour les lectures. Et l'ajout de ce registre a introduit une capacité dite de ''pipelining'', sur le même modèle que pour les mémoires SRAM synchrones. La donnée pouvait être maintenue sur le bus de données durant un certain temps, même après la remontée du signal CAS. Le registre de sortie maintenait la donnée lu tant que le signal RAS restait à 0, et tant qu'un nouveau signal CAS n'a pas été envoyé. Faire remonter le signal CAS à 1 n'invalidait pas la donnée en sortie. La conséquence est qu'on pouvait démarrer une nouvelle lecture alors que la donnée de l'accès précédent était encore présent sur le bus de données. Le pipeline obtenu avait deux étages : un où on présentait l'adresse et sélectionnait la colonne, un autre où la donnée était lue depuis le registre de sortie. Les mémoires EDO étaient donc plus rapides. [[File:EDO RAM.png|centre|vignette|upright=3|EDO RAM]] Cependant, cela marchait surtout pour les lectures, pas pour les écritures. Une écriture ne démarre que quand la lecture ou écriture précédente est totalement terminée. De même, on ne peut pas démarrer un nouvel accès mémoire tant qu'une écriture est en cours. ===Les mémoires EDO-RAM avec mode rafale=== Les '''EDO en rafale''' effectuent les accès à 4 octets consécutifs automatiquement : il suffit d'adresser le premier octet à lire. Les 4 octets étaient envoyés sur le bus les uns après les autres, au rythme d'un par cycle d’horloge : ce genre d'accès mémoire s'appelle un accès en rafale. [[File:Accès en rafale.png|centre|vignette|upright=2|Accès en rafale sur une DRAM EDO.]] Implémenter cette technique nécessite d'ajouter un compteur, capable de faire passer d'une colonne à une autre quand on lui demande, et quelques circuits annexes pour commander le tout. [[File:Modifications du contrôleur mémoire liées aux accès en rafale.png|centre|vignette|upright=2|Modifications du contrôleur de DRAM liées aux accès en rafale.]] ===Le rafraichissement mémoire=== Les mémoires FPM et EDO doivent être rafraichies régulièrement. Au début, le rafraichissement se faisait ligne par ligne. Le rafraichissement avait lieu quand le RAS passait à l'état haut, alors que le CAS restait à l'état bas. Le processeur, ou le contrôleur mémoire, sélectionnait la ligne à rafraichir en fournissant son adresse mémoire. D'où le nom de '''rafraichissement par adresse''' qui est donné à cette méthode de commande du rafraichissement mémoire. Divers processeurs implémentaient de quoi faciliter le rafraichissement par adresse. Par exemple, le Zilog Z80 contenait un compteur de ligne, un registre qui contenait le numéro de la prochaine ligne à rafraichir. Il était incrémenté à chaque rafraichissement mémoire, automatiquement, par le processeur lui-même. Un ''timer'' interne permettait de savoir quand rafraichir la mémoire : quand ce ''timer'' atteignait 0, une commande de rafraichissement était envoyée à la mémoire, et le ''timer'' était ''reset''. [[File:Rafraichissement mémoire manuel.png|centre|vignette|upright=2|Rafraichissement mémoire manuel.]] Par la suite, certaines mémoires ont implémenté un compteur interne d'adresse, pour déterminer la prochaine adresse à rafraichir sans la préciser sur le bus d'adresse. Le déclenchement du rafraichissement se faisait toujours par une commande externe, provenant du contrôleur de DRAM ou du processeur. Cette commande faisait passer le CAS à 0 avant le RAS. Cette méthode de rafraichissement se nomme '''rafraichissement interne'''. [[File:Rafraichissement sur CAS précoce.png|centre|vignette|upright=2|Rafraichissement sur CAS précoce.]] On peut noter qu'il est possible de déclencher plusieurs rafraichissements à la suite en laissant le signal CAS dans le même état. Ce genre de choses pouvait avoir lieu après une lecture : on pouvait profiter du fait que le CAS soit mis à zéro par la lecture ou l'écriture pour ensuite effectuer des rafraichissements en touchant au signal RAS. Dans cette situation, la donnée lue était maintenue sur la sortie durant les différents rafraichissements. [[File:Rafraichissements multiples sur CAS précoce.png|centre|vignette|upright=2|Rafraichissements multiples sur CAS précoce.]] ==Les mémoires SDRAM== Dans les années 90, les mémoires asynchrones ont laissé la place aux '''mémoires SDRAM''', qui sont synchronisées avec le bus par une horloge. L'utilisation d'une horloge a comme avantage des temps d'accès fixes : le processeur sait qu'un accès mémoire prendra un nombre déterminé de cycles d'horloge. Avec les mémoires asynchrones, le processeur ne pouvait pas prévoir quand la donnée serait disponible et ne faisait rien tant que la mémoire n'avait pas répondu : il exécutait ce qu'on appelle des ''wait states'' en attendant que la mémoire ait fini. Les mémoires SDRAM sont standardisées par un organisme international, le JEDEC. Le standard SDRAM impose des spécifications électriques bien précise pour les barrettes de mémoire et le bus mémoire, décrit le protocole utilisé pour communiquer avec les barrettes de mémoire, et bien d'autres choses encore. Les SDRAM ont été déclinées en versions de performances différentes, décrites dans le tableau ci-dessous : {| class="wikitable" ! Nom standard ! Fréquence ! Bande passante |- | PC66 | 66 mhz | 528 Mio/s |- | PC66 | 100 mhz | 800 Mio/s |- | PC66 | 133 mhz | 1064 Mio/s |- | PC66 | 150 mhz | 1200 Mio/s |} ===Les banques internes aux chips mémoires SDRAM=== L'intérieur d'une mémoire SDRAM contient plusieurs '''banques''', aussi appelées des banc mémoire. Concrètement, une banque est... une mémoire. Ou plutôt, une sorte de mini-mémoire miniature. Chaque banque a son propre tampon de ligne, ses propres multiplexeurs de colonne et ses propres décodeurs. C'est comme si une SDRAM regroupait plusieurs mémoires séparées dans un même circuit intégré. [[File:Arrangement vertical.jpg|centre|vignette|upright=2.5|Mémoire multi-banques.]] Un point important est que chaque banque a son propre tampon de ligne. Il est donc possible d'ouvrir plusieurs lignes en même temps, chacune dans une banque différente. Par exemple, on peut ouvrir une ligne dans la banque numéro 1, et une autre ligne dans la banque numéro 2. Et c'est une source d'optimisations très intéressantes. La première optimisation est liée au rafraichissement mémoire. Au lieu de rafraichir chaque adresse une par une, il est possible de rafraichir des banques indépendantes en même temps, ce qui divise le temps de rafraichissement par le nombre de banques. C'est ce que je sous-entendais plus haut quand je disais que le temps de rafraichissement n'est pas égal au temps de balayage sur les SDRAM, alors que c'est le cas sur les DRAM FPM et EDO. De plus, et sans rentrer dans les détails, cela permet de faire plusieurs accès mémoire en même temps, dans des banques différentes. La possibilité est limitée, mais elle existe et elle améliore grandement la performance de la SDRAM. Mais nous en reparlerons dans un chapitre ultérieur, car cette histoire d'accès simultanés a plus sa place dans le chapitre sur le parallélisme mémoire. Pour le moment, nous ne pouvons pas expliquer pourquoi ni comment un processeur pourrait émettre plusieurs accès mémoire simultanés. Un processeur est censé travailler une instruction à la fois, à ce stade du cours, il ne peut pas en faire plusieurs en même temps. Mais nous allons cependant mentionner un cas où cette possibilité est intéressante : une mémoire SDRAM partagée entre un processeur et une carte graphique. Les deux accèdent à des données séparées, qui sont dans des banques différentes. On suppose que la carte graphique accède plus fréquemment à la mémoire que le processeur. Le contrôleur mémoire reçoit les accès mémoire du CPU et du GPU et il tente d'intercaler des accès CPU entre deux accès de la carte graphique. Vu qu'ils tombent dans des banques différentes, un accès CPU et un accès carte graphique peuvent se faire presque en même temps. La seule contrainte est que si on lance plusieurs accès mémoire simultanés, ceux-ci ne peuvent pas utiliser le bus de données en même temps. {|class="wikitable" |+ Pipelining basique sur les SDRAM |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 |- ! Banque Numéro 1 | || || || || || || || || || || |- ! Banque Numéro 2 | || colspan="3" bgcolor="#A0FFFF" | Accès CPU || || || colspan="3" bgcolor="#A0FFFF" | Accès CPU || || |- ! Banque Numéro 3 | || || || || || || || || || || |- ! Banque Numéro 4 | || || || colspan="3" bgcolor="#FFA0FF" | Accès carte graphique || || colspan="3" bgcolor="#FFA0FF" | Accès carte graphique || |} ===Le mode rafale des SDRAM=== Un point important est que les SDRAM reprennent les optimisations des mémoires FPM et EDO. Elles utilisent aussi un tampon de ligne, avec la possibilité de lire plusieurs colonnes à la suite sans avoir à préciser l'adresse de ligne à chaque fois. Mais surtout, elles gèrent nativement le mode rafale. les paramètres qui ont trait au mode rafale sont configurables. Il est possible de configurer la SDRAM pour activer les accès sans rafale, ou les désactiver. Il y a aussi la possibilité de configurer le nombre d'octets consécutifs à lire ou écrire en mode rafale. On peut ainsi accéder à 1, 2, 4, ou 8 octets en une seule fois, alors que les EDO ne permettaient que des accès à 4 octets consécutifs. Enfin, on peut décider s'il faut faire un accès en mode linéaire ou entrelacé. La configuration de la SDRAM est mémorisée dans un registre de 10 bits, le '''registre de mode'''. Il faisait 10 bits sur les mémoires SDRAM, mais a été étendu à 13 bits sur la DDR2. Voici les 10 bits originels de ce registre : {|class="wikitable" |+ Signification des bits du registre de mode des SDRAM |- ! Bit n°9 | Type d'accès : en rafale ou normal |- ! Bit n°8 et 7 | Doivent valoir 00, sont réservés pour une utilisation ultérieur dans de futurs standards. |- ! Bit n°6, 5, et 4 | Latence CAS (voir plus bas) |- ! Bit n°3 | Type de rafale : linéaire ou entrelacée |- ! Bit n°2, 3, et 0 | Longueur de la rafale : indique le nombre d'octets à lire/écrire lors d'une rafale. |} ===L'interface d'une mémoire SDRAM=== Le bus de commandes d'une SDRAM contient au moins 18 fils, dont celui pour le signal d'horloge. L'interface d'une SDRAM contient tous les bits présents sur une mémoire DRAM classique : une entrée RAS, une entrée CAS, une entrée R/W, et un bus d'adresse. A cela, il faut cependant ajouter une entrée ''Chip Select'' (CS), qui permet d'activer/désactiver la mémoire SDRAM. Je rappelle que le bit CS a été introduit sur les mémoires SDRAM, il n'était pas présent sur les mémoires FPM/EDO. Deux autres bits de commande sont vraiment spécifiques des mémoires SDRAM. Il s'agit des bits CKE et DQM. Le '''bit CKE''' est l'abréviation de ''Clock Enable'', qui qui trahit sa fonction. Lorsque ce signal est à 0, le chip de SDRAM voit son signal d'horloge gelè. S'il est à 0, le contrôleur de DRAM peut envoyer ce qu'il veut sur le bus de commande ou d'adresse, la SDRAM ne réagira pas du tout, il ne se passera rien. Le '''bit DQM''' est une sorte de bit ''Output Enable'', avec une nuance importante. Le terme DQM est l'abréviation de ''Data Mask'', ce qui trahit encore une fois sa fonction. Il y a un bit DQM pour chaque octet du bus de données. Une SDRAM ayant un bus de 64 bits, cela fait 8 bits DQM par mémoire SDRAM. Lorsque le bit DQM est à 1, l'octet en question n'est simplement pas lu ou écrit, le bus de donnée est déconnecté pour cet octet. Le bus d'adresse est particulier, car il tient compte de la présence de ''banques mémoires''. Le bus d'adresse est découpé en deux portions : une portion pour sélectionner la banque, une autre pour sélectionner l'adresse à l'intérieur d'une banque. L'interface de la SDRAM fait ainsi la différence entre une '''adresse de banque''' et une '''adresse intra-banque'''. L'adresse de banque est codée sur quelques bits, généralement deux ou trois suivant la SDRAM. Le reste de l'adresse est codé sur 11 bits sur les SDRAM, mais cela a augmenté avec les DDR 1, 2, 3, 4, 5. Le bus de données d'une SDRAM fait 4, 8, ou 16 bits. Je précise bien qu'il s'agit là des puces de SDRAM, les barrettes de SDRAM combinent plusieurs puces SDRAM avec un arrangement horizontal, qui peut combiner plusieurs puces de 8 bits pour alimenter un bus de données de 64 bits. La taille des puces utilisées souvent indiquée sur la barrette de RAM, avec une mention x4, x8 ou x16. Les puces de SDRAM les plus courantes ont une interface de 8 bits pour les données. Les SDRAM de 4 bits sont surtout utilisées pour les serveurs, c'est lié au support de l'ECC. les puces x16 sont moins utilisées car elles ont généralement moins de banques que les autres. ===Les commandes SDRAM=== Le bus de commande permet d'envoyer des commandes à la mémoire, chaque commande étant précisée par une combinaison précise des bits CS, RAS, CAS, R/W, et autres. Les commandes en question sont des demandes de lecture, d'écriture, de préchargement et autres. Elles sont codées par une valeur bien précise qui est envoyée sur les 18 fils du bus de commande. Ces commandes sont nommées READ, READA, WRITE, WRITEA, PRECHARGE, ACT, ... Les plus importantes sont les commandes PRECHARGE, ACT et READ/WRITE. La commande ACT sélectionne une ligne : elle met le bit RAS à zéro et présente une adresse de ligne. Les commandes READ et WRITE sélectionnent une colonne, et déclenchent respectivement une lecture ou une écriture. Elles précisent une adresse de colonne, mettent le bit CAS à 0 et le bit RAS à 1, et précise la valeur du bit R/W. Les commandes READ et WRITE ne peuvent se faire qu'une fois que la banque a été activée par une commande ACT. Il est possible d'envoyer plusieurs commandes READ ou WRITE successives à des colonnes différentes, ce qui permet d'implémenter les optimisations des mémoires FPM. La commande PRECHARGE ferme la ligne courante et prépare l'ouverture de la suivante. Elle précharge les lignes de bit de la RAM, d'où son nom. Il est nécessaire d'en envoyer une avant d'envoyer une commande ACT. Notons que la commande PRECHARGE agit sur une banque, dont l'adresse est indiquée dans la commande PRECHARGE. Il existe une commande PRECHARGE ALL, qui agit sur toutes les banques de la SDRAM à la fois. Les commandes READA et WRITEA fusionnent une commande READ/WRITE avec une commande PRECHARGE. Elles permettent d'éviter d'avoir à envoyer une commande PRECHARGE pour fermer la ligne courante. Au lieu d'envoyer une commande READ ou WRITE, puis une commande PRECHARGE pour fermer la ligne, on envoie une seule commande READA/WRITEA. Il s'agit d'une petite optimisation, qui permet de réduire le nombre de commandes envoyées sur le bus. Les commandes sont encodées comme indiquées dans ce tableau. Une commande est tout simplement encodée en précisant une adresse si nécessaire, et une combinaison des bits CS, RAS, CAS et R/W. La seule subtilité est que le bit numéro 10 du bus d'adresse sert à commander les opérations de PRECHARGE, y compris celles implicites dans les opérations READA et WRITEA. {| class="wikitable" style="text-align:center" ! Bit CS || Bit RAS || Bit CAS || Bit WE || Bits de sélection de banque (2 bits) || Bit du bus d'adresse A10 || Reste du bus d'adresse || Nom de la commande : Description |- | 1 | colspan="6" | X | Absence de commandes. |- | 0 || 1 || 1 || 1 || colspan="3" | X || No Operation : Pas d'opération |- | 0 || 1 || 1 || 0 || colspan="3" | X || Burst Terminante : Stoppe un accès en rafale (en cours). |- | 0 || 1 || 0 || 1 || Adresse de la banque || 0 || Adresse de la colonne || READ : lit une donnée depuis la ligne active. |- | 0 || 1 || 0 || 1 || Adresse de la banque || 1 || Adresse de la colonne || READA : lit une donnée depuis la ligne active, puis ferme la ligne. |- | 0 || 1 || 0 || 0 || Adresse de la banque || 0 || Adresse de la colonne || WRITE : écrit une donnée dans la ligne active. |- | 0 || 1 || 0 || 0 || Adresse de la banque || 1 || Adresse de la colonne || WRITEA : écrit une donnée dans la ligne active, puis ferme la ligne. |- | 0 || 0 || 1 || 1 || Adresse de la banque || colspan="2" | Adresse de la ligne || ACT : charge une ligne dans le tampon de ligne. |- | 0 || 0 || 1 || 0 || Adresse de la banque || 0 || X || PRECHARGE : précharge le tampon de ligne dans la banque voulue. |- | 0 || 0 || 1 || 0 || Adresse de la X || 1 || X || PRECHARGE ALL : précharge le tampon de ligne dans toutes les banques. |- | 0 || 0 || 0 || 1 || colspan="3" | X || Auto refresh : Demande de rafraichissement, gérée par la SDRAM. |- | 0 || 0 || 0 || 0 || 00 || colspan="2" | Nouveau contenu du registre de mode || LOAD MODE REGISTER : configure le registre de mode. |} Les commandes ACT se font à partir de l'état de repos, l'état où toutes les banques sont préchargées. Par contre, les commandes MODE REGISTER SET et AUTO REFRESH ne peuvent se faire que si toutes les banques sont désactivées. Le fonctionnement simplifié d'une SDRAM peut se résumer dans ce diagramme : [[File:Fonctionnement simplifié d'une SDRAM.jpg|centre|vignette|upright=2|Fonctionnement simplifié d'une SDRAM.]] ===Les délais mémoires=== Les mémoires SDRAM n'étant pas infiniment rapides, il y a toujours un certain délais à respecter entre deux commandes. Par exemple, quand on envoie une commande ACT pour activer une ligne, on ne peut pas envoyer une commande READ/WRITE au cycle suivant. La plupart des SDRAM ne sont pas assez rapides pour ça. Il faut respecter un délai de quelques cycles, qui dépend de la mémoire. Et il n'y a pas que ce délai entre une commande ACT et la commande suivante. Une SDRAM doit gérer d'autres temps d'attente, appelés des '''délais mémoires''', ou encore des ''timings'' mémoire. Les délais mémoire le plus importants sont résumés ci-dessous : {|class="wikitable" |- !Timing!!Description |- | colspan="2" | |- ! colspan="2" | Délais primaires |- ||tRP|| Temps entre une commande PRECHARGE et une commande ACT |- ||tRCD|| Temps entre une commande ACT et une commande READ/WRITE. |- ||tCL|| Temps entre une commande READ et l'envoi de la donnée lue sur le bus de données. |- ||tDQSS|| Temps entre une commande WRITE et l'écriture de la donnée. |- ||tCAS-to-CAS|| Temps minimum entre deux commandes READ. |- ! colspan="2" | Délais secondaires |- ||tWTR|| Temps entre une lecture et une écriture consécutives. |- ||tRAS || Temps entre une commande ACT et une commande PRECHARGE. |} La façon de mesurer ces délais varie : sur les mémoires FPM et EDO, on les mesure en unités de temps (secondes, millisecondes, micro-secondes, etc.), tandis qu'on les mesure en cycles d'horloge sur les mémoires SDRAM. Les délais/timings mémoire ne sont pas les mêmes suivant la barrette de mémoire que vous achetez. Certaines mémoires sont ainsi conçues pour avoir des timings assez bas et sont donc plus rapides, et surtout : beaucoup plus chères que les autres. Le gain en performances dépend beaucoup du processeur utilisé et est assez minime comparé au prix de ces barrettes. Les délais mémoires d'une barrette sont mémorisés dans le ''Serial Presence Detect'' de la barrette et sont lus par le BIOS au démarrage de l'ordinateur, et sont ensuite utilisés pour configurer le contrôleur de DRAM. ==Les mémoires DDR== Les mémoires SDRAM récentes sont des mémoires de type ''dual data rate'', ce qui fait qu'elles portent le nom de mémoires DDR. Pour rappel, les mémoires ''dual data rate'' ont un plan mémoire deux fois plus large que le bus mémoire, avec un bus mémoire allant à une fréquence double. Par double, on veut dire que les transferts sur le bus mémoire ont lieu sur les fronts montants et descendants de l'horloge. Il y a donc deux transferts de données sur le bus pour chaque cycle d'horloge, ce qui permet de doubler le débit sans toucher à la fréquence du plan mémoire lui-même. Les mémoires DDR sont standardisées par un organisme international, le JEDEC, et ont été déclinées en plusieurs générations : DDR1, DDR2, DDR3, et DDR4. La différence entre ces modèles sont très nombreuses, mais les plus évidentes sont la fréquence de la mémoire et du bus mémoire. D'autres différences mineures existent entre les SDRAM et les mémoires DDR. Par exemple, la tension d'alimentation des mémoires DDR est plus faible que pour les SDRAM. ET elle a diminué dans le temps, d'une génération de DDR à l'autre. Avec les mémoires DDR2,la tension d'alimentation est passée de 2,5/2,6 Volts à 1,8 Volts. Avec les mémoires DDR3, la tension d'alimentation est notamment passée à 1,5 Volts. ===Les performances des mémoires DDR=== Les mémoires SDRAM ont évolué dans le temps, mais leur temps d'accès/fréquence n'a pas beaucoup changé. Il valait environ 10 nanosecondes pour les SDRAM, approximativement 5 ns pour la DDR-400, il a peu évolué pendant la génération DDR et DDR3, avant d'augmenter pendant les générations DDR4 et de stagner à nouveau pour la génération DDR5. L'usage du DDR, puis du QDR, visait à augmenter les performances malgré la stagnation des temps d'accès. En conséquence, la fréquence du bus a augmenté plus vite que celle des puces mémoire pour compenser. {|class="wikitable" |- ! Année ! Type de mémoire ! Fréquence de la mémoire (haut de gamme) ! Fréquence du bus ! Coefficient multiplicateur entre les deux fréquences |- | 1998 | DDR 1 | 100 - 200 MHz | 200 - 400 MHz | 2 |- | 2003 | DDR 2 | 100 - 266 MHz | 400 - 1066 MHz | 4 |- | 2007 | DDR 3 | 100 - 266 MHz | 800 - 2133 MHz | 8 |- | 2014 | DDR 4 | 200 - 400 MHz | 1600 - 3200 MHz | 8 |- | 2020 | DDR 5 | 200 - 450 MHz | 3200 - 7200 MHz | 8 à 16 |} Une conséquence est que la latence CAS, exprimée en nombre de cycles, a augmenté avec le temps. Si vous comparez des mémoires DDR2 avec une DDR4, par exemple, vous allez voir que la latence CAS est plus élevée pour la DDR4. Mais c'est parce que la latence est exprimée en nombre de cycles d'horloge, et que la fréquence a augmentée. En comparant les temps d'accès exprimés en secondes, on voit une amélioration. ===Les commandes des mémoires DDR=== Les commandes des mémoires DDR sont globalement les mêmes que celles des mémoires SDRAM, vues plus haut. Les modifications entre SDRAM, DDR1, DDR2, DDR3, DDR4, et DDR5 sont assez mineures. Les seules différences sont l'addition de bits pour la transmission des adresses, des bits en plus pour la sélection des banques, etc. En clair, une simple augmentation quantitative. Le registre de mode a été un peu modifié. Il est passé de 10 bits pour les SDRAM et DDR1, à 13 bits sur la DDR 2 et les suivantes. Les DDR ont aussi ajouté le support de plusieurs registres de mode, qui sont sélectionnés en réutilisant l'adresse de banque. Dans une commande LOAD MODE REGISTER, l'adresse de banque indique quel registre de mode il faut altérer. Avant la DDR4, les modifications des commandes sont mineures. La DDR2 supprime la commande ''Burst Terminate'', la DDR3 et la DDR4 utilisent le bit A12 pour préciser s'il faut faire une rafale complète, ou une rafale de moitié moins de données. Une optimisation des DDR2 et 3 est celle des '''CAS postés'''. L'idée est que le contrôleur de DRAM peut envoyer une commande ACT et une commande READ/WRITE sans se soucier des ''timings'' nécessaires entre les deux. En théorie, les deux commandes doivent être séparées par quelques cycles, sur une SDRAM ou une DDR1. Mais avec la DDR2, le contrôleur de DRAM peut envoyer les deux l'une après l'autre, au cycle suivant. C'est la mémoire qui mettra en attente la commande READ/WRITE pour respecter les ''timings'' mémoire. Cela complexifie le fonctionnement interne de la DDR, mais simplifie grandement le travail du contrôleur de DRAM. Mais avec la DDR4, les choses changent, notamment au niveau de la commande ACT. Avec l'augmentation de la capacité des barrettes mémoires, la taille des adresses est devenue trop importante. Pour éviter de rajouter des bits d'adresses, les concepteurs du standard DDR4 ont décidé de ruser. Lors d'une commande ACT, les bits RAS, CAS et WE sont utilisés comme bits d'adresse, alors qu'ils ont leur signification normale pour les autres commandes. Pour éviter toute confusion, un nouveau bit ACT est ajouté pour indiquer la présence d'une commande ACT : il est à 1 pour une commande ACT, 0 pour les autres commandes. {| class="wikitable" style="text-align:center" |+ Commandes d'une mémoire DDR4, seule la commande colorée change par rapport aux SDRAM ! Bit CS || style="background: #CCFFCC" | Bit ACT || Bit RAS || Bit CAS || Bit WE || Bits de sélection de banque (4 bits) || Bit du bas d'adresse A10 || Reste du bus d'adresse || Nom de la commande : Description |- | 1 | colspan="6" | X | Absence de commandes. |- | 0 || style="background: #CCFFCC" | 0 || 1 || 1 || 1 || colspan="3" | X || No Operation : Pas d'opération |- | 0 || style="background: #CCFFCC" | 0 || 1 || 1 || 0 || colspan="3" | X || Burst Terminante : Arrêt d'un accès en rafale en cours. |- | 0 || style="background: #CCFFCC" | 0 || 1 || 0 || 1 || Adresse de la banque || 0 || Adresse de la colonne || READ : lire une donnée depuis la ligne active. |- | 0 || style="background: #CCFFCC" | 0 || 1 || 0 || 1 || Adresse de la banque || 1 || Adresse de la colonne || READA : lire une donnée depuis la ligne active, avec rafraichissement automatique de la ligne. |- | 0 || style="background: #CCFFCC" | 0 || 1 || 0 || 0 || Adresse de la banque || 0 || Adresse de la colonne || WRITE : écrire une donnée depuis la ligne active. |- | 0 || style="background: #CCFFCC" | 0 || 1 || 0 || 0 || Adresse de la banque || 1 || Adresse de la colonne || WRITEA : écrire une donnée depuis la ligne active, avec rafraichissement automatique de la ligne. |- style="background: #CCFFCC" | 0 || style="background: #CCFFCC" | 1 || colspan="3" | Adresse de la ligne (bits de poids forts) || Adresse de la banque || colspan="2" | Adresse de la ligne (bits de poids faible) || ACT : charge une ligne dans le tampon de ligne. |- | 0 || style="background: #CCFFCC" | 0 || 0 || 1 || 0 || Adresse de la banque || 0 || X || PRECHARGE : précharge le tampon de ligne dans la banque voulue. |- | 0 || style="background: #CCFFCC" | 0 || 0 || 1 || 0 || Adresse de la X || 1 || X || PRECHARGE ALL : précharge le tampon de ligne' dans toutes les banques. |- | 0 || style="background: #CCFFCC" | 0 || 0 || 0 || 1 || colspan="3" | X || Auto refresh : Demande de rafraichissement, gérée par la SDRAM. |- | 0 || style="background: #CCFFCC" | 0 || 0 || 0 || 0 || Numéro de registre de mode || colspan="2" | Nouveau contenu du registre de mode || LOAD MODE REGISTER : configure le registre de mode. |} ==Les VRAM des cartes vidéo== Les cartes graphiques ont des besoins légèrement différents des DRAM des processeurs, ce qui fait qu'il existe des mémoires DRAM qui leur sont dédiées. Elles sont appelés des '''''Graphics RAM''''' (GRAM). La plupart incorporent des fonctionnalités utiles uniquement pour les mémoires vidéos, comme des fonctionnalités de masquage (appliquer un masque aux données lue ou à écrire), ou le remplissage d'un bloc de mémoire avec une donnée unique. Les anciennes cartes graphiques et les anciennes consoles utilisaient de la DRAM normale, faute de mieux. La première GRAM utilisée était la NEC μPD481850, qui a été utilisée sur la console de jeu PlayStation, à partir de son modèle SCPH-5000. D'autres modèles de GRAM ont rapidement suivi. Les anciennes consoles de jeu, mais aussi des cartes graphiquesn utilisaient des GRAM spécifiques. ===Les mémoires vidéo double port=== Sur les premières consoles de jeu et les premières cartes graphiques, le ''framebuffer'' était mémorisé dans une mémoire vidéo spécialisée appelée une '''mémoire vidéo double port'''. Le premier port était connecté au processeur ou à la carte graphique, alors que le second port était connecté à un écran CRT. Aussi, nous appellerons ces deux port le ''port CPU/GPU'' et l'autre sera appelé le ''port CRT''. Le premier port était utilisé pour enregistrer l'image à calculer et faire les calculs, alors que le second port était utilisé pour envoyer à l'écran l'image à afficher. Le port CPU/GPU est tout ce qu'il y a de plus normal : on peut lire ou écrire des données, en précisant l'adresse mémoire de la donnée, rien de compliqué. Le port CRT est assez original : il permet d'envoyer un paquet de données bit par bit. De telles mémoires étaient des mémoires à tampon de ligne, dont le support de mémorisation était organisé en ligne et colonnes. Une ligne à l'intérieur de la mémoire correspond à une ligne de pixel à l'écran, ce qui se marie bien avec le fait que les anciens écrans CRT affichaient les images ligne par ligne. L'envoi d'une ligne à l'écran se fait bit par bit, sur un câble assez simple comme un câble VGA ou autre. Le second port permettait de faire cela automatiquement, en permettant de lire une ligne bit par bit, les bits étant envoyés l'un après l'autre automatiquement. Pour cela, les mémoires vidéo double port incorporaient un tampon de ligne spécialisé pour le port lié à l'écran. Ce tampon de ligne n'était autre qu'un registre à décalage, contrairement au tampon de ligne normal. Lors de l'accès au second port, la carte graphique fournissait un numéro de ligne et la ligne était chargée dans le tampon de ligne associé à l'écran. La carte graphique envoyait un signal d'horloge de même fréquence que l'écran, qui commandait le tampon de ligne à décalage : un bit sortait à chaque cycle d'écran et les bits étaient envoyé dans le bon ordre. ===Les mémoires SGRAM et GDDR=== De nos jours, les cartes graphiques n'utilisent plus de mémoires double port, mais des mémoires simple port. Les mémoires graphiques actuelles sont des SDRAM modifiées pour fonctionner en tant que ''Graphic RAM''. Les plus connues sont les '''mémoires GDDR''', pour ''graphics double data rate'', utilisées presque exclusivement sur les cartes graphiques. Il en existe plusieurs types pendant que j'écris ce tutoriel : GDDR, GDDR2, GDDR3, GDDR4, et GDDR5. Mais attention, il y a des différences avec les DDR normales. Par exemple, les GDDR ont une fréquence plus élevée que les DDR normales, avec des temps d'accès plus élevés (sauf pour le tCAS). De plus, elles sont capables de laisser ouvertes deux lignes en même temps. Par contre, ce sont des mémoires simple port. ==Les mémoires SLDRAM, RDRAM et associées== Les mémoires précédentes sont généralement associées à des bus larges. Les mémoires SDRAM et DDR modernes ont des bus de données de 64 bits de large, avec des d'adresse et de commande de largeur similaire. Le nombre de fils du bus mémoire dépasse facilement la centaine de fils, avec autant de broches sur les barrettes de mémoire. Largeur de ces bus pose de problèmes problèmes électriques, dont la résolution n'est pas triviale. En conséquence, la fréquence du bus mémoire est généralement moins performantes comparé à ce qu'on aurait avec un bus moins large. Mais d'autres mémoires DRAM ont exploré une solution alternative : avoir un bus peu large mais de haute fréquence, sur lequel on envoie les commandes/données en plusieurs fois. Elles sont regroupées sous le nom de '''DRAM à commutation par paquets'''. Elles utilisent des bus spéciaux, où les commandes/adresses/données sont transmises par paquets, par trames, en plusieurs fois. En théorie, ce qu'on a dit sur le codage des trames dans le chapitre sur le bus devrait s'appliquer à de telles mémoires. En pratique, les protocoles de transmission sur le bus mémoire sont simplifiés, pour gérer le fonctionnement à haute fréquence. Le processeur envoie des paquets de commandes, les mémoires répondent avec des paquets de données ou des accusés de réception. Les mémoires à commutation par paquets sont peu nombreuses. Les plus connues sont les mémoires conçues par la société Rambus, à savoir la ''RDRAM'' (''Rambus DRAM'') et ses deux successeurs ''XDR RAM'' et ''XDR RAM 2''. La ''Synchronous-link DRAM'' (''SLDRAM'') est un format concurrent conçu par un consortium de plusieurs concepteurs de mémoire. ===La SLDRAM (''Synchronous-link DRAM'')=== Les '''mémoires SLDRAM''' avaient un bus de données de 64 bits allant à 200-400 Hz, avec technologie DDR, ce qui était dans la norme de l'époque pour la fréquence (début des années 2000). Elle utilisait un bus de commande de 11 bits, qui était utilisé pour transmettre des commandes de 40 bits, transmises en quatre cycles d'horloge consécutifs (en réalité, quatre fronts d'horloge donc deux cycles en DDR). Le bus de données était de 18 bits, mais les transferts de donnée se faisaient par paquets de 4 à 8 octets (32-65 bits). Pour résumer, données et commandes sont chacunes transmises en plusieurs cycles consécutifs, sur un bus de commande/données plus court que les données/commandes elle-mêmes. Là où les SDRAM sélectionnent la bonne barrette grâce à des signaux de commande dédiés, ce n'est pas le cas avec la SLDRAM. A la place, chaque barrette de mémoire reçoit un identifiant, un numéro codé sur 7-8 bits. Les commandes de lecture/écriture précisent l'identifiant dans la commande. Toutes les barrettes reçoivent la commande, elles vérifient si l'identifiant de la commande est le leur, et elles la prennent en compte seulement si c'est le cas. Voici le format d'une commande SLDRAM. Elle contient l'adresse, qui regroupe le numéro de banque, le numéro de ligne et le numéro de colonne. On trouve aussi un code commande qui indique s'il faut faire une lecture ou une écriture, et qui configure l'accès mémoire. Il configure notamment le mode rafale, en indiquant s'il faut lire/écrire 4 ou 8 octets. Enfin, il indique s'il faut fermer la ligne accédée une fois l'accès terminé, ou s'il faut la laisser ouverte. Le code commande peut aussi préciser que la commande est un rafraichissement ou non, effectuer des opérations de configuration, etc. L'identifiant de barrette mémoire est envoyé en premier, histoire que les barrettes sachent précocement si l'accès les concerne ou non. {|class="wikitable" style="text-align:center" |+SLDRAM Read, write or row op request packet ! FLAG || CA9 || CA8 || CA7 || CA6 || CA5 || CA4 || CA3 || CA2 || CA1 || CA0 |- ! 1 | colspan=9 bgcolor=#ffcccc| Identifiant de barrette mémoire|| bgcolor=#ccffcc| Code de commande |- ! 0 | colspan=5 bgcolor=#ccffcc| Code de commande ||colspan=3 bgcolor=#ff88ff| Banque||colspan=2 bgcolor=#ffffcc| Ligne |- ! 0 | colspan=9 bgcolor=#ffffcc| Ligne || 0 |- ! 0 | 0 || 0 || 0 ||colspan=7 bgcolor=#ccffff| Colonne |} ===Les mémoires Rambus=== Les mémoires conçues par la société Rambus regroupent la '''RDRAM''' (''Rambus DRAM'') et ses deux successeurs '''XDR RAM''' et '''XDR RAM 2'''. Les toutes premières étaient les '''mémoires RDRAM''', où le bus permettait de transmettre soit des commandes (adresse inclue), soit des données, avec un multiplexage total. Le processeur envoie un paquet contenant commandes et adresse à la mémoire, qui répond avec un paquet d'acquittement. Lors d'une lecture, le paquet d'acquittement contient la donnée lue. Lors d'une écriture, le paquet d'acquittement est réduit au strict minimum. Le bus de commandes est réduit au strict minimum, à savoir l'horloge et quelques bits absolument essentiels, les bits RW est transmis dans un paquet et n'ont pas de ligne dédiée, pareil pour le bit OE. Toutes les barrettes de mémoire doivent vérifier toutes les transmissions et déterminer si elles sont concernées en analysant l'adresse transmise dans la trame. Elles ont été utilisées dans des PC ou d'anciennes consoles de jeu. Par exemple, la Nintendo 64 incorporait 4 mébioctets de mémoire RDRAM en tant que mémoire principale. La RDRAM de la Nintendo 64 était cadencée à 500 MHz, utilisait un bus de 9 bits, et avait un débit binaire maximal théorique de 500 MB/s. La Playstation 2 contenait quant à elle 32 mébioctets de RDRAM en ''dual-channel'', pour un débit binaire de 3.2 Gibioctets par seconde. Les processeurs Pentium 3 pouvaient être associés à de la RDRAM sur certaines mères. Les Pentium 4 étaient eux aussi associés à la de RDRAM, mais les cartes mères ne géraient que ce genre de mémoire. La Playstation 3 contenait quant à elle de la XDR RAM. ==Les eDRAM : des DRAM adaptées aux ''chiplets''== Les '''mémoires eDRAM''', pour ''embedded DRAM'', sont des mémoires RAM qui sont destinées à être intégrée au processeur. Pour comparer, les DRAM normales sont placées sur des barrettes de RAM ou soudées à la carte mère. Dans la quasi-totalité des cas, l'eDRAM est utilisée pour implémenter une mémoire cache, elle ne sert pas de mémoire principale (cache L4, le plus proche de la mémoire sur ces puces). De ce fait, elles sont conçues pour être très rapides, avoir une grande bande passante, au détriment de leur capacité mémoire. Pour être plus précis, l'eDRAM est une puce de DRAM conçue pour être intégrée dans un ''chiplet'', , à savoir des circuits imprimés qui regroupent plusieurs puces électroniques distinctes, regroupées sur le même PCB. Typiquement, un processeur de type ''chiplet'' avec de l'eDRAM comprend deux puces séparées : une pour le processeur, une autre pour une puce de communication avec la RAM. Avec la mémoire eDRAM, les deux puces sont complétées par une troisième puce spécialisée qui incorpore l'eDRAM. Elle a été utilisée sur quelques processeurs, mais aussi dans des consoles de jeu vidéo, pour la carte graphique des consoles suivantes : la PlayStation 2, la PlayStation Portable, la GameCube, la Wii, la Wii U, et la XBOX 360. Sur ces consoles, la RAM de la carte graphique était intégrée avec le processeur graphique dans le même circuit. La fameuse mémoire vidéo et le GPU n'étaient qu'une seule et même puce électronique, un seul circuit intégré. Ce n'est pas le cas sur une carte graphique moderne : regardez votre carte graphique avec attention et vous verrez que le GPU est une puce carrée située sous les ventilateurs, alors que les puces mémoires sont situées juste autour et soudées sur le PCB de la carte. Les processeurs Intel Core de microarchitecture Broadwell disposaient d'un cache L4 de 128 mébioctets, intégralement implémenté avec de la mémoire eDRAM. Quelques processeurs de la microarchitecture précédente (Haswell), disposaient aussi de ce cache. Le cache L4 eDRAM était implémenté sur un chiplet à part, à savoir que le processeur était composé de trois puces séparées : une pour le processeur, une autre pour la gestion des entrées-sorties, et une autre pour le cache L4. La puce pour le cache L4 était appelée ''Crystal Well''. La puce ''Crystal Well'' était une puce gravée en 22nm, ce qui était une finesse de gravure plus élevée que celle des processeurs associés. ''Crystal Well'' était très optimisé pour l'époque. Par exemple, elle disposait de bus séparées pour la lecture et l'écriture, chose qu'on retrouve fréquemment sur les SRAM mais qui est absent sur les mémoires DRAM actuelles. Pour le reste, elle ressemblait beaucoup aux mémoires DDR de l'époque (système de ''double data rate'', entres autres), mais elle allait à une fréquence plus élevée que les DRAM de l'époque et avait un débit bien plus élevé, pour une consommation moindre. ''Crystal Well'' consommait entre 1 à 5 watts (1 watt en veille, 5 à pleine utilisation), pour un débit binaire de 102 GB/s et fonctionnait à 3.2 GHz. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les mémoires SRAM synchrones | prevText=Les mémoires SRAM synchrones | next=Contrôleur mémoire externe | nextText=Le contrôleur mémoire externe }}{{autocat}} </noinclude> teycts75kjfhstj8a9q34z6vvc7pjj5 764674 764646 2026-04-23T18:43:51Z Mewtow 31375 /* Les délais mémoires */ 764674 wikitext text/x-wiki Après avoir vu les mémoires statiques (SRAM), il est temps de passer aux mémoires RAM dynamiques, aussi appelées mémoires DRAM. Pour rappel, les DRAM dynamiques ont pour défaut que leurss données s'effacent après un certain temps, en quelques millièmes ou centièmes de secondes . En conséquence, il faut réécrire chaque bit de la mémoire régulièrement pour éviter qu'il ne s'efface. On dit qu'on doit effectuer régulièrement un '''rafraîchissement mémoire'''. Et celui-ci rend les DRAM très différentes des SRAM. Les DRAM des PC ont beaucoup évolués dans le temps. Les toutes premières mémoires DRAM étaient des mémoires asynchrones, mais elles ont été remplacées par des modèles synchrones. Les modèles asynchrones ont été très nombreux. Après l'apparition des premières DRAM, les mémoires ''Fast Page Mode'' sont apparues, suivies par les mémoires ''Extended Data Out'', puis les EDO en mode rafale. Elles ont été utilisées jusque dans la moitié des années 90, pour ensuite être supplantées par les mémoires SDRAM. Les mémoires DDR actuelles sont des améliorations des mémoires SDRAM actuelles. Le fait est que les DRAM sont des mémoires électroniques comme les autres, qui se présentent sous la forme de circuits intégrés, à savoir que ce sont des petits boitiers noirs avec des broches. Il est possible de souder ces boitiers sur une cartre mère, et c'est ce qui est fait sur nombre d'ordinateurs portables. Mais il est aussi possible de regrouper plusieurs boitiers sur une barrette de RAM séparée. Dans ce qui suit, nous les appellerons des '''chips mémoire''', ou encore, des puces mémoires. [[File:Canyon CN-WF514 - EtronTech EM638325TS-6-4022.jpg|centre|vignette|upright=2|Exemple de chip mémoire.]] Dans ce qui suit, nous allons étudier ces chips de DRAM, avant de voir comment ils sont regroupés sur une barrette de RAM. Puis, nous allons voire chaque type de mémoire, FPM, EDO, SDRAM, DDR, ... ; un par un. ==L'interface des DRAM et le contrôleur mémoire== L'interface d'une mémoire DRAM est plus compliquée que l'interface d'une SRAM basique. Et c'est suffisant pour qu'on ait besoin d'un intermédiaire pour faire la conversion entre processeur et DRAM. Les DRAM modernes ne sont pas connectées directement au processeur, mais le sont par l'intermédiaire d'un '''contrôleur mémoire externe'''. Il ne faut pas le confondre avec le contrôleur mémoire interne, placé dans la mémoire RAM, et qui contient notamment le décodeur. Les deux sont totalement différents, bien que leur nom soit similaire. Pour éviter toute confusion, j'utiliserais le terme de '''contrôleur de DRAM''', plus parlant. ===Le bus d'adresse des DRAM est multiplexé=== Un point important pour le contrôleur de DRAM est de transformer les adresses mémoires fournies par le processeur, en adresses utilisables par la DRAM. Car les DRAM ont une interface assez spécifique. Les DRAM ont ce qui s'appelle un '''bus d'adresse multiplexé'''. Avec de tels bus, l'adresse est envoyée en deux fois. Les bits de poids fort sont envoyés avant les bits de poids faible. On peut ainsi envoyer une adresse de 32 bits sur un bus d'adresse de 16 bits, par exemple. Le bus d'adresse contient alors environ moitié moins de fils que la normale. Pour rappel, l'avantage de cette méthode est qu'elle permet de limiter le nombre de fils du bus d'adresse, ce qui très intéressant sur les mémoires de grande capacité. Les mémoires DRAM étant utilisées comme mémoire principale d'un ordinateur, elles devaient avoir une grande capacité. Cependant, avoir un petit nombre de broches sur les barrettes de mémoire est clairement important, ce qui impose d'utiliser des stratagèmes. Envoyer l'adresse en deux fois répond parfaitement à ce problème : cela permet d'avoir des adresses larges et donc des mémoires de forte capacité, avec une performance acceptable et peu de fils sur le bus d'adresse. Les bus multiplexés se marient bien avec le fait que les DRAM sont des mémoires à adressage par coïncidence ou à tampon de ligne. Sur ces mémoires, l'adresse est découpée en deux : une adresse haute pour sélectionner la ligne, et une adresse basse qui sélectionne la colonne. L'adresse est envoyée en deux fois : la ligne, puis la colonne. Pour savoir si une donnée envoyée sur le bus d'adresse est une adresse de ligne ou de colonne, le bus de commande de ces mémoires contenait deux fils bien particuliers : les RAS et le CAS. Pour simplifier, le signal RAS permettait de sélectionner une ligne, et le signal CAS permettait de sélectionner une colonne. [[File:Signaux RAS et CAS.png|centre|vignette|upright=2|Signaux RAS et CAS.]] Si on a deux bits RAS et CAS, c'est parce que la mémoire prend en compte les signaux RAS et CAS quand ils passent de 1 à 0. C'est à ce moment là que la ligne ou colonne dont l'adresse est sur le bus sera sélectionnée. Tant que des signaux sont à zéro, la ligne ou colonne reste sélectionnée : on peut changer l'adresse sur le bus, cela ne désélectionnera pas la ligne ou la colonne et la valeur présente lors du front descendant est conservée. [[File:L'intérieur d'une FPM.png|centre|vignette|upright=2|L'intérieur d'une FPM.]] ===Le rafraichissement mémoire=== La spécificité des DRAM est qu'elles doivent être rafraichies régulièrement, sans quoi leurs cellules perdent leurs données. Le rafraichissement est basiquement une lecture camouflée. Elle lit les cellules mémoires, mais n'envoie pas le contenu lu sur le bus de données. Rappelons que la lecture sur une DRAM est destructive, à savoir qu'elle vide la cellule mémoire, mais que le système d'amplification de lecture régénère le contenu de la cellule automatiquement. La cellule est donc rafraichie automatiquement lors d'une lecture. La quasi-totalité des DRAM supporte des commandes de rafraichissement, séparées des lectures et écritures classiques. Une commande de rafraichissement ordonne de rafraichir une adresse, voire une ligne complète. Les commandes de rafraichissement sont générées par le contrôleur de DRAM, dans la grosse majorité des cas. Il est aussi possible que ce soit le processeur qui les génère, mais c'est beaucoup plus rare. Il est aussi possible d'envoyer des commandes de rafraichissement vides, qui ne précisent ni adresse ni numéro de ligne. Pour les gérer, la mémoire contient un compteur, qui pointe sur la prochaine ligne à rafraichir, qui est incrémenté à chaque commande de rafraichissement. Une commande de rafraichissement indique à la mémoire d'utiliser l'adresse dans ce compteur pour savoir quelle adresse/ligne rafraichir. [[File:Rafraichissement mémoire automatique.png|centre|vignette|upright=2|Rafraichissement mémoire automatique.]] Il existe des mémoires qui sont des intermédiaires entre les mémoires SRAM et DRAM. Il s'agit des '''mémoires pseudo-statiques''', qui sont techniquement des mémoires DRAM, utilisant des transistors et des condensateurs, mais qui gèrent leur rafraichissement mémoire toutes seules. Le rafraichissement mémoire est alors totalement automatique, ni le processeur, ni le contrôleur de DRAM ne devant s'en charger. Le rafraichissement est purement le fait des circuits de la mémoire RAM et devient une simple opération de maintenance interne, gérée par la RAM elle-même. L'envoi des commandes de rafraichissement peuvent se faire de deux manières : soit on les envoie toutes en même temps, soit on les disperse le plus possible. Le premier cas est un '''rafraichissement en rafale''', le second un '''rafraichissement étalé'''. Le rafraichissement en rafale n'est pas utilisé dans les PC, car il bloque la mémoire pendant un temps assez long. Mais les anciennes consoles de jeu gagnaient parfois à utiliser eu rafraichissement en rafale. En effet, la mémoire était souvent effacée entre l'affichage de deux images, pour éviter certains problèmes dont on ne parlera pas ici. Le rafraichissement de la mémoire était effectué à ce moment là : l'effacement rafraichissait la mémoire. Le temps mis pour rafraichir la mémoire est le temps mis pour parcourir toute la mémoire. Il s'agit du temps de balayage vu dans le chapitre sur les performances d'un ordinateur. Pour les mémoires FPM et EDO, il est défini en divisant la capacité de la mémoire par son débit binaire. C'est le temps nécessaire pour lire ou réécrire tout le contenu de la mémoire. Sur les SDRAM, les choses sont un peu différentes, pour une raison qu'on expliquera plus bas. ===Le contrôleur de DRAM=== Le contrôleur de DRAM gère le bus mémoire et tout ce qui est envoyé dessus. Il envoie des commandes aux barrettes de mémoire, commandes qui peuvent être des lectures, des écritures, ou des demandes de rafraichissement, parfois d'autres commandes. La mémoire répond à ces commandes par l'action adéquate : lire la donnée et la placer sur le bus de données pour une commande de lecture, par exemple. Le rôle du contrôleur de DRAM varie grandement suivant le contrôleur en question, ainsi que selon le type de DRAM. Les anciens contrôleurs de DRAM étaient des composants séparés du processeur, du ''chipset'' ou du reste de la carte mère. Par exemple, les contrôleur de DRAM Intel 8202, Intel 8203 et Intel 8207 étaient vendus dans des boitiers DIP et étaient soudés sur la carte mère. Par la suite, ils ont été intégré au ''chipset'' de la carte mère pendant les décennies 90-2000. Après les années 2000, ils ont été intégrés dans les processeurs. Il est possible de connecter plusieurs barrettes sur le même bus mémoire, ou alors celles-ci sont connectées au contrôleur de DRAM avec un bus par barrette/boitier. C'est ce qui permet de placer plusieurs barrettes de mémoire sur la même carte mère : toutes les barrettes sont connectées au contrôleur de DRAM d'une manière ou d'une autre. ==Les rangées : l'arrangement horizontal et vertical== Il est rare d'utiliser un chip mémoire seul, car ceux-ci n'ont pas une capacité suffisante. Pour donner quelques chiffres, à l'heure où j'écris ces lignes, la norme pour un ordinateur est d'avoir entre 8 et 64 gibioctets de RAM. Mais les chips mémoire font entre 1 et 4 gibioctets, rarement plus. La raison est que les ordinateurs combinent ensemble plusieurs chips mémoire pour additionner leurs capacités. La concaténation de plusieurs chips mémoire peut se faire de deux manières différentes, appelées l'arrangement horizontal et l'arrangement vertical. Les deux additionnent la capacité des chips mémoire, mais se distinguent sur un point : ce qui arrive respectivement au bus de données, et au nombre d'adresses. Intuitivement, on se dit que doubler la capacité mémoire implique de doubler le nombre d'adresses mémoire. C'est effectivement ce qui se passe avec l'arrangement vertical. Mais avec l'arrangement horizontal, le nombre d'adresse ne varie pas. Voyons cela en détail, et commençons par le cas le plus simple, celui de l'arrangement vertical seul. ===L'arrangement vertical : cumuler des adresses mémoire=== Introduisons l'arrangement vertical par un exemple. Imaginez que je souhaite obtenir de 4 mébioctets de RAM, en combinant 4 chips mémoires de 1 mébioctet chacun. L'idée est que le premier mébioctet est placé dans le premier chip mémoire, le second mébioctet dans le second chip, etc. Des adresses consécutives se trouvent ainsi dans le même chip mémoire, sauf pour quelques adresses où on passe d'un chip à l'autre. Avec cette organisation, le bus de donnée fait un octet, et les chips mémoire ont aussi un bus de données d'un octet. Je peux alors combiner les capacités de plusieurs chips mémoire, sans toucher au bus de données. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses avec arrangement vertical.]] Pour sélectionner le chip mémoire adéquat, il faut que chaque chip mémoire dispose d'une entrée ''Chip Select'', qui permet de l'activer ou de le désactiver. L'idée est que selon l'adresse demandée, on active le chip mémoire associé à cette adresse. Les signaux ''Chip Select'' sont générés par le contrôleur de DRAM, à partir de l'adresse. On dit qu'il y a un '''décodage d'adresse'''. Avec cet arrangement, les bits de poids fort de l'adresse sont utilisées pour sélectionner la banque adéquate, et le reste de l'adresse est envoyé sur le bus d'adresse. Par exemple, avec 4 chips mémoire, les deux bits de poids fort de l'adresse sont utilisés pour sélectionner le chip mémoire adéquat. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Adresse de banque !! Adresse dans la banque |- | Quelques bits de poids fort || Reste de l'adresse |} ===L'arrangement horizontal : élargir le bus de données=== L'arrangement horizontal permet lui aussi d'additionner les capacités mémoire de plusieurs chips mémoire. Cependant, il les combine d'une autre manière. Le nombre d'adresses mémoire n'est pas changé en utilisant plusieurs chips, mais le bus de données est élargi. Le mieux pour comprendre l'idée est de partir d'un exemple, et nous allons prendre celui d'une mémoire SDRAM. Les ordinateurs actuels ont un bus de données de 64 bits (on met de côté le cas du double ou triple canal). Cependant, il n'existe pas de chip mémoire avec un bus aussi large. Les puces de SDRAM/DDR ont un bus de 4, 8 ou 16 bits, ce sont les tailles les plus courantes. L'arrangement horizontal résout ce problème en combinant plusieurs chips mémoire de manière à ce que leurs bus de données s'"additionnent", se concatènent. Par exemple, on peut regrouper 8 chips mémoires de 8 bits, obtenir un bus mémoire de 64 bits. Il est aussi possible d'obtenir ces 64 bits avec des puces de 16 chips mémoire de 4 bits, ou 4 chips mémoire de 16 bits. [[File:Arrangement horizontal SDRAM - un Rank.png|centre|vignette|upright=2|Arrangement horizontal SDRAM.]] Avec cette organisation, on accède à tous les bancs en parallèle à chaque accès, avec la même adresse. Vu que les chips mémoires contiennent tous une partie de la donnée demandée, ils doivent tous être activés en même temps. Pour cela, l'adresse à lire est envoyée à tous les chips mémoire d'un même ''rank'', idem pour les signaux de commande. Un ensemble de N chips reliés de cette manière forme une '''rangée''' (le terme anglais est ''rank''). [[File:Arrangement horizontal.jpg|centre|vignette|upright=2|Arrangement horizontal.]] ===L'arrangement horizontal et vertical combinés=== Nous venons de voir l'arrangement vertical et horizontal, pour ce qui est des barrettes de mémoire. Précisons que ce qui vient d'être dit marche aussi bien pour les barrettes de RAM que pour la mémoire soudée sur la carte mère. Du moment qu'on combine plusieurs chips mémoire ensemble, ces concepts restent valides. Et il en est de même pour la suite, encore que ce soit nettement moins fréquent avec de la mémoire soudée. Il est possible de combiner à la fois l'arrangement vertical et l'arrangement horizontal. Rien de plus simple : il suffit d'utiliser un arrangement vertical entre plusieurs rangées, chacun composée de plusieurs chips mémoire. C'est surtout utilisé sur les barrettes de mémoire SDRAM, qui contiennent 1, 2, 4 ou 8 rangées, rarement plus. Par exemple, une SDRAM peut combiner 16 chips de DRAM de 8 bits chacun, dans deux rangées de 64 bits chacun, chaque rangée regroupant 8 chips. [[File:SDRAM avec 4 ranks.png|centre|vignette|upright=2|SDRAM avec 4 ranks]] Le choix entre la première ou la seconde rangée se fait en configurant les bits ''Chip Select'' de chaque rangée. Il faut noter que les bits de ''Chip Select'' sont générés par le contrôleur mémoire, et envoyés sur le bus de commande. [[File:Td6bfig3.png|centre|vignette|upright=2|Comparaison entre arrangement horizontal (à gauche) et arrangement vertical (à droite).]] Le contrôleur de DRAM peut adresser un certain nombre de rangées, dispersés sur plusieurs barrettes. La limite maximale dépend du contrôleur de DRAM, elle est souvent proche de 8 ou 16 rangées. Si on combine plusieurs barrettes de mémoire, il est possible de dépasser cette limite. Par exemple, prenez un contrôleur de DRAM supportant maximum 8 rangées. Avec 4 barrettes contenant 4 rangées chacune, la limite est dépassée. : Il faut noter que tout ce qui vient d'être dit vaut aussi pour les mémoires ROM et SRAM. Mais en pratique, les arrangements verticaux et horizontaux sont surtout utilisés sur les mémoires DRAM. Il faut dire que de tels arrangements servent à augmenter la capacité mémoire, ce qui colle plus avec des DRAM que des SRAM ou des ROM. ==Les barrettes de mémoire DRAM== [[File:Ram-module.svg|droite|vignette|upright=0.5|Barrette de mémoire RAM.]] Il est possible de souder plusieurs boitiers de DRAM sur une cartre mère, et c'est ce qui est fait sur nombre d'ordinateurs portables. Mais dans les PC fixes, les puces de DRAM sont regroupées sur des ''barrettes mémoires'''. Les barrettes de mémoire se fixent à la carte mère sur un connecteur standardisé, appelé '''slot mémoire'''. Le dessin ci-contre montre une barrette de mémoire, celui-ci ci-dessous est celui d'un ''slot'' mémoire. [[File:Dual channel slots.jpg|centre|vignette|Slots mémoires.]] Sur le schéma de droite, on remarque facilement les boitiers de DRAM, rectangulaires, de couleur sombre. Chaque barrette combine ces puces de manière à additionner leurs capacités : on peut ainsi créer une mémoire de 8 gibioctets à partir de 8 puces d'un gibioctet, par exemple. Ils sont soudés sur un PCB en plastique vert sur lequel sont gravés des connexions métalliques. Les trucs dorés situés en bas des barrettes de mémoire sont des broches qui connectent la barrette au bus mémoire. Les barrettes des mémoires FPM/EDO/SDRAM/DDR n'ont pas le même nombre de broches, pour des raisons de compatibilité. {|class="wikitable" |- !Type de barrette !Type de mémoire !Nombre de broches |- | rowspan="2" | SIMM | rowspan="2" | FPM/EDO |30 |- |72 |- | rowspan="4" | DIMM |SDRAM |168 |- |DDR |184 |- |DDR2 |214, 240 ou 244, suivant la barrette ou la carte mère. |- |DDR3 |204 ou 240, suivant la barrette ou la carte mère. |} ===Le format des barrettes de mémoire=== Certaines barrettes ont des puces mémoire d'un seul côté alors que d'autres en ont sur les deux faces. Cela permet de distinguer les barrettes SIMM et DIMM. * Les '''barrettes SIMM''' ont des puces sur une seule face de la barrette. Elles étaient utilisées pour les mémoires FPM et EDO-RAM. * Les '''barrettes DIMM''' ont des puces sur les deux côtés. Elles sont utilisées sur les SDRAM et les DDR. {| class="flexible" |+ '''Barrette SIMM''' |- |[[File:SIMM FPM 4 MB - C0448721-7229.jpg|vignette|SIMM recto.]] |[[File:SIMM FPM 4 MB - C0448721-7230.jpg|vignette|SIMM verso.]] |} : Les modules DIMM tendent à avoir plus de rangées que les modules SIMM, mais ce n'est pas systématique. Il est souvent dit que les barrettes DIMM ont deux rangées, contre seulement 1 pour les SIMM, mais les contre-exemples sont nombreux. Les barrettes '''SO-DIMM''', pour ordinateurs portables, sont différentes des barrettes DIMM normales des DDR/SDRAM. La raison est qu'il n'y a pas beaucoup de place à l'intérieur d'un PC portable, ce qui demande de diminuer la taille des barrettes. {| |- |[[File:Desktop DDR Memory Comparison.svg|centre|vignette|upright=1.5|Barrettes de DDR pour PC de bureau.]] |[[File:Laptop SODIMM DDR Memory Comparison V2.svg|centre|vignette|upright=1.5|Barrettes de DDR pour PC portables.]] |} Les barrettes de Rambus ont parfois été appelées des '''barrettes RB-DIMM''', mais ce sont en réalité des DIMM comme les autres. La différence principale est que la position des broches n'était pas la même que celle des formats DIMM normaux, sans compter que le connecteur Rambus n'était pas compatible avec les connecteurs SDR/DDR normaux. ===Les interconnexions à l'intérieur d'une barrette de mémoire=== Les boîtiers de DRAM noirs sont connectés au bus par le biais de connexions métalliques. Toutes les puces d'une même rangée sont connectées aux bus d'adresse et de commande. Et les chips d'une même rangée reçoivent exactement les mêmes signaux de commande/adresses, ce qui permet d'envoyer la même adresse/commande à toutes les puces en même temps. La manière dont ces puces sont reliées au bus de commande dépend selon la mémoire utilisée. Les DDR1 et 2 utilisent ce qu'on appelle une '''topologie en T''', illustrée ci-dessous. On voit que le bus de commande forme une sorte d'arbre, dont chaque extrémité est connectée à une puce. La topologie en T permet d'égaliser le délai de transmission des commandes à travers le bus : la commande transmise arrive en même temps sur toutes les puces. Mais elle a de nombreux défauts, à savoir : elle fonctionne mal à haute fréquence, elle est difficile à router en raisons des embranchements. [[File:Organisation des bus de commandes sur les DDR1-2.png|centre|vignette|upright=3.0|Organisation des bus de commandes sur les DDR1-2, nommée topologie en T.]] En comparaison, les DDR3 utilisent une '''topologie ''fly-by''''', où les puces sont connectées en série sur le bus de commande/adresse. La topologie ''fly-by'' n'a pas les problèmes de la topologie en T : elle est simple à router et fonctionne très bien à haute fréquence. [[File:Organisation des bus de commandes sur les DDR3 - topologie fly-by.png|centre|vignette|upright=3.0|Organisation des bus de commandes sur les DDR3 - topologie ''fly-by'']] ===Les barrettes tamponnées (à registres)=== Certaines barrettes intègrent un registre tampon, qui fait l'interface entre le bus et la barrette de RAM. L'utilité est d'améliorer la transmission du signal sur le bus mémoire. Sans ce registre, les signaux électriques doivent traverser le bus, puis traverser les connexions à l'intérieur de la barrette, jusqu'aux puces de mémoire. Avec un registre tampon, les signaux traversent le bus, sont mémorisés dans le registre et c'est tout. Le registre envoie les commandes/données jusqu'aux puces mémoire, mais le signal a été régénéré par le registre. Le signal transmis est donc de meilleure qualité, ce qui augmente la fiabilité du système mémoire. Le défaut est que la présence de ce registre fait que les barrettes ont un temps de latence est plus important que celui des barrettes normales, du fait de la latence du registre. Les barrettes de ce genre sont appelées des '''barrettes RIMM'''. Il en existe deux types : * Avec les '''barrettes RDIMM''', le registre fait l'interface pour le bus d'adresse et le bus de commande, mais pas pour le bus de données. * Avec les '''barrettes LRDIMM''' (''Load Reduced DIMMs''), le registre fait tampon pour tous les bus, y compris le bus de données. [[File:Organisation des bus de commandes sur les RDIMM.png|centre|vignette|upright=3.0|Organisation des bus de commandes sur les RDIMM.]] ===Le ''Serial Presence Detect''=== [[File:SPD SDRAM.jpg|vignette|Localisation du SPD sur une barrette de SDRAM.]] Toute barrette de mémoire assez récente contient une petite mémoire ROM qui stocke les différentes informations sur la mémoire : délais mémoire, capacité, marque, etc. Cette mémoire s'appelle le '''''Serial Presence Detect''''', aussi communément appelé le SPD. Ce SPD contient non seulement les timings de la mémoire RAM, mais aussi diverses informations, comme le numéro de série de la barrette, sa marque, et diverses informations. Le SPD est lu au démarrage de l'ordinateur par le BIOS, afin de pourvoir configurer ce qu'il faut. Le contenu de ce fameux SPD est standardisé par un organisme nommé le JEDEC, qui s'est chargé de standardiser le contenu de cette mémoire, ainsi que les fréquences, timings, tensions et autres paramètres des mémoires SDRAM et DDR. Pour les curieux, vous pouvez lire la page wikipédia sur le SPD, qui donne son contenu pour les mémoires SDR et DDR : [https://en.wikipedia.org/wiki/Serial_presence_detect Serial Presence Detect]. ==Les mémoires asynchrones à RAS/CAS : FPM et EDO-RAM== Avant l'invention des mémoires SDRAM et DDR, il exista un grand nombre de mémoires différentes, les plus connues étant les mémoires fast page mode et EDO-RAM. Ces mémoires n'étaient pas synchronisées par un signal d'horloge, c'était des '''mémoires asynchrones'''. Quand ces mémoires ont été créées, cela ne posait aucun problème : les accès mémoire étaient très rapides et le processeur était certain que la mémoire aurait déjà fini sa lecture ou écriture au cycle suivant. Les mémoires asynchrones les plus connues étaient les '''mémoires FPM''' et '''mémoires EDO'''. Pour ce qui est de leur interface, il faut signaler qu'elles n'ont pas d'entrée ''Chip Select'' ou d'entrée ''Output Enable''. Les signaux RAS et CAS remplacent en quelque sorte ces deux signaux. Le bit RAS fait office de ''Chip Select'', le bit CAS fait office d'''Output Enable''. ===Les mémoires FPM=== Les '''mémoires FPM (''Fast Page Mode'')''' possédaient une petite amélioration, qui rendait l'adressage plus simple. Avec elles, il n'y a pas besoin de préciser deux fois la ligne si celle-ci ne changeait pas lors de deux accès consécutifs : on pouvait garder la ligne sélectionnée durant plusieurs accès. Par contre, il faut quand même préciser les adresses de colonnes à chaque changement d'adresse. Il existe une petite différence entre les mémoire ''Page Mode'' et les mémoires ''Fast-Page Mode'' proprement dit. Sur les premières, le signal CAS est censé passer à 0 avant qu'on fournisse l'adresse de colonne. Avec les ''Fast-Page Mode'', l'adresse de colonne pouvait être fournie avant que l'on configure le signal CAS. Cela faisait gagner un petit peu de temps, en réduisant quelque peu le temps d'accès total. [[File:Sélection d'une ligne sur une mémoire FPM ou EDO.png|centre|vignette|upright=2|Sélection d'une ligne sur une mémoire FPM ou EDO.]] Avec les '''mémoires en mode quartet''', il est possible de lire quatre octets consécutifs sans avoir à préciser la ligne ou la colonne à chaque accès. On envoie l'adresse de ligne et l'adresse de colonne pour le premier accès, mais les accès suivants sont fait automatiquement. La seule contrainte est que l'on doit générer un front descendant sur le signal CAS pour passer à l'adresse suivante. Vous aurez noté la ressemblance avec le mode rafale vu il y a quelques chapitres, mais il y a une différence notable : le mode rafale vrai n'aurait pas besoin qu'on précise quand passer à l'adresse suivante avec le signal CAS. [[File:Mode quartet.png|centre|vignette|upright=3|Mode quartet.]] Les '''mémoires FPM à colonne statique''' se passent même du signal CAS. Le changement de l'adresse de colonne est détecté automatiquement par la mémoire et suffit pour passer à la colonne suivante. Dans ces conditions, un délai supplémentaire a fait son apparition : le temps minimum entre deux sélections de deux colonnes différentes, appelé tCAS-to-CAS. [[File:Accès en colonne statique.jpg|centre|vignette|upright=2.5|Accès en colonne statique.]] ===Les mémoires EDO-RAM=== L''''EDO-RAM''' a été inventée quelques années après la mémoire FPM. Elle a été déclinée en deux versions : la EDO simple, et la EDO en rafale. L'EDO simple ajoutait une entrée ''Ouput Enable'' à une mémoire FPM. Pour rappel, l'entrée ''Ouput Enable'' permet de connecter/déconnecter la DRAM du bus de données. S'il est mis à 0, les lectures et écritures sont empêchées. Pour ajouter cette entrée, il a fallu rajouter un registre sur la sortie de donnée, celle qui sert pour les lectures. Et l'ajout de ce registre a introduit une capacité dite de ''pipelining'', sur le même modèle que pour les mémoires SRAM synchrones. La donnée pouvait être maintenue sur le bus de données durant un certain temps, même après la remontée du signal CAS. Le registre de sortie maintenait la donnée lu tant que le signal RAS restait à 0, et tant qu'un nouveau signal CAS n'a pas été envoyé. Faire remonter le signal CAS à 1 n'invalidait pas la donnée en sortie. La conséquence est qu'on pouvait démarrer une nouvelle lecture alors que la donnée de l'accès précédent était encore présent sur le bus de données. Le pipeline obtenu avait deux étages : un où on présentait l'adresse et sélectionnait la colonne, un autre où la donnée était lue depuis le registre de sortie. Les mémoires EDO étaient donc plus rapides. [[File:EDO RAM.png|centre|vignette|upright=3|EDO RAM]] Cependant, cela marchait surtout pour les lectures, pas pour les écritures. Une écriture ne démarre que quand la lecture ou écriture précédente est totalement terminée. De même, on ne peut pas démarrer un nouvel accès mémoire tant qu'une écriture est en cours. ===Les mémoires EDO-RAM avec mode rafale=== Les '''EDO en rafale''' effectuent les accès à 4 octets consécutifs automatiquement : il suffit d'adresser le premier octet à lire. Les 4 octets étaient envoyés sur le bus les uns après les autres, au rythme d'un par cycle d’horloge : ce genre d'accès mémoire s'appelle un accès en rafale. [[File:Accès en rafale.png|centre|vignette|upright=2|Accès en rafale sur une DRAM EDO.]] Implémenter cette technique nécessite d'ajouter un compteur, capable de faire passer d'une colonne à une autre quand on lui demande, et quelques circuits annexes pour commander le tout. [[File:Modifications du contrôleur mémoire liées aux accès en rafale.png|centre|vignette|upright=2|Modifications du contrôleur de DRAM liées aux accès en rafale.]] ===Le rafraichissement mémoire=== Les mémoires FPM et EDO doivent être rafraichies régulièrement. Au début, le rafraichissement se faisait ligne par ligne. Le rafraichissement avait lieu quand le RAS passait à l'état haut, alors que le CAS restait à l'état bas. Le processeur, ou le contrôleur mémoire, sélectionnait la ligne à rafraichir en fournissant son adresse mémoire. D'où le nom de '''rafraichissement par adresse''' qui est donné à cette méthode de commande du rafraichissement mémoire. Divers processeurs implémentaient de quoi faciliter le rafraichissement par adresse. Par exemple, le Zilog Z80 contenait un compteur de ligne, un registre qui contenait le numéro de la prochaine ligne à rafraichir. Il était incrémenté à chaque rafraichissement mémoire, automatiquement, par le processeur lui-même. Un ''timer'' interne permettait de savoir quand rafraichir la mémoire : quand ce ''timer'' atteignait 0, une commande de rafraichissement était envoyée à la mémoire, et le ''timer'' était ''reset''. [[File:Rafraichissement mémoire manuel.png|centre|vignette|upright=2|Rafraichissement mémoire manuel.]] Par la suite, certaines mémoires ont implémenté un compteur interne d'adresse, pour déterminer la prochaine adresse à rafraichir sans la préciser sur le bus d'adresse. Le déclenchement du rafraichissement se faisait toujours par une commande externe, provenant du contrôleur de DRAM ou du processeur. Cette commande faisait passer le CAS à 0 avant le RAS. Cette méthode de rafraichissement se nomme '''rafraichissement interne'''. [[File:Rafraichissement sur CAS précoce.png|centre|vignette|upright=2|Rafraichissement sur CAS précoce.]] On peut noter qu'il est possible de déclencher plusieurs rafraichissements à la suite en laissant le signal CAS dans le même état. Ce genre de choses pouvait avoir lieu après une lecture : on pouvait profiter du fait que le CAS soit mis à zéro par la lecture ou l'écriture pour ensuite effectuer des rafraichissements en touchant au signal RAS. Dans cette situation, la donnée lue était maintenue sur la sortie durant les différents rafraichissements. [[File:Rafraichissements multiples sur CAS précoce.png|centre|vignette|upright=2|Rafraichissements multiples sur CAS précoce.]] ==Les mémoires SDRAM== Dans les années 90, les mémoires asynchrones ont laissé la place aux '''mémoires SDRAM''', qui sont synchronisées avec le bus par une horloge. L'utilisation d'une horloge a comme avantage des temps d'accès fixes : le processeur sait qu'un accès mémoire prendra un nombre déterminé de cycles d'horloge. Avec les mémoires asynchrones, le processeur ne pouvait pas prévoir quand la donnée serait disponible et ne faisait rien tant que la mémoire n'avait pas répondu : il exécutait ce qu'on appelle des ''wait states'' en attendant que la mémoire ait fini. Les mémoires SDRAM sont standardisées par un organisme international, le JEDEC. Le standard SDRAM impose des spécifications électriques bien précise pour les barrettes de mémoire et le bus mémoire, décrit le protocole utilisé pour communiquer avec les barrettes de mémoire, et bien d'autres choses encore. Les SDRAM ont été déclinées en versions de performances différentes, décrites dans le tableau ci-dessous : {| class="wikitable" ! Nom standard ! Fréquence ! Bande passante |- | PC66 | 66 mhz | 528 Mio/s |- | PC66 | 100 mhz | 800 Mio/s |- | PC66 | 133 mhz | 1064 Mio/s |- | PC66 | 150 mhz | 1200 Mio/s |} ===Les banques internes aux chips mémoires SDRAM=== L'intérieur d'une mémoire SDRAM contient plusieurs '''banques''', aussi appelées des banc mémoire. Concrètement, une banque est... une mémoire. Ou plutôt, une sorte de mini-mémoire miniature. Chaque banque a son propre tampon de ligne, ses propres multiplexeurs de colonne et ses propres décodeurs. C'est comme si une SDRAM regroupait plusieurs mémoires séparées dans un même circuit intégré. [[File:Arrangement vertical.jpg|centre|vignette|upright=2.5|Mémoire multi-banques.]] Un point important est que chaque banque a son propre tampon de ligne. Il est donc possible d'ouvrir plusieurs lignes en même temps, chacune dans une banque différente. Par exemple, on peut ouvrir une ligne dans la banque numéro 1, et une autre ligne dans la banque numéro 2. Et c'est une source d'optimisations très intéressantes. La première optimisation est liée au rafraichissement mémoire. Au lieu de rafraichir chaque adresse une par une, il est possible de rafraichir des banques indépendantes en même temps, ce qui divise le temps de rafraichissement par le nombre de banques. C'est ce que je sous-entendais plus haut quand je disais que le temps de rafraichissement n'est pas égal au temps de balayage sur les SDRAM, alors que c'est le cas sur les DRAM FPM et EDO. De plus, et sans rentrer dans les détails, cela permet de faire plusieurs accès mémoire en même temps, dans des banques différentes. La possibilité est limitée, mais elle existe et elle améliore grandement la performance de la SDRAM. Mais nous en reparlerons dans un chapitre ultérieur, car cette histoire d'accès simultanés a plus sa place dans le chapitre sur le parallélisme mémoire. Pour le moment, nous ne pouvons pas expliquer pourquoi ni comment un processeur pourrait émettre plusieurs accès mémoire simultanés. Un processeur est censé travailler une instruction à la fois, à ce stade du cours, il ne peut pas en faire plusieurs en même temps. Mais nous allons cependant mentionner un cas où cette possibilité est intéressante : une mémoire SDRAM partagée entre un processeur et une carte graphique. Les deux accèdent à des données séparées, qui sont dans des banques différentes. On suppose que la carte graphique accède plus fréquemment à la mémoire que le processeur. Le contrôleur mémoire reçoit les accès mémoire du CPU et du GPU et il tente d'intercaler des accès CPU entre deux accès de la carte graphique. Vu qu'ils tombent dans des banques différentes, un accès CPU et un accès carte graphique peuvent se faire presque en même temps. La seule contrainte est que si on lance plusieurs accès mémoire simultanés, ceux-ci ne peuvent pas utiliser le bus de données en même temps. {|class="wikitable" |+ Pipelining basique sur les SDRAM |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 |- ! Banque Numéro 1 | || || || || || || || || || || |- ! Banque Numéro 2 | || colspan="3" bgcolor="#A0FFFF" | Accès CPU || || || colspan="3" bgcolor="#A0FFFF" | Accès CPU || || |- ! Banque Numéro 3 | || || || || || || || || || || |- ! Banque Numéro 4 | || || || colspan="3" bgcolor="#FFA0FF" | Accès carte graphique || || colspan="3" bgcolor="#FFA0FF" | Accès carte graphique || |} ===Le mode rafale des SDRAM=== Un point important est que les SDRAM reprennent les optimisations des mémoires FPM et EDO. Elles utilisent aussi un tampon de ligne, avec la possibilité de lire plusieurs colonnes à la suite sans avoir à préciser l'adresse de ligne à chaque fois. Mais surtout, elles gèrent nativement le mode rafale. les paramètres qui ont trait au mode rafale sont configurables. Il est possible de configurer la SDRAM pour activer les accès sans rafale, ou les désactiver. Il y a aussi la possibilité de configurer le nombre d'octets consécutifs à lire ou écrire en mode rafale. On peut ainsi accéder à 1, 2, 4, ou 8 octets en une seule fois, alors que les EDO ne permettaient que des accès à 4 octets consécutifs. Enfin, on peut décider s'il faut faire un accès en mode linéaire ou entrelacé. La configuration de la SDRAM est mémorisée dans un registre de 10 bits, le '''registre de mode'''. Il faisait 10 bits sur les mémoires SDRAM, mais a été étendu à 13 bits sur la DDR2. Voici les 10 bits originels de ce registre : {|class="wikitable" |+ Signification des bits du registre de mode des SDRAM |- ! Bit n°9 | Type d'accès : en rafale ou normal |- ! Bit n°8 et 7 | Doivent valoir 00, sont réservés pour une utilisation ultérieur dans de futurs standards. |- ! Bit n°6, 5, et 4 | Latence CAS (voir plus bas) |- ! Bit n°3 | Type de rafale : linéaire ou entrelacée |- ! Bit n°2, 3, et 0 | Longueur de la rafale : indique le nombre d'octets à lire/écrire lors d'une rafale. |} ===L'interface d'une mémoire SDRAM=== Le bus de commandes d'une SDRAM contient au moins 18 fils, dont celui pour le signal d'horloge. L'interface d'une SDRAM contient tous les bits présents sur une mémoire DRAM classique : une entrée RAS, une entrée CAS, une entrée R/W, et un bus d'adresse. A cela, il faut cependant ajouter une entrée ''Chip Select'' (CS), qui permet d'activer/désactiver la mémoire SDRAM. Je rappelle que le bit CS a été introduit sur les mémoires SDRAM, il n'était pas présent sur les mémoires FPM/EDO. Deux autres bits de commande sont vraiment spécifiques des mémoires SDRAM. Il s'agit des bits CKE et DQM. Le '''bit CKE''' est l'abréviation de ''Clock Enable'', qui qui trahit sa fonction. Lorsque ce signal est à 0, le chip de SDRAM voit son signal d'horloge gelè. S'il est à 0, le contrôleur de DRAM peut envoyer ce qu'il veut sur le bus de commande ou d'adresse, la SDRAM ne réagira pas du tout, il ne se passera rien. Le '''bit DQM''' est une sorte de bit ''Output Enable'', avec une nuance importante. Le terme DQM est l'abréviation de ''Data Mask'', ce qui trahit encore une fois sa fonction. Il y a un bit DQM pour chaque octet du bus de données. Une SDRAM ayant un bus de 64 bits, cela fait 8 bits DQM par mémoire SDRAM. Lorsque le bit DQM est à 1, l'octet en question n'est simplement pas lu ou écrit, le bus de donnée est déconnecté pour cet octet. Le bus d'adresse est particulier, car il tient compte de la présence de ''banques mémoires''. Le bus d'adresse est découpé en deux portions : une portion pour sélectionner la banque, une autre pour sélectionner l'adresse à l'intérieur d'une banque. L'interface de la SDRAM fait ainsi la différence entre une '''adresse de banque''' et une '''adresse intra-banque'''. L'adresse de banque est codée sur quelques bits, généralement deux ou trois suivant la SDRAM. Le reste de l'adresse est codé sur 11 bits sur les SDRAM, mais cela a augmenté avec les DDR 1, 2, 3, 4, 5. Le bus de données d'une SDRAM fait 4, 8, ou 16 bits. Je précise bien qu'il s'agit là des puces de SDRAM, les barrettes de SDRAM combinent plusieurs puces SDRAM avec un arrangement horizontal, qui peut combiner plusieurs puces de 8 bits pour alimenter un bus de données de 64 bits. La taille des puces utilisées souvent indiquée sur la barrette de RAM, avec une mention x4, x8 ou x16. Les puces de SDRAM les plus courantes ont une interface de 8 bits pour les données. Les SDRAM de 4 bits sont surtout utilisées pour les serveurs, c'est lié au support de l'ECC. les puces x16 sont moins utilisées car elles ont généralement moins de banques que les autres. ===Les commandes SDRAM=== Le bus de commande permet d'envoyer des commandes à la mémoire, chaque commande étant précisée par une combinaison précise des bits CS, RAS, CAS, R/W, et autres. Les commandes en question sont des demandes de lecture, d'écriture, de préchargement et autres. Elles sont codées par une valeur bien précise qui est envoyée sur les 18 fils du bus de commande. Ces commandes sont nommées READ, READA, WRITE, WRITEA, PRECHARGE, ACT, ... Les plus importantes sont les commandes PRECHARGE, ACT et READ/WRITE. La commande ACT sélectionne une ligne : elle met le bit RAS à zéro et présente une adresse de ligne. Les commandes READ et WRITE sélectionnent une colonne, et déclenchent respectivement une lecture ou une écriture. Elles précisent une adresse de colonne, mettent le bit CAS à 0 et le bit RAS à 1, et précise la valeur du bit R/W. Les commandes READ et WRITE ne peuvent se faire qu'une fois que la banque a été activée par une commande ACT. Il est possible d'envoyer plusieurs commandes READ ou WRITE successives à des colonnes différentes, ce qui permet d'implémenter les optimisations des mémoires FPM. La commande PRECHARGE ferme la ligne courante et prépare l'ouverture de la suivante. Elle précharge les lignes de bit de la RAM, d'où son nom. Il est nécessaire d'en envoyer une avant d'envoyer une commande ACT. Notons que la commande PRECHARGE agit sur une banque, dont l'adresse est indiquée dans la commande PRECHARGE. Il existe une commande PRECHARGE ALL, qui agit sur toutes les banques de la SDRAM à la fois. Les commandes READA et WRITEA fusionnent une commande READ/WRITE avec une commande PRECHARGE. Elles permettent d'éviter d'avoir à envoyer une commande PRECHARGE pour fermer la ligne courante. Au lieu d'envoyer une commande READ ou WRITE, puis une commande PRECHARGE pour fermer la ligne, on envoie une seule commande READA/WRITEA. Il s'agit d'une petite optimisation, qui permet de réduire le nombre de commandes envoyées sur le bus. Les commandes sont encodées comme indiquées dans ce tableau. Une commande est tout simplement encodée en précisant une adresse si nécessaire, et une combinaison des bits CS, RAS, CAS et R/W. La seule subtilité est que le bit numéro 10 du bus d'adresse sert à commander les opérations de PRECHARGE, y compris celles implicites dans les opérations READA et WRITEA. {| class="wikitable" style="text-align:center" ! Bit CS || Bit RAS || Bit CAS || Bit WE || Bits de sélection de banque (2 bits) || Bit du bus d'adresse A10 || Reste du bus d'adresse || Nom de la commande : Description |- | 1 | colspan="6" | X | Absence de commandes. |- | 0 || 1 || 1 || 1 || colspan="3" | X || No Operation : Pas d'opération |- | 0 || 1 || 1 || 0 || colspan="3" | X || Burst Terminante : Stoppe un accès en rafale (en cours). |- | 0 || 1 || 0 || 1 || Adresse de la banque || 0 || Adresse de la colonne || READ : lit une donnée depuis la ligne active. |- | 0 || 1 || 0 || 1 || Adresse de la banque || 1 || Adresse de la colonne || READA : lit une donnée depuis la ligne active, puis ferme la ligne. |- | 0 || 1 || 0 || 0 || Adresse de la banque || 0 || Adresse de la colonne || WRITE : écrit une donnée dans la ligne active. |- | 0 || 1 || 0 || 0 || Adresse de la banque || 1 || Adresse de la colonne || WRITEA : écrit une donnée dans la ligne active, puis ferme la ligne. |- | 0 || 0 || 1 || 1 || Adresse de la banque || colspan="2" | Adresse de la ligne || ACT : charge une ligne dans le tampon de ligne. |- | 0 || 0 || 1 || 0 || Adresse de la banque || 0 || X || PRECHARGE : précharge le tampon de ligne dans la banque voulue. |- | 0 || 0 || 1 || 0 || Adresse de la X || 1 || X || PRECHARGE ALL : précharge le tampon de ligne dans toutes les banques. |- | 0 || 0 || 0 || 1 || colspan="3" | X || Auto refresh : Demande de rafraichissement, gérée par la SDRAM. |- | 0 || 0 || 0 || 0 || 00 || colspan="2" | Nouveau contenu du registre de mode || LOAD MODE REGISTER : configure le registre de mode. |} Les commandes ACT se font à partir de l'état de repos, l'état où toutes les banques sont préchargées. Par contre, les commandes MODE REGISTER SET et AUTO REFRESH ne peuvent se faire que si toutes les banques sont désactivées. Le fonctionnement simplifié d'une SDRAM peut se résumer dans ce diagramme : [[File:Fonctionnement simplifié d'une SDRAM.jpg|centre|vignette|upright=2|Fonctionnement simplifié d'une SDRAM.]] ===Les délais mémoires=== Les mémoires SDRAM n'étant pas infiniment rapides, il y a toujours un certain délais à respecter entre deux commandes. Par exemple, quand on envoie une commande ACT pour activer une ligne, on ne peut pas envoyer une commande READ/WRITE au cycle suivant. La plupart des SDRAM ne sont pas assez rapides pour ça. Il faut respecter un délai de quelques cycles, qui dépend de la mémoire. Et il n'y a pas que ce délai entre une commande ACT et la commande suivante. Une SDRAM doit gérer d'autres temps d'attente, appelés des '''délais mémoires''', ou encore des ''timings'' mémoire. Les délais mémoire le plus importants sont résumés ci-dessous : {|class="wikitable" |- !Timing!!Description |- | colspan="2" | |- ! colspan="2" | Délais primaires |- ||tRP|| Temps entre une commande PRECHARGE et une commande ACT |- ||tRCD|| Temps entre une commande ACT et une commande READ/WRITE. |- ||tCL|| Temps entre une commande READ et l'envoi de la donnée lue sur le bus de données. |- ||tDQSS|| Temps entre une commande WRITE et l'écriture de la donnée. |- ||tCAS-to-CAS|| Temps minimum entre deux commandes READ. |- ! colspan="2" | Délais secondaires |- ||tWTR|| Temps entre une lecture et une écriture consécutives. |- ||tRAS || Temps entre une commande ACT et une commande PRECHARGE. |} La façon de mesurer ces délais varie : sur les mémoires FPM et EDO, on les mesure en unités de temps (secondes, millisecondes, micro-secondes, etc.), tandis qu'on les mesure en cycles d'horloge sur les mémoires SDRAM. Les délais/timings mémoire ne sont pas les mêmes suivant la barrette de mémoire que vous achetez. Certaines mémoires sont ainsi conçues pour avoir des timings assez bas et sont donc plus rapides, et surtout : beaucoup plus chères que les autres. Le gain en performances dépend beaucoup du processeur utilisé et est assez minime comparé au prix de ces barrettes. Les délais mémoires d'une barrette sont mémorisés dans le ''Serial Presence Detect'' de la barrette et sont lus par le BIOS au démarrage de l'ordinateur, et sont ensuite utilisés pour configurer le contrôleur de DRAM. ===Les commandes anticipées=== Les SDRAM sont parfois capables de démarrer une commande avant que la précédente soit terminée. Concrètement, pendant que la commande précédente envoie sa dernière donnée sur le bus de données, on peut envoyer la commande suivante avec quelques cycles d'avance. L'exemple ci-dessous devrait être assez clair : on envoie une seconde commande au neuvième cycle, alors qu'une rafale est en cours. La machine à état tient compte de cette possibilité. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | || bgcolor="#A0FFFF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || || || bgcolor="#FFA0FF" | READ (2) || || || || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 | bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 | |} La possibilité est très limitée, car il faut tenir compte des délais mémoire. Il s'agit d'une forme très limitée de pipeline, similaire à celui des mémoires SRAM. Les SDRAM sont formées en entourant une RAM asynchrone de registres. Il est possible d'écrire dans les registres de données/commandes, pendant qu'un autre accès mémoire accède au cœur asynchrone. Les délais mémoire sont conçus pour éviter qu'une commande accède au coeur asynchrone en même temps que la suivante ou la précédente. C'est pour cela que les délais mémoire sont assez différents entre écritures et lectures, d'ailleurs. ==Les mémoires DDR== Les mémoires SDRAM récentes sont des mémoires de type ''dual data rate'', ce qui fait qu'elles portent le nom de mémoires DDR. Pour rappel, les mémoires ''dual data rate'' ont un plan mémoire deux fois plus large que le bus mémoire, avec un bus mémoire allant à une fréquence double. Par double, on veut dire que les transferts sur le bus mémoire ont lieu sur les fronts montants et descendants de l'horloge. Il y a donc deux transferts de données sur le bus pour chaque cycle d'horloge, ce qui permet de doubler le débit sans toucher à la fréquence du plan mémoire lui-même. Les mémoires DDR sont standardisées par un organisme international, le JEDEC, et ont été déclinées en plusieurs générations : DDR1, DDR2, DDR3, et DDR4. La différence entre ces modèles sont très nombreuses, mais les plus évidentes sont la fréquence de la mémoire et du bus mémoire. D'autres différences mineures existent entre les SDRAM et les mémoires DDR. Par exemple, la tension d'alimentation des mémoires DDR est plus faible que pour les SDRAM. ET elle a diminué dans le temps, d'une génération de DDR à l'autre. Avec les mémoires DDR2,la tension d'alimentation est passée de 2,5/2,6 Volts à 1,8 Volts. Avec les mémoires DDR3, la tension d'alimentation est notamment passée à 1,5 Volts. ===Les performances des mémoires DDR=== Les mémoires SDRAM ont évolué dans le temps, mais leur temps d'accès/fréquence n'a pas beaucoup changé. Il valait environ 10 nanosecondes pour les SDRAM, approximativement 5 ns pour la DDR-400, il a peu évolué pendant la génération DDR et DDR3, avant d'augmenter pendant les générations DDR4 et de stagner à nouveau pour la génération DDR5. L'usage du DDR, puis du QDR, visait à augmenter les performances malgré la stagnation des temps d'accès. En conséquence, la fréquence du bus a augmenté plus vite que celle des puces mémoire pour compenser. {|class="wikitable" |- ! Année ! Type de mémoire ! Fréquence de la mémoire (haut de gamme) ! Fréquence du bus ! Coefficient multiplicateur entre les deux fréquences |- | 1998 | DDR 1 | 100 - 200 MHz | 200 - 400 MHz | 2 |- | 2003 | DDR 2 | 100 - 266 MHz | 400 - 1066 MHz | 4 |- | 2007 | DDR 3 | 100 - 266 MHz | 800 - 2133 MHz | 8 |- | 2014 | DDR 4 | 200 - 400 MHz | 1600 - 3200 MHz | 8 |- | 2020 | DDR 5 | 200 - 450 MHz | 3200 - 7200 MHz | 8 à 16 |} Une conséquence est que la latence CAS, exprimée en nombre de cycles, a augmenté avec le temps. Si vous comparez des mémoires DDR2 avec une DDR4, par exemple, vous allez voir que la latence CAS est plus élevée pour la DDR4. Mais c'est parce que la latence est exprimée en nombre de cycles d'horloge, et que la fréquence a augmentée. En comparant les temps d'accès exprimés en secondes, on voit une amélioration. ===Les commandes des mémoires DDR=== Les commandes des mémoires DDR sont globalement les mêmes que celles des mémoires SDRAM, vues plus haut. Les modifications entre SDRAM, DDR1, DDR2, DDR3, DDR4, et DDR5 sont assez mineures. Les seules différences sont l'addition de bits pour la transmission des adresses, des bits en plus pour la sélection des banques, etc. En clair, une simple augmentation quantitative. Le registre de mode a été un peu modifié. Il est passé de 10 bits pour les SDRAM et DDR1, à 13 bits sur la DDR 2 et les suivantes. Les DDR ont aussi ajouté le support de plusieurs registres de mode, qui sont sélectionnés en réutilisant l'adresse de banque. Dans une commande LOAD MODE REGISTER, l'adresse de banque indique quel registre de mode il faut altérer. Avant la DDR4, les modifications des commandes sont mineures. La DDR2 supprime la commande ''Burst Terminate'', la DDR3 et la DDR4 utilisent le bit A12 pour préciser s'il faut faire une rafale complète, ou une rafale de moitié moins de données. Une optimisation des DDR2 et 3 est celle des '''CAS postés'''. L'idée est que le contrôleur de DRAM peut envoyer une commande ACT et une commande READ/WRITE sans se soucier des ''timings'' nécessaires entre les deux. En théorie, les deux commandes doivent être séparées par quelques cycles, sur une SDRAM ou une DDR1. Mais avec la DDR2, le contrôleur de DRAM peut envoyer les deux l'une après l'autre, au cycle suivant. C'est la mémoire qui mettra en attente la commande READ/WRITE pour respecter les ''timings'' mémoire. Cela complexifie le fonctionnement interne de la DDR, mais simplifie grandement le travail du contrôleur de DRAM. Mais avec la DDR4, les choses changent, notamment au niveau de la commande ACT. Avec l'augmentation de la capacité des barrettes mémoires, la taille des adresses est devenue trop importante. Pour éviter de rajouter des bits d'adresses, les concepteurs du standard DDR4 ont décidé de ruser. Lors d'une commande ACT, les bits RAS, CAS et WE sont utilisés comme bits d'adresse, alors qu'ils ont leur signification normale pour les autres commandes. Pour éviter toute confusion, un nouveau bit ACT est ajouté pour indiquer la présence d'une commande ACT : il est à 1 pour une commande ACT, 0 pour les autres commandes. {| class="wikitable" style="text-align:center" |+ Commandes d'une mémoire DDR4, seule la commande colorée change par rapport aux SDRAM ! Bit CS || style="background: #CCFFCC" | Bit ACT || Bit RAS || Bit CAS || Bit WE || Bits de sélection de banque (4 bits) || Bit du bas d'adresse A10 || Reste du bus d'adresse || Nom de la commande : Description |- | 1 | colspan="6" | X | Absence de commandes. |- | 0 || style="background: #CCFFCC" | 0 || 1 || 1 || 1 || colspan="3" | X || No Operation : Pas d'opération |- | 0 || style="background: #CCFFCC" | 0 || 1 || 1 || 0 || colspan="3" | X || Burst Terminante : Arrêt d'un accès en rafale en cours. |- | 0 || style="background: #CCFFCC" | 0 || 1 || 0 || 1 || Adresse de la banque || 0 || Adresse de la colonne || READ : lire une donnée depuis la ligne active. |- | 0 || style="background: #CCFFCC" | 0 || 1 || 0 || 1 || Adresse de la banque || 1 || Adresse de la colonne || READA : lire une donnée depuis la ligne active, avec rafraichissement automatique de la ligne. |- | 0 || style="background: #CCFFCC" | 0 || 1 || 0 || 0 || Adresse de la banque || 0 || Adresse de la colonne || WRITE : écrire une donnée depuis la ligne active. |- | 0 || style="background: #CCFFCC" | 0 || 1 || 0 || 0 || Adresse de la banque || 1 || Adresse de la colonne || WRITEA : écrire une donnée depuis la ligne active, avec rafraichissement automatique de la ligne. |- style="background: #CCFFCC" | 0 || style="background: #CCFFCC" | 1 || colspan="3" | Adresse de la ligne (bits de poids forts) || Adresse de la banque || colspan="2" | Adresse de la ligne (bits de poids faible) || ACT : charge une ligne dans le tampon de ligne. |- | 0 || style="background: #CCFFCC" | 0 || 0 || 1 || 0 || Adresse de la banque || 0 || X || PRECHARGE : précharge le tampon de ligne dans la banque voulue. |- | 0 || style="background: #CCFFCC" | 0 || 0 || 1 || 0 || Adresse de la X || 1 || X || PRECHARGE ALL : précharge le tampon de ligne' dans toutes les banques. |- | 0 || style="background: #CCFFCC" | 0 || 0 || 0 || 1 || colspan="3" | X || Auto refresh : Demande de rafraichissement, gérée par la SDRAM. |- | 0 || style="background: #CCFFCC" | 0 || 0 || 0 || 0 || Numéro de registre de mode || colspan="2" | Nouveau contenu du registre de mode || LOAD MODE REGISTER : configure le registre de mode. |} ==Les VRAM des cartes vidéo== Les cartes graphiques ont des besoins légèrement différents des DRAM des processeurs, ce qui fait qu'il existe des mémoires DRAM qui leur sont dédiées. Elles sont appelés des '''''Graphics RAM''''' (GRAM). La plupart incorporent des fonctionnalités utiles uniquement pour les mémoires vidéos, comme des fonctionnalités de masquage (appliquer un masque aux données lue ou à écrire), ou le remplissage d'un bloc de mémoire avec une donnée unique. Les anciennes cartes graphiques et les anciennes consoles utilisaient de la DRAM normale, faute de mieux. La première GRAM utilisée était la NEC μPD481850, qui a été utilisée sur la console de jeu PlayStation, à partir de son modèle SCPH-5000. D'autres modèles de GRAM ont rapidement suivi. Les anciennes consoles de jeu, mais aussi des cartes graphiquesn utilisaient des GRAM spécifiques. ===Les mémoires vidéo double port=== Sur les premières consoles de jeu et les premières cartes graphiques, le ''framebuffer'' était mémorisé dans une mémoire vidéo spécialisée appelée une '''mémoire vidéo double port'''. Le premier port était connecté au processeur ou à la carte graphique, alors que le second port était connecté à un écran CRT. Aussi, nous appellerons ces deux port le ''port CPU/GPU'' et l'autre sera appelé le ''port CRT''. Le premier port était utilisé pour enregistrer l'image à calculer et faire les calculs, alors que le second port était utilisé pour envoyer à l'écran l'image à afficher. Le port CPU/GPU est tout ce qu'il y a de plus normal : on peut lire ou écrire des données, en précisant l'adresse mémoire de la donnée, rien de compliqué. Le port CRT est assez original : il permet d'envoyer un paquet de données bit par bit. De telles mémoires étaient des mémoires à tampon de ligne, dont le support de mémorisation était organisé en ligne et colonnes. Une ligne à l'intérieur de la mémoire correspond à une ligne de pixel à l'écran, ce qui se marie bien avec le fait que les anciens écrans CRT affichaient les images ligne par ligne. L'envoi d'une ligne à l'écran se fait bit par bit, sur un câble assez simple comme un câble VGA ou autre. Le second port permettait de faire cela automatiquement, en permettant de lire une ligne bit par bit, les bits étant envoyés l'un après l'autre automatiquement. Pour cela, les mémoires vidéo double port incorporaient un tampon de ligne spécialisé pour le port lié à l'écran. Ce tampon de ligne n'était autre qu'un registre à décalage, contrairement au tampon de ligne normal. Lors de l'accès au second port, la carte graphique fournissait un numéro de ligne et la ligne était chargée dans le tampon de ligne associé à l'écran. La carte graphique envoyait un signal d'horloge de même fréquence que l'écran, qui commandait le tampon de ligne à décalage : un bit sortait à chaque cycle d'écran et les bits étaient envoyé dans le bon ordre. ===Les mémoires SGRAM et GDDR=== De nos jours, les cartes graphiques n'utilisent plus de mémoires double port, mais des mémoires simple port. Les mémoires graphiques actuelles sont des SDRAM modifiées pour fonctionner en tant que ''Graphic RAM''. Les plus connues sont les '''mémoires GDDR''', pour ''graphics double data rate'', utilisées presque exclusivement sur les cartes graphiques. Il en existe plusieurs types pendant que j'écris ce tutoriel : GDDR, GDDR2, GDDR3, GDDR4, et GDDR5. Mais attention, il y a des différences avec les DDR normales. Par exemple, les GDDR ont une fréquence plus élevée que les DDR normales, avec des temps d'accès plus élevés (sauf pour le tCAS). De plus, elles sont capables de laisser ouvertes deux lignes en même temps. Par contre, ce sont des mémoires simple port. ==Les mémoires SLDRAM, RDRAM et associées== Les mémoires précédentes sont généralement associées à des bus larges. Les mémoires SDRAM et DDR modernes ont des bus de données de 64 bits de large, avec des d'adresse et de commande de largeur similaire. Le nombre de fils du bus mémoire dépasse facilement la centaine de fils, avec autant de broches sur les barrettes de mémoire. Largeur de ces bus pose de problèmes problèmes électriques, dont la résolution n'est pas triviale. En conséquence, la fréquence du bus mémoire est généralement moins performantes comparé à ce qu'on aurait avec un bus moins large. Mais d'autres mémoires DRAM ont exploré une solution alternative : avoir un bus peu large mais de haute fréquence, sur lequel on envoie les commandes/données en plusieurs fois. Elles sont regroupées sous le nom de '''DRAM à commutation par paquets'''. Elles utilisent des bus spéciaux, où les commandes/adresses/données sont transmises par paquets, par trames, en plusieurs fois. En théorie, ce qu'on a dit sur le codage des trames dans le chapitre sur le bus devrait s'appliquer à de telles mémoires. En pratique, les protocoles de transmission sur le bus mémoire sont simplifiés, pour gérer le fonctionnement à haute fréquence. Le processeur envoie des paquets de commandes, les mémoires répondent avec des paquets de données ou des accusés de réception. Les mémoires à commutation par paquets sont peu nombreuses. Les plus connues sont les mémoires conçues par la société Rambus, à savoir la ''RDRAM'' (''Rambus DRAM'') et ses deux successeurs ''XDR RAM'' et ''XDR RAM 2''. La ''Synchronous-link DRAM'' (''SLDRAM'') est un format concurrent conçu par un consortium de plusieurs concepteurs de mémoire. ===La SLDRAM (''Synchronous-link DRAM'')=== Les '''mémoires SLDRAM''' avaient un bus de données de 64 bits allant à 200-400 Hz, avec technologie DDR, ce qui était dans la norme de l'époque pour la fréquence (début des années 2000). Elle utilisait un bus de commande de 11 bits, qui était utilisé pour transmettre des commandes de 40 bits, transmises en quatre cycles d'horloge consécutifs (en réalité, quatre fronts d'horloge donc deux cycles en DDR). Le bus de données était de 18 bits, mais les transferts de donnée se faisaient par paquets de 4 à 8 octets (32-65 bits). Pour résumer, données et commandes sont chacunes transmises en plusieurs cycles consécutifs, sur un bus de commande/données plus court que les données/commandes elle-mêmes. Là où les SDRAM sélectionnent la bonne barrette grâce à des signaux de commande dédiés, ce n'est pas le cas avec la SLDRAM. A la place, chaque barrette de mémoire reçoit un identifiant, un numéro codé sur 7-8 bits. Les commandes de lecture/écriture précisent l'identifiant dans la commande. Toutes les barrettes reçoivent la commande, elles vérifient si l'identifiant de la commande est le leur, et elles la prennent en compte seulement si c'est le cas. Voici le format d'une commande SLDRAM. Elle contient l'adresse, qui regroupe le numéro de banque, le numéro de ligne et le numéro de colonne. On trouve aussi un code commande qui indique s'il faut faire une lecture ou une écriture, et qui configure l'accès mémoire. Il configure notamment le mode rafale, en indiquant s'il faut lire/écrire 4 ou 8 octets. Enfin, il indique s'il faut fermer la ligne accédée une fois l'accès terminé, ou s'il faut la laisser ouverte. Le code commande peut aussi préciser que la commande est un rafraichissement ou non, effectuer des opérations de configuration, etc. L'identifiant de barrette mémoire est envoyé en premier, histoire que les barrettes sachent précocement si l'accès les concerne ou non. {|class="wikitable" style="text-align:center" |+SLDRAM Read, write or row op request packet ! FLAG || CA9 || CA8 || CA7 || CA6 || CA5 || CA4 || CA3 || CA2 || CA1 || CA0 |- ! 1 | colspan=9 bgcolor=#ffcccc| Identifiant de barrette mémoire|| bgcolor=#ccffcc| Code de commande |- ! 0 | colspan=5 bgcolor=#ccffcc| Code de commande ||colspan=3 bgcolor=#ff88ff| Banque||colspan=2 bgcolor=#ffffcc| Ligne |- ! 0 | colspan=9 bgcolor=#ffffcc| Ligne || 0 |- ! 0 | 0 || 0 || 0 ||colspan=7 bgcolor=#ccffff| Colonne |} ===Les mémoires Rambus=== Les mémoires conçues par la société Rambus regroupent la '''RDRAM''' (''Rambus DRAM'') et ses deux successeurs '''XDR RAM''' et '''XDR RAM 2'''. Les toutes premières étaient les '''mémoires RDRAM''', où le bus permettait de transmettre soit des commandes (adresse inclue), soit des données, avec un multiplexage total. Le processeur envoie un paquet contenant commandes et adresse à la mémoire, qui répond avec un paquet d'acquittement. Lors d'une lecture, le paquet d'acquittement contient la donnée lue. Lors d'une écriture, le paquet d'acquittement est réduit au strict minimum. Le bus de commandes est réduit au strict minimum, à savoir l'horloge et quelques bits absolument essentiels, les bits RW est transmis dans un paquet et n'ont pas de ligne dédiée, pareil pour le bit OE. Toutes les barrettes de mémoire doivent vérifier toutes les transmissions et déterminer si elles sont concernées en analysant l'adresse transmise dans la trame. Elles ont été utilisées dans des PC ou d'anciennes consoles de jeu. Par exemple, la Nintendo 64 incorporait 4 mébioctets de mémoire RDRAM en tant que mémoire principale. La RDRAM de la Nintendo 64 était cadencée à 500 MHz, utilisait un bus de 9 bits, et avait un débit binaire maximal théorique de 500 MB/s. La Playstation 2 contenait quant à elle 32 mébioctets de RDRAM en ''dual-channel'', pour un débit binaire de 3.2 Gibioctets par seconde. Les processeurs Pentium 3 pouvaient être associés à de la RDRAM sur certaines mères. Les Pentium 4 étaient eux aussi associés à la de RDRAM, mais les cartes mères ne géraient que ce genre de mémoire. La Playstation 3 contenait quant à elle de la XDR RAM. ==Les eDRAM : des DRAM adaptées aux ''chiplets''== Les '''mémoires eDRAM''', pour ''embedded DRAM'', sont des mémoires RAM qui sont destinées à être intégrée au processeur. Pour comparer, les DRAM normales sont placées sur des barrettes de RAM ou soudées à la carte mère. Dans la quasi-totalité des cas, l'eDRAM est utilisée pour implémenter une mémoire cache, elle ne sert pas de mémoire principale (cache L4, le plus proche de la mémoire sur ces puces). De ce fait, elles sont conçues pour être très rapides, avoir une grande bande passante, au détriment de leur capacité mémoire. Pour être plus précis, l'eDRAM est une puce de DRAM conçue pour être intégrée dans un ''chiplet'', , à savoir des circuits imprimés qui regroupent plusieurs puces électroniques distinctes, regroupées sur le même PCB. Typiquement, un processeur de type ''chiplet'' avec de l'eDRAM comprend deux puces séparées : une pour le processeur, une autre pour une puce de communication avec la RAM. Avec la mémoire eDRAM, les deux puces sont complétées par une troisième puce spécialisée qui incorpore l'eDRAM. Elle a été utilisée sur quelques processeurs, mais aussi dans des consoles de jeu vidéo, pour la carte graphique des consoles suivantes : la PlayStation 2, la PlayStation Portable, la GameCube, la Wii, la Wii U, et la XBOX 360. Sur ces consoles, la RAM de la carte graphique était intégrée avec le processeur graphique dans le même circuit. La fameuse mémoire vidéo et le GPU n'étaient qu'une seule et même puce électronique, un seul circuit intégré. Ce n'est pas le cas sur une carte graphique moderne : regardez votre carte graphique avec attention et vous verrez que le GPU est une puce carrée située sous les ventilateurs, alors que les puces mémoires sont situées juste autour et soudées sur le PCB de la carte. Les processeurs Intel Core de microarchitecture Broadwell disposaient d'un cache L4 de 128 mébioctets, intégralement implémenté avec de la mémoire eDRAM. Quelques processeurs de la microarchitecture précédente (Haswell), disposaient aussi de ce cache. Le cache L4 eDRAM était implémenté sur un chiplet à part, à savoir que le processeur était composé de trois puces séparées : une pour le processeur, une autre pour la gestion des entrées-sorties, et une autre pour le cache L4. La puce pour le cache L4 était appelée ''Crystal Well''. La puce ''Crystal Well'' était une puce gravée en 22nm, ce qui était une finesse de gravure plus élevée que celle des processeurs associés. ''Crystal Well'' était très optimisé pour l'époque. Par exemple, elle disposait de bus séparées pour la lecture et l'écriture, chose qu'on retrouve fréquemment sur les SRAM mais qui est absent sur les mémoires DRAM actuelles. Pour le reste, elle ressemblait beaucoup aux mémoires DDR de l'époque (système de ''double data rate'', entres autres), mais elle allait à une fréquence plus élevée que les DRAM de l'époque et avait un débit bien plus élevé, pour une consommation moindre. ''Crystal Well'' consommait entre 1 à 5 watts (1 watt en veille, 5 à pleine utilisation), pour un débit binaire de 102 GB/s et fonctionnait à 3.2 GHz. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les mémoires SRAM synchrones | prevText=Les mémoires SRAM synchrones | next=Contrôleur mémoire externe | nextText=Le contrôleur mémoire externe }}{{autocat}} </noinclude> 5cu7j1kr7ojfbwor4xp487l19oncqet 764676 764674 2026-04-23T18:48:43Z Mewtow 31375 /* Les commandes anticipées */ 764676 wikitext text/x-wiki Après avoir vu les mémoires statiques (SRAM), il est temps de passer aux mémoires RAM dynamiques, aussi appelées mémoires DRAM. Pour rappel, les DRAM dynamiques ont pour défaut que leurss données s'effacent après un certain temps, en quelques millièmes ou centièmes de secondes . En conséquence, il faut réécrire chaque bit de la mémoire régulièrement pour éviter qu'il ne s'efface. On dit qu'on doit effectuer régulièrement un '''rafraîchissement mémoire'''. Et celui-ci rend les DRAM très différentes des SRAM. Les DRAM des PC ont beaucoup évolués dans le temps. Les toutes premières mémoires DRAM étaient des mémoires asynchrones, mais elles ont été remplacées par des modèles synchrones. Les modèles asynchrones ont été très nombreux. Après l'apparition des premières DRAM, les mémoires ''Fast Page Mode'' sont apparues, suivies par les mémoires ''Extended Data Out'', puis les EDO en mode rafale. Elles ont été utilisées jusque dans la moitié des années 90, pour ensuite être supplantées par les mémoires SDRAM. Les mémoires DDR actuelles sont des améliorations des mémoires SDRAM actuelles. Le fait est que les DRAM sont des mémoires électroniques comme les autres, qui se présentent sous la forme de circuits intégrés, à savoir que ce sont des petits boitiers noirs avec des broches. Il est possible de souder ces boitiers sur une cartre mère, et c'est ce qui est fait sur nombre d'ordinateurs portables. Mais il est aussi possible de regrouper plusieurs boitiers sur une barrette de RAM séparée. Dans ce qui suit, nous les appellerons des '''chips mémoire''', ou encore, des puces mémoires. [[File:Canyon CN-WF514 - EtronTech EM638325TS-6-4022.jpg|centre|vignette|upright=2|Exemple de chip mémoire.]] Dans ce qui suit, nous allons étudier ces chips de DRAM, avant de voir comment ils sont regroupés sur une barrette de RAM. Puis, nous allons voire chaque type de mémoire, FPM, EDO, SDRAM, DDR, ... ; un par un. ==L'interface des DRAM et le contrôleur mémoire== L'interface d'une mémoire DRAM est plus compliquée que l'interface d'une SRAM basique. Et c'est suffisant pour qu'on ait besoin d'un intermédiaire pour faire la conversion entre processeur et DRAM. Les DRAM modernes ne sont pas connectées directement au processeur, mais le sont par l'intermédiaire d'un '''contrôleur mémoire externe'''. Il ne faut pas le confondre avec le contrôleur mémoire interne, placé dans la mémoire RAM, et qui contient notamment le décodeur. Les deux sont totalement différents, bien que leur nom soit similaire. Pour éviter toute confusion, j'utiliserais le terme de '''contrôleur de DRAM''', plus parlant. ===Le bus d'adresse des DRAM est multiplexé=== Un point important pour le contrôleur de DRAM est de transformer les adresses mémoires fournies par le processeur, en adresses utilisables par la DRAM. Car les DRAM ont une interface assez spécifique. Les DRAM ont ce qui s'appelle un '''bus d'adresse multiplexé'''. Avec de tels bus, l'adresse est envoyée en deux fois. Les bits de poids fort sont envoyés avant les bits de poids faible. On peut ainsi envoyer une adresse de 32 bits sur un bus d'adresse de 16 bits, par exemple. Le bus d'adresse contient alors environ moitié moins de fils que la normale. Pour rappel, l'avantage de cette méthode est qu'elle permet de limiter le nombre de fils du bus d'adresse, ce qui très intéressant sur les mémoires de grande capacité. Les mémoires DRAM étant utilisées comme mémoire principale d'un ordinateur, elles devaient avoir une grande capacité. Cependant, avoir un petit nombre de broches sur les barrettes de mémoire est clairement important, ce qui impose d'utiliser des stratagèmes. Envoyer l'adresse en deux fois répond parfaitement à ce problème : cela permet d'avoir des adresses larges et donc des mémoires de forte capacité, avec une performance acceptable et peu de fils sur le bus d'adresse. Les bus multiplexés se marient bien avec le fait que les DRAM sont des mémoires à adressage par coïncidence ou à tampon de ligne. Sur ces mémoires, l'adresse est découpée en deux : une adresse haute pour sélectionner la ligne, et une adresse basse qui sélectionne la colonne. L'adresse est envoyée en deux fois : la ligne, puis la colonne. Pour savoir si une donnée envoyée sur le bus d'adresse est une adresse de ligne ou de colonne, le bus de commande de ces mémoires contenait deux fils bien particuliers : les RAS et le CAS. Pour simplifier, le signal RAS permettait de sélectionner une ligne, et le signal CAS permettait de sélectionner une colonne. [[File:Signaux RAS et CAS.png|centre|vignette|upright=2|Signaux RAS et CAS.]] Si on a deux bits RAS et CAS, c'est parce que la mémoire prend en compte les signaux RAS et CAS quand ils passent de 1 à 0. C'est à ce moment là que la ligne ou colonne dont l'adresse est sur le bus sera sélectionnée. Tant que des signaux sont à zéro, la ligne ou colonne reste sélectionnée : on peut changer l'adresse sur le bus, cela ne désélectionnera pas la ligne ou la colonne et la valeur présente lors du front descendant est conservée. [[File:L'intérieur d'une FPM.png|centre|vignette|upright=2|L'intérieur d'une FPM.]] ===Le rafraichissement mémoire=== La spécificité des DRAM est qu'elles doivent être rafraichies régulièrement, sans quoi leurs cellules perdent leurs données. Le rafraichissement est basiquement une lecture camouflée. Elle lit les cellules mémoires, mais n'envoie pas le contenu lu sur le bus de données. Rappelons que la lecture sur une DRAM est destructive, à savoir qu'elle vide la cellule mémoire, mais que le système d'amplification de lecture régénère le contenu de la cellule automatiquement. La cellule est donc rafraichie automatiquement lors d'une lecture. La quasi-totalité des DRAM supporte des commandes de rafraichissement, séparées des lectures et écritures classiques. Une commande de rafraichissement ordonne de rafraichir une adresse, voire une ligne complète. Les commandes de rafraichissement sont générées par le contrôleur de DRAM, dans la grosse majorité des cas. Il est aussi possible que ce soit le processeur qui les génère, mais c'est beaucoup plus rare. Il est aussi possible d'envoyer des commandes de rafraichissement vides, qui ne précisent ni adresse ni numéro de ligne. Pour les gérer, la mémoire contient un compteur, qui pointe sur la prochaine ligne à rafraichir, qui est incrémenté à chaque commande de rafraichissement. Une commande de rafraichissement indique à la mémoire d'utiliser l'adresse dans ce compteur pour savoir quelle adresse/ligne rafraichir. [[File:Rafraichissement mémoire automatique.png|centre|vignette|upright=2|Rafraichissement mémoire automatique.]] Il existe des mémoires qui sont des intermédiaires entre les mémoires SRAM et DRAM. Il s'agit des '''mémoires pseudo-statiques''', qui sont techniquement des mémoires DRAM, utilisant des transistors et des condensateurs, mais qui gèrent leur rafraichissement mémoire toutes seules. Le rafraichissement mémoire est alors totalement automatique, ni le processeur, ni le contrôleur de DRAM ne devant s'en charger. Le rafraichissement est purement le fait des circuits de la mémoire RAM et devient une simple opération de maintenance interne, gérée par la RAM elle-même. L'envoi des commandes de rafraichissement peuvent se faire de deux manières : soit on les envoie toutes en même temps, soit on les disperse le plus possible. Le premier cas est un '''rafraichissement en rafale''', le second un '''rafraichissement étalé'''. Le rafraichissement en rafale n'est pas utilisé dans les PC, car il bloque la mémoire pendant un temps assez long. Mais les anciennes consoles de jeu gagnaient parfois à utiliser eu rafraichissement en rafale. En effet, la mémoire était souvent effacée entre l'affichage de deux images, pour éviter certains problèmes dont on ne parlera pas ici. Le rafraichissement de la mémoire était effectué à ce moment là : l'effacement rafraichissait la mémoire. Le temps mis pour rafraichir la mémoire est le temps mis pour parcourir toute la mémoire. Il s'agit du temps de balayage vu dans le chapitre sur les performances d'un ordinateur. Pour les mémoires FPM et EDO, il est défini en divisant la capacité de la mémoire par son débit binaire. C'est le temps nécessaire pour lire ou réécrire tout le contenu de la mémoire. Sur les SDRAM, les choses sont un peu différentes, pour une raison qu'on expliquera plus bas. ===Le contrôleur de DRAM=== Le contrôleur de DRAM gère le bus mémoire et tout ce qui est envoyé dessus. Il envoie des commandes aux barrettes de mémoire, commandes qui peuvent être des lectures, des écritures, ou des demandes de rafraichissement, parfois d'autres commandes. La mémoire répond à ces commandes par l'action adéquate : lire la donnée et la placer sur le bus de données pour une commande de lecture, par exemple. Le rôle du contrôleur de DRAM varie grandement suivant le contrôleur en question, ainsi que selon le type de DRAM. Les anciens contrôleurs de DRAM étaient des composants séparés du processeur, du ''chipset'' ou du reste de la carte mère. Par exemple, les contrôleur de DRAM Intel 8202, Intel 8203 et Intel 8207 étaient vendus dans des boitiers DIP et étaient soudés sur la carte mère. Par la suite, ils ont été intégré au ''chipset'' de la carte mère pendant les décennies 90-2000. Après les années 2000, ils ont été intégrés dans les processeurs. Il est possible de connecter plusieurs barrettes sur le même bus mémoire, ou alors celles-ci sont connectées au contrôleur de DRAM avec un bus par barrette/boitier. C'est ce qui permet de placer plusieurs barrettes de mémoire sur la même carte mère : toutes les barrettes sont connectées au contrôleur de DRAM d'une manière ou d'une autre. ==Les rangées : l'arrangement horizontal et vertical== Il est rare d'utiliser un chip mémoire seul, car ceux-ci n'ont pas une capacité suffisante. Pour donner quelques chiffres, à l'heure où j'écris ces lignes, la norme pour un ordinateur est d'avoir entre 8 et 64 gibioctets de RAM. Mais les chips mémoire font entre 1 et 4 gibioctets, rarement plus. La raison est que les ordinateurs combinent ensemble plusieurs chips mémoire pour additionner leurs capacités. La concaténation de plusieurs chips mémoire peut se faire de deux manières différentes, appelées l'arrangement horizontal et l'arrangement vertical. Les deux additionnent la capacité des chips mémoire, mais se distinguent sur un point : ce qui arrive respectivement au bus de données, et au nombre d'adresses. Intuitivement, on se dit que doubler la capacité mémoire implique de doubler le nombre d'adresses mémoire. C'est effectivement ce qui se passe avec l'arrangement vertical. Mais avec l'arrangement horizontal, le nombre d'adresse ne varie pas. Voyons cela en détail, et commençons par le cas le plus simple, celui de l'arrangement vertical seul. ===L'arrangement vertical : cumuler des adresses mémoire=== Introduisons l'arrangement vertical par un exemple. Imaginez que je souhaite obtenir de 4 mébioctets de RAM, en combinant 4 chips mémoires de 1 mébioctet chacun. L'idée est que le premier mébioctet est placé dans le premier chip mémoire, le second mébioctet dans le second chip, etc. Des adresses consécutives se trouvent ainsi dans le même chip mémoire, sauf pour quelques adresses où on passe d'un chip à l'autre. Avec cette organisation, le bus de donnée fait un octet, et les chips mémoire ont aussi un bus de données d'un octet. Je peux alors combiner les capacités de plusieurs chips mémoire, sans toucher au bus de données. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses avec arrangement vertical.]] Pour sélectionner le chip mémoire adéquat, il faut que chaque chip mémoire dispose d'une entrée ''Chip Select'', qui permet de l'activer ou de le désactiver. L'idée est que selon l'adresse demandée, on active le chip mémoire associé à cette adresse. Les signaux ''Chip Select'' sont générés par le contrôleur de DRAM, à partir de l'adresse. On dit qu'il y a un '''décodage d'adresse'''. Avec cet arrangement, les bits de poids fort de l'adresse sont utilisées pour sélectionner la banque adéquate, et le reste de l'adresse est envoyé sur le bus d'adresse. Par exemple, avec 4 chips mémoire, les deux bits de poids fort de l'adresse sont utilisés pour sélectionner le chip mémoire adéquat. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Adresse de banque !! Adresse dans la banque |- | Quelques bits de poids fort || Reste de l'adresse |} ===L'arrangement horizontal : élargir le bus de données=== L'arrangement horizontal permet lui aussi d'additionner les capacités mémoire de plusieurs chips mémoire. Cependant, il les combine d'une autre manière. Le nombre d'adresses mémoire n'est pas changé en utilisant plusieurs chips, mais le bus de données est élargi. Le mieux pour comprendre l'idée est de partir d'un exemple, et nous allons prendre celui d'une mémoire SDRAM. Les ordinateurs actuels ont un bus de données de 64 bits (on met de côté le cas du double ou triple canal). Cependant, il n'existe pas de chip mémoire avec un bus aussi large. Les puces de SDRAM/DDR ont un bus de 4, 8 ou 16 bits, ce sont les tailles les plus courantes. L'arrangement horizontal résout ce problème en combinant plusieurs chips mémoire de manière à ce que leurs bus de données s'"additionnent", se concatènent. Par exemple, on peut regrouper 8 chips mémoires de 8 bits, obtenir un bus mémoire de 64 bits. Il est aussi possible d'obtenir ces 64 bits avec des puces de 16 chips mémoire de 4 bits, ou 4 chips mémoire de 16 bits. [[File:Arrangement horizontal SDRAM - un Rank.png|centre|vignette|upright=2|Arrangement horizontal SDRAM.]] Avec cette organisation, on accède à tous les bancs en parallèle à chaque accès, avec la même adresse. Vu que les chips mémoires contiennent tous une partie de la donnée demandée, ils doivent tous être activés en même temps. Pour cela, l'adresse à lire est envoyée à tous les chips mémoire d'un même ''rank'', idem pour les signaux de commande. Un ensemble de N chips reliés de cette manière forme une '''rangée''' (le terme anglais est ''rank''). [[File:Arrangement horizontal.jpg|centre|vignette|upright=2|Arrangement horizontal.]] ===L'arrangement horizontal et vertical combinés=== Nous venons de voir l'arrangement vertical et horizontal, pour ce qui est des barrettes de mémoire. Précisons que ce qui vient d'être dit marche aussi bien pour les barrettes de RAM que pour la mémoire soudée sur la carte mère. Du moment qu'on combine plusieurs chips mémoire ensemble, ces concepts restent valides. Et il en est de même pour la suite, encore que ce soit nettement moins fréquent avec de la mémoire soudée. Il est possible de combiner à la fois l'arrangement vertical et l'arrangement horizontal. Rien de plus simple : il suffit d'utiliser un arrangement vertical entre plusieurs rangées, chacun composée de plusieurs chips mémoire. C'est surtout utilisé sur les barrettes de mémoire SDRAM, qui contiennent 1, 2, 4 ou 8 rangées, rarement plus. Par exemple, une SDRAM peut combiner 16 chips de DRAM de 8 bits chacun, dans deux rangées de 64 bits chacun, chaque rangée regroupant 8 chips. [[File:SDRAM avec 4 ranks.png|centre|vignette|upright=2|SDRAM avec 4 ranks]] Le choix entre la première ou la seconde rangée se fait en configurant les bits ''Chip Select'' de chaque rangée. Il faut noter que les bits de ''Chip Select'' sont générés par le contrôleur mémoire, et envoyés sur le bus de commande. [[File:Td6bfig3.png|centre|vignette|upright=2|Comparaison entre arrangement horizontal (à gauche) et arrangement vertical (à droite).]] Le contrôleur de DRAM peut adresser un certain nombre de rangées, dispersés sur plusieurs barrettes. La limite maximale dépend du contrôleur de DRAM, elle est souvent proche de 8 ou 16 rangées. Si on combine plusieurs barrettes de mémoire, il est possible de dépasser cette limite. Par exemple, prenez un contrôleur de DRAM supportant maximum 8 rangées. Avec 4 barrettes contenant 4 rangées chacune, la limite est dépassée. : Il faut noter que tout ce qui vient d'être dit vaut aussi pour les mémoires ROM et SRAM. Mais en pratique, les arrangements verticaux et horizontaux sont surtout utilisés sur les mémoires DRAM. Il faut dire que de tels arrangements servent à augmenter la capacité mémoire, ce qui colle plus avec des DRAM que des SRAM ou des ROM. ==Les barrettes de mémoire DRAM== [[File:Ram-module.svg|droite|vignette|upright=0.5|Barrette de mémoire RAM.]] Il est possible de souder plusieurs boitiers de DRAM sur une cartre mère, et c'est ce qui est fait sur nombre d'ordinateurs portables. Mais dans les PC fixes, les puces de DRAM sont regroupées sur des ''barrettes mémoires'''. Les barrettes de mémoire se fixent à la carte mère sur un connecteur standardisé, appelé '''slot mémoire'''. Le dessin ci-contre montre une barrette de mémoire, celui-ci ci-dessous est celui d'un ''slot'' mémoire. [[File:Dual channel slots.jpg|centre|vignette|Slots mémoires.]] Sur le schéma de droite, on remarque facilement les boitiers de DRAM, rectangulaires, de couleur sombre. Chaque barrette combine ces puces de manière à additionner leurs capacités : on peut ainsi créer une mémoire de 8 gibioctets à partir de 8 puces d'un gibioctet, par exemple. Ils sont soudés sur un PCB en plastique vert sur lequel sont gravés des connexions métalliques. Les trucs dorés situés en bas des barrettes de mémoire sont des broches qui connectent la barrette au bus mémoire. Les barrettes des mémoires FPM/EDO/SDRAM/DDR n'ont pas le même nombre de broches, pour des raisons de compatibilité. {|class="wikitable" |- !Type de barrette !Type de mémoire !Nombre de broches |- | rowspan="2" | SIMM | rowspan="2" | FPM/EDO |30 |- |72 |- | rowspan="4" | DIMM |SDRAM |168 |- |DDR |184 |- |DDR2 |214, 240 ou 244, suivant la barrette ou la carte mère. |- |DDR3 |204 ou 240, suivant la barrette ou la carte mère. |} ===Le format des barrettes de mémoire=== Certaines barrettes ont des puces mémoire d'un seul côté alors que d'autres en ont sur les deux faces. Cela permet de distinguer les barrettes SIMM et DIMM. * Les '''barrettes SIMM''' ont des puces sur une seule face de la barrette. Elles étaient utilisées pour les mémoires FPM et EDO-RAM. * Les '''barrettes DIMM''' ont des puces sur les deux côtés. Elles sont utilisées sur les SDRAM et les DDR. {| class="flexible" |+ '''Barrette SIMM''' |- |[[File:SIMM FPM 4 MB - C0448721-7229.jpg|vignette|SIMM recto.]] |[[File:SIMM FPM 4 MB - C0448721-7230.jpg|vignette|SIMM verso.]] |} : Les modules DIMM tendent à avoir plus de rangées que les modules SIMM, mais ce n'est pas systématique. Il est souvent dit que les barrettes DIMM ont deux rangées, contre seulement 1 pour les SIMM, mais les contre-exemples sont nombreux. Les barrettes '''SO-DIMM''', pour ordinateurs portables, sont différentes des barrettes DIMM normales des DDR/SDRAM. La raison est qu'il n'y a pas beaucoup de place à l'intérieur d'un PC portable, ce qui demande de diminuer la taille des barrettes. {| |- |[[File:Desktop DDR Memory Comparison.svg|centre|vignette|upright=1.5|Barrettes de DDR pour PC de bureau.]] |[[File:Laptop SODIMM DDR Memory Comparison V2.svg|centre|vignette|upright=1.5|Barrettes de DDR pour PC portables.]] |} Les barrettes de Rambus ont parfois été appelées des '''barrettes RB-DIMM''', mais ce sont en réalité des DIMM comme les autres. La différence principale est que la position des broches n'était pas la même que celle des formats DIMM normaux, sans compter que le connecteur Rambus n'était pas compatible avec les connecteurs SDR/DDR normaux. ===Les interconnexions à l'intérieur d'une barrette de mémoire=== Les boîtiers de DRAM noirs sont connectés au bus par le biais de connexions métalliques. Toutes les puces d'une même rangée sont connectées aux bus d'adresse et de commande. Et les chips d'une même rangée reçoivent exactement les mêmes signaux de commande/adresses, ce qui permet d'envoyer la même adresse/commande à toutes les puces en même temps. La manière dont ces puces sont reliées au bus de commande dépend selon la mémoire utilisée. Les DDR1 et 2 utilisent ce qu'on appelle une '''topologie en T''', illustrée ci-dessous. On voit que le bus de commande forme une sorte d'arbre, dont chaque extrémité est connectée à une puce. La topologie en T permet d'égaliser le délai de transmission des commandes à travers le bus : la commande transmise arrive en même temps sur toutes les puces. Mais elle a de nombreux défauts, à savoir : elle fonctionne mal à haute fréquence, elle est difficile à router en raisons des embranchements. [[File:Organisation des bus de commandes sur les DDR1-2.png|centre|vignette|upright=3.0|Organisation des bus de commandes sur les DDR1-2, nommée topologie en T.]] En comparaison, les DDR3 utilisent une '''topologie ''fly-by''''', où les puces sont connectées en série sur le bus de commande/adresse. La topologie ''fly-by'' n'a pas les problèmes de la topologie en T : elle est simple à router et fonctionne très bien à haute fréquence. [[File:Organisation des bus de commandes sur les DDR3 - topologie fly-by.png|centre|vignette|upright=3.0|Organisation des bus de commandes sur les DDR3 - topologie ''fly-by'']] ===Les barrettes tamponnées (à registres)=== Certaines barrettes intègrent un registre tampon, qui fait l'interface entre le bus et la barrette de RAM. L'utilité est d'améliorer la transmission du signal sur le bus mémoire. Sans ce registre, les signaux électriques doivent traverser le bus, puis traverser les connexions à l'intérieur de la barrette, jusqu'aux puces de mémoire. Avec un registre tampon, les signaux traversent le bus, sont mémorisés dans le registre et c'est tout. Le registre envoie les commandes/données jusqu'aux puces mémoire, mais le signal a été régénéré par le registre. Le signal transmis est donc de meilleure qualité, ce qui augmente la fiabilité du système mémoire. Le défaut est que la présence de ce registre fait que les barrettes ont un temps de latence est plus important que celui des barrettes normales, du fait de la latence du registre. Les barrettes de ce genre sont appelées des '''barrettes RIMM'''. Il en existe deux types : * Avec les '''barrettes RDIMM''', le registre fait l'interface pour le bus d'adresse et le bus de commande, mais pas pour le bus de données. * Avec les '''barrettes LRDIMM''' (''Load Reduced DIMMs''), le registre fait tampon pour tous les bus, y compris le bus de données. [[File:Organisation des bus de commandes sur les RDIMM.png|centre|vignette|upright=3.0|Organisation des bus de commandes sur les RDIMM.]] ===Le ''Serial Presence Detect''=== [[File:SPD SDRAM.jpg|vignette|Localisation du SPD sur une barrette de SDRAM.]] Toute barrette de mémoire assez récente contient une petite mémoire ROM qui stocke les différentes informations sur la mémoire : délais mémoire, capacité, marque, etc. Cette mémoire s'appelle le '''''Serial Presence Detect''''', aussi communément appelé le SPD. Ce SPD contient non seulement les timings de la mémoire RAM, mais aussi diverses informations, comme le numéro de série de la barrette, sa marque, et diverses informations. Le SPD est lu au démarrage de l'ordinateur par le BIOS, afin de pourvoir configurer ce qu'il faut. Le contenu de ce fameux SPD est standardisé par un organisme nommé le JEDEC, qui s'est chargé de standardiser le contenu de cette mémoire, ainsi que les fréquences, timings, tensions et autres paramètres des mémoires SDRAM et DDR. Pour les curieux, vous pouvez lire la page wikipédia sur le SPD, qui donne son contenu pour les mémoires SDR et DDR : [https://en.wikipedia.org/wiki/Serial_presence_detect Serial Presence Detect]. ==Les mémoires asynchrones à RAS/CAS : FPM et EDO-RAM== Avant l'invention des mémoires SDRAM et DDR, il exista un grand nombre de mémoires différentes, les plus connues étant les mémoires fast page mode et EDO-RAM. Ces mémoires n'étaient pas synchronisées par un signal d'horloge, c'était des '''mémoires asynchrones'''. Quand ces mémoires ont été créées, cela ne posait aucun problème : les accès mémoire étaient très rapides et le processeur était certain que la mémoire aurait déjà fini sa lecture ou écriture au cycle suivant. Les mémoires asynchrones les plus connues étaient les '''mémoires FPM''' et '''mémoires EDO'''. Pour ce qui est de leur interface, il faut signaler qu'elles n'ont pas d'entrée ''Chip Select'' ou d'entrée ''Output Enable''. Les signaux RAS et CAS remplacent en quelque sorte ces deux signaux. Le bit RAS fait office de ''Chip Select'', le bit CAS fait office d'''Output Enable''. ===Les mémoires FPM=== Les '''mémoires FPM (''Fast Page Mode'')''' possédaient une petite amélioration, qui rendait l'adressage plus simple. Avec elles, il n'y a pas besoin de préciser deux fois la ligne si celle-ci ne changeait pas lors de deux accès consécutifs : on pouvait garder la ligne sélectionnée durant plusieurs accès. Par contre, il faut quand même préciser les adresses de colonnes à chaque changement d'adresse. Il existe une petite différence entre les mémoire ''Page Mode'' et les mémoires ''Fast-Page Mode'' proprement dit. Sur les premières, le signal CAS est censé passer à 0 avant qu'on fournisse l'adresse de colonne. Avec les ''Fast-Page Mode'', l'adresse de colonne pouvait être fournie avant que l'on configure le signal CAS. Cela faisait gagner un petit peu de temps, en réduisant quelque peu le temps d'accès total. [[File:Sélection d'une ligne sur une mémoire FPM ou EDO.png|centre|vignette|upright=2|Sélection d'une ligne sur une mémoire FPM ou EDO.]] Avec les '''mémoires en mode quartet''', il est possible de lire quatre octets consécutifs sans avoir à préciser la ligne ou la colonne à chaque accès. On envoie l'adresse de ligne et l'adresse de colonne pour le premier accès, mais les accès suivants sont fait automatiquement. La seule contrainte est que l'on doit générer un front descendant sur le signal CAS pour passer à l'adresse suivante. Vous aurez noté la ressemblance avec le mode rafale vu il y a quelques chapitres, mais il y a une différence notable : le mode rafale vrai n'aurait pas besoin qu'on précise quand passer à l'adresse suivante avec le signal CAS. [[File:Mode quartet.png|centre|vignette|upright=3|Mode quartet.]] Les '''mémoires FPM à colonne statique''' se passent même du signal CAS. Le changement de l'adresse de colonne est détecté automatiquement par la mémoire et suffit pour passer à la colonne suivante. Dans ces conditions, un délai supplémentaire a fait son apparition : le temps minimum entre deux sélections de deux colonnes différentes, appelé tCAS-to-CAS. [[File:Accès en colonne statique.jpg|centre|vignette|upright=2.5|Accès en colonne statique.]] ===Les mémoires EDO-RAM=== L''''EDO-RAM''' a été inventée quelques années après la mémoire FPM. Elle a été déclinée en deux versions : la EDO simple, et la EDO en rafale. L'EDO simple ajoutait une entrée ''Ouput Enable'' à une mémoire FPM. Pour rappel, l'entrée ''Ouput Enable'' permet de connecter/déconnecter la DRAM du bus de données. S'il est mis à 0, les lectures et écritures sont empêchées. Pour ajouter cette entrée, il a fallu rajouter un registre sur la sortie de donnée, celle qui sert pour les lectures. Et l'ajout de ce registre a introduit une capacité dite de ''pipelining'', sur le même modèle que pour les mémoires SRAM synchrones. La donnée pouvait être maintenue sur le bus de données durant un certain temps, même après la remontée du signal CAS. Le registre de sortie maintenait la donnée lu tant que le signal RAS restait à 0, et tant qu'un nouveau signal CAS n'a pas été envoyé. Faire remonter le signal CAS à 1 n'invalidait pas la donnée en sortie. La conséquence est qu'on pouvait démarrer une nouvelle lecture alors que la donnée de l'accès précédent était encore présent sur le bus de données. Le pipeline obtenu avait deux étages : un où on présentait l'adresse et sélectionnait la colonne, un autre où la donnée était lue depuis le registre de sortie. Les mémoires EDO étaient donc plus rapides. [[File:EDO RAM.png|centre|vignette|upright=3|EDO RAM]] Cependant, cela marchait surtout pour les lectures, pas pour les écritures. Une écriture ne démarre que quand la lecture ou écriture précédente est totalement terminée. De même, on ne peut pas démarrer un nouvel accès mémoire tant qu'une écriture est en cours. ===Les mémoires EDO-RAM avec mode rafale=== Les '''EDO en rafale''' effectuent les accès à 4 octets consécutifs automatiquement : il suffit d'adresser le premier octet à lire. Les 4 octets étaient envoyés sur le bus les uns après les autres, au rythme d'un par cycle d’horloge : ce genre d'accès mémoire s'appelle un accès en rafale. [[File:Accès en rafale.png|centre|vignette|upright=2|Accès en rafale sur une DRAM EDO.]] Implémenter cette technique nécessite d'ajouter un compteur, capable de faire passer d'une colonne à une autre quand on lui demande, et quelques circuits annexes pour commander le tout. [[File:Modifications du contrôleur mémoire liées aux accès en rafale.png|centre|vignette|upright=2|Modifications du contrôleur de DRAM liées aux accès en rafale.]] ===Le rafraichissement mémoire=== Les mémoires FPM et EDO doivent être rafraichies régulièrement. Au début, le rafraichissement se faisait ligne par ligne. Le rafraichissement avait lieu quand le RAS passait à l'état haut, alors que le CAS restait à l'état bas. Le processeur, ou le contrôleur mémoire, sélectionnait la ligne à rafraichir en fournissant son adresse mémoire. D'où le nom de '''rafraichissement par adresse''' qui est donné à cette méthode de commande du rafraichissement mémoire. Divers processeurs implémentaient de quoi faciliter le rafraichissement par adresse. Par exemple, le Zilog Z80 contenait un compteur de ligne, un registre qui contenait le numéro de la prochaine ligne à rafraichir. Il était incrémenté à chaque rafraichissement mémoire, automatiquement, par le processeur lui-même. Un ''timer'' interne permettait de savoir quand rafraichir la mémoire : quand ce ''timer'' atteignait 0, une commande de rafraichissement était envoyée à la mémoire, et le ''timer'' était ''reset''. [[File:Rafraichissement mémoire manuel.png|centre|vignette|upright=2|Rafraichissement mémoire manuel.]] Par la suite, certaines mémoires ont implémenté un compteur interne d'adresse, pour déterminer la prochaine adresse à rafraichir sans la préciser sur le bus d'adresse. Le déclenchement du rafraichissement se faisait toujours par une commande externe, provenant du contrôleur de DRAM ou du processeur. Cette commande faisait passer le CAS à 0 avant le RAS. Cette méthode de rafraichissement se nomme '''rafraichissement interne'''. [[File:Rafraichissement sur CAS précoce.png|centre|vignette|upright=2|Rafraichissement sur CAS précoce.]] On peut noter qu'il est possible de déclencher plusieurs rafraichissements à la suite en laissant le signal CAS dans le même état. Ce genre de choses pouvait avoir lieu après une lecture : on pouvait profiter du fait que le CAS soit mis à zéro par la lecture ou l'écriture pour ensuite effectuer des rafraichissements en touchant au signal RAS. Dans cette situation, la donnée lue était maintenue sur la sortie durant les différents rafraichissements. [[File:Rafraichissements multiples sur CAS précoce.png|centre|vignette|upright=2|Rafraichissements multiples sur CAS précoce.]] ==Les mémoires SDRAM== Dans les années 90, les mémoires asynchrones ont laissé la place aux '''mémoires SDRAM''', qui sont synchronisées avec le bus par une horloge. L'utilisation d'une horloge a comme avantage des temps d'accès fixes : le processeur sait qu'un accès mémoire prendra un nombre déterminé de cycles d'horloge. Avec les mémoires asynchrones, le processeur ne pouvait pas prévoir quand la donnée serait disponible et ne faisait rien tant que la mémoire n'avait pas répondu : il exécutait ce qu'on appelle des ''wait states'' en attendant que la mémoire ait fini. Les mémoires SDRAM sont standardisées par un organisme international, le JEDEC. Le standard SDRAM impose des spécifications électriques bien précise pour les barrettes de mémoire et le bus mémoire, décrit le protocole utilisé pour communiquer avec les barrettes de mémoire, et bien d'autres choses encore. Les SDRAM ont été déclinées en versions de performances différentes, décrites dans le tableau ci-dessous : {| class="wikitable" ! Nom standard ! Fréquence ! Bande passante |- | PC66 | 66 mhz | 528 Mio/s |- | PC66 | 100 mhz | 800 Mio/s |- | PC66 | 133 mhz | 1064 Mio/s |- | PC66 | 150 mhz | 1200 Mio/s |} ===Les banques internes aux chips mémoires SDRAM=== L'intérieur d'une mémoire SDRAM contient plusieurs '''banques''', aussi appelées des banc mémoire. Concrètement, une banque est... une mémoire. Ou plutôt, une sorte de mini-mémoire miniature. Chaque banque a son propre tampon de ligne, ses propres multiplexeurs de colonne et ses propres décodeurs. C'est comme si une SDRAM regroupait plusieurs mémoires séparées dans un même circuit intégré. [[File:Arrangement vertical.jpg|centre|vignette|upright=2.5|Mémoire multi-banques.]] Un point important est que chaque banque a son propre tampon de ligne. Il est donc possible d'ouvrir plusieurs lignes en même temps, chacune dans une banque différente. Par exemple, on peut ouvrir une ligne dans la banque numéro 1, et une autre ligne dans la banque numéro 2. Et c'est une source d'optimisations très intéressantes. La première optimisation est liée au rafraichissement mémoire. Au lieu de rafraichir chaque adresse une par une, il est possible de rafraichir des banques indépendantes en même temps, ce qui divise le temps de rafraichissement par le nombre de banques. C'est ce que je sous-entendais plus haut quand je disais que le temps de rafraichissement n'est pas égal au temps de balayage sur les SDRAM, alors que c'est le cas sur les DRAM FPM et EDO. De plus, et sans rentrer dans les détails, cela permet de faire plusieurs accès mémoire en même temps, dans des banques différentes. La possibilité est limitée, mais elle existe et elle améliore grandement la performance de la SDRAM. Mais nous en reparlerons dans un chapitre ultérieur, car cette histoire d'accès simultanés a plus sa place dans le chapitre sur le parallélisme mémoire. Pour le moment, nous ne pouvons pas expliquer pourquoi ni comment un processeur pourrait émettre plusieurs accès mémoire simultanés. Un processeur est censé travailler une instruction à la fois, à ce stade du cours, il ne peut pas en faire plusieurs en même temps. Mais nous allons cependant mentionner un cas où cette possibilité est intéressante : une mémoire SDRAM partagée entre un processeur et une carte graphique. Les deux accèdent à des données séparées, qui sont dans des banques différentes. On suppose que la carte graphique accède plus fréquemment à la mémoire que le processeur. Le contrôleur mémoire reçoit les accès mémoire du CPU et du GPU et il tente d'intercaler des accès CPU entre deux accès de la carte graphique. Vu qu'ils tombent dans des banques différentes, un accès CPU et un accès carte graphique peuvent se faire presque en même temps. La seule contrainte est que si on lance plusieurs accès mémoire simultanés, ceux-ci ne peuvent pas utiliser le bus de données en même temps. {|class="wikitable" |+ Pipelining basique sur les SDRAM |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 |- ! Banque Numéro 1 | || || || || || || || || || || |- ! Banque Numéro 2 | || colspan="3" bgcolor="#A0FFFF" | Accès CPU || || || colspan="3" bgcolor="#A0FFFF" | Accès CPU || || |- ! Banque Numéro 3 | || || || || || || || || || || |- ! Banque Numéro 4 | || || || colspan="3" bgcolor="#FFA0FF" | Accès carte graphique || || colspan="3" bgcolor="#FFA0FF" | Accès carte graphique || |} ===Le mode rafale des SDRAM=== Un point important est que les SDRAM reprennent les optimisations des mémoires FPM et EDO. Elles utilisent aussi un tampon de ligne, avec la possibilité de lire plusieurs colonnes à la suite sans avoir à préciser l'adresse de ligne à chaque fois. Mais surtout, elles gèrent nativement le mode rafale. les paramètres qui ont trait au mode rafale sont configurables. Il est possible de configurer la SDRAM pour activer les accès sans rafale, ou les désactiver. Il y a aussi la possibilité de configurer le nombre d'octets consécutifs à lire ou écrire en mode rafale. On peut ainsi accéder à 1, 2, 4, ou 8 octets en une seule fois, alors que les EDO ne permettaient que des accès à 4 octets consécutifs. Enfin, on peut décider s'il faut faire un accès en mode linéaire ou entrelacé. La configuration de la SDRAM est mémorisée dans un registre de 10 bits, le '''registre de mode'''. Il faisait 10 bits sur les mémoires SDRAM, mais a été étendu à 13 bits sur la DDR2. Voici les 10 bits originels de ce registre : {|class="wikitable" |+ Signification des bits du registre de mode des SDRAM |- ! Bit n°9 | Type d'accès : en rafale ou normal |- ! Bit n°8 et 7 | Doivent valoir 00, sont réservés pour une utilisation ultérieur dans de futurs standards. |- ! Bit n°6, 5, et 4 | Latence CAS (voir plus bas) |- ! Bit n°3 | Type de rafale : linéaire ou entrelacée |- ! Bit n°2, 3, et 0 | Longueur de la rafale : indique le nombre d'octets à lire/écrire lors d'une rafale. |} ===L'interface d'une mémoire SDRAM=== Le bus de commandes d'une SDRAM contient au moins 18 fils, dont celui pour le signal d'horloge. L'interface d'une SDRAM contient tous les bits présents sur une mémoire DRAM classique : une entrée RAS, une entrée CAS, une entrée R/W, et un bus d'adresse. A cela, il faut cependant ajouter une entrée ''Chip Select'' (CS), qui permet d'activer/désactiver la mémoire SDRAM. Je rappelle que le bit CS a été introduit sur les mémoires SDRAM, il n'était pas présent sur les mémoires FPM/EDO. Deux autres bits de commande sont vraiment spécifiques des mémoires SDRAM. Il s'agit des bits CKE et DQM. Le '''bit CKE''' est l'abréviation de ''Clock Enable'', qui qui trahit sa fonction. Lorsque ce signal est à 0, le chip de SDRAM voit son signal d'horloge gelè. S'il est à 0, le contrôleur de DRAM peut envoyer ce qu'il veut sur le bus de commande ou d'adresse, la SDRAM ne réagira pas du tout, il ne se passera rien. Le '''bit DQM''' est une sorte de bit ''Output Enable'', avec une nuance importante. Le terme DQM est l'abréviation de ''Data Mask'', ce qui trahit encore une fois sa fonction. Il y a un bit DQM pour chaque octet du bus de données. Une SDRAM ayant un bus de 64 bits, cela fait 8 bits DQM par mémoire SDRAM. Lorsque le bit DQM est à 1, l'octet en question n'est simplement pas lu ou écrit, le bus de donnée est déconnecté pour cet octet. Le bus d'adresse est particulier, car il tient compte de la présence de ''banques mémoires''. Le bus d'adresse est découpé en deux portions : une portion pour sélectionner la banque, une autre pour sélectionner l'adresse à l'intérieur d'une banque. L'interface de la SDRAM fait ainsi la différence entre une '''adresse de banque''' et une '''adresse intra-banque'''. L'adresse de banque est codée sur quelques bits, généralement deux ou trois suivant la SDRAM. Le reste de l'adresse est codé sur 11 bits sur les SDRAM, mais cela a augmenté avec les DDR 1, 2, 3, 4, 5. Le bus de données d'une SDRAM fait 4, 8, ou 16 bits. Je précise bien qu'il s'agit là des puces de SDRAM, les barrettes de SDRAM combinent plusieurs puces SDRAM avec un arrangement horizontal, qui peut combiner plusieurs puces de 8 bits pour alimenter un bus de données de 64 bits. La taille des puces utilisées souvent indiquée sur la barrette de RAM, avec une mention x4, x8 ou x16. Les puces de SDRAM les plus courantes ont une interface de 8 bits pour les données. Les SDRAM de 4 bits sont surtout utilisées pour les serveurs, c'est lié au support de l'ECC. les puces x16 sont moins utilisées car elles ont généralement moins de banques que les autres. ===Les commandes SDRAM=== Le bus de commande permet d'envoyer des commandes à la mémoire, chaque commande étant précisée par une combinaison précise des bits CS, RAS, CAS, R/W, et autres. Les commandes en question sont des demandes de lecture, d'écriture, de préchargement et autres. Elles sont codées par une valeur bien précise qui est envoyée sur les 18 fils du bus de commande. Ces commandes sont nommées READ, READA, WRITE, WRITEA, PRECHARGE, ACT, ... Les plus importantes sont les commandes PRECHARGE, ACT et READ/WRITE. La commande ACT sélectionne une ligne : elle met le bit RAS à zéro et présente une adresse de ligne. Les commandes READ et WRITE sélectionnent une colonne, et déclenchent respectivement une lecture ou une écriture. Elles précisent une adresse de colonne, mettent le bit CAS à 0 et le bit RAS à 1, et précise la valeur du bit R/W. Les commandes READ et WRITE ne peuvent se faire qu'une fois que la banque a été activée par une commande ACT. Il est possible d'envoyer plusieurs commandes READ ou WRITE successives à des colonnes différentes, ce qui permet d'implémenter les optimisations des mémoires FPM. La commande PRECHARGE ferme la ligne courante et prépare l'ouverture de la suivante. Elle précharge les lignes de bit de la RAM, d'où son nom. Il est nécessaire d'en envoyer une avant d'envoyer une commande ACT. Notons que la commande PRECHARGE agit sur une banque, dont l'adresse est indiquée dans la commande PRECHARGE. Il existe une commande PRECHARGE ALL, qui agit sur toutes les banques de la SDRAM à la fois. Les commandes READA et WRITEA fusionnent une commande READ/WRITE avec une commande PRECHARGE. Elles permettent d'éviter d'avoir à envoyer une commande PRECHARGE pour fermer la ligne courante. Au lieu d'envoyer une commande READ ou WRITE, puis une commande PRECHARGE pour fermer la ligne, on envoie une seule commande READA/WRITEA. Il s'agit d'une petite optimisation, qui permet de réduire le nombre de commandes envoyées sur le bus. Les commandes sont encodées comme indiquées dans ce tableau. Une commande est tout simplement encodée en précisant une adresse si nécessaire, et une combinaison des bits CS, RAS, CAS et R/W. La seule subtilité est que le bit numéro 10 du bus d'adresse sert à commander les opérations de PRECHARGE, y compris celles implicites dans les opérations READA et WRITEA. {| class="wikitable" style="text-align:center" ! Bit CS || Bit RAS || Bit CAS || Bit WE || Bits de sélection de banque (2 bits) || Bit du bus d'adresse A10 || Reste du bus d'adresse || Nom de la commande : Description |- | 1 | colspan="6" | X | Absence de commandes. |- | 0 || 1 || 1 || 1 || colspan="3" | X || No Operation : Pas d'opération |- | 0 || 1 || 1 || 0 || colspan="3" | X || Burst Terminante : Stoppe un accès en rafale (en cours). |- | 0 || 1 || 0 || 1 || Adresse de la banque || 0 || Adresse de la colonne || READ : lit une donnée depuis la ligne active. |- | 0 || 1 || 0 || 1 || Adresse de la banque || 1 || Adresse de la colonne || READA : lit une donnée depuis la ligne active, puis ferme la ligne. |- | 0 || 1 || 0 || 0 || Adresse de la banque || 0 || Adresse de la colonne || WRITE : écrit une donnée dans la ligne active. |- | 0 || 1 || 0 || 0 || Adresse de la banque || 1 || Adresse de la colonne || WRITEA : écrit une donnée dans la ligne active, puis ferme la ligne. |- | 0 || 0 || 1 || 1 || Adresse de la banque || colspan="2" | Adresse de la ligne || ACT : charge une ligne dans le tampon de ligne. |- | 0 || 0 || 1 || 0 || Adresse de la banque || 0 || X || PRECHARGE : précharge le tampon de ligne dans la banque voulue. |- | 0 || 0 || 1 || 0 || Adresse de la X || 1 || X || PRECHARGE ALL : précharge le tampon de ligne dans toutes les banques. |- | 0 || 0 || 0 || 1 || colspan="3" | X || Auto refresh : Demande de rafraichissement, gérée par la SDRAM. |- | 0 || 0 || 0 || 0 || 00 || colspan="2" | Nouveau contenu du registre de mode || LOAD MODE REGISTER : configure le registre de mode. |} Les commandes ACT se font à partir de l'état de repos, l'état où toutes les banques sont préchargées. Par contre, les commandes MODE REGISTER SET et AUTO REFRESH ne peuvent se faire que si toutes les banques sont désactivées. Le fonctionnement simplifié d'une SDRAM peut se résumer dans ce diagramme : [[File:Fonctionnement simplifié d'une SDRAM.jpg|centre|vignette|upright=2|Fonctionnement simplifié d'une SDRAM.]] ===Les délais mémoires=== Les mémoires SDRAM n'étant pas infiniment rapides, il y a toujours un certain délais à respecter entre deux commandes. Par exemple, quand on envoie une commande ACT pour activer une ligne, on ne peut pas envoyer une commande READ/WRITE au cycle suivant. La plupart des SDRAM ne sont pas assez rapides pour ça. Il faut respecter un délai de quelques cycles, qui dépend de la mémoire. Et il n'y a pas que ce délai entre une commande ACT et la commande suivante. Une SDRAM doit gérer d'autres temps d'attente, appelés des '''délais mémoires''', ou encore des ''timings'' mémoire. Les délais mémoire le plus importants sont résumés ci-dessous : {|class="wikitable" |- !Timing!!Description |- | colspan="2" | |- ! colspan="2" | Délais primaires |- ||tRP|| Temps entre une commande PRECHARGE et une commande ACT |- ||tRCD|| Temps entre une commande ACT et une commande READ/WRITE. |- ||tCL|| Temps entre une commande READ et l'envoi de la donnée lue sur le bus de données. |- ||tDQSS|| Temps entre une commande WRITE et l'écriture de la donnée. |- ||tCAS-to-CAS|| Temps minimum entre deux commandes READ. |- ! colspan="2" | Délais secondaires |- ||tWTR|| Temps entre une lecture et une écriture consécutives. |- ||tRAS || Temps entre une commande ACT et une commande PRECHARGE. |} La façon de mesurer ces délais varie : sur les mémoires FPM et EDO, on les mesure en unités de temps (secondes, millisecondes, micro-secondes, etc.), tandis qu'on les mesure en cycles d'horloge sur les mémoires SDRAM. Les délais/timings mémoire ne sont pas les mêmes suivant la barrette de mémoire que vous achetez. Certaines mémoires sont ainsi conçues pour avoir des timings assez bas et sont donc plus rapides, et surtout : beaucoup plus chères que les autres. Le gain en performances dépend beaucoup du processeur utilisé et est assez minime comparé au prix de ces barrettes. Les délais mémoires d'une barrette sont mémorisés dans le ''Serial Presence Detect'' de la barrette et sont lus par le BIOS au démarrage de l'ordinateur, et sont ensuite utilisés pour configurer le contrôleur de DRAM. ===Les commandes anticipées=== Les SDRAM sont parfois capables de démarrer une commande avant que la précédente soit terminée. Concrètement, pendant que la commande précédente envoie sa dernière donnée sur le bus de données, on peut envoyer la commande suivante avec quelques cycles d'avance. L'exemple ci-dessous devrait être assez clair : on envoie une seconde commande au neuvième cycle, alors qu'une rafale est en cours. La machine à état tient compte de cette possibilité. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | || bgcolor="#A0FFFF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || || || bgcolor="#FFA0FF" | READ (2) || || || || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 | bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 | |} La possibilité est très limitée, car il faut tenir compte des délais mémoire. Il s'agit d'une forme très limitée de pipeline, similaire à celui des mémoires SRAM. Comme les SRAM, les SDRAM sont formées en entourant une RAM asynchrone de registres. Il est possible d'écrire dans les registres de données/commandes, pendant qu'un autre accès mémoire accède au cœur asynchrone. Les délais mémoire sont conçus pour éviter qu'une commande accède au cœur asynchrone en même temps que la suivante ou la précédente, idem pour l'usage des registres. C'est pour cela que les délais mémoire sont assez différents entre écritures et lectures, d'ailleurs. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Enregistrement de la commande dans le registre d'adresse/commande | bgcolor="#A0FFFF" | READ (1) || || || || bgcolor="#FFA0FF" | READ (2) || || || || || |- ! Accès au cœur asynchrone | || bgcolor="#A0FFFF" | READ (1) || bgcolor="#A0FFFF" | READ (1) || bgcolor="#A0FFFF" | READ (1) || || bgcolor="#FFA0FF" | READ (2) || bgcolor="#FFA0FF" | READ (2) || bgcolor="#FFA0FF" | READ (2) || || || |- ! Lecture/écriture du registre de données | || || || || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 | bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 | |} ==Les mémoires DDR== Les mémoires SDRAM récentes sont des mémoires de type ''dual data rate'', ce qui fait qu'elles portent le nom de mémoires DDR. Pour rappel, les mémoires ''dual data rate'' ont un plan mémoire deux fois plus large que le bus mémoire, avec un bus mémoire allant à une fréquence double. Par double, on veut dire que les transferts sur le bus mémoire ont lieu sur les fronts montants et descendants de l'horloge. Il y a donc deux transferts de données sur le bus pour chaque cycle d'horloge, ce qui permet de doubler le débit sans toucher à la fréquence du plan mémoire lui-même. Les mémoires DDR sont standardisées par un organisme international, le JEDEC, et ont été déclinées en plusieurs générations : DDR1, DDR2, DDR3, et DDR4. La différence entre ces modèles sont très nombreuses, mais les plus évidentes sont la fréquence de la mémoire et du bus mémoire. D'autres différences mineures existent entre les SDRAM et les mémoires DDR. Par exemple, la tension d'alimentation des mémoires DDR est plus faible que pour les SDRAM. ET elle a diminué dans le temps, d'une génération de DDR à l'autre. Avec les mémoires DDR2,la tension d'alimentation est passée de 2,5/2,6 Volts à 1,8 Volts. Avec les mémoires DDR3, la tension d'alimentation est notamment passée à 1,5 Volts. ===Les performances des mémoires DDR=== Les mémoires SDRAM ont évolué dans le temps, mais leur temps d'accès/fréquence n'a pas beaucoup changé. Il valait environ 10 nanosecondes pour les SDRAM, approximativement 5 ns pour la DDR-400, il a peu évolué pendant la génération DDR et DDR3, avant d'augmenter pendant les générations DDR4 et de stagner à nouveau pour la génération DDR5. L'usage du DDR, puis du QDR, visait à augmenter les performances malgré la stagnation des temps d'accès. En conséquence, la fréquence du bus a augmenté plus vite que celle des puces mémoire pour compenser. {|class="wikitable" |- ! Année ! Type de mémoire ! Fréquence de la mémoire (haut de gamme) ! Fréquence du bus ! Coefficient multiplicateur entre les deux fréquences |- | 1998 | DDR 1 | 100 - 200 MHz | 200 - 400 MHz | 2 |- | 2003 | DDR 2 | 100 - 266 MHz | 400 - 1066 MHz | 4 |- | 2007 | DDR 3 | 100 - 266 MHz | 800 - 2133 MHz | 8 |- | 2014 | DDR 4 | 200 - 400 MHz | 1600 - 3200 MHz | 8 |- | 2020 | DDR 5 | 200 - 450 MHz | 3200 - 7200 MHz | 8 à 16 |} Une conséquence est que la latence CAS, exprimée en nombre de cycles, a augmenté avec le temps. Si vous comparez des mémoires DDR2 avec une DDR4, par exemple, vous allez voir que la latence CAS est plus élevée pour la DDR4. Mais c'est parce que la latence est exprimée en nombre de cycles d'horloge, et que la fréquence a augmentée. En comparant les temps d'accès exprimés en secondes, on voit une amélioration. ===Les commandes des mémoires DDR=== Les commandes des mémoires DDR sont globalement les mêmes que celles des mémoires SDRAM, vues plus haut. Les modifications entre SDRAM, DDR1, DDR2, DDR3, DDR4, et DDR5 sont assez mineures. Les seules différences sont l'addition de bits pour la transmission des adresses, des bits en plus pour la sélection des banques, etc. En clair, une simple augmentation quantitative. Le registre de mode a été un peu modifié. Il est passé de 10 bits pour les SDRAM et DDR1, à 13 bits sur la DDR 2 et les suivantes. Les DDR ont aussi ajouté le support de plusieurs registres de mode, qui sont sélectionnés en réutilisant l'adresse de banque. Dans une commande LOAD MODE REGISTER, l'adresse de banque indique quel registre de mode il faut altérer. Avant la DDR4, les modifications des commandes sont mineures. La DDR2 supprime la commande ''Burst Terminate'', la DDR3 et la DDR4 utilisent le bit A12 pour préciser s'il faut faire une rafale complète, ou une rafale de moitié moins de données. Une optimisation des DDR2 et 3 est celle des '''CAS postés'''. L'idée est que le contrôleur de DRAM peut envoyer une commande ACT et une commande READ/WRITE sans se soucier des ''timings'' nécessaires entre les deux. En théorie, les deux commandes doivent être séparées par quelques cycles, sur une SDRAM ou une DDR1. Mais avec la DDR2, le contrôleur de DRAM peut envoyer les deux l'une après l'autre, au cycle suivant. C'est la mémoire qui mettra en attente la commande READ/WRITE pour respecter les ''timings'' mémoire. Cela complexifie le fonctionnement interne de la DDR, mais simplifie grandement le travail du contrôleur de DRAM. Mais avec la DDR4, les choses changent, notamment au niveau de la commande ACT. Avec l'augmentation de la capacité des barrettes mémoires, la taille des adresses est devenue trop importante. Pour éviter de rajouter des bits d'adresses, les concepteurs du standard DDR4 ont décidé de ruser. Lors d'une commande ACT, les bits RAS, CAS et WE sont utilisés comme bits d'adresse, alors qu'ils ont leur signification normale pour les autres commandes. Pour éviter toute confusion, un nouveau bit ACT est ajouté pour indiquer la présence d'une commande ACT : il est à 1 pour une commande ACT, 0 pour les autres commandes. {| class="wikitable" style="text-align:center" |+ Commandes d'une mémoire DDR4, seule la commande colorée change par rapport aux SDRAM ! Bit CS || style="background: #CCFFCC" | Bit ACT || Bit RAS || Bit CAS || Bit WE || Bits de sélection de banque (4 bits) || Bit du bas d'adresse A10 || Reste du bus d'adresse || Nom de la commande : Description |- | 1 | colspan="6" | X | Absence de commandes. |- | 0 || style="background: #CCFFCC" | 0 || 1 || 1 || 1 || colspan="3" | X || No Operation : Pas d'opération |- | 0 || style="background: #CCFFCC" | 0 || 1 || 1 || 0 || colspan="3" | X || Burst Terminante : Arrêt d'un accès en rafale en cours. |- | 0 || style="background: #CCFFCC" | 0 || 1 || 0 || 1 || Adresse de la banque || 0 || Adresse de la colonne || READ : lire une donnée depuis la ligne active. |- | 0 || style="background: #CCFFCC" | 0 || 1 || 0 || 1 || Adresse de la banque || 1 || Adresse de la colonne || READA : lire une donnée depuis la ligne active, avec rafraichissement automatique de la ligne. |- | 0 || style="background: #CCFFCC" | 0 || 1 || 0 || 0 || Adresse de la banque || 0 || Adresse de la colonne || WRITE : écrire une donnée depuis la ligne active. |- | 0 || style="background: #CCFFCC" | 0 || 1 || 0 || 0 || Adresse de la banque || 1 || Adresse de la colonne || WRITEA : écrire une donnée depuis la ligne active, avec rafraichissement automatique de la ligne. |- style="background: #CCFFCC" | 0 || style="background: #CCFFCC" | 1 || colspan="3" | Adresse de la ligne (bits de poids forts) || Adresse de la banque || colspan="2" | Adresse de la ligne (bits de poids faible) || ACT : charge une ligne dans le tampon de ligne. |- | 0 || style="background: #CCFFCC" | 0 || 0 || 1 || 0 || Adresse de la banque || 0 || X || PRECHARGE : précharge le tampon de ligne dans la banque voulue. |- | 0 || style="background: #CCFFCC" | 0 || 0 || 1 || 0 || Adresse de la X || 1 || X || PRECHARGE ALL : précharge le tampon de ligne' dans toutes les banques. |- | 0 || style="background: #CCFFCC" | 0 || 0 || 0 || 1 || colspan="3" | X || Auto refresh : Demande de rafraichissement, gérée par la SDRAM. |- | 0 || style="background: #CCFFCC" | 0 || 0 || 0 || 0 || Numéro de registre de mode || colspan="2" | Nouveau contenu du registre de mode || LOAD MODE REGISTER : configure le registre de mode. |} ==Les VRAM des cartes vidéo== Les cartes graphiques ont des besoins légèrement différents des DRAM des processeurs, ce qui fait qu'il existe des mémoires DRAM qui leur sont dédiées. Elles sont appelés des '''''Graphics RAM''''' (GRAM). La plupart incorporent des fonctionnalités utiles uniquement pour les mémoires vidéos, comme des fonctionnalités de masquage (appliquer un masque aux données lue ou à écrire), ou le remplissage d'un bloc de mémoire avec une donnée unique. Les anciennes cartes graphiques et les anciennes consoles utilisaient de la DRAM normale, faute de mieux. La première GRAM utilisée était la NEC μPD481850, qui a été utilisée sur la console de jeu PlayStation, à partir de son modèle SCPH-5000. D'autres modèles de GRAM ont rapidement suivi. Les anciennes consoles de jeu, mais aussi des cartes graphiquesn utilisaient des GRAM spécifiques. ===Les mémoires vidéo double port=== Sur les premières consoles de jeu et les premières cartes graphiques, le ''framebuffer'' était mémorisé dans une mémoire vidéo spécialisée appelée une '''mémoire vidéo double port'''. Le premier port était connecté au processeur ou à la carte graphique, alors que le second port était connecté à un écran CRT. Aussi, nous appellerons ces deux port le ''port CPU/GPU'' et l'autre sera appelé le ''port CRT''. Le premier port était utilisé pour enregistrer l'image à calculer et faire les calculs, alors que le second port était utilisé pour envoyer à l'écran l'image à afficher. Le port CPU/GPU est tout ce qu'il y a de plus normal : on peut lire ou écrire des données, en précisant l'adresse mémoire de la donnée, rien de compliqué. Le port CRT est assez original : il permet d'envoyer un paquet de données bit par bit. De telles mémoires étaient des mémoires à tampon de ligne, dont le support de mémorisation était organisé en ligne et colonnes. Une ligne à l'intérieur de la mémoire correspond à une ligne de pixel à l'écran, ce qui se marie bien avec le fait que les anciens écrans CRT affichaient les images ligne par ligne. L'envoi d'une ligne à l'écran se fait bit par bit, sur un câble assez simple comme un câble VGA ou autre. Le second port permettait de faire cela automatiquement, en permettant de lire une ligne bit par bit, les bits étant envoyés l'un après l'autre automatiquement. Pour cela, les mémoires vidéo double port incorporaient un tampon de ligne spécialisé pour le port lié à l'écran. Ce tampon de ligne n'était autre qu'un registre à décalage, contrairement au tampon de ligne normal. Lors de l'accès au second port, la carte graphique fournissait un numéro de ligne et la ligne était chargée dans le tampon de ligne associé à l'écran. La carte graphique envoyait un signal d'horloge de même fréquence que l'écran, qui commandait le tampon de ligne à décalage : un bit sortait à chaque cycle d'écran et les bits étaient envoyé dans le bon ordre. ===Les mémoires SGRAM et GDDR=== De nos jours, les cartes graphiques n'utilisent plus de mémoires double port, mais des mémoires simple port. Les mémoires graphiques actuelles sont des SDRAM modifiées pour fonctionner en tant que ''Graphic RAM''. Les plus connues sont les '''mémoires GDDR''', pour ''graphics double data rate'', utilisées presque exclusivement sur les cartes graphiques. Il en existe plusieurs types pendant que j'écris ce tutoriel : GDDR, GDDR2, GDDR3, GDDR4, et GDDR5. Mais attention, il y a des différences avec les DDR normales. Par exemple, les GDDR ont une fréquence plus élevée que les DDR normales, avec des temps d'accès plus élevés (sauf pour le tCAS). De plus, elles sont capables de laisser ouvertes deux lignes en même temps. Par contre, ce sont des mémoires simple port. ==Les mémoires SLDRAM, RDRAM et associées== Les mémoires précédentes sont généralement associées à des bus larges. Les mémoires SDRAM et DDR modernes ont des bus de données de 64 bits de large, avec des d'adresse et de commande de largeur similaire. Le nombre de fils du bus mémoire dépasse facilement la centaine de fils, avec autant de broches sur les barrettes de mémoire. Largeur de ces bus pose de problèmes problèmes électriques, dont la résolution n'est pas triviale. En conséquence, la fréquence du bus mémoire est généralement moins performantes comparé à ce qu'on aurait avec un bus moins large. Mais d'autres mémoires DRAM ont exploré une solution alternative : avoir un bus peu large mais de haute fréquence, sur lequel on envoie les commandes/données en plusieurs fois. Elles sont regroupées sous le nom de '''DRAM à commutation par paquets'''. Elles utilisent des bus spéciaux, où les commandes/adresses/données sont transmises par paquets, par trames, en plusieurs fois. En théorie, ce qu'on a dit sur le codage des trames dans le chapitre sur le bus devrait s'appliquer à de telles mémoires. En pratique, les protocoles de transmission sur le bus mémoire sont simplifiés, pour gérer le fonctionnement à haute fréquence. Le processeur envoie des paquets de commandes, les mémoires répondent avec des paquets de données ou des accusés de réception. Les mémoires à commutation par paquets sont peu nombreuses. Les plus connues sont les mémoires conçues par la société Rambus, à savoir la ''RDRAM'' (''Rambus DRAM'') et ses deux successeurs ''XDR RAM'' et ''XDR RAM 2''. La ''Synchronous-link DRAM'' (''SLDRAM'') est un format concurrent conçu par un consortium de plusieurs concepteurs de mémoire. ===La SLDRAM (''Synchronous-link DRAM'')=== Les '''mémoires SLDRAM''' avaient un bus de données de 64 bits allant à 200-400 Hz, avec technologie DDR, ce qui était dans la norme de l'époque pour la fréquence (début des années 2000). Elle utilisait un bus de commande de 11 bits, qui était utilisé pour transmettre des commandes de 40 bits, transmises en quatre cycles d'horloge consécutifs (en réalité, quatre fronts d'horloge donc deux cycles en DDR). Le bus de données était de 18 bits, mais les transferts de donnée se faisaient par paquets de 4 à 8 octets (32-65 bits). Pour résumer, données et commandes sont chacunes transmises en plusieurs cycles consécutifs, sur un bus de commande/données plus court que les données/commandes elle-mêmes. Là où les SDRAM sélectionnent la bonne barrette grâce à des signaux de commande dédiés, ce n'est pas le cas avec la SLDRAM. A la place, chaque barrette de mémoire reçoit un identifiant, un numéro codé sur 7-8 bits. Les commandes de lecture/écriture précisent l'identifiant dans la commande. Toutes les barrettes reçoivent la commande, elles vérifient si l'identifiant de la commande est le leur, et elles la prennent en compte seulement si c'est le cas. Voici le format d'une commande SLDRAM. Elle contient l'adresse, qui regroupe le numéro de banque, le numéro de ligne et le numéro de colonne. On trouve aussi un code commande qui indique s'il faut faire une lecture ou une écriture, et qui configure l'accès mémoire. Il configure notamment le mode rafale, en indiquant s'il faut lire/écrire 4 ou 8 octets. Enfin, il indique s'il faut fermer la ligne accédée une fois l'accès terminé, ou s'il faut la laisser ouverte. Le code commande peut aussi préciser que la commande est un rafraichissement ou non, effectuer des opérations de configuration, etc. L'identifiant de barrette mémoire est envoyé en premier, histoire que les barrettes sachent précocement si l'accès les concerne ou non. {|class="wikitable" style="text-align:center" |+SLDRAM Read, write or row op request packet ! FLAG || CA9 || CA8 || CA7 || CA6 || CA5 || CA4 || CA3 || CA2 || CA1 || CA0 |- ! 1 | colspan=9 bgcolor=#ffcccc| Identifiant de barrette mémoire|| bgcolor=#ccffcc| Code de commande |- ! 0 | colspan=5 bgcolor=#ccffcc| Code de commande ||colspan=3 bgcolor=#ff88ff| Banque||colspan=2 bgcolor=#ffffcc| Ligne |- ! 0 | colspan=9 bgcolor=#ffffcc| Ligne || 0 |- ! 0 | 0 || 0 || 0 ||colspan=7 bgcolor=#ccffff| Colonne |} ===Les mémoires Rambus=== Les mémoires conçues par la société Rambus regroupent la '''RDRAM''' (''Rambus DRAM'') et ses deux successeurs '''XDR RAM''' et '''XDR RAM 2'''. Les toutes premières étaient les '''mémoires RDRAM''', où le bus permettait de transmettre soit des commandes (adresse inclue), soit des données, avec un multiplexage total. Le processeur envoie un paquet contenant commandes et adresse à la mémoire, qui répond avec un paquet d'acquittement. Lors d'une lecture, le paquet d'acquittement contient la donnée lue. Lors d'une écriture, le paquet d'acquittement est réduit au strict minimum. Le bus de commandes est réduit au strict minimum, à savoir l'horloge et quelques bits absolument essentiels, les bits RW est transmis dans un paquet et n'ont pas de ligne dédiée, pareil pour le bit OE. Toutes les barrettes de mémoire doivent vérifier toutes les transmissions et déterminer si elles sont concernées en analysant l'adresse transmise dans la trame. Elles ont été utilisées dans des PC ou d'anciennes consoles de jeu. Par exemple, la Nintendo 64 incorporait 4 mébioctets de mémoire RDRAM en tant que mémoire principale. La RDRAM de la Nintendo 64 était cadencée à 500 MHz, utilisait un bus de 9 bits, et avait un débit binaire maximal théorique de 500 MB/s. La Playstation 2 contenait quant à elle 32 mébioctets de RDRAM en ''dual-channel'', pour un débit binaire de 3.2 Gibioctets par seconde. Les processeurs Pentium 3 pouvaient être associés à de la RDRAM sur certaines mères. Les Pentium 4 étaient eux aussi associés à la de RDRAM, mais les cartes mères ne géraient que ce genre de mémoire. La Playstation 3 contenait quant à elle de la XDR RAM. ==Les eDRAM : des DRAM adaptées aux ''chiplets''== Les '''mémoires eDRAM''', pour ''embedded DRAM'', sont des mémoires RAM qui sont destinées à être intégrée au processeur. Pour comparer, les DRAM normales sont placées sur des barrettes de RAM ou soudées à la carte mère. Dans la quasi-totalité des cas, l'eDRAM est utilisée pour implémenter une mémoire cache, elle ne sert pas de mémoire principale (cache L4, le plus proche de la mémoire sur ces puces). De ce fait, elles sont conçues pour être très rapides, avoir une grande bande passante, au détriment de leur capacité mémoire. Pour être plus précis, l'eDRAM est une puce de DRAM conçue pour être intégrée dans un ''chiplet'', , à savoir des circuits imprimés qui regroupent plusieurs puces électroniques distinctes, regroupées sur le même PCB. Typiquement, un processeur de type ''chiplet'' avec de l'eDRAM comprend deux puces séparées : une pour le processeur, une autre pour une puce de communication avec la RAM. Avec la mémoire eDRAM, les deux puces sont complétées par une troisième puce spécialisée qui incorpore l'eDRAM. Elle a été utilisée sur quelques processeurs, mais aussi dans des consoles de jeu vidéo, pour la carte graphique des consoles suivantes : la PlayStation 2, la PlayStation Portable, la GameCube, la Wii, la Wii U, et la XBOX 360. Sur ces consoles, la RAM de la carte graphique était intégrée avec le processeur graphique dans le même circuit. La fameuse mémoire vidéo et le GPU n'étaient qu'une seule et même puce électronique, un seul circuit intégré. Ce n'est pas le cas sur une carte graphique moderne : regardez votre carte graphique avec attention et vous verrez que le GPU est une puce carrée située sous les ventilateurs, alors que les puces mémoires sont situées juste autour et soudées sur le PCB de la carte. Les processeurs Intel Core de microarchitecture Broadwell disposaient d'un cache L4 de 128 mébioctets, intégralement implémenté avec de la mémoire eDRAM. Quelques processeurs de la microarchitecture précédente (Haswell), disposaient aussi de ce cache. Le cache L4 eDRAM était implémenté sur un chiplet à part, à savoir que le processeur était composé de trois puces séparées : une pour le processeur, une autre pour la gestion des entrées-sorties, et une autre pour le cache L4. La puce pour le cache L4 était appelée ''Crystal Well''. La puce ''Crystal Well'' était une puce gravée en 22nm, ce qui était une finesse de gravure plus élevée que celle des processeurs associés. ''Crystal Well'' était très optimisé pour l'époque. Par exemple, elle disposait de bus séparées pour la lecture et l'écriture, chose qu'on retrouve fréquemment sur les SRAM mais qui est absent sur les mémoires DRAM actuelles. Pour le reste, elle ressemblait beaucoup aux mémoires DDR de l'époque (système de ''double data rate'', entres autres), mais elle allait à une fréquence plus élevée que les DRAM de l'époque et avait un débit bien plus élevé, pour une consommation moindre. ''Crystal Well'' consommait entre 1 à 5 watts (1 watt en veille, 5 à pleine utilisation), pour un débit binaire de 102 GB/s et fonctionnait à 3.2 GHz. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les mémoires SRAM synchrones | prevText=Les mémoires SRAM synchrones | next=Contrôleur mémoire externe | nextText=Le contrôleur mémoire externe }}{{autocat}} </noinclude> 3orr4ok6r4u5yhwmdmgkrvn5vxb41ht 764677 764676 2026-04-23T18:50:41Z Mewtow 31375 /* Les commandes anticipées */ 764677 wikitext text/x-wiki Après avoir vu les mémoires statiques (SRAM), il est temps de passer aux mémoires RAM dynamiques, aussi appelées mémoires DRAM. Pour rappel, les DRAM dynamiques ont pour défaut que leurss données s'effacent après un certain temps, en quelques millièmes ou centièmes de secondes . En conséquence, il faut réécrire chaque bit de la mémoire régulièrement pour éviter qu'il ne s'efface. On dit qu'on doit effectuer régulièrement un '''rafraîchissement mémoire'''. Et celui-ci rend les DRAM très différentes des SRAM. Les DRAM des PC ont beaucoup évolués dans le temps. Les toutes premières mémoires DRAM étaient des mémoires asynchrones, mais elles ont été remplacées par des modèles synchrones. Les modèles asynchrones ont été très nombreux. Après l'apparition des premières DRAM, les mémoires ''Fast Page Mode'' sont apparues, suivies par les mémoires ''Extended Data Out'', puis les EDO en mode rafale. Elles ont été utilisées jusque dans la moitié des années 90, pour ensuite être supplantées par les mémoires SDRAM. Les mémoires DDR actuelles sont des améliorations des mémoires SDRAM actuelles. Le fait est que les DRAM sont des mémoires électroniques comme les autres, qui se présentent sous la forme de circuits intégrés, à savoir que ce sont des petits boitiers noirs avec des broches. Il est possible de souder ces boitiers sur une cartre mère, et c'est ce qui est fait sur nombre d'ordinateurs portables. Mais il est aussi possible de regrouper plusieurs boitiers sur une barrette de RAM séparée. Dans ce qui suit, nous les appellerons des '''chips mémoire''', ou encore, des puces mémoires. [[File:Canyon CN-WF514 - EtronTech EM638325TS-6-4022.jpg|centre|vignette|upright=2|Exemple de chip mémoire.]] Dans ce qui suit, nous allons étudier ces chips de DRAM, avant de voir comment ils sont regroupés sur une barrette de RAM. Puis, nous allons voire chaque type de mémoire, FPM, EDO, SDRAM, DDR, ... ; un par un. ==L'interface des DRAM et le contrôleur mémoire== L'interface d'une mémoire DRAM est plus compliquée que l'interface d'une SRAM basique. Et c'est suffisant pour qu'on ait besoin d'un intermédiaire pour faire la conversion entre processeur et DRAM. Les DRAM modernes ne sont pas connectées directement au processeur, mais le sont par l'intermédiaire d'un '''contrôleur mémoire externe'''. Il ne faut pas le confondre avec le contrôleur mémoire interne, placé dans la mémoire RAM, et qui contient notamment le décodeur. Les deux sont totalement différents, bien que leur nom soit similaire. Pour éviter toute confusion, j'utiliserais le terme de '''contrôleur de DRAM''', plus parlant. ===Le bus d'adresse des DRAM est multiplexé=== Un point important pour le contrôleur de DRAM est de transformer les adresses mémoires fournies par le processeur, en adresses utilisables par la DRAM. Car les DRAM ont une interface assez spécifique. Les DRAM ont ce qui s'appelle un '''bus d'adresse multiplexé'''. Avec de tels bus, l'adresse est envoyée en deux fois. Les bits de poids fort sont envoyés avant les bits de poids faible. On peut ainsi envoyer une adresse de 32 bits sur un bus d'adresse de 16 bits, par exemple. Le bus d'adresse contient alors environ moitié moins de fils que la normale. Pour rappel, l'avantage de cette méthode est qu'elle permet de limiter le nombre de fils du bus d'adresse, ce qui très intéressant sur les mémoires de grande capacité. Les mémoires DRAM étant utilisées comme mémoire principale d'un ordinateur, elles devaient avoir une grande capacité. Cependant, avoir un petit nombre de broches sur les barrettes de mémoire est clairement important, ce qui impose d'utiliser des stratagèmes. Envoyer l'adresse en deux fois répond parfaitement à ce problème : cela permet d'avoir des adresses larges et donc des mémoires de forte capacité, avec une performance acceptable et peu de fils sur le bus d'adresse. Les bus multiplexés se marient bien avec le fait que les DRAM sont des mémoires à adressage par coïncidence ou à tampon de ligne. Sur ces mémoires, l'adresse est découpée en deux : une adresse haute pour sélectionner la ligne, et une adresse basse qui sélectionne la colonne. L'adresse est envoyée en deux fois : la ligne, puis la colonne. Pour savoir si une donnée envoyée sur le bus d'adresse est une adresse de ligne ou de colonne, le bus de commande de ces mémoires contenait deux fils bien particuliers : les RAS et le CAS. Pour simplifier, le signal RAS permettait de sélectionner une ligne, et le signal CAS permettait de sélectionner une colonne. [[File:Signaux RAS et CAS.png|centre|vignette|upright=2|Signaux RAS et CAS.]] Si on a deux bits RAS et CAS, c'est parce que la mémoire prend en compte les signaux RAS et CAS quand ils passent de 1 à 0. C'est à ce moment là que la ligne ou colonne dont l'adresse est sur le bus sera sélectionnée. Tant que des signaux sont à zéro, la ligne ou colonne reste sélectionnée : on peut changer l'adresse sur le bus, cela ne désélectionnera pas la ligne ou la colonne et la valeur présente lors du front descendant est conservée. [[File:L'intérieur d'une FPM.png|centre|vignette|upright=2|L'intérieur d'une FPM.]] ===Le rafraichissement mémoire=== La spécificité des DRAM est qu'elles doivent être rafraichies régulièrement, sans quoi leurs cellules perdent leurs données. Le rafraichissement est basiquement une lecture camouflée. Elle lit les cellules mémoires, mais n'envoie pas le contenu lu sur le bus de données. Rappelons que la lecture sur une DRAM est destructive, à savoir qu'elle vide la cellule mémoire, mais que le système d'amplification de lecture régénère le contenu de la cellule automatiquement. La cellule est donc rafraichie automatiquement lors d'une lecture. La quasi-totalité des DRAM supporte des commandes de rafraichissement, séparées des lectures et écritures classiques. Une commande de rafraichissement ordonne de rafraichir une adresse, voire une ligne complète. Les commandes de rafraichissement sont générées par le contrôleur de DRAM, dans la grosse majorité des cas. Il est aussi possible que ce soit le processeur qui les génère, mais c'est beaucoup plus rare. Il est aussi possible d'envoyer des commandes de rafraichissement vides, qui ne précisent ni adresse ni numéro de ligne. Pour les gérer, la mémoire contient un compteur, qui pointe sur la prochaine ligne à rafraichir, qui est incrémenté à chaque commande de rafraichissement. Une commande de rafraichissement indique à la mémoire d'utiliser l'adresse dans ce compteur pour savoir quelle adresse/ligne rafraichir. [[File:Rafraichissement mémoire automatique.png|centre|vignette|upright=2|Rafraichissement mémoire automatique.]] Il existe des mémoires qui sont des intermédiaires entre les mémoires SRAM et DRAM. Il s'agit des '''mémoires pseudo-statiques''', qui sont techniquement des mémoires DRAM, utilisant des transistors et des condensateurs, mais qui gèrent leur rafraichissement mémoire toutes seules. Le rafraichissement mémoire est alors totalement automatique, ni le processeur, ni le contrôleur de DRAM ne devant s'en charger. Le rafraichissement est purement le fait des circuits de la mémoire RAM et devient une simple opération de maintenance interne, gérée par la RAM elle-même. L'envoi des commandes de rafraichissement peuvent se faire de deux manières : soit on les envoie toutes en même temps, soit on les disperse le plus possible. Le premier cas est un '''rafraichissement en rafale''', le second un '''rafraichissement étalé'''. Le rafraichissement en rafale n'est pas utilisé dans les PC, car il bloque la mémoire pendant un temps assez long. Mais les anciennes consoles de jeu gagnaient parfois à utiliser eu rafraichissement en rafale. En effet, la mémoire était souvent effacée entre l'affichage de deux images, pour éviter certains problèmes dont on ne parlera pas ici. Le rafraichissement de la mémoire était effectué à ce moment là : l'effacement rafraichissait la mémoire. Le temps mis pour rafraichir la mémoire est le temps mis pour parcourir toute la mémoire. Il s'agit du temps de balayage vu dans le chapitre sur les performances d'un ordinateur. Pour les mémoires FPM et EDO, il est défini en divisant la capacité de la mémoire par son débit binaire. C'est le temps nécessaire pour lire ou réécrire tout le contenu de la mémoire. Sur les SDRAM, les choses sont un peu différentes, pour une raison qu'on expliquera plus bas. ===Le contrôleur de DRAM=== Le contrôleur de DRAM gère le bus mémoire et tout ce qui est envoyé dessus. Il envoie des commandes aux barrettes de mémoire, commandes qui peuvent être des lectures, des écritures, ou des demandes de rafraichissement, parfois d'autres commandes. La mémoire répond à ces commandes par l'action adéquate : lire la donnée et la placer sur le bus de données pour une commande de lecture, par exemple. Le rôle du contrôleur de DRAM varie grandement suivant le contrôleur en question, ainsi que selon le type de DRAM. Les anciens contrôleurs de DRAM étaient des composants séparés du processeur, du ''chipset'' ou du reste de la carte mère. Par exemple, les contrôleur de DRAM Intel 8202, Intel 8203 et Intel 8207 étaient vendus dans des boitiers DIP et étaient soudés sur la carte mère. Par la suite, ils ont été intégré au ''chipset'' de la carte mère pendant les décennies 90-2000. Après les années 2000, ils ont été intégrés dans les processeurs. Il est possible de connecter plusieurs barrettes sur le même bus mémoire, ou alors celles-ci sont connectées au contrôleur de DRAM avec un bus par barrette/boitier. C'est ce qui permet de placer plusieurs barrettes de mémoire sur la même carte mère : toutes les barrettes sont connectées au contrôleur de DRAM d'une manière ou d'une autre. ==Les rangées : l'arrangement horizontal et vertical== Il est rare d'utiliser un chip mémoire seul, car ceux-ci n'ont pas une capacité suffisante. Pour donner quelques chiffres, à l'heure où j'écris ces lignes, la norme pour un ordinateur est d'avoir entre 8 et 64 gibioctets de RAM. Mais les chips mémoire font entre 1 et 4 gibioctets, rarement plus. La raison est que les ordinateurs combinent ensemble plusieurs chips mémoire pour additionner leurs capacités. La concaténation de plusieurs chips mémoire peut se faire de deux manières différentes, appelées l'arrangement horizontal et l'arrangement vertical. Les deux additionnent la capacité des chips mémoire, mais se distinguent sur un point : ce qui arrive respectivement au bus de données, et au nombre d'adresses. Intuitivement, on se dit que doubler la capacité mémoire implique de doubler le nombre d'adresses mémoire. C'est effectivement ce qui se passe avec l'arrangement vertical. Mais avec l'arrangement horizontal, le nombre d'adresse ne varie pas. Voyons cela en détail, et commençons par le cas le plus simple, celui de l'arrangement vertical seul. ===L'arrangement vertical : cumuler des adresses mémoire=== Introduisons l'arrangement vertical par un exemple. Imaginez que je souhaite obtenir de 4 mébioctets de RAM, en combinant 4 chips mémoires de 1 mébioctet chacun. L'idée est que le premier mébioctet est placé dans le premier chip mémoire, le second mébioctet dans le second chip, etc. Des adresses consécutives se trouvent ainsi dans le même chip mémoire, sauf pour quelques adresses où on passe d'un chip à l'autre. Avec cette organisation, le bus de donnée fait un octet, et les chips mémoire ont aussi un bus de données d'un octet. Je peux alors combiner les capacités de plusieurs chips mémoire, sans toucher au bus de données. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses avec arrangement vertical.]] Pour sélectionner le chip mémoire adéquat, il faut que chaque chip mémoire dispose d'une entrée ''Chip Select'', qui permet de l'activer ou de le désactiver. L'idée est que selon l'adresse demandée, on active le chip mémoire associé à cette adresse. Les signaux ''Chip Select'' sont générés par le contrôleur de DRAM, à partir de l'adresse. On dit qu'il y a un '''décodage d'adresse'''. Avec cet arrangement, les bits de poids fort de l'adresse sont utilisées pour sélectionner la banque adéquate, et le reste de l'adresse est envoyé sur le bus d'adresse. Par exemple, avec 4 chips mémoire, les deux bits de poids fort de l'adresse sont utilisés pour sélectionner le chip mémoire adéquat. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Adresse de banque !! Adresse dans la banque |- | Quelques bits de poids fort || Reste de l'adresse |} ===L'arrangement horizontal : élargir le bus de données=== L'arrangement horizontal permet lui aussi d'additionner les capacités mémoire de plusieurs chips mémoire. Cependant, il les combine d'une autre manière. Le nombre d'adresses mémoire n'est pas changé en utilisant plusieurs chips, mais le bus de données est élargi. Le mieux pour comprendre l'idée est de partir d'un exemple, et nous allons prendre celui d'une mémoire SDRAM. Les ordinateurs actuels ont un bus de données de 64 bits (on met de côté le cas du double ou triple canal). Cependant, il n'existe pas de chip mémoire avec un bus aussi large. Les puces de SDRAM/DDR ont un bus de 4, 8 ou 16 bits, ce sont les tailles les plus courantes. L'arrangement horizontal résout ce problème en combinant plusieurs chips mémoire de manière à ce que leurs bus de données s'"additionnent", se concatènent. Par exemple, on peut regrouper 8 chips mémoires de 8 bits, obtenir un bus mémoire de 64 bits. Il est aussi possible d'obtenir ces 64 bits avec des puces de 16 chips mémoire de 4 bits, ou 4 chips mémoire de 16 bits. [[File:Arrangement horizontal SDRAM - un Rank.png|centre|vignette|upright=2|Arrangement horizontal SDRAM.]] Avec cette organisation, on accède à tous les bancs en parallèle à chaque accès, avec la même adresse. Vu que les chips mémoires contiennent tous une partie de la donnée demandée, ils doivent tous être activés en même temps. Pour cela, l'adresse à lire est envoyée à tous les chips mémoire d'un même ''rank'', idem pour les signaux de commande. Un ensemble de N chips reliés de cette manière forme une '''rangée''' (le terme anglais est ''rank''). [[File:Arrangement horizontal.jpg|centre|vignette|upright=2|Arrangement horizontal.]] ===L'arrangement horizontal et vertical combinés=== Nous venons de voir l'arrangement vertical et horizontal, pour ce qui est des barrettes de mémoire. Précisons que ce qui vient d'être dit marche aussi bien pour les barrettes de RAM que pour la mémoire soudée sur la carte mère. Du moment qu'on combine plusieurs chips mémoire ensemble, ces concepts restent valides. Et il en est de même pour la suite, encore que ce soit nettement moins fréquent avec de la mémoire soudée. Il est possible de combiner à la fois l'arrangement vertical et l'arrangement horizontal. Rien de plus simple : il suffit d'utiliser un arrangement vertical entre plusieurs rangées, chacun composée de plusieurs chips mémoire. C'est surtout utilisé sur les barrettes de mémoire SDRAM, qui contiennent 1, 2, 4 ou 8 rangées, rarement plus. Par exemple, une SDRAM peut combiner 16 chips de DRAM de 8 bits chacun, dans deux rangées de 64 bits chacun, chaque rangée regroupant 8 chips. [[File:SDRAM avec 4 ranks.png|centre|vignette|upright=2|SDRAM avec 4 ranks]] Le choix entre la première ou la seconde rangée se fait en configurant les bits ''Chip Select'' de chaque rangée. Il faut noter que les bits de ''Chip Select'' sont générés par le contrôleur mémoire, et envoyés sur le bus de commande. [[File:Td6bfig3.png|centre|vignette|upright=2|Comparaison entre arrangement horizontal (à gauche) et arrangement vertical (à droite).]] Le contrôleur de DRAM peut adresser un certain nombre de rangées, dispersés sur plusieurs barrettes. La limite maximale dépend du contrôleur de DRAM, elle est souvent proche de 8 ou 16 rangées. Si on combine plusieurs barrettes de mémoire, il est possible de dépasser cette limite. Par exemple, prenez un contrôleur de DRAM supportant maximum 8 rangées. Avec 4 barrettes contenant 4 rangées chacune, la limite est dépassée. : Il faut noter que tout ce qui vient d'être dit vaut aussi pour les mémoires ROM et SRAM. Mais en pratique, les arrangements verticaux et horizontaux sont surtout utilisés sur les mémoires DRAM. Il faut dire que de tels arrangements servent à augmenter la capacité mémoire, ce qui colle plus avec des DRAM que des SRAM ou des ROM. ==Les barrettes de mémoire DRAM== [[File:Ram-module.svg|droite|vignette|upright=0.5|Barrette de mémoire RAM.]] Il est possible de souder plusieurs boitiers de DRAM sur une cartre mère, et c'est ce qui est fait sur nombre d'ordinateurs portables. Mais dans les PC fixes, les puces de DRAM sont regroupées sur des ''barrettes mémoires'''. Les barrettes de mémoire se fixent à la carte mère sur un connecteur standardisé, appelé '''slot mémoire'''. Le dessin ci-contre montre une barrette de mémoire, celui-ci ci-dessous est celui d'un ''slot'' mémoire. [[File:Dual channel slots.jpg|centre|vignette|Slots mémoires.]] Sur le schéma de droite, on remarque facilement les boitiers de DRAM, rectangulaires, de couleur sombre. Chaque barrette combine ces puces de manière à additionner leurs capacités : on peut ainsi créer une mémoire de 8 gibioctets à partir de 8 puces d'un gibioctet, par exemple. Ils sont soudés sur un PCB en plastique vert sur lequel sont gravés des connexions métalliques. Les trucs dorés situés en bas des barrettes de mémoire sont des broches qui connectent la barrette au bus mémoire. Les barrettes des mémoires FPM/EDO/SDRAM/DDR n'ont pas le même nombre de broches, pour des raisons de compatibilité. {|class="wikitable" |- !Type de barrette !Type de mémoire !Nombre de broches |- | rowspan="2" | SIMM | rowspan="2" | FPM/EDO |30 |- |72 |- | rowspan="4" | DIMM |SDRAM |168 |- |DDR |184 |- |DDR2 |214, 240 ou 244, suivant la barrette ou la carte mère. |- |DDR3 |204 ou 240, suivant la barrette ou la carte mère. |} ===Le format des barrettes de mémoire=== Certaines barrettes ont des puces mémoire d'un seul côté alors que d'autres en ont sur les deux faces. Cela permet de distinguer les barrettes SIMM et DIMM. * Les '''barrettes SIMM''' ont des puces sur une seule face de la barrette. Elles étaient utilisées pour les mémoires FPM et EDO-RAM. * Les '''barrettes DIMM''' ont des puces sur les deux côtés. Elles sont utilisées sur les SDRAM et les DDR. {| class="flexible" |+ '''Barrette SIMM''' |- |[[File:SIMM FPM 4 MB - C0448721-7229.jpg|vignette|SIMM recto.]] |[[File:SIMM FPM 4 MB - C0448721-7230.jpg|vignette|SIMM verso.]] |} : Les modules DIMM tendent à avoir plus de rangées que les modules SIMM, mais ce n'est pas systématique. Il est souvent dit que les barrettes DIMM ont deux rangées, contre seulement 1 pour les SIMM, mais les contre-exemples sont nombreux. Les barrettes '''SO-DIMM''', pour ordinateurs portables, sont différentes des barrettes DIMM normales des DDR/SDRAM. La raison est qu'il n'y a pas beaucoup de place à l'intérieur d'un PC portable, ce qui demande de diminuer la taille des barrettes. {| |- |[[File:Desktop DDR Memory Comparison.svg|centre|vignette|upright=1.5|Barrettes de DDR pour PC de bureau.]] |[[File:Laptop SODIMM DDR Memory Comparison V2.svg|centre|vignette|upright=1.5|Barrettes de DDR pour PC portables.]] |} Les barrettes de Rambus ont parfois été appelées des '''barrettes RB-DIMM''', mais ce sont en réalité des DIMM comme les autres. La différence principale est que la position des broches n'était pas la même que celle des formats DIMM normaux, sans compter que le connecteur Rambus n'était pas compatible avec les connecteurs SDR/DDR normaux. ===Les interconnexions à l'intérieur d'une barrette de mémoire=== Les boîtiers de DRAM noirs sont connectés au bus par le biais de connexions métalliques. Toutes les puces d'une même rangée sont connectées aux bus d'adresse et de commande. Et les chips d'une même rangée reçoivent exactement les mêmes signaux de commande/adresses, ce qui permet d'envoyer la même adresse/commande à toutes les puces en même temps. La manière dont ces puces sont reliées au bus de commande dépend selon la mémoire utilisée. Les DDR1 et 2 utilisent ce qu'on appelle une '''topologie en T''', illustrée ci-dessous. On voit que le bus de commande forme une sorte d'arbre, dont chaque extrémité est connectée à une puce. La topologie en T permet d'égaliser le délai de transmission des commandes à travers le bus : la commande transmise arrive en même temps sur toutes les puces. Mais elle a de nombreux défauts, à savoir : elle fonctionne mal à haute fréquence, elle est difficile à router en raisons des embranchements. [[File:Organisation des bus de commandes sur les DDR1-2.png|centre|vignette|upright=3.0|Organisation des bus de commandes sur les DDR1-2, nommée topologie en T.]] En comparaison, les DDR3 utilisent une '''topologie ''fly-by''''', où les puces sont connectées en série sur le bus de commande/adresse. La topologie ''fly-by'' n'a pas les problèmes de la topologie en T : elle est simple à router et fonctionne très bien à haute fréquence. [[File:Organisation des bus de commandes sur les DDR3 - topologie fly-by.png|centre|vignette|upright=3.0|Organisation des bus de commandes sur les DDR3 - topologie ''fly-by'']] ===Les barrettes tamponnées (à registres)=== Certaines barrettes intègrent un registre tampon, qui fait l'interface entre le bus et la barrette de RAM. L'utilité est d'améliorer la transmission du signal sur le bus mémoire. Sans ce registre, les signaux électriques doivent traverser le bus, puis traverser les connexions à l'intérieur de la barrette, jusqu'aux puces de mémoire. Avec un registre tampon, les signaux traversent le bus, sont mémorisés dans le registre et c'est tout. Le registre envoie les commandes/données jusqu'aux puces mémoire, mais le signal a été régénéré par le registre. Le signal transmis est donc de meilleure qualité, ce qui augmente la fiabilité du système mémoire. Le défaut est que la présence de ce registre fait que les barrettes ont un temps de latence est plus important que celui des barrettes normales, du fait de la latence du registre. Les barrettes de ce genre sont appelées des '''barrettes RIMM'''. Il en existe deux types : * Avec les '''barrettes RDIMM''', le registre fait l'interface pour le bus d'adresse et le bus de commande, mais pas pour le bus de données. * Avec les '''barrettes LRDIMM''' (''Load Reduced DIMMs''), le registre fait tampon pour tous les bus, y compris le bus de données. [[File:Organisation des bus de commandes sur les RDIMM.png|centre|vignette|upright=3.0|Organisation des bus de commandes sur les RDIMM.]] ===Le ''Serial Presence Detect''=== [[File:SPD SDRAM.jpg|vignette|Localisation du SPD sur une barrette de SDRAM.]] Toute barrette de mémoire assez récente contient une petite mémoire ROM qui stocke les différentes informations sur la mémoire : délais mémoire, capacité, marque, etc. Cette mémoire s'appelle le '''''Serial Presence Detect''''', aussi communément appelé le SPD. Ce SPD contient non seulement les timings de la mémoire RAM, mais aussi diverses informations, comme le numéro de série de la barrette, sa marque, et diverses informations. Le SPD est lu au démarrage de l'ordinateur par le BIOS, afin de pourvoir configurer ce qu'il faut. Le contenu de ce fameux SPD est standardisé par un organisme nommé le JEDEC, qui s'est chargé de standardiser le contenu de cette mémoire, ainsi que les fréquences, timings, tensions et autres paramètres des mémoires SDRAM et DDR. Pour les curieux, vous pouvez lire la page wikipédia sur le SPD, qui donne son contenu pour les mémoires SDR et DDR : [https://en.wikipedia.org/wiki/Serial_presence_detect Serial Presence Detect]. ==Les mémoires asynchrones à RAS/CAS : FPM et EDO-RAM== Avant l'invention des mémoires SDRAM et DDR, il exista un grand nombre de mémoires différentes, les plus connues étant les mémoires fast page mode et EDO-RAM. Ces mémoires n'étaient pas synchronisées par un signal d'horloge, c'était des '''mémoires asynchrones'''. Quand ces mémoires ont été créées, cela ne posait aucun problème : les accès mémoire étaient très rapides et le processeur était certain que la mémoire aurait déjà fini sa lecture ou écriture au cycle suivant. Les mémoires asynchrones les plus connues étaient les '''mémoires FPM''' et '''mémoires EDO'''. Pour ce qui est de leur interface, il faut signaler qu'elles n'ont pas d'entrée ''Chip Select'' ou d'entrée ''Output Enable''. Les signaux RAS et CAS remplacent en quelque sorte ces deux signaux. Le bit RAS fait office de ''Chip Select'', le bit CAS fait office d'''Output Enable''. ===Les mémoires FPM=== Les '''mémoires FPM (''Fast Page Mode'')''' possédaient une petite amélioration, qui rendait l'adressage plus simple. Avec elles, il n'y a pas besoin de préciser deux fois la ligne si celle-ci ne changeait pas lors de deux accès consécutifs : on pouvait garder la ligne sélectionnée durant plusieurs accès. Par contre, il faut quand même préciser les adresses de colonnes à chaque changement d'adresse. Il existe une petite différence entre les mémoire ''Page Mode'' et les mémoires ''Fast-Page Mode'' proprement dit. Sur les premières, le signal CAS est censé passer à 0 avant qu'on fournisse l'adresse de colonne. Avec les ''Fast-Page Mode'', l'adresse de colonne pouvait être fournie avant que l'on configure le signal CAS. Cela faisait gagner un petit peu de temps, en réduisant quelque peu le temps d'accès total. [[File:Sélection d'une ligne sur une mémoire FPM ou EDO.png|centre|vignette|upright=2|Sélection d'une ligne sur une mémoire FPM ou EDO.]] Avec les '''mémoires en mode quartet''', il est possible de lire quatre octets consécutifs sans avoir à préciser la ligne ou la colonne à chaque accès. On envoie l'adresse de ligne et l'adresse de colonne pour le premier accès, mais les accès suivants sont fait automatiquement. La seule contrainte est que l'on doit générer un front descendant sur le signal CAS pour passer à l'adresse suivante. Vous aurez noté la ressemblance avec le mode rafale vu il y a quelques chapitres, mais il y a une différence notable : le mode rafale vrai n'aurait pas besoin qu'on précise quand passer à l'adresse suivante avec le signal CAS. [[File:Mode quartet.png|centre|vignette|upright=3|Mode quartet.]] Les '''mémoires FPM à colonne statique''' se passent même du signal CAS. Le changement de l'adresse de colonne est détecté automatiquement par la mémoire et suffit pour passer à la colonne suivante. Dans ces conditions, un délai supplémentaire a fait son apparition : le temps minimum entre deux sélections de deux colonnes différentes, appelé tCAS-to-CAS. [[File:Accès en colonne statique.jpg|centre|vignette|upright=2.5|Accès en colonne statique.]] ===Les mémoires EDO-RAM=== L''''EDO-RAM''' a été inventée quelques années après la mémoire FPM. Elle a été déclinée en deux versions : la EDO simple, et la EDO en rafale. L'EDO simple ajoutait une entrée ''Ouput Enable'' à une mémoire FPM. Pour rappel, l'entrée ''Ouput Enable'' permet de connecter/déconnecter la DRAM du bus de données. S'il est mis à 0, les lectures et écritures sont empêchées. Pour ajouter cette entrée, il a fallu rajouter un registre sur la sortie de donnée, celle qui sert pour les lectures. Et l'ajout de ce registre a introduit une capacité dite de ''pipelining'', sur le même modèle que pour les mémoires SRAM synchrones. La donnée pouvait être maintenue sur le bus de données durant un certain temps, même après la remontée du signal CAS. Le registre de sortie maintenait la donnée lu tant que le signal RAS restait à 0, et tant qu'un nouveau signal CAS n'a pas été envoyé. Faire remonter le signal CAS à 1 n'invalidait pas la donnée en sortie. La conséquence est qu'on pouvait démarrer une nouvelle lecture alors que la donnée de l'accès précédent était encore présent sur le bus de données. Le pipeline obtenu avait deux étages : un où on présentait l'adresse et sélectionnait la colonne, un autre où la donnée était lue depuis le registre de sortie. Les mémoires EDO étaient donc plus rapides. [[File:EDO RAM.png|centre|vignette|upright=3|EDO RAM]] Cependant, cela marchait surtout pour les lectures, pas pour les écritures. Une écriture ne démarre que quand la lecture ou écriture précédente est totalement terminée. De même, on ne peut pas démarrer un nouvel accès mémoire tant qu'une écriture est en cours. ===Les mémoires EDO-RAM avec mode rafale=== Les '''EDO en rafale''' effectuent les accès à 4 octets consécutifs automatiquement : il suffit d'adresser le premier octet à lire. Les 4 octets étaient envoyés sur le bus les uns après les autres, au rythme d'un par cycle d’horloge : ce genre d'accès mémoire s'appelle un accès en rafale. [[File:Accès en rafale.png|centre|vignette|upright=2|Accès en rafale sur une DRAM EDO.]] Implémenter cette technique nécessite d'ajouter un compteur, capable de faire passer d'une colonne à une autre quand on lui demande, et quelques circuits annexes pour commander le tout. [[File:Modifications du contrôleur mémoire liées aux accès en rafale.png|centre|vignette|upright=2|Modifications du contrôleur de DRAM liées aux accès en rafale.]] ===Le rafraichissement mémoire=== Les mémoires FPM et EDO doivent être rafraichies régulièrement. Au début, le rafraichissement se faisait ligne par ligne. Le rafraichissement avait lieu quand le RAS passait à l'état haut, alors que le CAS restait à l'état bas. Le processeur, ou le contrôleur mémoire, sélectionnait la ligne à rafraichir en fournissant son adresse mémoire. D'où le nom de '''rafraichissement par adresse''' qui est donné à cette méthode de commande du rafraichissement mémoire. Divers processeurs implémentaient de quoi faciliter le rafraichissement par adresse. Par exemple, le Zilog Z80 contenait un compteur de ligne, un registre qui contenait le numéro de la prochaine ligne à rafraichir. Il était incrémenté à chaque rafraichissement mémoire, automatiquement, par le processeur lui-même. Un ''timer'' interne permettait de savoir quand rafraichir la mémoire : quand ce ''timer'' atteignait 0, une commande de rafraichissement était envoyée à la mémoire, et le ''timer'' était ''reset''. [[File:Rafraichissement mémoire manuel.png|centre|vignette|upright=2|Rafraichissement mémoire manuel.]] Par la suite, certaines mémoires ont implémenté un compteur interne d'adresse, pour déterminer la prochaine adresse à rafraichir sans la préciser sur le bus d'adresse. Le déclenchement du rafraichissement se faisait toujours par une commande externe, provenant du contrôleur de DRAM ou du processeur. Cette commande faisait passer le CAS à 0 avant le RAS. Cette méthode de rafraichissement se nomme '''rafraichissement interne'''. [[File:Rafraichissement sur CAS précoce.png|centre|vignette|upright=2|Rafraichissement sur CAS précoce.]] On peut noter qu'il est possible de déclencher plusieurs rafraichissements à la suite en laissant le signal CAS dans le même état. Ce genre de choses pouvait avoir lieu après une lecture : on pouvait profiter du fait que le CAS soit mis à zéro par la lecture ou l'écriture pour ensuite effectuer des rafraichissements en touchant au signal RAS. Dans cette situation, la donnée lue était maintenue sur la sortie durant les différents rafraichissements. [[File:Rafraichissements multiples sur CAS précoce.png|centre|vignette|upright=2|Rafraichissements multiples sur CAS précoce.]] ==Les mémoires SDRAM== Dans les années 90, les mémoires asynchrones ont laissé la place aux '''mémoires SDRAM''', qui sont synchronisées avec le bus par une horloge. L'utilisation d'une horloge a comme avantage des temps d'accès fixes : le processeur sait qu'un accès mémoire prendra un nombre déterminé de cycles d'horloge. Avec les mémoires asynchrones, le processeur ne pouvait pas prévoir quand la donnée serait disponible et ne faisait rien tant que la mémoire n'avait pas répondu : il exécutait ce qu'on appelle des ''wait states'' en attendant que la mémoire ait fini. Les mémoires SDRAM sont standardisées par un organisme international, le JEDEC. Le standard SDRAM impose des spécifications électriques bien précise pour les barrettes de mémoire et le bus mémoire, décrit le protocole utilisé pour communiquer avec les barrettes de mémoire, et bien d'autres choses encore. Les SDRAM ont été déclinées en versions de performances différentes, décrites dans le tableau ci-dessous : {| class="wikitable" ! Nom standard ! Fréquence ! Bande passante |- | PC66 | 66 mhz | 528 Mio/s |- | PC66 | 100 mhz | 800 Mio/s |- | PC66 | 133 mhz | 1064 Mio/s |- | PC66 | 150 mhz | 1200 Mio/s |} ===Les banques internes aux chips mémoires SDRAM=== L'intérieur d'une mémoire SDRAM contient plusieurs '''banques''', aussi appelées des banc mémoire. Concrètement, une banque est... une mémoire. Ou plutôt, une sorte de mini-mémoire miniature. Chaque banque a son propre tampon de ligne, ses propres multiplexeurs de colonne et ses propres décodeurs. C'est comme si une SDRAM regroupait plusieurs mémoires séparées dans un même circuit intégré. [[File:Arrangement vertical.jpg|centre|vignette|upright=2.5|Mémoire multi-banques.]] Un point important est que chaque banque a son propre tampon de ligne. Il est donc possible d'ouvrir plusieurs lignes en même temps, chacune dans une banque différente. Par exemple, on peut ouvrir une ligne dans la banque numéro 1, et une autre ligne dans la banque numéro 2. Et c'est une source d'optimisations très intéressantes. La première optimisation est liée au rafraichissement mémoire. Au lieu de rafraichir chaque adresse une par une, il est possible de rafraichir des banques indépendantes en même temps, ce qui divise le temps de rafraichissement par le nombre de banques. C'est ce que je sous-entendais plus haut quand je disais que le temps de rafraichissement n'est pas égal au temps de balayage sur les SDRAM, alors que c'est le cas sur les DRAM FPM et EDO. De plus, et sans rentrer dans les détails, cela permet de faire plusieurs accès mémoire en même temps, dans des banques différentes. La possibilité est limitée, mais elle existe et elle améliore grandement la performance de la SDRAM. Mais nous en reparlerons dans un chapitre ultérieur, car cette histoire d'accès simultanés a plus sa place dans le chapitre sur le parallélisme mémoire. Pour le moment, nous ne pouvons pas expliquer pourquoi ni comment un processeur pourrait émettre plusieurs accès mémoire simultanés. Un processeur est censé travailler une instruction à la fois, à ce stade du cours, il ne peut pas en faire plusieurs en même temps. Mais nous allons cependant mentionner un cas où cette possibilité est intéressante : une mémoire SDRAM partagée entre un processeur et une carte graphique. Les deux accèdent à des données séparées, qui sont dans des banques différentes. On suppose que la carte graphique accède plus fréquemment à la mémoire que le processeur. Le contrôleur mémoire reçoit les accès mémoire du CPU et du GPU et il tente d'intercaler des accès CPU entre deux accès de la carte graphique. Vu qu'ils tombent dans des banques différentes, un accès CPU et un accès carte graphique peuvent se faire presque en même temps. La seule contrainte est que si on lance plusieurs accès mémoire simultanés, ceux-ci ne peuvent pas utiliser le bus de données en même temps. {|class="wikitable" |+ Pipelining basique sur les SDRAM |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 |- ! Banque Numéro 1 | || || || || || || || || || || |- ! Banque Numéro 2 | || colspan="3" bgcolor="#A0FFFF" | Accès CPU || || || colspan="3" bgcolor="#A0FFFF" | Accès CPU || || |- ! Banque Numéro 3 | || || || || || || || || || || |- ! Banque Numéro 4 | || || || colspan="3" bgcolor="#FFA0FF" | Accès carte graphique || || colspan="3" bgcolor="#FFA0FF" | Accès carte graphique || |} ===Le mode rafale des SDRAM=== Un point important est que les SDRAM reprennent les optimisations des mémoires FPM et EDO. Elles utilisent aussi un tampon de ligne, avec la possibilité de lire plusieurs colonnes à la suite sans avoir à préciser l'adresse de ligne à chaque fois. Mais surtout, elles gèrent nativement le mode rafale. les paramètres qui ont trait au mode rafale sont configurables. Il est possible de configurer la SDRAM pour activer les accès sans rafale, ou les désactiver. Il y a aussi la possibilité de configurer le nombre d'octets consécutifs à lire ou écrire en mode rafale. On peut ainsi accéder à 1, 2, 4, ou 8 octets en une seule fois, alors que les EDO ne permettaient que des accès à 4 octets consécutifs. Enfin, on peut décider s'il faut faire un accès en mode linéaire ou entrelacé. La configuration de la SDRAM est mémorisée dans un registre de 10 bits, le '''registre de mode'''. Il faisait 10 bits sur les mémoires SDRAM, mais a été étendu à 13 bits sur la DDR2. Voici les 10 bits originels de ce registre : {|class="wikitable" |+ Signification des bits du registre de mode des SDRAM |- ! Bit n°9 | Type d'accès : en rafale ou normal |- ! Bit n°8 et 7 | Doivent valoir 00, sont réservés pour une utilisation ultérieur dans de futurs standards. |- ! Bit n°6, 5, et 4 | Latence CAS (voir plus bas) |- ! Bit n°3 | Type de rafale : linéaire ou entrelacée |- ! Bit n°2, 3, et 0 | Longueur de la rafale : indique le nombre d'octets à lire/écrire lors d'une rafale. |} ===L'interface d'une mémoire SDRAM=== Le bus de commandes d'une SDRAM contient au moins 18 fils, dont celui pour le signal d'horloge. L'interface d'une SDRAM contient tous les bits présents sur une mémoire DRAM classique : une entrée RAS, une entrée CAS, une entrée R/W, et un bus d'adresse. A cela, il faut cependant ajouter une entrée ''Chip Select'' (CS), qui permet d'activer/désactiver la mémoire SDRAM. Je rappelle que le bit CS a été introduit sur les mémoires SDRAM, il n'était pas présent sur les mémoires FPM/EDO. Deux autres bits de commande sont vraiment spécifiques des mémoires SDRAM. Il s'agit des bits CKE et DQM. Le '''bit CKE''' est l'abréviation de ''Clock Enable'', qui qui trahit sa fonction. Lorsque ce signal est à 0, le chip de SDRAM voit son signal d'horloge gelè. S'il est à 0, le contrôleur de DRAM peut envoyer ce qu'il veut sur le bus de commande ou d'adresse, la SDRAM ne réagira pas du tout, il ne se passera rien. Le '''bit DQM''' est une sorte de bit ''Output Enable'', avec une nuance importante. Le terme DQM est l'abréviation de ''Data Mask'', ce qui trahit encore une fois sa fonction. Il y a un bit DQM pour chaque octet du bus de données. Une SDRAM ayant un bus de 64 bits, cela fait 8 bits DQM par mémoire SDRAM. Lorsque le bit DQM est à 1, l'octet en question n'est simplement pas lu ou écrit, le bus de donnée est déconnecté pour cet octet. Le bus d'adresse est particulier, car il tient compte de la présence de ''banques mémoires''. Le bus d'adresse est découpé en deux portions : une portion pour sélectionner la banque, une autre pour sélectionner l'adresse à l'intérieur d'une banque. L'interface de la SDRAM fait ainsi la différence entre une '''adresse de banque''' et une '''adresse intra-banque'''. L'adresse de banque est codée sur quelques bits, généralement deux ou trois suivant la SDRAM. Le reste de l'adresse est codé sur 11 bits sur les SDRAM, mais cela a augmenté avec les DDR 1, 2, 3, 4, 5. Le bus de données d'une SDRAM fait 4, 8, ou 16 bits. Je précise bien qu'il s'agit là des puces de SDRAM, les barrettes de SDRAM combinent plusieurs puces SDRAM avec un arrangement horizontal, qui peut combiner plusieurs puces de 8 bits pour alimenter un bus de données de 64 bits. La taille des puces utilisées souvent indiquée sur la barrette de RAM, avec une mention x4, x8 ou x16. Les puces de SDRAM les plus courantes ont une interface de 8 bits pour les données. Les SDRAM de 4 bits sont surtout utilisées pour les serveurs, c'est lié au support de l'ECC. les puces x16 sont moins utilisées car elles ont généralement moins de banques que les autres. ===Les commandes SDRAM=== Le bus de commande permet d'envoyer des commandes à la mémoire, chaque commande étant précisée par une combinaison précise des bits CS, RAS, CAS, R/W, et autres. Les commandes en question sont des demandes de lecture, d'écriture, de préchargement et autres. Elles sont codées par une valeur bien précise qui est envoyée sur les 18 fils du bus de commande. Ces commandes sont nommées READ, READA, WRITE, WRITEA, PRECHARGE, ACT, ... Les plus importantes sont les commandes PRECHARGE, ACT et READ/WRITE. La commande ACT sélectionne une ligne : elle met le bit RAS à zéro et présente une adresse de ligne. Les commandes READ et WRITE sélectionnent une colonne, et déclenchent respectivement une lecture ou une écriture. Elles précisent une adresse de colonne, mettent le bit CAS à 0 et le bit RAS à 1, et précise la valeur du bit R/W. Les commandes READ et WRITE ne peuvent se faire qu'une fois que la banque a été activée par une commande ACT. Il est possible d'envoyer plusieurs commandes READ ou WRITE successives à des colonnes différentes, ce qui permet d'implémenter les optimisations des mémoires FPM. La commande PRECHARGE ferme la ligne courante et prépare l'ouverture de la suivante. Elle précharge les lignes de bit de la RAM, d'où son nom. Il est nécessaire d'en envoyer une avant d'envoyer une commande ACT. Notons que la commande PRECHARGE agit sur une banque, dont l'adresse est indiquée dans la commande PRECHARGE. Il existe une commande PRECHARGE ALL, qui agit sur toutes les banques de la SDRAM à la fois. Les commandes READA et WRITEA fusionnent une commande READ/WRITE avec une commande PRECHARGE. Elles permettent d'éviter d'avoir à envoyer une commande PRECHARGE pour fermer la ligne courante. Au lieu d'envoyer une commande READ ou WRITE, puis une commande PRECHARGE pour fermer la ligne, on envoie une seule commande READA/WRITEA. Il s'agit d'une petite optimisation, qui permet de réduire le nombre de commandes envoyées sur le bus. Les commandes sont encodées comme indiquées dans ce tableau. Une commande est tout simplement encodée en précisant une adresse si nécessaire, et une combinaison des bits CS, RAS, CAS et R/W. La seule subtilité est que le bit numéro 10 du bus d'adresse sert à commander les opérations de PRECHARGE, y compris celles implicites dans les opérations READA et WRITEA. {| class="wikitable" style="text-align:center" ! Bit CS || Bit RAS || Bit CAS || Bit WE || Bits de sélection de banque (2 bits) || Bit du bus d'adresse A10 || Reste du bus d'adresse || Nom de la commande : Description |- | 1 | colspan="6" | X | Absence de commandes. |- | 0 || 1 || 1 || 1 || colspan="3" | X || No Operation : Pas d'opération |- | 0 || 1 || 1 || 0 || colspan="3" | X || Burst Terminante : Stoppe un accès en rafale (en cours). |- | 0 || 1 || 0 || 1 || Adresse de la banque || 0 || Adresse de la colonne || READ : lit une donnée depuis la ligne active. |- | 0 || 1 || 0 || 1 || Adresse de la banque || 1 || Adresse de la colonne || READA : lit une donnée depuis la ligne active, puis ferme la ligne. |- | 0 || 1 || 0 || 0 || Adresse de la banque || 0 || Adresse de la colonne || WRITE : écrit une donnée dans la ligne active. |- | 0 || 1 || 0 || 0 || Adresse de la banque || 1 || Adresse de la colonne || WRITEA : écrit une donnée dans la ligne active, puis ferme la ligne. |- | 0 || 0 || 1 || 1 || Adresse de la banque || colspan="2" | Adresse de la ligne || ACT : charge une ligne dans le tampon de ligne. |- | 0 || 0 || 1 || 0 || Adresse de la banque || 0 || X || PRECHARGE : précharge le tampon de ligne dans la banque voulue. |- | 0 || 0 || 1 || 0 || Adresse de la X || 1 || X || PRECHARGE ALL : précharge le tampon de ligne dans toutes les banques. |- | 0 || 0 || 0 || 1 || colspan="3" | X || Auto refresh : Demande de rafraichissement, gérée par la SDRAM. |- | 0 || 0 || 0 || 0 || 00 || colspan="2" | Nouveau contenu du registre de mode || LOAD MODE REGISTER : configure le registre de mode. |} Les commandes ACT se font à partir de l'état de repos, l'état où toutes les banques sont préchargées. Par contre, les commandes MODE REGISTER SET et AUTO REFRESH ne peuvent se faire que si toutes les banques sont désactivées. Le fonctionnement simplifié d'une SDRAM peut se résumer dans ce diagramme : [[File:Fonctionnement simplifié d'une SDRAM.jpg|centre|vignette|upright=2|Fonctionnement simplifié d'une SDRAM.]] ===Les délais mémoires=== Les mémoires SDRAM n'étant pas infiniment rapides, il y a toujours un certain délais à respecter entre deux commandes. Par exemple, quand on envoie une commande ACT pour activer une ligne, on ne peut pas envoyer une commande READ/WRITE au cycle suivant. La plupart des SDRAM ne sont pas assez rapides pour ça. Il faut respecter un délai de quelques cycles, qui dépend de la mémoire. Et il n'y a pas que ce délai entre une commande ACT et la commande suivante. Une SDRAM doit gérer d'autres temps d'attente, appelés des '''délais mémoires''', ou encore des ''timings'' mémoire. Les délais mémoire le plus importants sont résumés ci-dessous : {|class="wikitable" |- !Timing!!Description |- | colspan="2" | |- ! colspan="2" | Délais primaires |- ||tRP|| Temps entre une commande PRECHARGE et une commande ACT |- ||tRCD|| Temps entre une commande ACT et une commande READ/WRITE. |- ||tCL|| Temps entre une commande READ et l'envoi de la donnée lue sur le bus de données. |- ||tDQSS|| Temps entre une commande WRITE et l'écriture de la donnée. |- ||tCAS-to-CAS|| Temps minimum entre deux commandes READ. |- ! colspan="2" | Délais secondaires |- ||tWTR|| Temps entre une lecture et une écriture consécutives. |- ||tRAS || Temps entre une commande ACT et une commande PRECHARGE. |} La façon de mesurer ces délais varie : sur les mémoires FPM et EDO, on les mesure en unités de temps (secondes, millisecondes, micro-secondes, etc.), tandis qu'on les mesure en cycles d'horloge sur les mémoires SDRAM. Les délais/timings mémoire ne sont pas les mêmes suivant la barrette de mémoire que vous achetez. Certaines mémoires sont ainsi conçues pour avoir des timings assez bas et sont donc plus rapides, et surtout : beaucoup plus chères que les autres. Le gain en performances dépend beaucoup du processeur utilisé et est assez minime comparé au prix de ces barrettes. Les délais mémoires d'une barrette sont mémorisés dans le ''Serial Presence Detect'' de la barrette et sont lus par le BIOS au démarrage de l'ordinateur, et sont ensuite utilisés pour configurer le contrôleur de DRAM. ===Les commandes anticipées=== Les SDRAM sont parfois capables de démarrer une commande avant que la précédente soit terminée. Concrètement, pendant que la commande précédente envoie sa dernière donnée sur le bus de données, on peut envoyer la commande suivante avec quelques cycles d'avance. L'exemple ci-dessous devrait être assez clair : on envoie une seconde commande au neuvième cycle, alors qu'une rafale est en cours. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | || bgcolor="#A0FFFF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || || || bgcolor="#FFA0FF" | READ (2) || || || || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 | bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 | |} Il s'agit d'une forme très limitée de pipeline, tellement limitée qu'on peut légitimement douter que c'est un vrai pipeline. Dans ce qui suit, j'ai décidé d'appeler cette possibilité sous le terme de '''commandes SDRAM anticipées'''. La possibilité est très limitée, car il faut tenir compte des délais mémoire. Les commandes SDRAM anticipées sont possibles car les SDRAM sont formées en entourant une RAM asynchrone de registres, exactement comme les SRAM synchrones. Il est possible d'écrire dans les registres de données/commandes, pendant qu'un autre accès mémoire accède au cœur asynchrone. Les délais mémoire sont conçus pour éviter qu'une commande accède au cœur asynchrone en même temps que la suivante ou la précédente, idem pour l'usage des registres. C'est pour cela que les délais mémoire sont assez différents entre écritures et lectures, d'ailleurs. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Enregistrement de la commande dans le registre d'adresse/commande | bgcolor="#A0FFFF" | READ (1) || || || || bgcolor="#FFA0FF" | READ (2) || || || || || |- ! Accès au cœur asynchrone | || bgcolor="#A0FFFF" | READ (1) || bgcolor="#A0FFFF" | READ (1) || bgcolor="#A0FFFF" | READ (1) || || bgcolor="#FFA0FF" | READ (2) || bgcolor="#FFA0FF" | READ (2) || bgcolor="#FFA0FF" | READ (2) || || || |- ! Lecture/écriture du registre de données | || || || || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 | bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 | |} ==Les mémoires DDR== Les mémoires SDRAM récentes sont des mémoires de type ''dual data rate'', ce qui fait qu'elles portent le nom de mémoires DDR. Pour rappel, les mémoires ''dual data rate'' ont un plan mémoire deux fois plus large que le bus mémoire, avec un bus mémoire allant à une fréquence double. Par double, on veut dire que les transferts sur le bus mémoire ont lieu sur les fronts montants et descendants de l'horloge. Il y a donc deux transferts de données sur le bus pour chaque cycle d'horloge, ce qui permet de doubler le débit sans toucher à la fréquence du plan mémoire lui-même. Les mémoires DDR sont standardisées par un organisme international, le JEDEC, et ont été déclinées en plusieurs générations : DDR1, DDR2, DDR3, et DDR4. La différence entre ces modèles sont très nombreuses, mais les plus évidentes sont la fréquence de la mémoire et du bus mémoire. D'autres différences mineures existent entre les SDRAM et les mémoires DDR. Par exemple, la tension d'alimentation des mémoires DDR est plus faible que pour les SDRAM. ET elle a diminué dans le temps, d'une génération de DDR à l'autre. Avec les mémoires DDR2,la tension d'alimentation est passée de 2,5/2,6 Volts à 1,8 Volts. Avec les mémoires DDR3, la tension d'alimentation est notamment passée à 1,5 Volts. ===Les performances des mémoires DDR=== Les mémoires SDRAM ont évolué dans le temps, mais leur temps d'accès/fréquence n'a pas beaucoup changé. Il valait environ 10 nanosecondes pour les SDRAM, approximativement 5 ns pour la DDR-400, il a peu évolué pendant la génération DDR et DDR3, avant d'augmenter pendant les générations DDR4 et de stagner à nouveau pour la génération DDR5. L'usage du DDR, puis du QDR, visait à augmenter les performances malgré la stagnation des temps d'accès. En conséquence, la fréquence du bus a augmenté plus vite que celle des puces mémoire pour compenser. {|class="wikitable" |- ! Année ! Type de mémoire ! Fréquence de la mémoire (haut de gamme) ! Fréquence du bus ! Coefficient multiplicateur entre les deux fréquences |- | 1998 | DDR 1 | 100 - 200 MHz | 200 - 400 MHz | 2 |- | 2003 | DDR 2 | 100 - 266 MHz | 400 - 1066 MHz | 4 |- | 2007 | DDR 3 | 100 - 266 MHz | 800 - 2133 MHz | 8 |- | 2014 | DDR 4 | 200 - 400 MHz | 1600 - 3200 MHz | 8 |- | 2020 | DDR 5 | 200 - 450 MHz | 3200 - 7200 MHz | 8 à 16 |} Une conséquence est que la latence CAS, exprimée en nombre de cycles, a augmenté avec le temps. Si vous comparez des mémoires DDR2 avec une DDR4, par exemple, vous allez voir que la latence CAS est plus élevée pour la DDR4. Mais c'est parce que la latence est exprimée en nombre de cycles d'horloge, et que la fréquence a augmentée. En comparant les temps d'accès exprimés en secondes, on voit une amélioration. ===Les commandes des mémoires DDR=== Les commandes des mémoires DDR sont globalement les mêmes que celles des mémoires SDRAM, vues plus haut. Les modifications entre SDRAM, DDR1, DDR2, DDR3, DDR4, et DDR5 sont assez mineures. Les seules différences sont l'addition de bits pour la transmission des adresses, des bits en plus pour la sélection des banques, etc. En clair, une simple augmentation quantitative. Le registre de mode a été un peu modifié. Il est passé de 10 bits pour les SDRAM et DDR1, à 13 bits sur la DDR 2 et les suivantes. Les DDR ont aussi ajouté le support de plusieurs registres de mode, qui sont sélectionnés en réutilisant l'adresse de banque. Dans une commande LOAD MODE REGISTER, l'adresse de banque indique quel registre de mode il faut altérer. Avant la DDR4, les modifications des commandes sont mineures. La DDR2 supprime la commande ''Burst Terminate'', la DDR3 et la DDR4 utilisent le bit A12 pour préciser s'il faut faire une rafale complète, ou une rafale de moitié moins de données. Une optimisation des DDR2 et 3 est celle des '''CAS postés'''. L'idée est que le contrôleur de DRAM peut envoyer une commande ACT et une commande READ/WRITE sans se soucier des ''timings'' nécessaires entre les deux. En théorie, les deux commandes doivent être séparées par quelques cycles, sur une SDRAM ou une DDR1. Mais avec la DDR2, le contrôleur de DRAM peut envoyer les deux l'une après l'autre, au cycle suivant. C'est la mémoire qui mettra en attente la commande READ/WRITE pour respecter les ''timings'' mémoire. Cela complexifie le fonctionnement interne de la DDR, mais simplifie grandement le travail du contrôleur de DRAM. Mais avec la DDR4, les choses changent, notamment au niveau de la commande ACT. Avec l'augmentation de la capacité des barrettes mémoires, la taille des adresses est devenue trop importante. Pour éviter de rajouter des bits d'adresses, les concepteurs du standard DDR4 ont décidé de ruser. Lors d'une commande ACT, les bits RAS, CAS et WE sont utilisés comme bits d'adresse, alors qu'ils ont leur signification normale pour les autres commandes. Pour éviter toute confusion, un nouveau bit ACT est ajouté pour indiquer la présence d'une commande ACT : il est à 1 pour une commande ACT, 0 pour les autres commandes. {| class="wikitable" style="text-align:center" |+ Commandes d'une mémoire DDR4, seule la commande colorée change par rapport aux SDRAM ! Bit CS || style="background: #CCFFCC" | Bit ACT || Bit RAS || Bit CAS || Bit WE || Bits de sélection de banque (4 bits) || Bit du bas d'adresse A10 || Reste du bus d'adresse || Nom de la commande : Description |- | 1 | colspan="6" | X | Absence de commandes. |- | 0 || style="background: #CCFFCC" | 0 || 1 || 1 || 1 || colspan="3" | X || No Operation : Pas d'opération |- | 0 || style="background: #CCFFCC" | 0 || 1 || 1 || 0 || colspan="3" | X || Burst Terminante : Arrêt d'un accès en rafale en cours. |- | 0 || style="background: #CCFFCC" | 0 || 1 || 0 || 1 || Adresse de la banque || 0 || Adresse de la colonne || READ : lire une donnée depuis la ligne active. |- | 0 || style="background: #CCFFCC" | 0 || 1 || 0 || 1 || Adresse de la banque || 1 || Adresse de la colonne || READA : lire une donnée depuis la ligne active, avec rafraichissement automatique de la ligne. |- | 0 || style="background: #CCFFCC" | 0 || 1 || 0 || 0 || Adresse de la banque || 0 || Adresse de la colonne || WRITE : écrire une donnée depuis la ligne active. |- | 0 || style="background: #CCFFCC" | 0 || 1 || 0 || 0 || Adresse de la banque || 1 || Adresse de la colonne || WRITEA : écrire une donnée depuis la ligne active, avec rafraichissement automatique de la ligne. |- style="background: #CCFFCC" | 0 || style="background: #CCFFCC" | 1 || colspan="3" | Adresse de la ligne (bits de poids forts) || Adresse de la banque || colspan="2" | Adresse de la ligne (bits de poids faible) || ACT : charge une ligne dans le tampon de ligne. |- | 0 || style="background: #CCFFCC" | 0 || 0 || 1 || 0 || Adresse de la banque || 0 || X || PRECHARGE : précharge le tampon de ligne dans la banque voulue. |- | 0 || style="background: #CCFFCC" | 0 || 0 || 1 || 0 || Adresse de la X || 1 || X || PRECHARGE ALL : précharge le tampon de ligne' dans toutes les banques. |- | 0 || style="background: #CCFFCC" | 0 || 0 || 0 || 1 || colspan="3" | X || Auto refresh : Demande de rafraichissement, gérée par la SDRAM. |- | 0 || style="background: #CCFFCC" | 0 || 0 || 0 || 0 || Numéro de registre de mode || colspan="2" | Nouveau contenu du registre de mode || LOAD MODE REGISTER : configure le registre de mode. |} ==Les VRAM des cartes vidéo== Les cartes graphiques ont des besoins légèrement différents des DRAM des processeurs, ce qui fait qu'il existe des mémoires DRAM qui leur sont dédiées. Elles sont appelés des '''''Graphics RAM''''' (GRAM). La plupart incorporent des fonctionnalités utiles uniquement pour les mémoires vidéos, comme des fonctionnalités de masquage (appliquer un masque aux données lue ou à écrire), ou le remplissage d'un bloc de mémoire avec une donnée unique. Les anciennes cartes graphiques et les anciennes consoles utilisaient de la DRAM normale, faute de mieux. La première GRAM utilisée était la NEC μPD481850, qui a été utilisée sur la console de jeu PlayStation, à partir de son modèle SCPH-5000. D'autres modèles de GRAM ont rapidement suivi. Les anciennes consoles de jeu, mais aussi des cartes graphiquesn utilisaient des GRAM spécifiques. ===Les mémoires vidéo double port=== Sur les premières consoles de jeu et les premières cartes graphiques, le ''framebuffer'' était mémorisé dans une mémoire vidéo spécialisée appelée une '''mémoire vidéo double port'''. Le premier port était connecté au processeur ou à la carte graphique, alors que le second port était connecté à un écran CRT. Aussi, nous appellerons ces deux port le ''port CPU/GPU'' et l'autre sera appelé le ''port CRT''. Le premier port était utilisé pour enregistrer l'image à calculer et faire les calculs, alors que le second port était utilisé pour envoyer à l'écran l'image à afficher. Le port CPU/GPU est tout ce qu'il y a de plus normal : on peut lire ou écrire des données, en précisant l'adresse mémoire de la donnée, rien de compliqué. Le port CRT est assez original : il permet d'envoyer un paquet de données bit par bit. De telles mémoires étaient des mémoires à tampon de ligne, dont le support de mémorisation était organisé en ligne et colonnes. Une ligne à l'intérieur de la mémoire correspond à une ligne de pixel à l'écran, ce qui se marie bien avec le fait que les anciens écrans CRT affichaient les images ligne par ligne. L'envoi d'une ligne à l'écran se fait bit par bit, sur un câble assez simple comme un câble VGA ou autre. Le second port permettait de faire cela automatiquement, en permettant de lire une ligne bit par bit, les bits étant envoyés l'un après l'autre automatiquement. Pour cela, les mémoires vidéo double port incorporaient un tampon de ligne spécialisé pour le port lié à l'écran. Ce tampon de ligne n'était autre qu'un registre à décalage, contrairement au tampon de ligne normal. Lors de l'accès au second port, la carte graphique fournissait un numéro de ligne et la ligne était chargée dans le tampon de ligne associé à l'écran. La carte graphique envoyait un signal d'horloge de même fréquence que l'écran, qui commandait le tampon de ligne à décalage : un bit sortait à chaque cycle d'écran et les bits étaient envoyé dans le bon ordre. ===Les mémoires SGRAM et GDDR=== De nos jours, les cartes graphiques n'utilisent plus de mémoires double port, mais des mémoires simple port. Les mémoires graphiques actuelles sont des SDRAM modifiées pour fonctionner en tant que ''Graphic RAM''. Les plus connues sont les '''mémoires GDDR''', pour ''graphics double data rate'', utilisées presque exclusivement sur les cartes graphiques. Il en existe plusieurs types pendant que j'écris ce tutoriel : GDDR, GDDR2, GDDR3, GDDR4, et GDDR5. Mais attention, il y a des différences avec les DDR normales. Par exemple, les GDDR ont une fréquence plus élevée que les DDR normales, avec des temps d'accès plus élevés (sauf pour le tCAS). De plus, elles sont capables de laisser ouvertes deux lignes en même temps. Par contre, ce sont des mémoires simple port. ==Les mémoires SLDRAM, RDRAM et associées== Les mémoires précédentes sont généralement associées à des bus larges. Les mémoires SDRAM et DDR modernes ont des bus de données de 64 bits de large, avec des d'adresse et de commande de largeur similaire. Le nombre de fils du bus mémoire dépasse facilement la centaine de fils, avec autant de broches sur les barrettes de mémoire. Largeur de ces bus pose de problèmes problèmes électriques, dont la résolution n'est pas triviale. En conséquence, la fréquence du bus mémoire est généralement moins performantes comparé à ce qu'on aurait avec un bus moins large. Mais d'autres mémoires DRAM ont exploré une solution alternative : avoir un bus peu large mais de haute fréquence, sur lequel on envoie les commandes/données en plusieurs fois. Elles sont regroupées sous le nom de '''DRAM à commutation par paquets'''. Elles utilisent des bus spéciaux, où les commandes/adresses/données sont transmises par paquets, par trames, en plusieurs fois. En théorie, ce qu'on a dit sur le codage des trames dans le chapitre sur le bus devrait s'appliquer à de telles mémoires. En pratique, les protocoles de transmission sur le bus mémoire sont simplifiés, pour gérer le fonctionnement à haute fréquence. Le processeur envoie des paquets de commandes, les mémoires répondent avec des paquets de données ou des accusés de réception. Les mémoires à commutation par paquets sont peu nombreuses. Les plus connues sont les mémoires conçues par la société Rambus, à savoir la ''RDRAM'' (''Rambus DRAM'') et ses deux successeurs ''XDR RAM'' et ''XDR RAM 2''. La ''Synchronous-link DRAM'' (''SLDRAM'') est un format concurrent conçu par un consortium de plusieurs concepteurs de mémoire. ===La SLDRAM (''Synchronous-link DRAM'')=== Les '''mémoires SLDRAM''' avaient un bus de données de 64 bits allant à 200-400 Hz, avec technologie DDR, ce qui était dans la norme de l'époque pour la fréquence (début des années 2000). Elle utilisait un bus de commande de 11 bits, qui était utilisé pour transmettre des commandes de 40 bits, transmises en quatre cycles d'horloge consécutifs (en réalité, quatre fronts d'horloge donc deux cycles en DDR). Le bus de données était de 18 bits, mais les transferts de donnée se faisaient par paquets de 4 à 8 octets (32-65 bits). Pour résumer, données et commandes sont chacunes transmises en plusieurs cycles consécutifs, sur un bus de commande/données plus court que les données/commandes elle-mêmes. Là où les SDRAM sélectionnent la bonne barrette grâce à des signaux de commande dédiés, ce n'est pas le cas avec la SLDRAM. A la place, chaque barrette de mémoire reçoit un identifiant, un numéro codé sur 7-8 bits. Les commandes de lecture/écriture précisent l'identifiant dans la commande. Toutes les barrettes reçoivent la commande, elles vérifient si l'identifiant de la commande est le leur, et elles la prennent en compte seulement si c'est le cas. Voici le format d'une commande SLDRAM. Elle contient l'adresse, qui regroupe le numéro de banque, le numéro de ligne et le numéro de colonne. On trouve aussi un code commande qui indique s'il faut faire une lecture ou une écriture, et qui configure l'accès mémoire. Il configure notamment le mode rafale, en indiquant s'il faut lire/écrire 4 ou 8 octets. Enfin, il indique s'il faut fermer la ligne accédée une fois l'accès terminé, ou s'il faut la laisser ouverte. Le code commande peut aussi préciser que la commande est un rafraichissement ou non, effectuer des opérations de configuration, etc. L'identifiant de barrette mémoire est envoyé en premier, histoire que les barrettes sachent précocement si l'accès les concerne ou non. {|class="wikitable" style="text-align:center" |+SLDRAM Read, write or row op request packet ! FLAG || CA9 || CA8 || CA7 || CA6 || CA5 || CA4 || CA3 || CA2 || CA1 || CA0 |- ! 1 | colspan=9 bgcolor=#ffcccc| Identifiant de barrette mémoire|| bgcolor=#ccffcc| Code de commande |- ! 0 | colspan=5 bgcolor=#ccffcc| Code de commande ||colspan=3 bgcolor=#ff88ff| Banque||colspan=2 bgcolor=#ffffcc| Ligne |- ! 0 | colspan=9 bgcolor=#ffffcc| Ligne || 0 |- ! 0 | 0 || 0 || 0 ||colspan=7 bgcolor=#ccffff| Colonne |} ===Les mémoires Rambus=== Les mémoires conçues par la société Rambus regroupent la '''RDRAM''' (''Rambus DRAM'') et ses deux successeurs '''XDR RAM''' et '''XDR RAM 2'''. Les toutes premières étaient les '''mémoires RDRAM''', où le bus permettait de transmettre soit des commandes (adresse inclue), soit des données, avec un multiplexage total. Le processeur envoie un paquet contenant commandes et adresse à la mémoire, qui répond avec un paquet d'acquittement. Lors d'une lecture, le paquet d'acquittement contient la donnée lue. Lors d'une écriture, le paquet d'acquittement est réduit au strict minimum. Le bus de commandes est réduit au strict minimum, à savoir l'horloge et quelques bits absolument essentiels, les bits RW est transmis dans un paquet et n'ont pas de ligne dédiée, pareil pour le bit OE. Toutes les barrettes de mémoire doivent vérifier toutes les transmissions et déterminer si elles sont concernées en analysant l'adresse transmise dans la trame. Elles ont été utilisées dans des PC ou d'anciennes consoles de jeu. Par exemple, la Nintendo 64 incorporait 4 mébioctets de mémoire RDRAM en tant que mémoire principale. La RDRAM de la Nintendo 64 était cadencée à 500 MHz, utilisait un bus de 9 bits, et avait un débit binaire maximal théorique de 500 MB/s. La Playstation 2 contenait quant à elle 32 mébioctets de RDRAM en ''dual-channel'', pour un débit binaire de 3.2 Gibioctets par seconde. Les processeurs Pentium 3 pouvaient être associés à de la RDRAM sur certaines mères. Les Pentium 4 étaient eux aussi associés à la de RDRAM, mais les cartes mères ne géraient que ce genre de mémoire. La Playstation 3 contenait quant à elle de la XDR RAM. ==Les eDRAM : des DRAM adaptées aux ''chiplets''== Les '''mémoires eDRAM''', pour ''embedded DRAM'', sont des mémoires RAM qui sont destinées à être intégrée au processeur. Pour comparer, les DRAM normales sont placées sur des barrettes de RAM ou soudées à la carte mère. Dans la quasi-totalité des cas, l'eDRAM est utilisée pour implémenter une mémoire cache, elle ne sert pas de mémoire principale (cache L4, le plus proche de la mémoire sur ces puces). De ce fait, elles sont conçues pour être très rapides, avoir une grande bande passante, au détriment de leur capacité mémoire. Pour être plus précis, l'eDRAM est une puce de DRAM conçue pour être intégrée dans un ''chiplet'', , à savoir des circuits imprimés qui regroupent plusieurs puces électroniques distinctes, regroupées sur le même PCB. Typiquement, un processeur de type ''chiplet'' avec de l'eDRAM comprend deux puces séparées : une pour le processeur, une autre pour une puce de communication avec la RAM. Avec la mémoire eDRAM, les deux puces sont complétées par une troisième puce spécialisée qui incorpore l'eDRAM. Elle a été utilisée sur quelques processeurs, mais aussi dans des consoles de jeu vidéo, pour la carte graphique des consoles suivantes : la PlayStation 2, la PlayStation Portable, la GameCube, la Wii, la Wii U, et la XBOX 360. Sur ces consoles, la RAM de la carte graphique était intégrée avec le processeur graphique dans le même circuit. La fameuse mémoire vidéo et le GPU n'étaient qu'une seule et même puce électronique, un seul circuit intégré. Ce n'est pas le cas sur une carte graphique moderne : regardez votre carte graphique avec attention et vous verrez que le GPU est une puce carrée située sous les ventilateurs, alors que les puces mémoires sont situées juste autour et soudées sur le PCB de la carte. Les processeurs Intel Core de microarchitecture Broadwell disposaient d'un cache L4 de 128 mébioctets, intégralement implémenté avec de la mémoire eDRAM. Quelques processeurs de la microarchitecture précédente (Haswell), disposaient aussi de ce cache. Le cache L4 eDRAM était implémenté sur un chiplet à part, à savoir que le processeur était composé de trois puces séparées : une pour le processeur, une autre pour la gestion des entrées-sorties, et une autre pour le cache L4. La puce pour le cache L4 était appelée ''Crystal Well''. La puce ''Crystal Well'' était une puce gravée en 22nm, ce qui était une finesse de gravure plus élevée que celle des processeurs associés. ''Crystal Well'' était très optimisé pour l'époque. Par exemple, elle disposait de bus séparées pour la lecture et l'écriture, chose qu'on retrouve fréquemment sur les SRAM mais qui est absent sur les mémoires DRAM actuelles. Pour le reste, elle ressemblait beaucoup aux mémoires DDR de l'époque (système de ''double data rate'', entres autres), mais elle allait à une fréquence plus élevée que les DRAM de l'époque et avait un débit bien plus élevé, pour une consommation moindre. ''Crystal Well'' consommait entre 1 à 5 watts (1 watt en veille, 5 à pleine utilisation), pour un débit binaire de 102 GB/s et fonctionnait à 3.2 GHz. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les mémoires SRAM synchrones | prevText=Les mémoires SRAM synchrones | next=Contrôleur mémoire externe | nextText=Le contrôleur mémoire externe }}{{autocat}} </noinclude> 7bjx3qcpiv5j6bw2wuys2brtq1qmnl3 764678 764677 2026-04-23T18:52:17Z Mewtow 31375 /* Les commandes anticipées */ 764678 wikitext text/x-wiki Après avoir vu les mémoires statiques (SRAM), il est temps de passer aux mémoires RAM dynamiques, aussi appelées mémoires DRAM. Pour rappel, les DRAM dynamiques ont pour défaut que leurss données s'effacent après un certain temps, en quelques millièmes ou centièmes de secondes . En conséquence, il faut réécrire chaque bit de la mémoire régulièrement pour éviter qu'il ne s'efface. On dit qu'on doit effectuer régulièrement un '''rafraîchissement mémoire'''. Et celui-ci rend les DRAM très différentes des SRAM. Les DRAM des PC ont beaucoup évolués dans le temps. Les toutes premières mémoires DRAM étaient des mémoires asynchrones, mais elles ont été remplacées par des modèles synchrones. Les modèles asynchrones ont été très nombreux. Après l'apparition des premières DRAM, les mémoires ''Fast Page Mode'' sont apparues, suivies par les mémoires ''Extended Data Out'', puis les EDO en mode rafale. Elles ont été utilisées jusque dans la moitié des années 90, pour ensuite être supplantées par les mémoires SDRAM. Les mémoires DDR actuelles sont des améliorations des mémoires SDRAM actuelles. Le fait est que les DRAM sont des mémoires électroniques comme les autres, qui se présentent sous la forme de circuits intégrés, à savoir que ce sont des petits boitiers noirs avec des broches. Il est possible de souder ces boitiers sur une cartre mère, et c'est ce qui est fait sur nombre d'ordinateurs portables. Mais il est aussi possible de regrouper plusieurs boitiers sur une barrette de RAM séparée. Dans ce qui suit, nous les appellerons des '''chips mémoire''', ou encore, des puces mémoires. [[File:Canyon CN-WF514 - EtronTech EM638325TS-6-4022.jpg|centre|vignette|upright=2|Exemple de chip mémoire.]] Dans ce qui suit, nous allons étudier ces chips de DRAM, avant de voir comment ils sont regroupés sur une barrette de RAM. Puis, nous allons voire chaque type de mémoire, FPM, EDO, SDRAM, DDR, ... ; un par un. ==L'interface des DRAM et le contrôleur mémoire== L'interface d'une mémoire DRAM est plus compliquée que l'interface d'une SRAM basique. Et c'est suffisant pour qu'on ait besoin d'un intermédiaire pour faire la conversion entre processeur et DRAM. Les DRAM modernes ne sont pas connectées directement au processeur, mais le sont par l'intermédiaire d'un '''contrôleur mémoire externe'''. Il ne faut pas le confondre avec le contrôleur mémoire interne, placé dans la mémoire RAM, et qui contient notamment le décodeur. Les deux sont totalement différents, bien que leur nom soit similaire. Pour éviter toute confusion, j'utiliserais le terme de '''contrôleur de DRAM''', plus parlant. ===Le bus d'adresse des DRAM est multiplexé=== Un point important pour le contrôleur de DRAM est de transformer les adresses mémoires fournies par le processeur, en adresses utilisables par la DRAM. Car les DRAM ont une interface assez spécifique. Les DRAM ont ce qui s'appelle un '''bus d'adresse multiplexé'''. Avec de tels bus, l'adresse est envoyée en deux fois. Les bits de poids fort sont envoyés avant les bits de poids faible. On peut ainsi envoyer une adresse de 32 bits sur un bus d'adresse de 16 bits, par exemple. Le bus d'adresse contient alors environ moitié moins de fils que la normale. Pour rappel, l'avantage de cette méthode est qu'elle permet de limiter le nombre de fils du bus d'adresse, ce qui très intéressant sur les mémoires de grande capacité. Les mémoires DRAM étant utilisées comme mémoire principale d'un ordinateur, elles devaient avoir une grande capacité. Cependant, avoir un petit nombre de broches sur les barrettes de mémoire est clairement important, ce qui impose d'utiliser des stratagèmes. Envoyer l'adresse en deux fois répond parfaitement à ce problème : cela permet d'avoir des adresses larges et donc des mémoires de forte capacité, avec une performance acceptable et peu de fils sur le bus d'adresse. Les bus multiplexés se marient bien avec le fait que les DRAM sont des mémoires à adressage par coïncidence ou à tampon de ligne. Sur ces mémoires, l'adresse est découpée en deux : une adresse haute pour sélectionner la ligne, et une adresse basse qui sélectionne la colonne. L'adresse est envoyée en deux fois : la ligne, puis la colonne. Pour savoir si une donnée envoyée sur le bus d'adresse est une adresse de ligne ou de colonne, le bus de commande de ces mémoires contenait deux fils bien particuliers : les RAS et le CAS. Pour simplifier, le signal RAS permettait de sélectionner une ligne, et le signal CAS permettait de sélectionner une colonne. [[File:Signaux RAS et CAS.png|centre|vignette|upright=2|Signaux RAS et CAS.]] Si on a deux bits RAS et CAS, c'est parce que la mémoire prend en compte les signaux RAS et CAS quand ils passent de 1 à 0. C'est à ce moment là que la ligne ou colonne dont l'adresse est sur le bus sera sélectionnée. Tant que des signaux sont à zéro, la ligne ou colonne reste sélectionnée : on peut changer l'adresse sur le bus, cela ne désélectionnera pas la ligne ou la colonne et la valeur présente lors du front descendant est conservée. [[File:L'intérieur d'une FPM.png|centre|vignette|upright=2|L'intérieur d'une FPM.]] ===Le rafraichissement mémoire=== La spécificité des DRAM est qu'elles doivent être rafraichies régulièrement, sans quoi leurs cellules perdent leurs données. Le rafraichissement est basiquement une lecture camouflée. Elle lit les cellules mémoires, mais n'envoie pas le contenu lu sur le bus de données. Rappelons que la lecture sur une DRAM est destructive, à savoir qu'elle vide la cellule mémoire, mais que le système d'amplification de lecture régénère le contenu de la cellule automatiquement. La cellule est donc rafraichie automatiquement lors d'une lecture. La quasi-totalité des DRAM supporte des commandes de rafraichissement, séparées des lectures et écritures classiques. Une commande de rafraichissement ordonne de rafraichir une adresse, voire une ligne complète. Les commandes de rafraichissement sont générées par le contrôleur de DRAM, dans la grosse majorité des cas. Il est aussi possible que ce soit le processeur qui les génère, mais c'est beaucoup plus rare. Il est aussi possible d'envoyer des commandes de rafraichissement vides, qui ne précisent ni adresse ni numéro de ligne. Pour les gérer, la mémoire contient un compteur, qui pointe sur la prochaine ligne à rafraichir, qui est incrémenté à chaque commande de rafraichissement. Une commande de rafraichissement indique à la mémoire d'utiliser l'adresse dans ce compteur pour savoir quelle adresse/ligne rafraichir. [[File:Rafraichissement mémoire automatique.png|centre|vignette|upright=2|Rafraichissement mémoire automatique.]] Il existe des mémoires qui sont des intermédiaires entre les mémoires SRAM et DRAM. Il s'agit des '''mémoires pseudo-statiques''', qui sont techniquement des mémoires DRAM, utilisant des transistors et des condensateurs, mais qui gèrent leur rafraichissement mémoire toutes seules. Le rafraichissement mémoire est alors totalement automatique, ni le processeur, ni le contrôleur de DRAM ne devant s'en charger. Le rafraichissement est purement le fait des circuits de la mémoire RAM et devient une simple opération de maintenance interne, gérée par la RAM elle-même. L'envoi des commandes de rafraichissement peuvent se faire de deux manières : soit on les envoie toutes en même temps, soit on les disperse le plus possible. Le premier cas est un '''rafraichissement en rafale''', le second un '''rafraichissement étalé'''. Le rafraichissement en rafale n'est pas utilisé dans les PC, car il bloque la mémoire pendant un temps assez long. Mais les anciennes consoles de jeu gagnaient parfois à utiliser eu rafraichissement en rafale. En effet, la mémoire était souvent effacée entre l'affichage de deux images, pour éviter certains problèmes dont on ne parlera pas ici. Le rafraichissement de la mémoire était effectué à ce moment là : l'effacement rafraichissait la mémoire. Le temps mis pour rafraichir la mémoire est le temps mis pour parcourir toute la mémoire. Il s'agit du temps de balayage vu dans le chapitre sur les performances d'un ordinateur. Pour les mémoires FPM et EDO, il est défini en divisant la capacité de la mémoire par son débit binaire. C'est le temps nécessaire pour lire ou réécrire tout le contenu de la mémoire. Sur les SDRAM, les choses sont un peu différentes, pour une raison qu'on expliquera plus bas. ===Le contrôleur de DRAM=== Le contrôleur de DRAM gère le bus mémoire et tout ce qui est envoyé dessus. Il envoie des commandes aux barrettes de mémoire, commandes qui peuvent être des lectures, des écritures, ou des demandes de rafraichissement, parfois d'autres commandes. La mémoire répond à ces commandes par l'action adéquate : lire la donnée et la placer sur le bus de données pour une commande de lecture, par exemple. Le rôle du contrôleur de DRAM varie grandement suivant le contrôleur en question, ainsi que selon le type de DRAM. Les anciens contrôleurs de DRAM étaient des composants séparés du processeur, du ''chipset'' ou du reste de la carte mère. Par exemple, les contrôleur de DRAM Intel 8202, Intel 8203 et Intel 8207 étaient vendus dans des boitiers DIP et étaient soudés sur la carte mère. Par la suite, ils ont été intégré au ''chipset'' de la carte mère pendant les décennies 90-2000. Après les années 2000, ils ont été intégrés dans les processeurs. Il est possible de connecter plusieurs barrettes sur le même bus mémoire, ou alors celles-ci sont connectées au contrôleur de DRAM avec un bus par barrette/boitier. C'est ce qui permet de placer plusieurs barrettes de mémoire sur la même carte mère : toutes les barrettes sont connectées au contrôleur de DRAM d'une manière ou d'une autre. ==Les rangées : l'arrangement horizontal et vertical== Il est rare d'utiliser un chip mémoire seul, car ceux-ci n'ont pas une capacité suffisante. Pour donner quelques chiffres, à l'heure où j'écris ces lignes, la norme pour un ordinateur est d'avoir entre 8 et 64 gibioctets de RAM. Mais les chips mémoire font entre 1 et 4 gibioctets, rarement plus. La raison est que les ordinateurs combinent ensemble plusieurs chips mémoire pour additionner leurs capacités. La concaténation de plusieurs chips mémoire peut se faire de deux manières différentes, appelées l'arrangement horizontal et l'arrangement vertical. Les deux additionnent la capacité des chips mémoire, mais se distinguent sur un point : ce qui arrive respectivement au bus de données, et au nombre d'adresses. Intuitivement, on se dit que doubler la capacité mémoire implique de doubler le nombre d'adresses mémoire. C'est effectivement ce qui se passe avec l'arrangement vertical. Mais avec l'arrangement horizontal, le nombre d'adresse ne varie pas. Voyons cela en détail, et commençons par le cas le plus simple, celui de l'arrangement vertical seul. ===L'arrangement vertical : cumuler des adresses mémoire=== Introduisons l'arrangement vertical par un exemple. Imaginez que je souhaite obtenir de 4 mébioctets de RAM, en combinant 4 chips mémoires de 1 mébioctet chacun. L'idée est que le premier mébioctet est placé dans le premier chip mémoire, le second mébioctet dans le second chip, etc. Des adresses consécutives se trouvent ainsi dans le même chip mémoire, sauf pour quelques adresses où on passe d'un chip à l'autre. Avec cette organisation, le bus de donnée fait un octet, et les chips mémoire ont aussi un bus de données d'un octet. Je peux alors combiner les capacités de plusieurs chips mémoire, sans toucher au bus de données. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses avec arrangement vertical.]] Pour sélectionner le chip mémoire adéquat, il faut que chaque chip mémoire dispose d'une entrée ''Chip Select'', qui permet de l'activer ou de le désactiver. L'idée est que selon l'adresse demandée, on active le chip mémoire associé à cette adresse. Les signaux ''Chip Select'' sont générés par le contrôleur de DRAM, à partir de l'adresse. On dit qu'il y a un '''décodage d'adresse'''. Avec cet arrangement, les bits de poids fort de l'adresse sont utilisées pour sélectionner la banque adéquate, et le reste de l'adresse est envoyé sur le bus d'adresse. Par exemple, avec 4 chips mémoire, les deux bits de poids fort de l'adresse sont utilisés pour sélectionner le chip mémoire adéquat. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Adresse de banque !! Adresse dans la banque |- | Quelques bits de poids fort || Reste de l'adresse |} ===L'arrangement horizontal : élargir le bus de données=== L'arrangement horizontal permet lui aussi d'additionner les capacités mémoire de plusieurs chips mémoire. Cependant, il les combine d'une autre manière. Le nombre d'adresses mémoire n'est pas changé en utilisant plusieurs chips, mais le bus de données est élargi. Le mieux pour comprendre l'idée est de partir d'un exemple, et nous allons prendre celui d'une mémoire SDRAM. Les ordinateurs actuels ont un bus de données de 64 bits (on met de côté le cas du double ou triple canal). Cependant, il n'existe pas de chip mémoire avec un bus aussi large. Les puces de SDRAM/DDR ont un bus de 4, 8 ou 16 bits, ce sont les tailles les plus courantes. L'arrangement horizontal résout ce problème en combinant plusieurs chips mémoire de manière à ce que leurs bus de données s'"additionnent", se concatènent. Par exemple, on peut regrouper 8 chips mémoires de 8 bits, obtenir un bus mémoire de 64 bits. Il est aussi possible d'obtenir ces 64 bits avec des puces de 16 chips mémoire de 4 bits, ou 4 chips mémoire de 16 bits. [[File:Arrangement horizontal SDRAM - un Rank.png|centre|vignette|upright=2|Arrangement horizontal SDRAM.]] Avec cette organisation, on accède à tous les bancs en parallèle à chaque accès, avec la même adresse. Vu que les chips mémoires contiennent tous une partie de la donnée demandée, ils doivent tous être activés en même temps. Pour cela, l'adresse à lire est envoyée à tous les chips mémoire d'un même ''rank'', idem pour les signaux de commande. Un ensemble de N chips reliés de cette manière forme une '''rangée''' (le terme anglais est ''rank''). [[File:Arrangement horizontal.jpg|centre|vignette|upright=2|Arrangement horizontal.]] ===L'arrangement horizontal et vertical combinés=== Nous venons de voir l'arrangement vertical et horizontal, pour ce qui est des barrettes de mémoire. Précisons que ce qui vient d'être dit marche aussi bien pour les barrettes de RAM que pour la mémoire soudée sur la carte mère. Du moment qu'on combine plusieurs chips mémoire ensemble, ces concepts restent valides. Et il en est de même pour la suite, encore que ce soit nettement moins fréquent avec de la mémoire soudée. Il est possible de combiner à la fois l'arrangement vertical et l'arrangement horizontal. Rien de plus simple : il suffit d'utiliser un arrangement vertical entre plusieurs rangées, chacun composée de plusieurs chips mémoire. C'est surtout utilisé sur les barrettes de mémoire SDRAM, qui contiennent 1, 2, 4 ou 8 rangées, rarement plus. Par exemple, une SDRAM peut combiner 16 chips de DRAM de 8 bits chacun, dans deux rangées de 64 bits chacun, chaque rangée regroupant 8 chips. [[File:SDRAM avec 4 ranks.png|centre|vignette|upright=2|SDRAM avec 4 ranks]] Le choix entre la première ou la seconde rangée se fait en configurant les bits ''Chip Select'' de chaque rangée. Il faut noter que les bits de ''Chip Select'' sont générés par le contrôleur mémoire, et envoyés sur le bus de commande. [[File:Td6bfig3.png|centre|vignette|upright=2|Comparaison entre arrangement horizontal (à gauche) et arrangement vertical (à droite).]] Le contrôleur de DRAM peut adresser un certain nombre de rangées, dispersés sur plusieurs barrettes. La limite maximale dépend du contrôleur de DRAM, elle est souvent proche de 8 ou 16 rangées. Si on combine plusieurs barrettes de mémoire, il est possible de dépasser cette limite. Par exemple, prenez un contrôleur de DRAM supportant maximum 8 rangées. Avec 4 barrettes contenant 4 rangées chacune, la limite est dépassée. : Il faut noter que tout ce qui vient d'être dit vaut aussi pour les mémoires ROM et SRAM. Mais en pratique, les arrangements verticaux et horizontaux sont surtout utilisés sur les mémoires DRAM. Il faut dire que de tels arrangements servent à augmenter la capacité mémoire, ce qui colle plus avec des DRAM que des SRAM ou des ROM. ==Les barrettes de mémoire DRAM== [[File:Ram-module.svg|droite|vignette|upright=0.5|Barrette de mémoire RAM.]] Il est possible de souder plusieurs boitiers de DRAM sur une cartre mère, et c'est ce qui est fait sur nombre d'ordinateurs portables. Mais dans les PC fixes, les puces de DRAM sont regroupées sur des ''barrettes mémoires'''. Les barrettes de mémoire se fixent à la carte mère sur un connecteur standardisé, appelé '''slot mémoire'''. Le dessin ci-contre montre une barrette de mémoire, celui-ci ci-dessous est celui d'un ''slot'' mémoire. [[File:Dual channel slots.jpg|centre|vignette|Slots mémoires.]] Sur le schéma de droite, on remarque facilement les boitiers de DRAM, rectangulaires, de couleur sombre. Chaque barrette combine ces puces de manière à additionner leurs capacités : on peut ainsi créer une mémoire de 8 gibioctets à partir de 8 puces d'un gibioctet, par exemple. Ils sont soudés sur un PCB en plastique vert sur lequel sont gravés des connexions métalliques. Les trucs dorés situés en bas des barrettes de mémoire sont des broches qui connectent la barrette au bus mémoire. Les barrettes des mémoires FPM/EDO/SDRAM/DDR n'ont pas le même nombre de broches, pour des raisons de compatibilité. {|class="wikitable" |- !Type de barrette !Type de mémoire !Nombre de broches |- | rowspan="2" | SIMM | rowspan="2" | FPM/EDO |30 |- |72 |- | rowspan="4" | DIMM |SDRAM |168 |- |DDR |184 |- |DDR2 |214, 240 ou 244, suivant la barrette ou la carte mère. |- |DDR3 |204 ou 240, suivant la barrette ou la carte mère. |} ===Le format des barrettes de mémoire=== Certaines barrettes ont des puces mémoire d'un seul côté alors que d'autres en ont sur les deux faces. Cela permet de distinguer les barrettes SIMM et DIMM. * Les '''barrettes SIMM''' ont des puces sur une seule face de la barrette. Elles étaient utilisées pour les mémoires FPM et EDO-RAM. * Les '''barrettes DIMM''' ont des puces sur les deux côtés. Elles sont utilisées sur les SDRAM et les DDR. {| class="flexible" |+ '''Barrette SIMM''' |- |[[File:SIMM FPM 4 MB - C0448721-7229.jpg|vignette|SIMM recto.]] |[[File:SIMM FPM 4 MB - C0448721-7230.jpg|vignette|SIMM verso.]] |} : Les modules DIMM tendent à avoir plus de rangées que les modules SIMM, mais ce n'est pas systématique. Il est souvent dit que les barrettes DIMM ont deux rangées, contre seulement 1 pour les SIMM, mais les contre-exemples sont nombreux. Les barrettes '''SO-DIMM''', pour ordinateurs portables, sont différentes des barrettes DIMM normales des DDR/SDRAM. La raison est qu'il n'y a pas beaucoup de place à l'intérieur d'un PC portable, ce qui demande de diminuer la taille des barrettes. {| |- |[[File:Desktop DDR Memory Comparison.svg|centre|vignette|upright=1.5|Barrettes de DDR pour PC de bureau.]] |[[File:Laptop SODIMM DDR Memory Comparison V2.svg|centre|vignette|upright=1.5|Barrettes de DDR pour PC portables.]] |} Les barrettes de Rambus ont parfois été appelées des '''barrettes RB-DIMM''', mais ce sont en réalité des DIMM comme les autres. La différence principale est que la position des broches n'était pas la même que celle des formats DIMM normaux, sans compter que le connecteur Rambus n'était pas compatible avec les connecteurs SDR/DDR normaux. ===Les interconnexions à l'intérieur d'une barrette de mémoire=== Les boîtiers de DRAM noirs sont connectés au bus par le biais de connexions métalliques. Toutes les puces d'une même rangée sont connectées aux bus d'adresse et de commande. Et les chips d'une même rangée reçoivent exactement les mêmes signaux de commande/adresses, ce qui permet d'envoyer la même adresse/commande à toutes les puces en même temps. La manière dont ces puces sont reliées au bus de commande dépend selon la mémoire utilisée. Les DDR1 et 2 utilisent ce qu'on appelle une '''topologie en T''', illustrée ci-dessous. On voit que le bus de commande forme une sorte d'arbre, dont chaque extrémité est connectée à une puce. La topologie en T permet d'égaliser le délai de transmission des commandes à travers le bus : la commande transmise arrive en même temps sur toutes les puces. Mais elle a de nombreux défauts, à savoir : elle fonctionne mal à haute fréquence, elle est difficile à router en raisons des embranchements. [[File:Organisation des bus de commandes sur les DDR1-2.png|centre|vignette|upright=3.0|Organisation des bus de commandes sur les DDR1-2, nommée topologie en T.]] En comparaison, les DDR3 utilisent une '''topologie ''fly-by''''', où les puces sont connectées en série sur le bus de commande/adresse. La topologie ''fly-by'' n'a pas les problèmes de la topologie en T : elle est simple à router et fonctionne très bien à haute fréquence. [[File:Organisation des bus de commandes sur les DDR3 - topologie fly-by.png|centre|vignette|upright=3.0|Organisation des bus de commandes sur les DDR3 - topologie ''fly-by'']] ===Les barrettes tamponnées (à registres)=== Certaines barrettes intègrent un registre tampon, qui fait l'interface entre le bus et la barrette de RAM. L'utilité est d'améliorer la transmission du signal sur le bus mémoire. Sans ce registre, les signaux électriques doivent traverser le bus, puis traverser les connexions à l'intérieur de la barrette, jusqu'aux puces de mémoire. Avec un registre tampon, les signaux traversent le bus, sont mémorisés dans le registre et c'est tout. Le registre envoie les commandes/données jusqu'aux puces mémoire, mais le signal a été régénéré par le registre. Le signal transmis est donc de meilleure qualité, ce qui augmente la fiabilité du système mémoire. Le défaut est que la présence de ce registre fait que les barrettes ont un temps de latence est plus important que celui des barrettes normales, du fait de la latence du registre. Les barrettes de ce genre sont appelées des '''barrettes RIMM'''. Il en existe deux types : * Avec les '''barrettes RDIMM''', le registre fait l'interface pour le bus d'adresse et le bus de commande, mais pas pour le bus de données. * Avec les '''barrettes LRDIMM''' (''Load Reduced DIMMs''), le registre fait tampon pour tous les bus, y compris le bus de données. [[File:Organisation des bus de commandes sur les RDIMM.png|centre|vignette|upright=3.0|Organisation des bus de commandes sur les RDIMM.]] ===Le ''Serial Presence Detect''=== [[File:SPD SDRAM.jpg|vignette|Localisation du SPD sur une barrette de SDRAM.]] Toute barrette de mémoire assez récente contient une petite mémoire ROM qui stocke les différentes informations sur la mémoire : délais mémoire, capacité, marque, etc. Cette mémoire s'appelle le '''''Serial Presence Detect''''', aussi communément appelé le SPD. Ce SPD contient non seulement les timings de la mémoire RAM, mais aussi diverses informations, comme le numéro de série de la barrette, sa marque, et diverses informations. Le SPD est lu au démarrage de l'ordinateur par le BIOS, afin de pourvoir configurer ce qu'il faut. Le contenu de ce fameux SPD est standardisé par un organisme nommé le JEDEC, qui s'est chargé de standardiser le contenu de cette mémoire, ainsi que les fréquences, timings, tensions et autres paramètres des mémoires SDRAM et DDR. Pour les curieux, vous pouvez lire la page wikipédia sur le SPD, qui donne son contenu pour les mémoires SDR et DDR : [https://en.wikipedia.org/wiki/Serial_presence_detect Serial Presence Detect]. ==Les mémoires asynchrones à RAS/CAS : FPM et EDO-RAM== Avant l'invention des mémoires SDRAM et DDR, il exista un grand nombre de mémoires différentes, les plus connues étant les mémoires fast page mode et EDO-RAM. Ces mémoires n'étaient pas synchronisées par un signal d'horloge, c'était des '''mémoires asynchrones'''. Quand ces mémoires ont été créées, cela ne posait aucun problème : les accès mémoire étaient très rapides et le processeur était certain que la mémoire aurait déjà fini sa lecture ou écriture au cycle suivant. Les mémoires asynchrones les plus connues étaient les '''mémoires FPM''' et '''mémoires EDO'''. Pour ce qui est de leur interface, il faut signaler qu'elles n'ont pas d'entrée ''Chip Select'' ou d'entrée ''Output Enable''. Les signaux RAS et CAS remplacent en quelque sorte ces deux signaux. Le bit RAS fait office de ''Chip Select'', le bit CAS fait office d'''Output Enable''. ===Les mémoires FPM=== Les '''mémoires FPM (''Fast Page Mode'')''' possédaient une petite amélioration, qui rendait l'adressage plus simple. Avec elles, il n'y a pas besoin de préciser deux fois la ligne si celle-ci ne changeait pas lors de deux accès consécutifs : on pouvait garder la ligne sélectionnée durant plusieurs accès. Par contre, il faut quand même préciser les adresses de colonnes à chaque changement d'adresse. Il existe une petite différence entre les mémoire ''Page Mode'' et les mémoires ''Fast-Page Mode'' proprement dit. Sur les premières, le signal CAS est censé passer à 0 avant qu'on fournisse l'adresse de colonne. Avec les ''Fast-Page Mode'', l'adresse de colonne pouvait être fournie avant que l'on configure le signal CAS. Cela faisait gagner un petit peu de temps, en réduisant quelque peu le temps d'accès total. [[File:Sélection d'une ligne sur une mémoire FPM ou EDO.png|centre|vignette|upright=2|Sélection d'une ligne sur une mémoire FPM ou EDO.]] Avec les '''mémoires en mode quartet''', il est possible de lire quatre octets consécutifs sans avoir à préciser la ligne ou la colonne à chaque accès. On envoie l'adresse de ligne et l'adresse de colonne pour le premier accès, mais les accès suivants sont fait automatiquement. La seule contrainte est que l'on doit générer un front descendant sur le signal CAS pour passer à l'adresse suivante. Vous aurez noté la ressemblance avec le mode rafale vu il y a quelques chapitres, mais il y a une différence notable : le mode rafale vrai n'aurait pas besoin qu'on précise quand passer à l'adresse suivante avec le signal CAS. [[File:Mode quartet.png|centre|vignette|upright=3|Mode quartet.]] Les '''mémoires FPM à colonne statique''' se passent même du signal CAS. Le changement de l'adresse de colonne est détecté automatiquement par la mémoire et suffit pour passer à la colonne suivante. Dans ces conditions, un délai supplémentaire a fait son apparition : le temps minimum entre deux sélections de deux colonnes différentes, appelé tCAS-to-CAS. [[File:Accès en colonne statique.jpg|centre|vignette|upright=2.5|Accès en colonne statique.]] ===Les mémoires EDO-RAM=== L''''EDO-RAM''' a été inventée quelques années après la mémoire FPM. Elle a été déclinée en deux versions : la EDO simple, et la EDO en rafale. L'EDO simple ajoutait une entrée ''Ouput Enable'' à une mémoire FPM. Pour rappel, l'entrée ''Ouput Enable'' permet de connecter/déconnecter la DRAM du bus de données. S'il est mis à 0, les lectures et écritures sont empêchées. Pour ajouter cette entrée, il a fallu rajouter un registre sur la sortie de donnée, celle qui sert pour les lectures. Et l'ajout de ce registre a introduit une capacité dite de ''pipelining'', sur le même modèle que pour les mémoires SRAM synchrones. La donnée pouvait être maintenue sur le bus de données durant un certain temps, même après la remontée du signal CAS. Le registre de sortie maintenait la donnée lu tant que le signal RAS restait à 0, et tant qu'un nouveau signal CAS n'a pas été envoyé. Faire remonter le signal CAS à 1 n'invalidait pas la donnée en sortie. La conséquence est qu'on pouvait démarrer une nouvelle lecture alors que la donnée de l'accès précédent était encore présent sur le bus de données. Le pipeline obtenu avait deux étages : un où on présentait l'adresse et sélectionnait la colonne, un autre où la donnée était lue depuis le registre de sortie. Les mémoires EDO étaient donc plus rapides. [[File:EDO RAM.png|centre|vignette|upright=3|EDO RAM]] Cependant, cela marchait surtout pour les lectures, pas pour les écritures. Une écriture ne démarre que quand la lecture ou écriture précédente est totalement terminée. De même, on ne peut pas démarrer un nouvel accès mémoire tant qu'une écriture est en cours. ===Les mémoires EDO-RAM avec mode rafale=== Les '''EDO en rafale''' effectuent les accès à 4 octets consécutifs automatiquement : il suffit d'adresser le premier octet à lire. Les 4 octets étaient envoyés sur le bus les uns après les autres, au rythme d'un par cycle d’horloge : ce genre d'accès mémoire s'appelle un accès en rafale. [[File:Accès en rafale.png|centre|vignette|upright=2|Accès en rafale sur une DRAM EDO.]] Implémenter cette technique nécessite d'ajouter un compteur, capable de faire passer d'une colonne à une autre quand on lui demande, et quelques circuits annexes pour commander le tout. [[File:Modifications du contrôleur mémoire liées aux accès en rafale.png|centre|vignette|upright=2|Modifications du contrôleur de DRAM liées aux accès en rafale.]] ===Le rafraichissement mémoire=== Les mémoires FPM et EDO doivent être rafraichies régulièrement. Au début, le rafraichissement se faisait ligne par ligne. Le rafraichissement avait lieu quand le RAS passait à l'état haut, alors que le CAS restait à l'état bas. Le processeur, ou le contrôleur mémoire, sélectionnait la ligne à rafraichir en fournissant son adresse mémoire. D'où le nom de '''rafraichissement par adresse''' qui est donné à cette méthode de commande du rafraichissement mémoire. Divers processeurs implémentaient de quoi faciliter le rafraichissement par adresse. Par exemple, le Zilog Z80 contenait un compteur de ligne, un registre qui contenait le numéro de la prochaine ligne à rafraichir. Il était incrémenté à chaque rafraichissement mémoire, automatiquement, par le processeur lui-même. Un ''timer'' interne permettait de savoir quand rafraichir la mémoire : quand ce ''timer'' atteignait 0, une commande de rafraichissement était envoyée à la mémoire, et le ''timer'' était ''reset''. [[File:Rafraichissement mémoire manuel.png|centre|vignette|upright=2|Rafraichissement mémoire manuel.]] Par la suite, certaines mémoires ont implémenté un compteur interne d'adresse, pour déterminer la prochaine adresse à rafraichir sans la préciser sur le bus d'adresse. Le déclenchement du rafraichissement se faisait toujours par une commande externe, provenant du contrôleur de DRAM ou du processeur. Cette commande faisait passer le CAS à 0 avant le RAS. Cette méthode de rafraichissement se nomme '''rafraichissement interne'''. [[File:Rafraichissement sur CAS précoce.png|centre|vignette|upright=2|Rafraichissement sur CAS précoce.]] On peut noter qu'il est possible de déclencher plusieurs rafraichissements à la suite en laissant le signal CAS dans le même état. Ce genre de choses pouvait avoir lieu après une lecture : on pouvait profiter du fait que le CAS soit mis à zéro par la lecture ou l'écriture pour ensuite effectuer des rafraichissements en touchant au signal RAS. Dans cette situation, la donnée lue était maintenue sur la sortie durant les différents rafraichissements. [[File:Rafraichissements multiples sur CAS précoce.png|centre|vignette|upright=2|Rafraichissements multiples sur CAS précoce.]] ==Les mémoires SDRAM== Dans les années 90, les mémoires asynchrones ont laissé la place aux '''mémoires SDRAM''', qui sont synchronisées avec le bus par une horloge. L'utilisation d'une horloge a comme avantage des temps d'accès fixes : le processeur sait qu'un accès mémoire prendra un nombre déterminé de cycles d'horloge. Avec les mémoires asynchrones, le processeur ne pouvait pas prévoir quand la donnée serait disponible et ne faisait rien tant que la mémoire n'avait pas répondu : il exécutait ce qu'on appelle des ''wait states'' en attendant que la mémoire ait fini. Les mémoires SDRAM sont standardisées par un organisme international, le JEDEC. Le standard SDRAM impose des spécifications électriques bien précise pour les barrettes de mémoire et le bus mémoire, décrit le protocole utilisé pour communiquer avec les barrettes de mémoire, et bien d'autres choses encore. Les SDRAM ont été déclinées en versions de performances différentes, décrites dans le tableau ci-dessous : {| class="wikitable" ! Nom standard ! Fréquence ! Bande passante |- | PC66 | 66 mhz | 528 Mio/s |- | PC66 | 100 mhz | 800 Mio/s |- | PC66 | 133 mhz | 1064 Mio/s |- | PC66 | 150 mhz | 1200 Mio/s |} ===Les banques internes aux chips mémoires SDRAM=== L'intérieur d'une mémoire SDRAM contient plusieurs '''banques''', aussi appelées des banc mémoire. Concrètement, une banque est... une mémoire. Ou plutôt, une sorte de mini-mémoire miniature. Chaque banque a son propre tampon de ligne, ses propres multiplexeurs de colonne et ses propres décodeurs. C'est comme si une SDRAM regroupait plusieurs mémoires séparées dans un même circuit intégré. [[File:Arrangement vertical.jpg|centre|vignette|upright=2.5|Mémoire multi-banques.]] Un point important est que chaque banque a son propre tampon de ligne. Il est donc possible d'ouvrir plusieurs lignes en même temps, chacune dans une banque différente. Par exemple, on peut ouvrir une ligne dans la banque numéro 1, et une autre ligne dans la banque numéro 2. Et c'est une source d'optimisations très intéressantes. La première optimisation est liée au rafraichissement mémoire. Au lieu de rafraichir chaque adresse une par une, il est possible de rafraichir des banques indépendantes en même temps, ce qui divise le temps de rafraichissement par le nombre de banques. C'est ce que je sous-entendais plus haut quand je disais que le temps de rafraichissement n'est pas égal au temps de balayage sur les SDRAM, alors que c'est le cas sur les DRAM FPM et EDO. De plus, et sans rentrer dans les détails, cela permet de faire plusieurs accès mémoire en même temps, dans des banques différentes. La possibilité est limitée, mais elle existe et elle améliore grandement la performance de la SDRAM. Mais nous en reparlerons dans un chapitre ultérieur, car cette histoire d'accès simultanés a plus sa place dans le chapitre sur le parallélisme mémoire. Pour le moment, nous ne pouvons pas expliquer pourquoi ni comment un processeur pourrait émettre plusieurs accès mémoire simultanés. Un processeur est censé travailler une instruction à la fois, à ce stade du cours, il ne peut pas en faire plusieurs en même temps. Mais nous allons cependant mentionner un cas où cette possibilité est intéressante : une mémoire SDRAM partagée entre un processeur et une carte graphique. Les deux accèdent à des données séparées, qui sont dans des banques différentes. On suppose que la carte graphique accède plus fréquemment à la mémoire que le processeur. Le contrôleur mémoire reçoit les accès mémoire du CPU et du GPU et il tente d'intercaler des accès CPU entre deux accès de la carte graphique. Vu qu'ils tombent dans des banques différentes, un accès CPU et un accès carte graphique peuvent se faire presque en même temps. La seule contrainte est que si on lance plusieurs accès mémoire simultanés, ceux-ci ne peuvent pas utiliser le bus de données en même temps. {|class="wikitable" |+ Pipelining basique sur les SDRAM |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 |- ! Banque Numéro 1 | || || || || || || || || || || |- ! Banque Numéro 2 | || colspan="3" bgcolor="#A0FFFF" | Accès CPU || || || colspan="3" bgcolor="#A0FFFF" | Accès CPU || || |- ! Banque Numéro 3 | || || || || || || || || || || |- ! Banque Numéro 4 | || || || colspan="3" bgcolor="#FFA0FF" | Accès carte graphique || || colspan="3" bgcolor="#FFA0FF" | Accès carte graphique || |} ===Le mode rafale des SDRAM=== Un point important est que les SDRAM reprennent les optimisations des mémoires FPM et EDO. Elles utilisent aussi un tampon de ligne, avec la possibilité de lire plusieurs colonnes à la suite sans avoir à préciser l'adresse de ligne à chaque fois. Mais surtout, elles gèrent nativement le mode rafale. les paramètres qui ont trait au mode rafale sont configurables. Il est possible de configurer la SDRAM pour activer les accès sans rafale, ou les désactiver. Il y a aussi la possibilité de configurer le nombre d'octets consécutifs à lire ou écrire en mode rafale. On peut ainsi accéder à 1, 2, 4, ou 8 octets en une seule fois, alors que les EDO ne permettaient que des accès à 4 octets consécutifs. Enfin, on peut décider s'il faut faire un accès en mode linéaire ou entrelacé. La configuration de la SDRAM est mémorisée dans un registre de 10 bits, le '''registre de mode'''. Il faisait 10 bits sur les mémoires SDRAM, mais a été étendu à 13 bits sur la DDR2. Voici les 10 bits originels de ce registre : {|class="wikitable" |+ Signification des bits du registre de mode des SDRAM |- ! Bit n°9 | Type d'accès : en rafale ou normal |- ! Bit n°8 et 7 | Doivent valoir 00, sont réservés pour une utilisation ultérieur dans de futurs standards. |- ! Bit n°6, 5, et 4 | Latence CAS (voir plus bas) |- ! Bit n°3 | Type de rafale : linéaire ou entrelacée |- ! Bit n°2, 3, et 0 | Longueur de la rafale : indique le nombre d'octets à lire/écrire lors d'une rafale. |} ===L'interface d'une mémoire SDRAM=== Le bus de commandes d'une SDRAM contient au moins 18 fils, dont celui pour le signal d'horloge. L'interface d'une SDRAM contient tous les bits présents sur une mémoire DRAM classique : une entrée RAS, une entrée CAS, une entrée R/W, et un bus d'adresse. A cela, il faut cependant ajouter une entrée ''Chip Select'' (CS), qui permet d'activer/désactiver la mémoire SDRAM. Je rappelle que le bit CS a été introduit sur les mémoires SDRAM, il n'était pas présent sur les mémoires FPM/EDO. Deux autres bits de commande sont vraiment spécifiques des mémoires SDRAM. Il s'agit des bits CKE et DQM. Le '''bit CKE''' est l'abréviation de ''Clock Enable'', qui qui trahit sa fonction. Lorsque ce signal est à 0, le chip de SDRAM voit son signal d'horloge gelè. S'il est à 0, le contrôleur de DRAM peut envoyer ce qu'il veut sur le bus de commande ou d'adresse, la SDRAM ne réagira pas du tout, il ne se passera rien. Le '''bit DQM''' est une sorte de bit ''Output Enable'', avec une nuance importante. Le terme DQM est l'abréviation de ''Data Mask'', ce qui trahit encore une fois sa fonction. Il y a un bit DQM pour chaque octet du bus de données. Une SDRAM ayant un bus de 64 bits, cela fait 8 bits DQM par mémoire SDRAM. Lorsque le bit DQM est à 1, l'octet en question n'est simplement pas lu ou écrit, le bus de donnée est déconnecté pour cet octet. Le bus d'adresse est particulier, car il tient compte de la présence de ''banques mémoires''. Le bus d'adresse est découpé en deux portions : une portion pour sélectionner la banque, une autre pour sélectionner l'adresse à l'intérieur d'une banque. L'interface de la SDRAM fait ainsi la différence entre une '''adresse de banque''' et une '''adresse intra-banque'''. L'adresse de banque est codée sur quelques bits, généralement deux ou trois suivant la SDRAM. Le reste de l'adresse est codé sur 11 bits sur les SDRAM, mais cela a augmenté avec les DDR 1, 2, 3, 4, 5. Le bus de données d'une SDRAM fait 4, 8, ou 16 bits. Je précise bien qu'il s'agit là des puces de SDRAM, les barrettes de SDRAM combinent plusieurs puces SDRAM avec un arrangement horizontal, qui peut combiner plusieurs puces de 8 bits pour alimenter un bus de données de 64 bits. La taille des puces utilisées souvent indiquée sur la barrette de RAM, avec une mention x4, x8 ou x16. Les puces de SDRAM les plus courantes ont une interface de 8 bits pour les données. Les SDRAM de 4 bits sont surtout utilisées pour les serveurs, c'est lié au support de l'ECC. les puces x16 sont moins utilisées car elles ont généralement moins de banques que les autres. ===Les commandes SDRAM=== Le bus de commande permet d'envoyer des commandes à la mémoire, chaque commande étant précisée par une combinaison précise des bits CS, RAS, CAS, R/W, et autres. Les commandes en question sont des demandes de lecture, d'écriture, de préchargement et autres. Elles sont codées par une valeur bien précise qui est envoyée sur les 18 fils du bus de commande. Ces commandes sont nommées READ, READA, WRITE, WRITEA, PRECHARGE, ACT, ... Les plus importantes sont les commandes PRECHARGE, ACT et READ/WRITE. La commande ACT sélectionne une ligne : elle met le bit RAS à zéro et présente une adresse de ligne. Les commandes READ et WRITE sélectionnent une colonne, et déclenchent respectivement une lecture ou une écriture. Elles précisent une adresse de colonne, mettent le bit CAS à 0 et le bit RAS à 1, et précise la valeur du bit R/W. Les commandes READ et WRITE ne peuvent se faire qu'une fois que la banque a été activée par une commande ACT. Il est possible d'envoyer plusieurs commandes READ ou WRITE successives à des colonnes différentes, ce qui permet d'implémenter les optimisations des mémoires FPM. La commande PRECHARGE ferme la ligne courante et prépare l'ouverture de la suivante. Elle précharge les lignes de bit de la RAM, d'où son nom. Il est nécessaire d'en envoyer une avant d'envoyer une commande ACT. Notons que la commande PRECHARGE agit sur une banque, dont l'adresse est indiquée dans la commande PRECHARGE. Il existe une commande PRECHARGE ALL, qui agit sur toutes les banques de la SDRAM à la fois. Les commandes READA et WRITEA fusionnent une commande READ/WRITE avec une commande PRECHARGE. Elles permettent d'éviter d'avoir à envoyer une commande PRECHARGE pour fermer la ligne courante. Au lieu d'envoyer une commande READ ou WRITE, puis une commande PRECHARGE pour fermer la ligne, on envoie une seule commande READA/WRITEA. Il s'agit d'une petite optimisation, qui permet de réduire le nombre de commandes envoyées sur le bus. Les commandes sont encodées comme indiquées dans ce tableau. Une commande est tout simplement encodée en précisant une adresse si nécessaire, et une combinaison des bits CS, RAS, CAS et R/W. La seule subtilité est que le bit numéro 10 du bus d'adresse sert à commander les opérations de PRECHARGE, y compris celles implicites dans les opérations READA et WRITEA. {| class="wikitable" style="text-align:center" ! Bit CS || Bit RAS || Bit CAS || Bit WE || Bits de sélection de banque (2 bits) || Bit du bus d'adresse A10 || Reste du bus d'adresse || Nom de la commande : Description |- | 1 | colspan="6" | X | Absence de commandes. |- | 0 || 1 || 1 || 1 || colspan="3" | X || No Operation : Pas d'opération |- | 0 || 1 || 1 || 0 || colspan="3" | X || Burst Terminante : Stoppe un accès en rafale (en cours). |- | 0 || 1 || 0 || 1 || Adresse de la banque || 0 || Adresse de la colonne || READ : lit une donnée depuis la ligne active. |- | 0 || 1 || 0 || 1 || Adresse de la banque || 1 || Adresse de la colonne || READA : lit une donnée depuis la ligne active, puis ferme la ligne. |- | 0 || 1 || 0 || 0 || Adresse de la banque || 0 || Adresse de la colonne || WRITE : écrit une donnée dans la ligne active. |- | 0 || 1 || 0 || 0 || Adresse de la banque || 1 || Adresse de la colonne || WRITEA : écrit une donnée dans la ligne active, puis ferme la ligne. |- | 0 || 0 || 1 || 1 || Adresse de la banque || colspan="2" | Adresse de la ligne || ACT : charge une ligne dans le tampon de ligne. |- | 0 || 0 || 1 || 0 || Adresse de la banque || 0 || X || PRECHARGE : précharge le tampon de ligne dans la banque voulue. |- | 0 || 0 || 1 || 0 || Adresse de la X || 1 || X || PRECHARGE ALL : précharge le tampon de ligne dans toutes les banques. |- | 0 || 0 || 0 || 1 || colspan="3" | X || Auto refresh : Demande de rafraichissement, gérée par la SDRAM. |- | 0 || 0 || 0 || 0 || 00 || colspan="2" | Nouveau contenu du registre de mode || LOAD MODE REGISTER : configure le registre de mode. |} Les commandes ACT se font à partir de l'état de repos, l'état où toutes les banques sont préchargées. Par contre, les commandes MODE REGISTER SET et AUTO REFRESH ne peuvent se faire que si toutes les banques sont désactivées. Le fonctionnement simplifié d'une SDRAM peut se résumer dans ce diagramme : [[File:Fonctionnement simplifié d'une SDRAM.jpg|centre|vignette|upright=2|Fonctionnement simplifié d'une SDRAM.]] ===Les délais mémoires=== Les mémoires SDRAM n'étant pas infiniment rapides, il y a toujours un certain délais à respecter entre deux commandes. Par exemple, quand on envoie une commande ACT pour activer une ligne, on ne peut pas envoyer une commande READ/WRITE au cycle suivant. La plupart des SDRAM ne sont pas assez rapides pour ça. Il faut respecter un délai de quelques cycles, qui dépend de la mémoire. Et il n'y a pas que ce délai entre une commande ACT et la commande suivante. Une SDRAM doit gérer d'autres temps d'attente, appelés des '''délais mémoires''', ou encore des ''timings'' mémoire. Les délais mémoire le plus importants sont résumés ci-dessous : {|class="wikitable" |- !Timing!!Description |- | colspan="2" | |- ! colspan="2" | Délais primaires |- ||tRP|| Temps entre une commande PRECHARGE et une commande ACT |- ||tRCD|| Temps entre une commande ACT et une commande READ/WRITE. |- ||tCL|| Temps entre une commande READ et l'envoi de la donnée lue sur le bus de données. |- ||tDQSS|| Temps entre une commande WRITE et l'écriture de la donnée. |- ||tCAS-to-CAS|| Temps minimum entre deux commandes READ. |- ! colspan="2" | Délais secondaires |- ||tWTR|| Temps entre une lecture et une écriture consécutives. |- ||tRAS || Temps entre une commande ACT et une commande PRECHARGE. |} La façon de mesurer ces délais varie : sur les mémoires FPM et EDO, on les mesure en unités de temps (secondes, millisecondes, micro-secondes, etc.), tandis qu'on les mesure en cycles d'horloge sur les mémoires SDRAM. Les délais/timings mémoire ne sont pas les mêmes suivant la barrette de mémoire que vous achetez. Certaines mémoires sont ainsi conçues pour avoir des timings assez bas et sont donc plus rapides, et surtout : beaucoup plus chères que les autres. Le gain en performances dépend beaucoup du processeur utilisé et est assez minime comparé au prix de ces barrettes. Les délais mémoires d'une barrette sont mémorisés dans le ''Serial Presence Detect'' de la barrette et sont lus par le BIOS au démarrage de l'ordinateur, et sont ensuite utilisés pour configurer le contrôleur de DRAM. ===Les commandes anticipées=== Les SDRAM sont parfois capables de démarrer une commande avant que la précédente soit terminée. Concrètement, pendant que la commande précédente envoie sa dernière donnée sur le bus de données, on peut envoyer la commande suivante avec quelques cycles d'avance. L'exemple ci-dessous devrait être assez clair : on envoie une seconde commande au neuvième cycle, alors qu'une rafale est en cours. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | || bgcolor="#A0FFFF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || || || bgcolor="#FFA0FF" | READ (2) || || || || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 | bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 | |} Il s'agit d'une forme très limitée de pipeline, tellement limitée qu'on peut légitimement douter que c'est un vrai pipeline. Dans ce qui suit, j'ai décidé d'appeler cette possibilité sous le terme de '''commandes SDRAM anticipées'''. La possibilité est très limitée, car il faut tenir compte des délais mémoire. Elle améliore un peu les performances dans certaines circonstances où la RAM doit traiter plusieurs accès mémoire consécutifs, très rapprochés. L'exemple typique est celui du transfert d'un bloc de données entre mémoire cache et mémoire RAM, qui dépasse la taille d'une rafale, qui envoie plusieurs accès mémoire d'un seul coup au contrôleur mémoire. Mais d'autres exemples sont possibles, on ne peut juste pas les expliquer à ce stade du cours. Les commandes SDRAM anticipées sont possibles car les SDRAM sont formées en entourant une RAM asynchrone de registres, exactement comme les SRAM synchrones. Il est possible d'écrire dans les registres de données/commandes, pendant qu'un autre accès mémoire accède au cœur asynchrone. Les délais mémoire sont conçus pour éviter qu'une commande accède au cœur asynchrone en même temps que la suivante ou la précédente, idem pour l'usage des registres. C'est pour cela que les délais mémoire sont assez différents entre écritures et lectures, d'ailleurs. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Enregistrement de la commande dans le registre d'adresse/commande | bgcolor="#A0FFFF" | READ (1) || || || || bgcolor="#FFA0FF" | READ (2) || || || || || |- ! Accès au cœur asynchrone | || bgcolor="#A0FFFF" | READ (1) || bgcolor="#A0FFFF" | READ (1) || bgcolor="#A0FFFF" | READ (1) || || bgcolor="#FFA0FF" | READ (2) || bgcolor="#FFA0FF" | READ (2) || bgcolor="#FFA0FF" | READ (2) || || || |- ! Lecture/écriture du registre de données | || || || || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 | bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 | |} ==Les mémoires DDR== Les mémoires SDRAM récentes sont des mémoires de type ''dual data rate'', ce qui fait qu'elles portent le nom de mémoires DDR. Pour rappel, les mémoires ''dual data rate'' ont un plan mémoire deux fois plus large que le bus mémoire, avec un bus mémoire allant à une fréquence double. Par double, on veut dire que les transferts sur le bus mémoire ont lieu sur les fronts montants et descendants de l'horloge. Il y a donc deux transferts de données sur le bus pour chaque cycle d'horloge, ce qui permet de doubler le débit sans toucher à la fréquence du plan mémoire lui-même. Les mémoires DDR sont standardisées par un organisme international, le JEDEC, et ont été déclinées en plusieurs générations : DDR1, DDR2, DDR3, et DDR4. La différence entre ces modèles sont très nombreuses, mais les plus évidentes sont la fréquence de la mémoire et du bus mémoire. D'autres différences mineures existent entre les SDRAM et les mémoires DDR. Par exemple, la tension d'alimentation des mémoires DDR est plus faible que pour les SDRAM. ET elle a diminué dans le temps, d'une génération de DDR à l'autre. Avec les mémoires DDR2,la tension d'alimentation est passée de 2,5/2,6 Volts à 1,8 Volts. Avec les mémoires DDR3, la tension d'alimentation est notamment passée à 1,5 Volts. ===Les performances des mémoires DDR=== Les mémoires SDRAM ont évolué dans le temps, mais leur temps d'accès/fréquence n'a pas beaucoup changé. Il valait environ 10 nanosecondes pour les SDRAM, approximativement 5 ns pour la DDR-400, il a peu évolué pendant la génération DDR et DDR3, avant d'augmenter pendant les générations DDR4 et de stagner à nouveau pour la génération DDR5. L'usage du DDR, puis du QDR, visait à augmenter les performances malgré la stagnation des temps d'accès. En conséquence, la fréquence du bus a augmenté plus vite que celle des puces mémoire pour compenser. {|class="wikitable" |- ! Année ! Type de mémoire ! Fréquence de la mémoire (haut de gamme) ! Fréquence du bus ! Coefficient multiplicateur entre les deux fréquences |- | 1998 | DDR 1 | 100 - 200 MHz | 200 - 400 MHz | 2 |- | 2003 | DDR 2 | 100 - 266 MHz | 400 - 1066 MHz | 4 |- | 2007 | DDR 3 | 100 - 266 MHz | 800 - 2133 MHz | 8 |- | 2014 | DDR 4 | 200 - 400 MHz | 1600 - 3200 MHz | 8 |- | 2020 | DDR 5 | 200 - 450 MHz | 3200 - 7200 MHz | 8 à 16 |} Une conséquence est que la latence CAS, exprimée en nombre de cycles, a augmenté avec le temps. Si vous comparez des mémoires DDR2 avec une DDR4, par exemple, vous allez voir que la latence CAS est plus élevée pour la DDR4. Mais c'est parce que la latence est exprimée en nombre de cycles d'horloge, et que la fréquence a augmentée. En comparant les temps d'accès exprimés en secondes, on voit une amélioration. ===Les commandes des mémoires DDR=== Les commandes des mémoires DDR sont globalement les mêmes que celles des mémoires SDRAM, vues plus haut. Les modifications entre SDRAM, DDR1, DDR2, DDR3, DDR4, et DDR5 sont assez mineures. Les seules différences sont l'addition de bits pour la transmission des adresses, des bits en plus pour la sélection des banques, etc. En clair, une simple augmentation quantitative. Le registre de mode a été un peu modifié. Il est passé de 10 bits pour les SDRAM et DDR1, à 13 bits sur la DDR 2 et les suivantes. Les DDR ont aussi ajouté le support de plusieurs registres de mode, qui sont sélectionnés en réutilisant l'adresse de banque. Dans une commande LOAD MODE REGISTER, l'adresse de banque indique quel registre de mode il faut altérer. Avant la DDR4, les modifications des commandes sont mineures. La DDR2 supprime la commande ''Burst Terminate'', la DDR3 et la DDR4 utilisent le bit A12 pour préciser s'il faut faire une rafale complète, ou une rafale de moitié moins de données. Une optimisation des DDR2 et 3 est celle des '''CAS postés'''. L'idée est que le contrôleur de DRAM peut envoyer une commande ACT et une commande READ/WRITE sans se soucier des ''timings'' nécessaires entre les deux. En théorie, les deux commandes doivent être séparées par quelques cycles, sur une SDRAM ou une DDR1. Mais avec la DDR2, le contrôleur de DRAM peut envoyer les deux l'une après l'autre, au cycle suivant. C'est la mémoire qui mettra en attente la commande READ/WRITE pour respecter les ''timings'' mémoire. Cela complexifie le fonctionnement interne de la DDR, mais simplifie grandement le travail du contrôleur de DRAM. Mais avec la DDR4, les choses changent, notamment au niveau de la commande ACT. Avec l'augmentation de la capacité des barrettes mémoires, la taille des adresses est devenue trop importante. Pour éviter de rajouter des bits d'adresses, les concepteurs du standard DDR4 ont décidé de ruser. Lors d'une commande ACT, les bits RAS, CAS et WE sont utilisés comme bits d'adresse, alors qu'ils ont leur signification normale pour les autres commandes. Pour éviter toute confusion, un nouveau bit ACT est ajouté pour indiquer la présence d'une commande ACT : il est à 1 pour une commande ACT, 0 pour les autres commandes. {| class="wikitable" style="text-align:center" |+ Commandes d'une mémoire DDR4, seule la commande colorée change par rapport aux SDRAM ! Bit CS || style="background: #CCFFCC" | Bit ACT || Bit RAS || Bit CAS || Bit WE || Bits de sélection de banque (4 bits) || Bit du bas d'adresse A10 || Reste du bus d'adresse || Nom de la commande : Description |- | 1 | colspan="6" | X | Absence de commandes. |- | 0 || style="background: #CCFFCC" | 0 || 1 || 1 || 1 || colspan="3" | X || No Operation : Pas d'opération |- | 0 || style="background: #CCFFCC" | 0 || 1 || 1 || 0 || colspan="3" | X || Burst Terminante : Arrêt d'un accès en rafale en cours. |- | 0 || style="background: #CCFFCC" | 0 || 1 || 0 || 1 || Adresse de la banque || 0 || Adresse de la colonne || READ : lire une donnée depuis la ligne active. |- | 0 || style="background: #CCFFCC" | 0 || 1 || 0 || 1 || Adresse de la banque || 1 || Adresse de la colonne || READA : lire une donnée depuis la ligne active, avec rafraichissement automatique de la ligne. |- | 0 || style="background: #CCFFCC" | 0 || 1 || 0 || 0 || Adresse de la banque || 0 || Adresse de la colonne || WRITE : écrire une donnée depuis la ligne active. |- | 0 || style="background: #CCFFCC" | 0 || 1 || 0 || 0 || Adresse de la banque || 1 || Adresse de la colonne || WRITEA : écrire une donnée depuis la ligne active, avec rafraichissement automatique de la ligne. |- style="background: #CCFFCC" | 0 || style="background: #CCFFCC" | 1 || colspan="3" | Adresse de la ligne (bits de poids forts) || Adresse de la banque || colspan="2" | Adresse de la ligne (bits de poids faible) || ACT : charge une ligne dans le tampon de ligne. |- | 0 || style="background: #CCFFCC" | 0 || 0 || 1 || 0 || Adresse de la banque || 0 || X || PRECHARGE : précharge le tampon de ligne dans la banque voulue. |- | 0 || style="background: #CCFFCC" | 0 || 0 || 1 || 0 || Adresse de la X || 1 || X || PRECHARGE ALL : précharge le tampon de ligne' dans toutes les banques. |- | 0 || style="background: #CCFFCC" | 0 || 0 || 0 || 1 || colspan="3" | X || Auto refresh : Demande de rafraichissement, gérée par la SDRAM. |- | 0 || style="background: #CCFFCC" | 0 || 0 || 0 || 0 || Numéro de registre de mode || colspan="2" | Nouveau contenu du registre de mode || LOAD MODE REGISTER : configure le registre de mode. |} ==Les VRAM des cartes vidéo== Les cartes graphiques ont des besoins légèrement différents des DRAM des processeurs, ce qui fait qu'il existe des mémoires DRAM qui leur sont dédiées. Elles sont appelés des '''''Graphics RAM''''' (GRAM). La plupart incorporent des fonctionnalités utiles uniquement pour les mémoires vidéos, comme des fonctionnalités de masquage (appliquer un masque aux données lue ou à écrire), ou le remplissage d'un bloc de mémoire avec une donnée unique. Les anciennes cartes graphiques et les anciennes consoles utilisaient de la DRAM normale, faute de mieux. La première GRAM utilisée était la NEC μPD481850, qui a été utilisée sur la console de jeu PlayStation, à partir de son modèle SCPH-5000. D'autres modèles de GRAM ont rapidement suivi. Les anciennes consoles de jeu, mais aussi des cartes graphiquesn utilisaient des GRAM spécifiques. ===Les mémoires vidéo double port=== Sur les premières consoles de jeu et les premières cartes graphiques, le ''framebuffer'' était mémorisé dans une mémoire vidéo spécialisée appelée une '''mémoire vidéo double port'''. Le premier port était connecté au processeur ou à la carte graphique, alors que le second port était connecté à un écran CRT. Aussi, nous appellerons ces deux port le ''port CPU/GPU'' et l'autre sera appelé le ''port CRT''. Le premier port était utilisé pour enregistrer l'image à calculer et faire les calculs, alors que le second port était utilisé pour envoyer à l'écran l'image à afficher. Le port CPU/GPU est tout ce qu'il y a de plus normal : on peut lire ou écrire des données, en précisant l'adresse mémoire de la donnée, rien de compliqué. Le port CRT est assez original : il permet d'envoyer un paquet de données bit par bit. De telles mémoires étaient des mémoires à tampon de ligne, dont le support de mémorisation était organisé en ligne et colonnes. Une ligne à l'intérieur de la mémoire correspond à une ligne de pixel à l'écran, ce qui se marie bien avec le fait que les anciens écrans CRT affichaient les images ligne par ligne. L'envoi d'une ligne à l'écran se fait bit par bit, sur un câble assez simple comme un câble VGA ou autre. Le second port permettait de faire cela automatiquement, en permettant de lire une ligne bit par bit, les bits étant envoyés l'un après l'autre automatiquement. Pour cela, les mémoires vidéo double port incorporaient un tampon de ligne spécialisé pour le port lié à l'écran. Ce tampon de ligne n'était autre qu'un registre à décalage, contrairement au tampon de ligne normal. Lors de l'accès au second port, la carte graphique fournissait un numéro de ligne et la ligne était chargée dans le tampon de ligne associé à l'écran. La carte graphique envoyait un signal d'horloge de même fréquence que l'écran, qui commandait le tampon de ligne à décalage : un bit sortait à chaque cycle d'écran et les bits étaient envoyé dans le bon ordre. ===Les mémoires SGRAM et GDDR=== De nos jours, les cartes graphiques n'utilisent plus de mémoires double port, mais des mémoires simple port. Les mémoires graphiques actuelles sont des SDRAM modifiées pour fonctionner en tant que ''Graphic RAM''. Les plus connues sont les '''mémoires GDDR''', pour ''graphics double data rate'', utilisées presque exclusivement sur les cartes graphiques. Il en existe plusieurs types pendant que j'écris ce tutoriel : GDDR, GDDR2, GDDR3, GDDR4, et GDDR5. Mais attention, il y a des différences avec les DDR normales. Par exemple, les GDDR ont une fréquence plus élevée que les DDR normales, avec des temps d'accès plus élevés (sauf pour le tCAS). De plus, elles sont capables de laisser ouvertes deux lignes en même temps. Par contre, ce sont des mémoires simple port. ==Les mémoires SLDRAM, RDRAM et associées== Les mémoires précédentes sont généralement associées à des bus larges. Les mémoires SDRAM et DDR modernes ont des bus de données de 64 bits de large, avec des d'adresse et de commande de largeur similaire. Le nombre de fils du bus mémoire dépasse facilement la centaine de fils, avec autant de broches sur les barrettes de mémoire. Largeur de ces bus pose de problèmes problèmes électriques, dont la résolution n'est pas triviale. En conséquence, la fréquence du bus mémoire est généralement moins performantes comparé à ce qu'on aurait avec un bus moins large. Mais d'autres mémoires DRAM ont exploré une solution alternative : avoir un bus peu large mais de haute fréquence, sur lequel on envoie les commandes/données en plusieurs fois. Elles sont regroupées sous le nom de '''DRAM à commutation par paquets'''. Elles utilisent des bus spéciaux, où les commandes/adresses/données sont transmises par paquets, par trames, en plusieurs fois. En théorie, ce qu'on a dit sur le codage des trames dans le chapitre sur le bus devrait s'appliquer à de telles mémoires. En pratique, les protocoles de transmission sur le bus mémoire sont simplifiés, pour gérer le fonctionnement à haute fréquence. Le processeur envoie des paquets de commandes, les mémoires répondent avec des paquets de données ou des accusés de réception. Les mémoires à commutation par paquets sont peu nombreuses. Les plus connues sont les mémoires conçues par la société Rambus, à savoir la ''RDRAM'' (''Rambus DRAM'') et ses deux successeurs ''XDR RAM'' et ''XDR RAM 2''. La ''Synchronous-link DRAM'' (''SLDRAM'') est un format concurrent conçu par un consortium de plusieurs concepteurs de mémoire. ===La SLDRAM (''Synchronous-link DRAM'')=== Les '''mémoires SLDRAM''' avaient un bus de données de 64 bits allant à 200-400 Hz, avec technologie DDR, ce qui était dans la norme de l'époque pour la fréquence (début des années 2000). Elle utilisait un bus de commande de 11 bits, qui était utilisé pour transmettre des commandes de 40 bits, transmises en quatre cycles d'horloge consécutifs (en réalité, quatre fronts d'horloge donc deux cycles en DDR). Le bus de données était de 18 bits, mais les transferts de donnée se faisaient par paquets de 4 à 8 octets (32-65 bits). Pour résumer, données et commandes sont chacunes transmises en plusieurs cycles consécutifs, sur un bus de commande/données plus court que les données/commandes elle-mêmes. Là où les SDRAM sélectionnent la bonne barrette grâce à des signaux de commande dédiés, ce n'est pas le cas avec la SLDRAM. A la place, chaque barrette de mémoire reçoit un identifiant, un numéro codé sur 7-8 bits. Les commandes de lecture/écriture précisent l'identifiant dans la commande. Toutes les barrettes reçoivent la commande, elles vérifient si l'identifiant de la commande est le leur, et elles la prennent en compte seulement si c'est le cas. Voici le format d'une commande SLDRAM. Elle contient l'adresse, qui regroupe le numéro de banque, le numéro de ligne et le numéro de colonne. On trouve aussi un code commande qui indique s'il faut faire une lecture ou une écriture, et qui configure l'accès mémoire. Il configure notamment le mode rafale, en indiquant s'il faut lire/écrire 4 ou 8 octets. Enfin, il indique s'il faut fermer la ligne accédée une fois l'accès terminé, ou s'il faut la laisser ouverte. Le code commande peut aussi préciser que la commande est un rafraichissement ou non, effectuer des opérations de configuration, etc. L'identifiant de barrette mémoire est envoyé en premier, histoire que les barrettes sachent précocement si l'accès les concerne ou non. {|class="wikitable" style="text-align:center" |+SLDRAM Read, write or row op request packet ! FLAG || CA9 || CA8 || CA7 || CA6 || CA5 || CA4 || CA3 || CA2 || CA1 || CA0 |- ! 1 | colspan=9 bgcolor=#ffcccc| Identifiant de barrette mémoire|| bgcolor=#ccffcc| Code de commande |- ! 0 | colspan=5 bgcolor=#ccffcc| Code de commande ||colspan=3 bgcolor=#ff88ff| Banque||colspan=2 bgcolor=#ffffcc| Ligne |- ! 0 | colspan=9 bgcolor=#ffffcc| Ligne || 0 |- ! 0 | 0 || 0 || 0 ||colspan=7 bgcolor=#ccffff| Colonne |} ===Les mémoires Rambus=== Les mémoires conçues par la société Rambus regroupent la '''RDRAM''' (''Rambus DRAM'') et ses deux successeurs '''XDR RAM''' et '''XDR RAM 2'''. Les toutes premières étaient les '''mémoires RDRAM''', où le bus permettait de transmettre soit des commandes (adresse inclue), soit des données, avec un multiplexage total. Le processeur envoie un paquet contenant commandes et adresse à la mémoire, qui répond avec un paquet d'acquittement. Lors d'une lecture, le paquet d'acquittement contient la donnée lue. Lors d'une écriture, le paquet d'acquittement est réduit au strict minimum. Le bus de commandes est réduit au strict minimum, à savoir l'horloge et quelques bits absolument essentiels, les bits RW est transmis dans un paquet et n'ont pas de ligne dédiée, pareil pour le bit OE. Toutes les barrettes de mémoire doivent vérifier toutes les transmissions et déterminer si elles sont concernées en analysant l'adresse transmise dans la trame. Elles ont été utilisées dans des PC ou d'anciennes consoles de jeu. Par exemple, la Nintendo 64 incorporait 4 mébioctets de mémoire RDRAM en tant que mémoire principale. La RDRAM de la Nintendo 64 était cadencée à 500 MHz, utilisait un bus de 9 bits, et avait un débit binaire maximal théorique de 500 MB/s. La Playstation 2 contenait quant à elle 32 mébioctets de RDRAM en ''dual-channel'', pour un débit binaire de 3.2 Gibioctets par seconde. Les processeurs Pentium 3 pouvaient être associés à de la RDRAM sur certaines mères. Les Pentium 4 étaient eux aussi associés à la de RDRAM, mais les cartes mères ne géraient que ce genre de mémoire. La Playstation 3 contenait quant à elle de la XDR RAM. ==Les eDRAM : des DRAM adaptées aux ''chiplets''== Les '''mémoires eDRAM''', pour ''embedded DRAM'', sont des mémoires RAM qui sont destinées à être intégrée au processeur. Pour comparer, les DRAM normales sont placées sur des barrettes de RAM ou soudées à la carte mère. Dans la quasi-totalité des cas, l'eDRAM est utilisée pour implémenter une mémoire cache, elle ne sert pas de mémoire principale (cache L4, le plus proche de la mémoire sur ces puces). De ce fait, elles sont conçues pour être très rapides, avoir une grande bande passante, au détriment de leur capacité mémoire. Pour être plus précis, l'eDRAM est une puce de DRAM conçue pour être intégrée dans un ''chiplet'', , à savoir des circuits imprimés qui regroupent plusieurs puces électroniques distinctes, regroupées sur le même PCB. Typiquement, un processeur de type ''chiplet'' avec de l'eDRAM comprend deux puces séparées : une pour le processeur, une autre pour une puce de communication avec la RAM. Avec la mémoire eDRAM, les deux puces sont complétées par une troisième puce spécialisée qui incorpore l'eDRAM. Elle a été utilisée sur quelques processeurs, mais aussi dans des consoles de jeu vidéo, pour la carte graphique des consoles suivantes : la PlayStation 2, la PlayStation Portable, la GameCube, la Wii, la Wii U, et la XBOX 360. Sur ces consoles, la RAM de la carte graphique était intégrée avec le processeur graphique dans le même circuit. La fameuse mémoire vidéo et le GPU n'étaient qu'une seule et même puce électronique, un seul circuit intégré. Ce n'est pas le cas sur une carte graphique moderne : regardez votre carte graphique avec attention et vous verrez que le GPU est une puce carrée située sous les ventilateurs, alors que les puces mémoires sont situées juste autour et soudées sur le PCB de la carte. Les processeurs Intel Core de microarchitecture Broadwell disposaient d'un cache L4 de 128 mébioctets, intégralement implémenté avec de la mémoire eDRAM. Quelques processeurs de la microarchitecture précédente (Haswell), disposaient aussi de ce cache. Le cache L4 eDRAM était implémenté sur un chiplet à part, à savoir que le processeur était composé de trois puces séparées : une pour le processeur, une autre pour la gestion des entrées-sorties, et une autre pour le cache L4. La puce pour le cache L4 était appelée ''Crystal Well''. La puce ''Crystal Well'' était une puce gravée en 22nm, ce qui était une finesse de gravure plus élevée que celle des processeurs associés. ''Crystal Well'' était très optimisé pour l'époque. Par exemple, elle disposait de bus séparées pour la lecture et l'écriture, chose qu'on retrouve fréquemment sur les SRAM mais qui est absent sur les mémoires DRAM actuelles. Pour le reste, elle ressemblait beaucoup aux mémoires DDR de l'époque (système de ''double data rate'', entres autres), mais elle allait à une fréquence plus élevée que les DRAM de l'époque et avait un débit bien plus élevé, pour une consommation moindre. ''Crystal Well'' consommait entre 1 à 5 watts (1 watt en veille, 5 à pleine utilisation), pour un débit binaire de 102 GB/s et fonctionnait à 3.2 GHz. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les mémoires SRAM synchrones | prevText=Les mémoires SRAM synchrones | next=Contrôleur mémoire externe | nextText=Le contrôleur mémoire externe }}{{autocat}} </noinclude> a2ch03v5gqm0hwcc8upv3nfecqezn7e 764680 764678 2026-04-23T18:54:32Z Mewtow 31375 /* Les commandes anticipées */ 764680 wikitext text/x-wiki Après avoir vu les mémoires statiques (SRAM), il est temps de passer aux mémoires RAM dynamiques, aussi appelées mémoires DRAM. Pour rappel, les DRAM dynamiques ont pour défaut que leurss données s'effacent après un certain temps, en quelques millièmes ou centièmes de secondes . En conséquence, il faut réécrire chaque bit de la mémoire régulièrement pour éviter qu'il ne s'efface. On dit qu'on doit effectuer régulièrement un '''rafraîchissement mémoire'''. Et celui-ci rend les DRAM très différentes des SRAM. Les DRAM des PC ont beaucoup évolués dans le temps. Les toutes premières mémoires DRAM étaient des mémoires asynchrones, mais elles ont été remplacées par des modèles synchrones. Les modèles asynchrones ont été très nombreux. Après l'apparition des premières DRAM, les mémoires ''Fast Page Mode'' sont apparues, suivies par les mémoires ''Extended Data Out'', puis les EDO en mode rafale. Elles ont été utilisées jusque dans la moitié des années 90, pour ensuite être supplantées par les mémoires SDRAM. Les mémoires DDR actuelles sont des améliorations des mémoires SDRAM actuelles. Le fait est que les DRAM sont des mémoires électroniques comme les autres, qui se présentent sous la forme de circuits intégrés, à savoir que ce sont des petits boitiers noirs avec des broches. Il est possible de souder ces boitiers sur une cartre mère, et c'est ce qui est fait sur nombre d'ordinateurs portables. Mais il est aussi possible de regrouper plusieurs boitiers sur une barrette de RAM séparée. Dans ce qui suit, nous les appellerons des '''chips mémoire''', ou encore, des puces mémoires. [[File:Canyon CN-WF514 - EtronTech EM638325TS-6-4022.jpg|centre|vignette|upright=2|Exemple de chip mémoire.]] Dans ce qui suit, nous allons étudier ces chips de DRAM, avant de voir comment ils sont regroupés sur une barrette de RAM. Puis, nous allons voire chaque type de mémoire, FPM, EDO, SDRAM, DDR, ... ; un par un. ==L'interface des DRAM et le contrôleur mémoire== L'interface d'une mémoire DRAM est plus compliquée que l'interface d'une SRAM basique. Et c'est suffisant pour qu'on ait besoin d'un intermédiaire pour faire la conversion entre processeur et DRAM. Les DRAM modernes ne sont pas connectées directement au processeur, mais le sont par l'intermédiaire d'un '''contrôleur mémoire externe'''. Il ne faut pas le confondre avec le contrôleur mémoire interne, placé dans la mémoire RAM, et qui contient notamment le décodeur. Les deux sont totalement différents, bien que leur nom soit similaire. Pour éviter toute confusion, j'utiliserais le terme de '''contrôleur de DRAM''', plus parlant. ===Le bus d'adresse des DRAM est multiplexé=== Un point important pour le contrôleur de DRAM est de transformer les adresses mémoires fournies par le processeur, en adresses utilisables par la DRAM. Car les DRAM ont une interface assez spécifique. Les DRAM ont ce qui s'appelle un '''bus d'adresse multiplexé'''. Avec de tels bus, l'adresse est envoyée en deux fois. Les bits de poids fort sont envoyés avant les bits de poids faible. On peut ainsi envoyer une adresse de 32 bits sur un bus d'adresse de 16 bits, par exemple. Le bus d'adresse contient alors environ moitié moins de fils que la normale. Pour rappel, l'avantage de cette méthode est qu'elle permet de limiter le nombre de fils du bus d'adresse, ce qui très intéressant sur les mémoires de grande capacité. Les mémoires DRAM étant utilisées comme mémoire principale d'un ordinateur, elles devaient avoir une grande capacité. Cependant, avoir un petit nombre de broches sur les barrettes de mémoire est clairement important, ce qui impose d'utiliser des stratagèmes. Envoyer l'adresse en deux fois répond parfaitement à ce problème : cela permet d'avoir des adresses larges et donc des mémoires de forte capacité, avec une performance acceptable et peu de fils sur le bus d'adresse. Les bus multiplexés se marient bien avec le fait que les DRAM sont des mémoires à adressage par coïncidence ou à tampon de ligne. Sur ces mémoires, l'adresse est découpée en deux : une adresse haute pour sélectionner la ligne, et une adresse basse qui sélectionne la colonne. L'adresse est envoyée en deux fois : la ligne, puis la colonne. Pour savoir si une donnée envoyée sur le bus d'adresse est une adresse de ligne ou de colonne, le bus de commande de ces mémoires contenait deux fils bien particuliers : les RAS et le CAS. Pour simplifier, le signal RAS permettait de sélectionner une ligne, et le signal CAS permettait de sélectionner une colonne. [[File:Signaux RAS et CAS.png|centre|vignette|upright=2|Signaux RAS et CAS.]] Si on a deux bits RAS et CAS, c'est parce que la mémoire prend en compte les signaux RAS et CAS quand ils passent de 1 à 0. C'est à ce moment là que la ligne ou colonne dont l'adresse est sur le bus sera sélectionnée. Tant que des signaux sont à zéro, la ligne ou colonne reste sélectionnée : on peut changer l'adresse sur le bus, cela ne désélectionnera pas la ligne ou la colonne et la valeur présente lors du front descendant est conservée. [[File:L'intérieur d'une FPM.png|centre|vignette|upright=2|L'intérieur d'une FPM.]] ===Le rafraichissement mémoire=== La spécificité des DRAM est qu'elles doivent être rafraichies régulièrement, sans quoi leurs cellules perdent leurs données. Le rafraichissement est basiquement une lecture camouflée. Elle lit les cellules mémoires, mais n'envoie pas le contenu lu sur le bus de données. Rappelons que la lecture sur une DRAM est destructive, à savoir qu'elle vide la cellule mémoire, mais que le système d'amplification de lecture régénère le contenu de la cellule automatiquement. La cellule est donc rafraichie automatiquement lors d'une lecture. La quasi-totalité des DRAM supporte des commandes de rafraichissement, séparées des lectures et écritures classiques. Une commande de rafraichissement ordonne de rafraichir une adresse, voire une ligne complète. Les commandes de rafraichissement sont générées par le contrôleur de DRAM, dans la grosse majorité des cas. Il est aussi possible que ce soit le processeur qui les génère, mais c'est beaucoup plus rare. Il est aussi possible d'envoyer des commandes de rafraichissement vides, qui ne précisent ni adresse ni numéro de ligne. Pour les gérer, la mémoire contient un compteur, qui pointe sur la prochaine ligne à rafraichir, qui est incrémenté à chaque commande de rafraichissement. Une commande de rafraichissement indique à la mémoire d'utiliser l'adresse dans ce compteur pour savoir quelle adresse/ligne rafraichir. [[File:Rafraichissement mémoire automatique.png|centre|vignette|upright=2|Rafraichissement mémoire automatique.]] Il existe des mémoires qui sont des intermédiaires entre les mémoires SRAM et DRAM. Il s'agit des '''mémoires pseudo-statiques''', qui sont techniquement des mémoires DRAM, utilisant des transistors et des condensateurs, mais qui gèrent leur rafraichissement mémoire toutes seules. Le rafraichissement mémoire est alors totalement automatique, ni le processeur, ni le contrôleur de DRAM ne devant s'en charger. Le rafraichissement est purement le fait des circuits de la mémoire RAM et devient une simple opération de maintenance interne, gérée par la RAM elle-même. L'envoi des commandes de rafraichissement peuvent se faire de deux manières : soit on les envoie toutes en même temps, soit on les disperse le plus possible. Le premier cas est un '''rafraichissement en rafale''', le second un '''rafraichissement étalé'''. Le rafraichissement en rafale n'est pas utilisé dans les PC, car il bloque la mémoire pendant un temps assez long. Mais les anciennes consoles de jeu gagnaient parfois à utiliser eu rafraichissement en rafale. En effet, la mémoire était souvent effacée entre l'affichage de deux images, pour éviter certains problèmes dont on ne parlera pas ici. Le rafraichissement de la mémoire était effectué à ce moment là : l'effacement rafraichissait la mémoire. Le temps mis pour rafraichir la mémoire est le temps mis pour parcourir toute la mémoire. Il s'agit du temps de balayage vu dans le chapitre sur les performances d'un ordinateur. Pour les mémoires FPM et EDO, il est défini en divisant la capacité de la mémoire par son débit binaire. C'est le temps nécessaire pour lire ou réécrire tout le contenu de la mémoire. Sur les SDRAM, les choses sont un peu différentes, pour une raison qu'on expliquera plus bas. ===Le contrôleur de DRAM=== Le contrôleur de DRAM gère le bus mémoire et tout ce qui est envoyé dessus. Il envoie des commandes aux barrettes de mémoire, commandes qui peuvent être des lectures, des écritures, ou des demandes de rafraichissement, parfois d'autres commandes. La mémoire répond à ces commandes par l'action adéquate : lire la donnée et la placer sur le bus de données pour une commande de lecture, par exemple. Le rôle du contrôleur de DRAM varie grandement suivant le contrôleur en question, ainsi que selon le type de DRAM. Les anciens contrôleurs de DRAM étaient des composants séparés du processeur, du ''chipset'' ou du reste de la carte mère. Par exemple, les contrôleur de DRAM Intel 8202, Intel 8203 et Intel 8207 étaient vendus dans des boitiers DIP et étaient soudés sur la carte mère. Par la suite, ils ont été intégré au ''chipset'' de la carte mère pendant les décennies 90-2000. Après les années 2000, ils ont été intégrés dans les processeurs. Il est possible de connecter plusieurs barrettes sur le même bus mémoire, ou alors celles-ci sont connectées au contrôleur de DRAM avec un bus par barrette/boitier. C'est ce qui permet de placer plusieurs barrettes de mémoire sur la même carte mère : toutes les barrettes sont connectées au contrôleur de DRAM d'une manière ou d'une autre. ==Les rangées : l'arrangement horizontal et vertical== Il est rare d'utiliser un chip mémoire seul, car ceux-ci n'ont pas une capacité suffisante. Pour donner quelques chiffres, à l'heure où j'écris ces lignes, la norme pour un ordinateur est d'avoir entre 8 et 64 gibioctets de RAM. Mais les chips mémoire font entre 1 et 4 gibioctets, rarement plus. La raison est que les ordinateurs combinent ensemble plusieurs chips mémoire pour additionner leurs capacités. La concaténation de plusieurs chips mémoire peut se faire de deux manières différentes, appelées l'arrangement horizontal et l'arrangement vertical. Les deux additionnent la capacité des chips mémoire, mais se distinguent sur un point : ce qui arrive respectivement au bus de données, et au nombre d'adresses. Intuitivement, on se dit que doubler la capacité mémoire implique de doubler le nombre d'adresses mémoire. C'est effectivement ce qui se passe avec l'arrangement vertical. Mais avec l'arrangement horizontal, le nombre d'adresse ne varie pas. Voyons cela en détail, et commençons par le cas le plus simple, celui de l'arrangement vertical seul. ===L'arrangement vertical : cumuler des adresses mémoire=== Introduisons l'arrangement vertical par un exemple. Imaginez que je souhaite obtenir de 4 mébioctets de RAM, en combinant 4 chips mémoires de 1 mébioctet chacun. L'idée est que le premier mébioctet est placé dans le premier chip mémoire, le second mébioctet dans le second chip, etc. Des adresses consécutives se trouvent ainsi dans le même chip mémoire, sauf pour quelques adresses où on passe d'un chip à l'autre. Avec cette organisation, le bus de donnée fait un octet, et les chips mémoire ont aussi un bus de données d'un octet. Je peux alors combiner les capacités de plusieurs chips mémoire, sans toucher au bus de données. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses avec arrangement vertical.]] Pour sélectionner le chip mémoire adéquat, il faut que chaque chip mémoire dispose d'une entrée ''Chip Select'', qui permet de l'activer ou de le désactiver. L'idée est que selon l'adresse demandée, on active le chip mémoire associé à cette adresse. Les signaux ''Chip Select'' sont générés par le contrôleur de DRAM, à partir de l'adresse. On dit qu'il y a un '''décodage d'adresse'''. Avec cet arrangement, les bits de poids fort de l'adresse sont utilisées pour sélectionner la banque adéquate, et le reste de l'adresse est envoyé sur le bus d'adresse. Par exemple, avec 4 chips mémoire, les deux bits de poids fort de l'adresse sont utilisés pour sélectionner le chip mémoire adéquat. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Adresse de banque !! Adresse dans la banque |- | Quelques bits de poids fort || Reste de l'adresse |} ===L'arrangement horizontal : élargir le bus de données=== L'arrangement horizontal permet lui aussi d'additionner les capacités mémoire de plusieurs chips mémoire. Cependant, il les combine d'une autre manière. Le nombre d'adresses mémoire n'est pas changé en utilisant plusieurs chips, mais le bus de données est élargi. Le mieux pour comprendre l'idée est de partir d'un exemple, et nous allons prendre celui d'une mémoire SDRAM. Les ordinateurs actuels ont un bus de données de 64 bits (on met de côté le cas du double ou triple canal). Cependant, il n'existe pas de chip mémoire avec un bus aussi large. Les puces de SDRAM/DDR ont un bus de 4, 8 ou 16 bits, ce sont les tailles les plus courantes. L'arrangement horizontal résout ce problème en combinant plusieurs chips mémoire de manière à ce que leurs bus de données s'"additionnent", se concatènent. Par exemple, on peut regrouper 8 chips mémoires de 8 bits, obtenir un bus mémoire de 64 bits. Il est aussi possible d'obtenir ces 64 bits avec des puces de 16 chips mémoire de 4 bits, ou 4 chips mémoire de 16 bits. [[File:Arrangement horizontal SDRAM - un Rank.png|centre|vignette|upright=2|Arrangement horizontal SDRAM.]] Avec cette organisation, on accède à tous les bancs en parallèle à chaque accès, avec la même adresse. Vu que les chips mémoires contiennent tous une partie de la donnée demandée, ils doivent tous être activés en même temps. Pour cela, l'adresse à lire est envoyée à tous les chips mémoire d'un même ''rank'', idem pour les signaux de commande. Un ensemble de N chips reliés de cette manière forme une '''rangée''' (le terme anglais est ''rank''). [[File:Arrangement horizontal.jpg|centre|vignette|upright=2|Arrangement horizontal.]] ===L'arrangement horizontal et vertical combinés=== Nous venons de voir l'arrangement vertical et horizontal, pour ce qui est des barrettes de mémoire. Précisons que ce qui vient d'être dit marche aussi bien pour les barrettes de RAM que pour la mémoire soudée sur la carte mère. Du moment qu'on combine plusieurs chips mémoire ensemble, ces concepts restent valides. Et il en est de même pour la suite, encore que ce soit nettement moins fréquent avec de la mémoire soudée. Il est possible de combiner à la fois l'arrangement vertical et l'arrangement horizontal. Rien de plus simple : il suffit d'utiliser un arrangement vertical entre plusieurs rangées, chacun composée de plusieurs chips mémoire. C'est surtout utilisé sur les barrettes de mémoire SDRAM, qui contiennent 1, 2, 4 ou 8 rangées, rarement plus. Par exemple, une SDRAM peut combiner 16 chips de DRAM de 8 bits chacun, dans deux rangées de 64 bits chacun, chaque rangée regroupant 8 chips. [[File:SDRAM avec 4 ranks.png|centre|vignette|upright=2|SDRAM avec 4 ranks]] Le choix entre la première ou la seconde rangée se fait en configurant les bits ''Chip Select'' de chaque rangée. Il faut noter que les bits de ''Chip Select'' sont générés par le contrôleur mémoire, et envoyés sur le bus de commande. [[File:Td6bfig3.png|centre|vignette|upright=2|Comparaison entre arrangement horizontal (à gauche) et arrangement vertical (à droite).]] Le contrôleur de DRAM peut adresser un certain nombre de rangées, dispersés sur plusieurs barrettes. La limite maximale dépend du contrôleur de DRAM, elle est souvent proche de 8 ou 16 rangées. Si on combine plusieurs barrettes de mémoire, il est possible de dépasser cette limite. Par exemple, prenez un contrôleur de DRAM supportant maximum 8 rangées. Avec 4 barrettes contenant 4 rangées chacune, la limite est dépassée. : Il faut noter que tout ce qui vient d'être dit vaut aussi pour les mémoires ROM et SRAM. Mais en pratique, les arrangements verticaux et horizontaux sont surtout utilisés sur les mémoires DRAM. Il faut dire que de tels arrangements servent à augmenter la capacité mémoire, ce qui colle plus avec des DRAM que des SRAM ou des ROM. ==Les barrettes de mémoire DRAM== [[File:Ram-module.svg|droite|vignette|upright=0.5|Barrette de mémoire RAM.]] Il est possible de souder plusieurs boitiers de DRAM sur une cartre mère, et c'est ce qui est fait sur nombre d'ordinateurs portables. Mais dans les PC fixes, les puces de DRAM sont regroupées sur des ''barrettes mémoires'''. Les barrettes de mémoire se fixent à la carte mère sur un connecteur standardisé, appelé '''slot mémoire'''. Le dessin ci-contre montre une barrette de mémoire, celui-ci ci-dessous est celui d'un ''slot'' mémoire. [[File:Dual channel slots.jpg|centre|vignette|Slots mémoires.]] Sur le schéma de droite, on remarque facilement les boitiers de DRAM, rectangulaires, de couleur sombre. Chaque barrette combine ces puces de manière à additionner leurs capacités : on peut ainsi créer une mémoire de 8 gibioctets à partir de 8 puces d'un gibioctet, par exemple. Ils sont soudés sur un PCB en plastique vert sur lequel sont gravés des connexions métalliques. Les trucs dorés situés en bas des barrettes de mémoire sont des broches qui connectent la barrette au bus mémoire. Les barrettes des mémoires FPM/EDO/SDRAM/DDR n'ont pas le même nombre de broches, pour des raisons de compatibilité. {|class="wikitable" |- !Type de barrette !Type de mémoire !Nombre de broches |- | rowspan="2" | SIMM | rowspan="2" | FPM/EDO |30 |- |72 |- | rowspan="4" | DIMM |SDRAM |168 |- |DDR |184 |- |DDR2 |214, 240 ou 244, suivant la barrette ou la carte mère. |- |DDR3 |204 ou 240, suivant la barrette ou la carte mère. |} ===Le format des barrettes de mémoire=== Certaines barrettes ont des puces mémoire d'un seul côté alors que d'autres en ont sur les deux faces. Cela permet de distinguer les barrettes SIMM et DIMM. * Les '''barrettes SIMM''' ont des puces sur une seule face de la barrette. Elles étaient utilisées pour les mémoires FPM et EDO-RAM. * Les '''barrettes DIMM''' ont des puces sur les deux côtés. Elles sont utilisées sur les SDRAM et les DDR. {| class="flexible" |+ '''Barrette SIMM''' |- |[[File:SIMM FPM 4 MB - C0448721-7229.jpg|vignette|SIMM recto.]] |[[File:SIMM FPM 4 MB - C0448721-7230.jpg|vignette|SIMM verso.]] |} : Les modules DIMM tendent à avoir plus de rangées que les modules SIMM, mais ce n'est pas systématique. Il est souvent dit que les barrettes DIMM ont deux rangées, contre seulement 1 pour les SIMM, mais les contre-exemples sont nombreux. Les barrettes '''SO-DIMM''', pour ordinateurs portables, sont différentes des barrettes DIMM normales des DDR/SDRAM. La raison est qu'il n'y a pas beaucoup de place à l'intérieur d'un PC portable, ce qui demande de diminuer la taille des barrettes. {| |- |[[File:Desktop DDR Memory Comparison.svg|centre|vignette|upright=1.5|Barrettes de DDR pour PC de bureau.]] |[[File:Laptop SODIMM DDR Memory Comparison V2.svg|centre|vignette|upright=1.5|Barrettes de DDR pour PC portables.]] |} Les barrettes de Rambus ont parfois été appelées des '''barrettes RB-DIMM''', mais ce sont en réalité des DIMM comme les autres. La différence principale est que la position des broches n'était pas la même que celle des formats DIMM normaux, sans compter que le connecteur Rambus n'était pas compatible avec les connecteurs SDR/DDR normaux. ===Les interconnexions à l'intérieur d'une barrette de mémoire=== Les boîtiers de DRAM noirs sont connectés au bus par le biais de connexions métalliques. Toutes les puces d'une même rangée sont connectées aux bus d'adresse et de commande. Et les chips d'une même rangée reçoivent exactement les mêmes signaux de commande/adresses, ce qui permet d'envoyer la même adresse/commande à toutes les puces en même temps. La manière dont ces puces sont reliées au bus de commande dépend selon la mémoire utilisée. Les DDR1 et 2 utilisent ce qu'on appelle une '''topologie en T''', illustrée ci-dessous. On voit que le bus de commande forme une sorte d'arbre, dont chaque extrémité est connectée à une puce. La topologie en T permet d'égaliser le délai de transmission des commandes à travers le bus : la commande transmise arrive en même temps sur toutes les puces. Mais elle a de nombreux défauts, à savoir : elle fonctionne mal à haute fréquence, elle est difficile à router en raisons des embranchements. [[File:Organisation des bus de commandes sur les DDR1-2.png|centre|vignette|upright=3.0|Organisation des bus de commandes sur les DDR1-2, nommée topologie en T.]] En comparaison, les DDR3 utilisent une '''topologie ''fly-by''''', où les puces sont connectées en série sur le bus de commande/adresse. La topologie ''fly-by'' n'a pas les problèmes de la topologie en T : elle est simple à router et fonctionne très bien à haute fréquence. [[File:Organisation des bus de commandes sur les DDR3 - topologie fly-by.png|centre|vignette|upright=3.0|Organisation des bus de commandes sur les DDR3 - topologie ''fly-by'']] ===Les barrettes tamponnées (à registres)=== Certaines barrettes intègrent un registre tampon, qui fait l'interface entre le bus et la barrette de RAM. L'utilité est d'améliorer la transmission du signal sur le bus mémoire. Sans ce registre, les signaux électriques doivent traverser le bus, puis traverser les connexions à l'intérieur de la barrette, jusqu'aux puces de mémoire. Avec un registre tampon, les signaux traversent le bus, sont mémorisés dans le registre et c'est tout. Le registre envoie les commandes/données jusqu'aux puces mémoire, mais le signal a été régénéré par le registre. Le signal transmis est donc de meilleure qualité, ce qui augmente la fiabilité du système mémoire. Le défaut est que la présence de ce registre fait que les barrettes ont un temps de latence est plus important que celui des barrettes normales, du fait de la latence du registre. Les barrettes de ce genre sont appelées des '''barrettes RIMM'''. Il en existe deux types : * Avec les '''barrettes RDIMM''', le registre fait l'interface pour le bus d'adresse et le bus de commande, mais pas pour le bus de données. * Avec les '''barrettes LRDIMM''' (''Load Reduced DIMMs''), le registre fait tampon pour tous les bus, y compris le bus de données. [[File:Organisation des bus de commandes sur les RDIMM.png|centre|vignette|upright=3.0|Organisation des bus de commandes sur les RDIMM.]] ===Le ''Serial Presence Detect''=== [[File:SPD SDRAM.jpg|vignette|Localisation du SPD sur une barrette de SDRAM.]] Toute barrette de mémoire assez récente contient une petite mémoire ROM qui stocke les différentes informations sur la mémoire : délais mémoire, capacité, marque, etc. Cette mémoire s'appelle le '''''Serial Presence Detect''''', aussi communément appelé le SPD. Ce SPD contient non seulement les timings de la mémoire RAM, mais aussi diverses informations, comme le numéro de série de la barrette, sa marque, et diverses informations. Le SPD est lu au démarrage de l'ordinateur par le BIOS, afin de pourvoir configurer ce qu'il faut. Le contenu de ce fameux SPD est standardisé par un organisme nommé le JEDEC, qui s'est chargé de standardiser le contenu de cette mémoire, ainsi que les fréquences, timings, tensions et autres paramètres des mémoires SDRAM et DDR. Pour les curieux, vous pouvez lire la page wikipédia sur le SPD, qui donne son contenu pour les mémoires SDR et DDR : [https://en.wikipedia.org/wiki/Serial_presence_detect Serial Presence Detect]. ==Les mémoires asynchrones à RAS/CAS : FPM et EDO-RAM== Avant l'invention des mémoires SDRAM et DDR, il exista un grand nombre de mémoires différentes, les plus connues étant les mémoires fast page mode et EDO-RAM. Ces mémoires n'étaient pas synchronisées par un signal d'horloge, c'était des '''mémoires asynchrones'''. Quand ces mémoires ont été créées, cela ne posait aucun problème : les accès mémoire étaient très rapides et le processeur était certain que la mémoire aurait déjà fini sa lecture ou écriture au cycle suivant. Les mémoires asynchrones les plus connues étaient les '''mémoires FPM''' et '''mémoires EDO'''. Pour ce qui est de leur interface, il faut signaler qu'elles n'ont pas d'entrée ''Chip Select'' ou d'entrée ''Output Enable''. Les signaux RAS et CAS remplacent en quelque sorte ces deux signaux. Le bit RAS fait office de ''Chip Select'', le bit CAS fait office d'''Output Enable''. ===Les mémoires FPM=== Les '''mémoires FPM (''Fast Page Mode'')''' possédaient une petite amélioration, qui rendait l'adressage plus simple. Avec elles, il n'y a pas besoin de préciser deux fois la ligne si celle-ci ne changeait pas lors de deux accès consécutifs : on pouvait garder la ligne sélectionnée durant plusieurs accès. Par contre, il faut quand même préciser les adresses de colonnes à chaque changement d'adresse. Il existe une petite différence entre les mémoire ''Page Mode'' et les mémoires ''Fast-Page Mode'' proprement dit. Sur les premières, le signal CAS est censé passer à 0 avant qu'on fournisse l'adresse de colonne. Avec les ''Fast-Page Mode'', l'adresse de colonne pouvait être fournie avant que l'on configure le signal CAS. Cela faisait gagner un petit peu de temps, en réduisant quelque peu le temps d'accès total. [[File:Sélection d'une ligne sur une mémoire FPM ou EDO.png|centre|vignette|upright=2|Sélection d'une ligne sur une mémoire FPM ou EDO.]] Avec les '''mémoires en mode quartet''', il est possible de lire quatre octets consécutifs sans avoir à préciser la ligne ou la colonne à chaque accès. On envoie l'adresse de ligne et l'adresse de colonne pour le premier accès, mais les accès suivants sont fait automatiquement. La seule contrainte est que l'on doit générer un front descendant sur le signal CAS pour passer à l'adresse suivante. Vous aurez noté la ressemblance avec le mode rafale vu il y a quelques chapitres, mais il y a une différence notable : le mode rafale vrai n'aurait pas besoin qu'on précise quand passer à l'adresse suivante avec le signal CAS. [[File:Mode quartet.png|centre|vignette|upright=3|Mode quartet.]] Les '''mémoires FPM à colonne statique''' se passent même du signal CAS. Le changement de l'adresse de colonne est détecté automatiquement par la mémoire et suffit pour passer à la colonne suivante. Dans ces conditions, un délai supplémentaire a fait son apparition : le temps minimum entre deux sélections de deux colonnes différentes, appelé tCAS-to-CAS. [[File:Accès en colonne statique.jpg|centre|vignette|upright=2.5|Accès en colonne statique.]] ===Les mémoires EDO-RAM=== L''''EDO-RAM''' a été inventée quelques années après la mémoire FPM. Elle a été déclinée en deux versions : la EDO simple, et la EDO en rafale. L'EDO simple ajoutait une entrée ''Ouput Enable'' à une mémoire FPM. Pour rappel, l'entrée ''Ouput Enable'' permet de connecter/déconnecter la DRAM du bus de données. S'il est mis à 0, les lectures et écritures sont empêchées. Pour ajouter cette entrée, il a fallu rajouter un registre sur la sortie de donnée, celle qui sert pour les lectures. Et l'ajout de ce registre a introduit une capacité dite de ''pipelining'', sur le même modèle que pour les mémoires SRAM synchrones. La donnée pouvait être maintenue sur le bus de données durant un certain temps, même après la remontée du signal CAS. Le registre de sortie maintenait la donnée lu tant que le signal RAS restait à 0, et tant qu'un nouveau signal CAS n'a pas été envoyé. Faire remonter le signal CAS à 1 n'invalidait pas la donnée en sortie. La conséquence est qu'on pouvait démarrer une nouvelle lecture alors que la donnée de l'accès précédent était encore présent sur le bus de données. Le pipeline obtenu avait deux étages : un où on présentait l'adresse et sélectionnait la colonne, un autre où la donnée était lue depuis le registre de sortie. Les mémoires EDO étaient donc plus rapides. [[File:EDO RAM.png|centre|vignette|upright=3|EDO RAM]] Cependant, cela marchait surtout pour les lectures, pas pour les écritures. Une écriture ne démarre que quand la lecture ou écriture précédente est totalement terminée. De même, on ne peut pas démarrer un nouvel accès mémoire tant qu'une écriture est en cours. ===Les mémoires EDO-RAM avec mode rafale=== Les '''EDO en rafale''' effectuent les accès à 4 octets consécutifs automatiquement : il suffit d'adresser le premier octet à lire. Les 4 octets étaient envoyés sur le bus les uns après les autres, au rythme d'un par cycle d’horloge : ce genre d'accès mémoire s'appelle un accès en rafale. [[File:Accès en rafale.png|centre|vignette|upright=2|Accès en rafale sur une DRAM EDO.]] Implémenter cette technique nécessite d'ajouter un compteur, capable de faire passer d'une colonne à une autre quand on lui demande, et quelques circuits annexes pour commander le tout. [[File:Modifications du contrôleur mémoire liées aux accès en rafale.png|centre|vignette|upright=2|Modifications du contrôleur de DRAM liées aux accès en rafale.]] ===Le rafraichissement mémoire=== Les mémoires FPM et EDO doivent être rafraichies régulièrement. Au début, le rafraichissement se faisait ligne par ligne. Le rafraichissement avait lieu quand le RAS passait à l'état haut, alors que le CAS restait à l'état bas. Le processeur, ou le contrôleur mémoire, sélectionnait la ligne à rafraichir en fournissant son adresse mémoire. D'où le nom de '''rafraichissement par adresse''' qui est donné à cette méthode de commande du rafraichissement mémoire. Divers processeurs implémentaient de quoi faciliter le rafraichissement par adresse. Par exemple, le Zilog Z80 contenait un compteur de ligne, un registre qui contenait le numéro de la prochaine ligne à rafraichir. Il était incrémenté à chaque rafraichissement mémoire, automatiquement, par le processeur lui-même. Un ''timer'' interne permettait de savoir quand rafraichir la mémoire : quand ce ''timer'' atteignait 0, une commande de rafraichissement était envoyée à la mémoire, et le ''timer'' était ''reset''. [[File:Rafraichissement mémoire manuel.png|centre|vignette|upright=2|Rafraichissement mémoire manuel.]] Par la suite, certaines mémoires ont implémenté un compteur interne d'adresse, pour déterminer la prochaine adresse à rafraichir sans la préciser sur le bus d'adresse. Le déclenchement du rafraichissement se faisait toujours par une commande externe, provenant du contrôleur de DRAM ou du processeur. Cette commande faisait passer le CAS à 0 avant le RAS. Cette méthode de rafraichissement se nomme '''rafraichissement interne'''. [[File:Rafraichissement sur CAS précoce.png|centre|vignette|upright=2|Rafraichissement sur CAS précoce.]] On peut noter qu'il est possible de déclencher plusieurs rafraichissements à la suite en laissant le signal CAS dans le même état. Ce genre de choses pouvait avoir lieu après une lecture : on pouvait profiter du fait que le CAS soit mis à zéro par la lecture ou l'écriture pour ensuite effectuer des rafraichissements en touchant au signal RAS. Dans cette situation, la donnée lue était maintenue sur la sortie durant les différents rafraichissements. [[File:Rafraichissements multiples sur CAS précoce.png|centre|vignette|upright=2|Rafraichissements multiples sur CAS précoce.]] ==Les mémoires SDRAM== Dans les années 90, les mémoires asynchrones ont laissé la place aux '''mémoires SDRAM''', qui sont synchronisées avec le bus par une horloge. L'utilisation d'une horloge a comme avantage des temps d'accès fixes : le processeur sait qu'un accès mémoire prendra un nombre déterminé de cycles d'horloge. Avec les mémoires asynchrones, le processeur ne pouvait pas prévoir quand la donnée serait disponible et ne faisait rien tant que la mémoire n'avait pas répondu : il exécutait ce qu'on appelle des ''wait states'' en attendant que la mémoire ait fini. Les mémoires SDRAM sont standardisées par un organisme international, le JEDEC. Le standard SDRAM impose des spécifications électriques bien précise pour les barrettes de mémoire et le bus mémoire, décrit le protocole utilisé pour communiquer avec les barrettes de mémoire, et bien d'autres choses encore. Les SDRAM ont été déclinées en versions de performances différentes, décrites dans le tableau ci-dessous : {| class="wikitable" ! Nom standard ! Fréquence ! Bande passante |- | PC66 | 66 mhz | 528 Mio/s |- | PC66 | 100 mhz | 800 Mio/s |- | PC66 | 133 mhz | 1064 Mio/s |- | PC66 | 150 mhz | 1200 Mio/s |} ===Les banques internes aux chips mémoires SDRAM=== L'intérieur d'une mémoire SDRAM contient plusieurs '''banques''', aussi appelées des banc mémoire. Concrètement, une banque est... une mémoire. Ou plutôt, une sorte de mini-mémoire miniature. Chaque banque a son propre tampon de ligne, ses propres multiplexeurs de colonne et ses propres décodeurs. C'est comme si une SDRAM regroupait plusieurs mémoires séparées dans un même circuit intégré. [[File:Arrangement vertical.jpg|centre|vignette|upright=2.5|Mémoire multi-banques.]] Un point important est que chaque banque a son propre tampon de ligne. Il est donc possible d'ouvrir plusieurs lignes en même temps, chacune dans une banque différente. Par exemple, on peut ouvrir une ligne dans la banque numéro 1, et une autre ligne dans la banque numéro 2. Et c'est une source d'optimisations très intéressantes. La première optimisation est liée au rafraichissement mémoire. Au lieu de rafraichir chaque adresse une par une, il est possible de rafraichir des banques indépendantes en même temps, ce qui divise le temps de rafraichissement par le nombre de banques. C'est ce que je sous-entendais plus haut quand je disais que le temps de rafraichissement n'est pas égal au temps de balayage sur les SDRAM, alors que c'est le cas sur les DRAM FPM et EDO. De plus, et sans rentrer dans les détails, cela permet de faire plusieurs accès mémoire en même temps, dans des banques différentes. La possibilité est limitée, mais elle existe et elle améliore grandement la performance de la SDRAM. Mais nous en reparlerons dans un chapitre ultérieur, car cette histoire d'accès simultanés a plus sa place dans le chapitre sur le parallélisme mémoire. Pour le moment, nous ne pouvons pas expliquer pourquoi ni comment un processeur pourrait émettre plusieurs accès mémoire simultanés. Un processeur est censé travailler une instruction à la fois, à ce stade du cours, il ne peut pas en faire plusieurs en même temps. Mais nous allons cependant mentionner un cas où cette possibilité est intéressante : une mémoire SDRAM partagée entre un processeur et une carte graphique. Les deux accèdent à des données séparées, qui sont dans des banques différentes. On suppose que la carte graphique accède plus fréquemment à la mémoire que le processeur. Le contrôleur mémoire reçoit les accès mémoire du CPU et du GPU et il tente d'intercaler des accès CPU entre deux accès de la carte graphique. Vu qu'ils tombent dans des banques différentes, un accès CPU et un accès carte graphique peuvent se faire presque en même temps. La seule contrainte est que si on lance plusieurs accès mémoire simultanés, ceux-ci ne peuvent pas utiliser le bus de données en même temps. {|class="wikitable" |+ Pipelining basique sur les SDRAM |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 |- ! Banque Numéro 1 | || || || || || || || || || || |- ! Banque Numéro 2 | || colspan="3" bgcolor="#A0FFFF" | Accès CPU || || || colspan="3" bgcolor="#A0FFFF" | Accès CPU || || |- ! Banque Numéro 3 | || || || || || || || || || || |- ! Banque Numéro 4 | || || || colspan="3" bgcolor="#FFA0FF" | Accès carte graphique || || colspan="3" bgcolor="#FFA0FF" | Accès carte graphique || |} ===Le mode rafale des SDRAM=== Un point important est que les SDRAM reprennent les optimisations des mémoires FPM et EDO. Elles utilisent aussi un tampon de ligne, avec la possibilité de lire plusieurs colonnes à la suite sans avoir à préciser l'adresse de ligne à chaque fois. Mais surtout, elles gèrent nativement le mode rafale. les paramètres qui ont trait au mode rafale sont configurables. Il est possible de configurer la SDRAM pour activer les accès sans rafale, ou les désactiver. Il y a aussi la possibilité de configurer le nombre d'octets consécutifs à lire ou écrire en mode rafale. On peut ainsi accéder à 1, 2, 4, ou 8 octets en une seule fois, alors que les EDO ne permettaient que des accès à 4 octets consécutifs. Enfin, on peut décider s'il faut faire un accès en mode linéaire ou entrelacé. La configuration de la SDRAM est mémorisée dans un registre de 10 bits, le '''registre de mode'''. Il faisait 10 bits sur les mémoires SDRAM, mais a été étendu à 13 bits sur la DDR2. Voici les 10 bits originels de ce registre : {|class="wikitable" |+ Signification des bits du registre de mode des SDRAM |- ! Bit n°9 | Type d'accès : en rafale ou normal |- ! Bit n°8 et 7 | Doivent valoir 00, sont réservés pour une utilisation ultérieur dans de futurs standards. |- ! Bit n°6, 5, et 4 | Latence CAS (voir plus bas) |- ! Bit n°3 | Type de rafale : linéaire ou entrelacée |- ! Bit n°2, 3, et 0 | Longueur de la rafale : indique le nombre d'octets à lire/écrire lors d'une rafale. |} ===L'interface d'une mémoire SDRAM=== Le bus de commandes d'une SDRAM contient au moins 18 fils, dont celui pour le signal d'horloge. L'interface d'une SDRAM contient tous les bits présents sur une mémoire DRAM classique : une entrée RAS, une entrée CAS, une entrée R/W, et un bus d'adresse. A cela, il faut cependant ajouter une entrée ''Chip Select'' (CS), qui permet d'activer/désactiver la mémoire SDRAM. Je rappelle que le bit CS a été introduit sur les mémoires SDRAM, il n'était pas présent sur les mémoires FPM/EDO. Deux autres bits de commande sont vraiment spécifiques des mémoires SDRAM. Il s'agit des bits CKE et DQM. Le '''bit CKE''' est l'abréviation de ''Clock Enable'', qui qui trahit sa fonction. Lorsque ce signal est à 0, le chip de SDRAM voit son signal d'horloge gelè. S'il est à 0, le contrôleur de DRAM peut envoyer ce qu'il veut sur le bus de commande ou d'adresse, la SDRAM ne réagira pas du tout, il ne se passera rien. Le '''bit DQM''' est une sorte de bit ''Output Enable'', avec une nuance importante. Le terme DQM est l'abréviation de ''Data Mask'', ce qui trahit encore une fois sa fonction. Il y a un bit DQM pour chaque octet du bus de données. Une SDRAM ayant un bus de 64 bits, cela fait 8 bits DQM par mémoire SDRAM. Lorsque le bit DQM est à 1, l'octet en question n'est simplement pas lu ou écrit, le bus de donnée est déconnecté pour cet octet. Le bus d'adresse est particulier, car il tient compte de la présence de ''banques mémoires''. Le bus d'adresse est découpé en deux portions : une portion pour sélectionner la banque, une autre pour sélectionner l'adresse à l'intérieur d'une banque. L'interface de la SDRAM fait ainsi la différence entre une '''adresse de banque''' et une '''adresse intra-banque'''. L'adresse de banque est codée sur quelques bits, généralement deux ou trois suivant la SDRAM. Le reste de l'adresse est codé sur 11 bits sur les SDRAM, mais cela a augmenté avec les DDR 1, 2, 3, 4, 5. Le bus de données d'une SDRAM fait 4, 8, ou 16 bits. Je précise bien qu'il s'agit là des puces de SDRAM, les barrettes de SDRAM combinent plusieurs puces SDRAM avec un arrangement horizontal, qui peut combiner plusieurs puces de 8 bits pour alimenter un bus de données de 64 bits. La taille des puces utilisées souvent indiquée sur la barrette de RAM, avec une mention x4, x8 ou x16. Les puces de SDRAM les plus courantes ont une interface de 8 bits pour les données. Les SDRAM de 4 bits sont surtout utilisées pour les serveurs, c'est lié au support de l'ECC. les puces x16 sont moins utilisées car elles ont généralement moins de banques que les autres. ===Les commandes SDRAM=== Le bus de commande permet d'envoyer des commandes à la mémoire, chaque commande étant précisée par une combinaison précise des bits CS, RAS, CAS, R/W, et autres. Les commandes en question sont des demandes de lecture, d'écriture, de préchargement et autres. Elles sont codées par une valeur bien précise qui est envoyée sur les 18 fils du bus de commande. Ces commandes sont nommées READ, READA, WRITE, WRITEA, PRECHARGE, ACT, ... Les plus importantes sont les commandes PRECHARGE, ACT et READ/WRITE. La commande ACT sélectionne une ligne : elle met le bit RAS à zéro et présente une adresse de ligne. Les commandes READ et WRITE sélectionnent une colonne, et déclenchent respectivement une lecture ou une écriture. Elles précisent une adresse de colonne, mettent le bit CAS à 0 et le bit RAS à 1, et précise la valeur du bit R/W. Les commandes READ et WRITE ne peuvent se faire qu'une fois que la banque a été activée par une commande ACT. Il est possible d'envoyer plusieurs commandes READ ou WRITE successives à des colonnes différentes, ce qui permet d'implémenter les optimisations des mémoires FPM. La commande PRECHARGE ferme la ligne courante et prépare l'ouverture de la suivante. Elle précharge les lignes de bit de la RAM, d'où son nom. Il est nécessaire d'en envoyer une avant d'envoyer une commande ACT. Notons que la commande PRECHARGE agit sur une banque, dont l'adresse est indiquée dans la commande PRECHARGE. Il existe une commande PRECHARGE ALL, qui agit sur toutes les banques de la SDRAM à la fois. Les commandes READA et WRITEA fusionnent une commande READ/WRITE avec une commande PRECHARGE. Elles permettent d'éviter d'avoir à envoyer une commande PRECHARGE pour fermer la ligne courante. Au lieu d'envoyer une commande READ ou WRITE, puis une commande PRECHARGE pour fermer la ligne, on envoie une seule commande READA/WRITEA. Il s'agit d'une petite optimisation, qui permet de réduire le nombre de commandes envoyées sur le bus. Les commandes sont encodées comme indiquées dans ce tableau. Une commande est tout simplement encodée en précisant une adresse si nécessaire, et une combinaison des bits CS, RAS, CAS et R/W. La seule subtilité est que le bit numéro 10 du bus d'adresse sert à commander les opérations de PRECHARGE, y compris celles implicites dans les opérations READA et WRITEA. {| class="wikitable" style="text-align:center" ! Bit CS || Bit RAS || Bit CAS || Bit WE || Bits de sélection de banque (2 bits) || Bit du bus d'adresse A10 || Reste du bus d'adresse || Nom de la commande : Description |- | 1 | colspan="6" | X | Absence de commandes. |- | 0 || 1 || 1 || 1 || colspan="3" | X || No Operation : Pas d'opération |- | 0 || 1 || 1 || 0 || colspan="3" | X || Burst Terminante : Stoppe un accès en rafale (en cours). |- | 0 || 1 || 0 || 1 || Adresse de la banque || 0 || Adresse de la colonne || READ : lit une donnée depuis la ligne active. |- | 0 || 1 || 0 || 1 || Adresse de la banque || 1 || Adresse de la colonne || READA : lit une donnée depuis la ligne active, puis ferme la ligne. |- | 0 || 1 || 0 || 0 || Adresse de la banque || 0 || Adresse de la colonne || WRITE : écrit une donnée dans la ligne active. |- | 0 || 1 || 0 || 0 || Adresse de la banque || 1 || Adresse de la colonne || WRITEA : écrit une donnée dans la ligne active, puis ferme la ligne. |- | 0 || 0 || 1 || 1 || Adresse de la banque || colspan="2" | Adresse de la ligne || ACT : charge une ligne dans le tampon de ligne. |- | 0 || 0 || 1 || 0 || Adresse de la banque || 0 || X || PRECHARGE : précharge le tampon de ligne dans la banque voulue. |- | 0 || 0 || 1 || 0 || Adresse de la X || 1 || X || PRECHARGE ALL : précharge le tampon de ligne dans toutes les banques. |- | 0 || 0 || 0 || 1 || colspan="3" | X || Auto refresh : Demande de rafraichissement, gérée par la SDRAM. |- | 0 || 0 || 0 || 0 || 00 || colspan="2" | Nouveau contenu du registre de mode || LOAD MODE REGISTER : configure le registre de mode. |} Les commandes ACT se font à partir de l'état de repos, l'état où toutes les banques sont préchargées. Par contre, les commandes MODE REGISTER SET et AUTO REFRESH ne peuvent se faire que si toutes les banques sont désactivées. Le fonctionnement simplifié d'une SDRAM peut se résumer dans ce diagramme : [[File:Fonctionnement simplifié d'une SDRAM.jpg|centre|vignette|upright=2|Fonctionnement simplifié d'une SDRAM.]] ===Les délais mémoires=== Les mémoires SDRAM n'étant pas infiniment rapides, il y a toujours un certain délais à respecter entre deux commandes. Par exemple, quand on envoie une commande ACT pour activer une ligne, on ne peut pas envoyer une commande READ/WRITE au cycle suivant. La plupart des SDRAM ne sont pas assez rapides pour ça. Il faut respecter un délai de quelques cycles, qui dépend de la mémoire. Et il n'y a pas que ce délai entre une commande ACT et la commande suivante. Une SDRAM doit gérer d'autres temps d'attente, appelés des '''délais mémoires''', ou encore des ''timings'' mémoire. Les délais mémoire le plus importants sont résumés ci-dessous : {|class="wikitable" |- !Timing!!Description |- | colspan="2" | |- ! colspan="2" | Délais primaires |- ||tRP|| Temps entre une commande PRECHARGE et une commande ACT |- ||tRCD|| Temps entre une commande ACT et une commande READ/WRITE. |- ||tCL|| Temps entre une commande READ et l'envoi de la donnée lue sur le bus de données. |- ||tDQSS|| Temps entre une commande WRITE et l'écriture de la donnée. |- ||tCAS-to-CAS|| Temps minimum entre deux commandes READ. |- ! colspan="2" | Délais secondaires |- ||tWTR|| Temps entre une lecture et une écriture consécutives. |- ||tRAS || Temps entre une commande ACT et une commande PRECHARGE. |} La façon de mesurer ces délais varie : sur les mémoires FPM et EDO, on les mesure en unités de temps (secondes, millisecondes, micro-secondes, etc.), tandis qu'on les mesure en cycles d'horloge sur les mémoires SDRAM. Les délais/timings mémoire ne sont pas les mêmes suivant la barrette de mémoire que vous achetez. Certaines mémoires sont ainsi conçues pour avoir des timings assez bas et sont donc plus rapides, et surtout : beaucoup plus chères que les autres. Le gain en performances dépend beaucoup du processeur utilisé et est assez minime comparé au prix de ces barrettes. Les délais mémoires d'une barrette sont mémorisés dans le ''Serial Presence Detect'' de la barrette et sont lus par le BIOS au démarrage de l'ordinateur, et sont ensuite utilisés pour configurer le contrôleur de DRAM. ===Les commandes anticipées=== Les SDRAM sont parfois capables de démarrer une commande avant que la précédente soit terminée. Concrètement, pendant que la commande précédente envoie sa dernière donnée sur le bus de données, on peut envoyer la commande suivante avec quelques cycles d'avance. L'exemple ci-dessous devrait être assez clair : on envoie une seconde commande au neuvième cycle, alors qu'une rafale est en cours. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | || bgcolor="#A0FFFF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || || || bgcolor="#FFA0FF" | READ (2) || || || || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 | bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 | |} Il s'agit d'une forme très limitée de pipeline, tellement limitée qu'on peut légitimement douter que c'est un vrai pipeline. Dans ce qui suit, j'ai décidé d'appeler cette possibilité sous le terme de '''commandes SDRAM anticipées'''. La possibilité est très limitée, car il faut tenir compte des délais mémoire. Elle améliore un peu les performances dans certaines circonstances où la RAM doit traiter plusieurs accès mémoire consécutifs, très rapprochés. L'exemple typique est celui du transfert d'un bloc de données entre mémoire cache et mémoire RAM, qui dépasse la taille d'une rafale, qui envoie plusieurs accès mémoire d'un seul coup au contrôleur mémoire. Mais d'autres exemples sont possibles, on ne peut juste pas les expliquer à ce stade du cours. Les commandes SDRAM anticipées sont possibles car les SDRAM sont formées en entourant une RAM asynchrone de registres, exactement comme les SRAM synchrones. Il est possible d'écrire dans les registres de données/commandes, pendant qu'un autre accès mémoire accède au cœur asynchrone. Les délais mémoire sont conçus pour éviter qu'une commande accède au cœur asynchrone en même temps que la suivante ou la précédente, idem pour l'usage des registres. C'est pour cela que les délais mémoire sont assez différents entre écritures et lectures, d'ailleurs. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Enregistrement de la commande dans le registre d'adresse/commande | bgcolor="#A0FFFF" | READ (1) || || || || bgcolor="#FFA0FF" | READ (2) || || || || || |- ! Accès au cœur asynchrone | || bgcolor="#A0FFFF" | READ (1) || bgcolor="#A0FFFF" | READ (1) || bgcolor="#A0FFFF" | READ (1) || || bgcolor="#FFA0FF" | READ (2) || bgcolor="#FFA0FF" | READ (2) || bgcolor="#FFA0FF" | READ (2) || || || |- ! Lecture/écriture du registre de données | || || || || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 | bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 | |} Les DDR2 et 3 vont encore plus loin avec l'optimisation des '''CAS postés'''. L'idée est que le contrôleur mémoire peut envoyer une commande READ/WRITE sans se soucier des ''timings''. En théorie, les deux commandes doivent être séparées par quelques cycles, sur une SDRAM ou une DDR1. Mais avec la DDR2, le contrôleur mémoire peut envoyer les deux l'une après l'autre, au cycle suivant. C'est la mémoire qui mettra en attente la commande READ/WRITE pour respecter les ''timings'' mémoire. Cela complexifie le fonctionnement interne de la DDR, mais simplifie grandement le travail du contrôleur mémoire. ==Les mémoires DDR== Les mémoires SDRAM récentes sont des mémoires de type ''dual data rate'', ce qui fait qu'elles portent le nom de mémoires DDR. Pour rappel, les mémoires ''dual data rate'' ont un plan mémoire deux fois plus large que le bus mémoire, avec un bus mémoire allant à une fréquence double. Par double, on veut dire que les transferts sur le bus mémoire ont lieu sur les fronts montants et descendants de l'horloge. Il y a donc deux transferts de données sur le bus pour chaque cycle d'horloge, ce qui permet de doubler le débit sans toucher à la fréquence du plan mémoire lui-même. Les mémoires DDR sont standardisées par un organisme international, le JEDEC, et ont été déclinées en plusieurs générations : DDR1, DDR2, DDR3, et DDR4. La différence entre ces modèles sont très nombreuses, mais les plus évidentes sont la fréquence de la mémoire et du bus mémoire. D'autres différences mineures existent entre les SDRAM et les mémoires DDR. Par exemple, la tension d'alimentation des mémoires DDR est plus faible que pour les SDRAM. ET elle a diminué dans le temps, d'une génération de DDR à l'autre. Avec les mémoires DDR2,la tension d'alimentation est passée de 2,5/2,6 Volts à 1,8 Volts. Avec les mémoires DDR3, la tension d'alimentation est notamment passée à 1,5 Volts. ===Les performances des mémoires DDR=== Les mémoires SDRAM ont évolué dans le temps, mais leur temps d'accès/fréquence n'a pas beaucoup changé. Il valait environ 10 nanosecondes pour les SDRAM, approximativement 5 ns pour la DDR-400, il a peu évolué pendant la génération DDR et DDR3, avant d'augmenter pendant les générations DDR4 et de stagner à nouveau pour la génération DDR5. L'usage du DDR, puis du QDR, visait à augmenter les performances malgré la stagnation des temps d'accès. En conséquence, la fréquence du bus a augmenté plus vite que celle des puces mémoire pour compenser. {|class="wikitable" |- ! Année ! Type de mémoire ! Fréquence de la mémoire (haut de gamme) ! Fréquence du bus ! Coefficient multiplicateur entre les deux fréquences |- | 1998 | DDR 1 | 100 - 200 MHz | 200 - 400 MHz | 2 |- | 2003 | DDR 2 | 100 - 266 MHz | 400 - 1066 MHz | 4 |- | 2007 | DDR 3 | 100 - 266 MHz | 800 - 2133 MHz | 8 |- | 2014 | DDR 4 | 200 - 400 MHz | 1600 - 3200 MHz | 8 |- | 2020 | DDR 5 | 200 - 450 MHz | 3200 - 7200 MHz | 8 à 16 |} Une conséquence est que la latence CAS, exprimée en nombre de cycles, a augmenté avec le temps. Si vous comparez des mémoires DDR2 avec une DDR4, par exemple, vous allez voir que la latence CAS est plus élevée pour la DDR4. Mais c'est parce que la latence est exprimée en nombre de cycles d'horloge, et que la fréquence a augmentée. En comparant les temps d'accès exprimés en secondes, on voit une amélioration. ===Les commandes des mémoires DDR=== Les commandes des mémoires DDR sont globalement les mêmes que celles des mémoires SDRAM, vues plus haut. Les modifications entre SDRAM, DDR1, DDR2, DDR3, DDR4, et DDR5 sont assez mineures. Les seules différences sont l'addition de bits pour la transmission des adresses, des bits en plus pour la sélection des banques, etc. En clair, une simple augmentation quantitative. Le registre de mode a été un peu modifié. Il est passé de 10 bits pour les SDRAM et DDR1, à 13 bits sur la DDR 2 et les suivantes. Les DDR ont aussi ajouté le support de plusieurs registres de mode, qui sont sélectionnés en réutilisant l'adresse de banque. Dans une commande LOAD MODE REGISTER, l'adresse de banque indique quel registre de mode il faut altérer. Avant la DDR4, les modifications des commandes sont mineures. La DDR2 supprime la commande ''Burst Terminate'', la DDR3 et la DDR4 utilisent le bit A12 pour préciser s'il faut faire une rafale complète, ou une rafale de moitié moins de données. Une optimisation des DDR2 et 3 est celle des '''CAS postés'''. L'idée est que le contrôleur de DRAM peut envoyer une commande ACT et une commande READ/WRITE sans se soucier des ''timings'' nécessaires entre les deux. En théorie, les deux commandes doivent être séparées par quelques cycles, sur une SDRAM ou une DDR1. Mais avec la DDR2, le contrôleur de DRAM peut envoyer les deux l'une après l'autre, au cycle suivant. C'est la mémoire qui mettra en attente la commande READ/WRITE pour respecter les ''timings'' mémoire. Cela complexifie le fonctionnement interne de la DDR, mais simplifie grandement le travail du contrôleur de DRAM. Mais avec la DDR4, les choses changent, notamment au niveau de la commande ACT. Avec l'augmentation de la capacité des barrettes mémoires, la taille des adresses est devenue trop importante. Pour éviter de rajouter des bits d'adresses, les concepteurs du standard DDR4 ont décidé de ruser. Lors d'une commande ACT, les bits RAS, CAS et WE sont utilisés comme bits d'adresse, alors qu'ils ont leur signification normale pour les autres commandes. Pour éviter toute confusion, un nouveau bit ACT est ajouté pour indiquer la présence d'une commande ACT : il est à 1 pour une commande ACT, 0 pour les autres commandes. {| class="wikitable" style="text-align:center" |+ Commandes d'une mémoire DDR4, seule la commande colorée change par rapport aux SDRAM ! Bit CS || style="background: #CCFFCC" | Bit ACT || Bit RAS || Bit CAS || Bit WE || Bits de sélection de banque (4 bits) || Bit du bas d'adresse A10 || Reste du bus d'adresse || Nom de la commande : Description |- | 1 | colspan="6" | X | Absence de commandes. |- | 0 || style="background: #CCFFCC" | 0 || 1 || 1 || 1 || colspan="3" | X || No Operation : Pas d'opération |- | 0 || style="background: #CCFFCC" | 0 || 1 || 1 || 0 || colspan="3" | X || Burst Terminante : Arrêt d'un accès en rafale en cours. |- | 0 || style="background: #CCFFCC" | 0 || 1 || 0 || 1 || Adresse de la banque || 0 || Adresse de la colonne || READ : lire une donnée depuis la ligne active. |- | 0 || style="background: #CCFFCC" | 0 || 1 || 0 || 1 || Adresse de la banque || 1 || Adresse de la colonne || READA : lire une donnée depuis la ligne active, avec rafraichissement automatique de la ligne. |- | 0 || style="background: #CCFFCC" | 0 || 1 || 0 || 0 || Adresse de la banque || 0 || Adresse de la colonne || WRITE : écrire une donnée depuis la ligne active. |- | 0 || style="background: #CCFFCC" | 0 || 1 || 0 || 0 || Adresse de la banque || 1 || Adresse de la colonne || WRITEA : écrire une donnée depuis la ligne active, avec rafraichissement automatique de la ligne. |- style="background: #CCFFCC" | 0 || style="background: #CCFFCC" | 1 || colspan="3" | Adresse de la ligne (bits de poids forts) || Adresse de la banque || colspan="2" | Adresse de la ligne (bits de poids faible) || ACT : charge une ligne dans le tampon de ligne. |- | 0 || style="background: #CCFFCC" | 0 || 0 || 1 || 0 || Adresse de la banque || 0 || X || PRECHARGE : précharge le tampon de ligne dans la banque voulue. |- | 0 || style="background: #CCFFCC" | 0 || 0 || 1 || 0 || Adresse de la X || 1 || X || PRECHARGE ALL : précharge le tampon de ligne' dans toutes les banques. |- | 0 || style="background: #CCFFCC" | 0 || 0 || 0 || 1 || colspan="3" | X || Auto refresh : Demande de rafraichissement, gérée par la SDRAM. |- | 0 || style="background: #CCFFCC" | 0 || 0 || 0 || 0 || Numéro de registre de mode || colspan="2" | Nouveau contenu du registre de mode || LOAD MODE REGISTER : configure le registre de mode. |} ==Les VRAM des cartes vidéo== Les cartes graphiques ont des besoins légèrement différents des DRAM des processeurs, ce qui fait qu'il existe des mémoires DRAM qui leur sont dédiées. Elles sont appelés des '''''Graphics RAM''''' (GRAM). La plupart incorporent des fonctionnalités utiles uniquement pour les mémoires vidéos, comme des fonctionnalités de masquage (appliquer un masque aux données lue ou à écrire), ou le remplissage d'un bloc de mémoire avec une donnée unique. Les anciennes cartes graphiques et les anciennes consoles utilisaient de la DRAM normale, faute de mieux. La première GRAM utilisée était la NEC μPD481850, qui a été utilisée sur la console de jeu PlayStation, à partir de son modèle SCPH-5000. D'autres modèles de GRAM ont rapidement suivi. Les anciennes consoles de jeu, mais aussi des cartes graphiquesn utilisaient des GRAM spécifiques. ===Les mémoires vidéo double port=== Sur les premières consoles de jeu et les premières cartes graphiques, le ''framebuffer'' était mémorisé dans une mémoire vidéo spécialisée appelée une '''mémoire vidéo double port'''. Le premier port était connecté au processeur ou à la carte graphique, alors que le second port était connecté à un écran CRT. Aussi, nous appellerons ces deux port le ''port CPU/GPU'' et l'autre sera appelé le ''port CRT''. Le premier port était utilisé pour enregistrer l'image à calculer et faire les calculs, alors que le second port était utilisé pour envoyer à l'écran l'image à afficher. Le port CPU/GPU est tout ce qu'il y a de plus normal : on peut lire ou écrire des données, en précisant l'adresse mémoire de la donnée, rien de compliqué. Le port CRT est assez original : il permet d'envoyer un paquet de données bit par bit. De telles mémoires étaient des mémoires à tampon de ligne, dont le support de mémorisation était organisé en ligne et colonnes. Une ligne à l'intérieur de la mémoire correspond à une ligne de pixel à l'écran, ce qui se marie bien avec le fait que les anciens écrans CRT affichaient les images ligne par ligne. L'envoi d'une ligne à l'écran se fait bit par bit, sur un câble assez simple comme un câble VGA ou autre. Le second port permettait de faire cela automatiquement, en permettant de lire une ligne bit par bit, les bits étant envoyés l'un après l'autre automatiquement. Pour cela, les mémoires vidéo double port incorporaient un tampon de ligne spécialisé pour le port lié à l'écran. Ce tampon de ligne n'était autre qu'un registre à décalage, contrairement au tampon de ligne normal. Lors de l'accès au second port, la carte graphique fournissait un numéro de ligne et la ligne était chargée dans le tampon de ligne associé à l'écran. La carte graphique envoyait un signal d'horloge de même fréquence que l'écran, qui commandait le tampon de ligne à décalage : un bit sortait à chaque cycle d'écran et les bits étaient envoyé dans le bon ordre. ===Les mémoires SGRAM et GDDR=== De nos jours, les cartes graphiques n'utilisent plus de mémoires double port, mais des mémoires simple port. Les mémoires graphiques actuelles sont des SDRAM modifiées pour fonctionner en tant que ''Graphic RAM''. Les plus connues sont les '''mémoires GDDR''', pour ''graphics double data rate'', utilisées presque exclusivement sur les cartes graphiques. Il en existe plusieurs types pendant que j'écris ce tutoriel : GDDR, GDDR2, GDDR3, GDDR4, et GDDR5. Mais attention, il y a des différences avec les DDR normales. Par exemple, les GDDR ont une fréquence plus élevée que les DDR normales, avec des temps d'accès plus élevés (sauf pour le tCAS). De plus, elles sont capables de laisser ouvertes deux lignes en même temps. Par contre, ce sont des mémoires simple port. ==Les mémoires SLDRAM, RDRAM et associées== Les mémoires précédentes sont généralement associées à des bus larges. Les mémoires SDRAM et DDR modernes ont des bus de données de 64 bits de large, avec des d'adresse et de commande de largeur similaire. Le nombre de fils du bus mémoire dépasse facilement la centaine de fils, avec autant de broches sur les barrettes de mémoire. Largeur de ces bus pose de problèmes problèmes électriques, dont la résolution n'est pas triviale. En conséquence, la fréquence du bus mémoire est généralement moins performantes comparé à ce qu'on aurait avec un bus moins large. Mais d'autres mémoires DRAM ont exploré une solution alternative : avoir un bus peu large mais de haute fréquence, sur lequel on envoie les commandes/données en plusieurs fois. Elles sont regroupées sous le nom de '''DRAM à commutation par paquets'''. Elles utilisent des bus spéciaux, où les commandes/adresses/données sont transmises par paquets, par trames, en plusieurs fois. En théorie, ce qu'on a dit sur le codage des trames dans le chapitre sur le bus devrait s'appliquer à de telles mémoires. En pratique, les protocoles de transmission sur le bus mémoire sont simplifiés, pour gérer le fonctionnement à haute fréquence. Le processeur envoie des paquets de commandes, les mémoires répondent avec des paquets de données ou des accusés de réception. Les mémoires à commutation par paquets sont peu nombreuses. Les plus connues sont les mémoires conçues par la société Rambus, à savoir la ''RDRAM'' (''Rambus DRAM'') et ses deux successeurs ''XDR RAM'' et ''XDR RAM 2''. La ''Synchronous-link DRAM'' (''SLDRAM'') est un format concurrent conçu par un consortium de plusieurs concepteurs de mémoire. ===La SLDRAM (''Synchronous-link DRAM'')=== Les '''mémoires SLDRAM''' avaient un bus de données de 64 bits allant à 200-400 Hz, avec technologie DDR, ce qui était dans la norme de l'époque pour la fréquence (début des années 2000). Elle utilisait un bus de commande de 11 bits, qui était utilisé pour transmettre des commandes de 40 bits, transmises en quatre cycles d'horloge consécutifs (en réalité, quatre fronts d'horloge donc deux cycles en DDR). Le bus de données était de 18 bits, mais les transferts de donnée se faisaient par paquets de 4 à 8 octets (32-65 bits). Pour résumer, données et commandes sont chacunes transmises en plusieurs cycles consécutifs, sur un bus de commande/données plus court que les données/commandes elle-mêmes. Là où les SDRAM sélectionnent la bonne barrette grâce à des signaux de commande dédiés, ce n'est pas le cas avec la SLDRAM. A la place, chaque barrette de mémoire reçoit un identifiant, un numéro codé sur 7-8 bits. Les commandes de lecture/écriture précisent l'identifiant dans la commande. Toutes les barrettes reçoivent la commande, elles vérifient si l'identifiant de la commande est le leur, et elles la prennent en compte seulement si c'est le cas. Voici le format d'une commande SLDRAM. Elle contient l'adresse, qui regroupe le numéro de banque, le numéro de ligne et le numéro de colonne. On trouve aussi un code commande qui indique s'il faut faire une lecture ou une écriture, et qui configure l'accès mémoire. Il configure notamment le mode rafale, en indiquant s'il faut lire/écrire 4 ou 8 octets. Enfin, il indique s'il faut fermer la ligne accédée une fois l'accès terminé, ou s'il faut la laisser ouverte. Le code commande peut aussi préciser que la commande est un rafraichissement ou non, effectuer des opérations de configuration, etc. L'identifiant de barrette mémoire est envoyé en premier, histoire que les barrettes sachent précocement si l'accès les concerne ou non. {|class="wikitable" style="text-align:center" |+SLDRAM Read, write or row op request packet ! FLAG || CA9 || CA8 || CA7 || CA6 || CA5 || CA4 || CA3 || CA2 || CA1 || CA0 |- ! 1 | colspan=9 bgcolor=#ffcccc| Identifiant de barrette mémoire|| bgcolor=#ccffcc| Code de commande |- ! 0 | colspan=5 bgcolor=#ccffcc| Code de commande ||colspan=3 bgcolor=#ff88ff| Banque||colspan=2 bgcolor=#ffffcc| Ligne |- ! 0 | colspan=9 bgcolor=#ffffcc| Ligne || 0 |- ! 0 | 0 || 0 || 0 ||colspan=7 bgcolor=#ccffff| Colonne |} ===Les mémoires Rambus=== Les mémoires conçues par la société Rambus regroupent la '''RDRAM''' (''Rambus DRAM'') et ses deux successeurs '''XDR RAM''' et '''XDR RAM 2'''. Les toutes premières étaient les '''mémoires RDRAM''', où le bus permettait de transmettre soit des commandes (adresse inclue), soit des données, avec un multiplexage total. Le processeur envoie un paquet contenant commandes et adresse à la mémoire, qui répond avec un paquet d'acquittement. Lors d'une lecture, le paquet d'acquittement contient la donnée lue. Lors d'une écriture, le paquet d'acquittement est réduit au strict minimum. Le bus de commandes est réduit au strict minimum, à savoir l'horloge et quelques bits absolument essentiels, les bits RW est transmis dans un paquet et n'ont pas de ligne dédiée, pareil pour le bit OE. Toutes les barrettes de mémoire doivent vérifier toutes les transmissions et déterminer si elles sont concernées en analysant l'adresse transmise dans la trame. Elles ont été utilisées dans des PC ou d'anciennes consoles de jeu. Par exemple, la Nintendo 64 incorporait 4 mébioctets de mémoire RDRAM en tant que mémoire principale. La RDRAM de la Nintendo 64 était cadencée à 500 MHz, utilisait un bus de 9 bits, et avait un débit binaire maximal théorique de 500 MB/s. La Playstation 2 contenait quant à elle 32 mébioctets de RDRAM en ''dual-channel'', pour un débit binaire de 3.2 Gibioctets par seconde. Les processeurs Pentium 3 pouvaient être associés à de la RDRAM sur certaines mères. Les Pentium 4 étaient eux aussi associés à la de RDRAM, mais les cartes mères ne géraient que ce genre de mémoire. La Playstation 3 contenait quant à elle de la XDR RAM. ==Les eDRAM : des DRAM adaptées aux ''chiplets''== Les '''mémoires eDRAM''', pour ''embedded DRAM'', sont des mémoires RAM qui sont destinées à être intégrée au processeur. Pour comparer, les DRAM normales sont placées sur des barrettes de RAM ou soudées à la carte mère. Dans la quasi-totalité des cas, l'eDRAM est utilisée pour implémenter une mémoire cache, elle ne sert pas de mémoire principale (cache L4, le plus proche de la mémoire sur ces puces). De ce fait, elles sont conçues pour être très rapides, avoir une grande bande passante, au détriment de leur capacité mémoire. Pour être plus précis, l'eDRAM est une puce de DRAM conçue pour être intégrée dans un ''chiplet'', , à savoir des circuits imprimés qui regroupent plusieurs puces électroniques distinctes, regroupées sur le même PCB. Typiquement, un processeur de type ''chiplet'' avec de l'eDRAM comprend deux puces séparées : une pour le processeur, une autre pour une puce de communication avec la RAM. Avec la mémoire eDRAM, les deux puces sont complétées par une troisième puce spécialisée qui incorpore l'eDRAM. Elle a été utilisée sur quelques processeurs, mais aussi dans des consoles de jeu vidéo, pour la carte graphique des consoles suivantes : la PlayStation 2, la PlayStation Portable, la GameCube, la Wii, la Wii U, et la XBOX 360. Sur ces consoles, la RAM de la carte graphique était intégrée avec le processeur graphique dans le même circuit. La fameuse mémoire vidéo et le GPU n'étaient qu'une seule et même puce électronique, un seul circuit intégré. Ce n'est pas le cas sur une carte graphique moderne : regardez votre carte graphique avec attention et vous verrez que le GPU est une puce carrée située sous les ventilateurs, alors que les puces mémoires sont situées juste autour et soudées sur le PCB de la carte. Les processeurs Intel Core de microarchitecture Broadwell disposaient d'un cache L4 de 128 mébioctets, intégralement implémenté avec de la mémoire eDRAM. Quelques processeurs de la microarchitecture précédente (Haswell), disposaient aussi de ce cache. Le cache L4 eDRAM était implémenté sur un chiplet à part, à savoir que le processeur était composé de trois puces séparées : une pour le processeur, une autre pour la gestion des entrées-sorties, et une autre pour le cache L4. La puce pour le cache L4 était appelée ''Crystal Well''. La puce ''Crystal Well'' était une puce gravée en 22nm, ce qui était une finesse de gravure plus élevée que celle des processeurs associés. ''Crystal Well'' était très optimisé pour l'époque. Par exemple, elle disposait de bus séparées pour la lecture et l'écriture, chose qu'on retrouve fréquemment sur les SRAM mais qui est absent sur les mémoires DRAM actuelles. Pour le reste, elle ressemblait beaucoup aux mémoires DDR de l'époque (système de ''double data rate'', entres autres), mais elle allait à une fréquence plus élevée que les DRAM de l'époque et avait un débit bien plus élevé, pour une consommation moindre. ''Crystal Well'' consommait entre 1 à 5 watts (1 watt en veille, 5 à pleine utilisation), pour un débit binaire de 102 GB/s et fonctionnait à 3.2 GHz. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les mémoires SRAM synchrones | prevText=Les mémoires SRAM synchrones | next=Contrôleur mémoire externe | nextText=Le contrôleur mémoire externe }}{{autocat}} </noinclude> s9euy2ciqxg242wefzwjwqab7rq6wdg 764702 764680 2026-04-23T20:43:31Z Mewtow 31375 /* Les commandes SDRAM */ 764702 wikitext text/x-wiki Après avoir vu les mémoires statiques (SRAM), il est temps de passer aux mémoires RAM dynamiques, aussi appelées mémoires DRAM. Pour rappel, les DRAM dynamiques ont pour défaut que leurss données s'effacent après un certain temps, en quelques millièmes ou centièmes de secondes . En conséquence, il faut réécrire chaque bit de la mémoire régulièrement pour éviter qu'il ne s'efface. On dit qu'on doit effectuer régulièrement un '''rafraîchissement mémoire'''. Et celui-ci rend les DRAM très différentes des SRAM. Les DRAM des PC ont beaucoup évolués dans le temps. Les toutes premières mémoires DRAM étaient des mémoires asynchrones, mais elles ont été remplacées par des modèles synchrones. Les modèles asynchrones ont été très nombreux. Après l'apparition des premières DRAM, les mémoires ''Fast Page Mode'' sont apparues, suivies par les mémoires ''Extended Data Out'', puis les EDO en mode rafale. Elles ont été utilisées jusque dans la moitié des années 90, pour ensuite être supplantées par les mémoires SDRAM. Les mémoires DDR actuelles sont des améliorations des mémoires SDRAM actuelles. Le fait est que les DRAM sont des mémoires électroniques comme les autres, qui se présentent sous la forme de circuits intégrés, à savoir que ce sont des petits boitiers noirs avec des broches. Il est possible de souder ces boitiers sur une cartre mère, et c'est ce qui est fait sur nombre d'ordinateurs portables. Mais il est aussi possible de regrouper plusieurs boitiers sur une barrette de RAM séparée. Dans ce qui suit, nous les appellerons des '''chips mémoire''', ou encore, des puces mémoires. [[File:Canyon CN-WF514 - EtronTech EM638325TS-6-4022.jpg|centre|vignette|upright=2|Exemple de chip mémoire.]] Dans ce qui suit, nous allons étudier ces chips de DRAM, avant de voir comment ils sont regroupés sur une barrette de RAM. Puis, nous allons voire chaque type de mémoire, FPM, EDO, SDRAM, DDR, ... ; un par un. ==L'interface des DRAM et le contrôleur mémoire== L'interface d'une mémoire DRAM est plus compliquée que l'interface d'une SRAM basique. Et c'est suffisant pour qu'on ait besoin d'un intermédiaire pour faire la conversion entre processeur et DRAM. Les DRAM modernes ne sont pas connectées directement au processeur, mais le sont par l'intermédiaire d'un '''contrôleur mémoire externe'''. Il ne faut pas le confondre avec le contrôleur mémoire interne, placé dans la mémoire RAM, et qui contient notamment le décodeur. Les deux sont totalement différents, bien que leur nom soit similaire. Pour éviter toute confusion, j'utiliserais le terme de '''contrôleur de DRAM''', plus parlant. ===Le bus d'adresse des DRAM est multiplexé=== Un point important pour le contrôleur de DRAM est de transformer les adresses mémoires fournies par le processeur, en adresses utilisables par la DRAM. Car les DRAM ont une interface assez spécifique. Les DRAM ont ce qui s'appelle un '''bus d'adresse multiplexé'''. Avec de tels bus, l'adresse est envoyée en deux fois. Les bits de poids fort sont envoyés avant les bits de poids faible. On peut ainsi envoyer une adresse de 32 bits sur un bus d'adresse de 16 bits, par exemple. Le bus d'adresse contient alors environ moitié moins de fils que la normale. Pour rappel, l'avantage de cette méthode est qu'elle permet de limiter le nombre de fils du bus d'adresse, ce qui très intéressant sur les mémoires de grande capacité. Les mémoires DRAM étant utilisées comme mémoire principale d'un ordinateur, elles devaient avoir une grande capacité. Cependant, avoir un petit nombre de broches sur les barrettes de mémoire est clairement important, ce qui impose d'utiliser des stratagèmes. Envoyer l'adresse en deux fois répond parfaitement à ce problème : cela permet d'avoir des adresses larges et donc des mémoires de forte capacité, avec une performance acceptable et peu de fils sur le bus d'adresse. Les bus multiplexés se marient bien avec le fait que les DRAM sont des mémoires à adressage par coïncidence ou à tampon de ligne. Sur ces mémoires, l'adresse est découpée en deux : une adresse haute pour sélectionner la ligne, et une adresse basse qui sélectionne la colonne. L'adresse est envoyée en deux fois : la ligne, puis la colonne. Pour savoir si une donnée envoyée sur le bus d'adresse est une adresse de ligne ou de colonne, le bus de commande de ces mémoires contenait deux fils bien particuliers : les RAS et le CAS. Pour simplifier, le signal RAS permettait de sélectionner une ligne, et le signal CAS permettait de sélectionner une colonne. [[File:Signaux RAS et CAS.png|centre|vignette|upright=2|Signaux RAS et CAS.]] Si on a deux bits RAS et CAS, c'est parce que la mémoire prend en compte les signaux RAS et CAS quand ils passent de 1 à 0. C'est à ce moment là que la ligne ou colonne dont l'adresse est sur le bus sera sélectionnée. Tant que des signaux sont à zéro, la ligne ou colonne reste sélectionnée : on peut changer l'adresse sur le bus, cela ne désélectionnera pas la ligne ou la colonne et la valeur présente lors du front descendant est conservée. [[File:L'intérieur d'une FPM.png|centre|vignette|upright=2|L'intérieur d'une FPM.]] ===Le rafraichissement mémoire=== La spécificité des DRAM est qu'elles doivent être rafraichies régulièrement, sans quoi leurs cellules perdent leurs données. Le rafraichissement est basiquement une lecture camouflée. Elle lit les cellules mémoires, mais n'envoie pas le contenu lu sur le bus de données. Rappelons que la lecture sur une DRAM est destructive, à savoir qu'elle vide la cellule mémoire, mais que le système d'amplification de lecture régénère le contenu de la cellule automatiquement. La cellule est donc rafraichie automatiquement lors d'une lecture. La quasi-totalité des DRAM supporte des commandes de rafraichissement, séparées des lectures et écritures classiques. Une commande de rafraichissement ordonne de rafraichir une adresse, voire une ligne complète. Les commandes de rafraichissement sont générées par le contrôleur de DRAM, dans la grosse majorité des cas. Il est aussi possible que ce soit le processeur qui les génère, mais c'est beaucoup plus rare. Il est aussi possible d'envoyer des commandes de rafraichissement vides, qui ne précisent ni adresse ni numéro de ligne. Pour les gérer, la mémoire contient un compteur, qui pointe sur la prochaine ligne à rafraichir, qui est incrémenté à chaque commande de rafraichissement. Une commande de rafraichissement indique à la mémoire d'utiliser l'adresse dans ce compteur pour savoir quelle adresse/ligne rafraichir. [[File:Rafraichissement mémoire automatique.png|centre|vignette|upright=2|Rafraichissement mémoire automatique.]] Il existe des mémoires qui sont des intermédiaires entre les mémoires SRAM et DRAM. Il s'agit des '''mémoires pseudo-statiques''', qui sont techniquement des mémoires DRAM, utilisant des transistors et des condensateurs, mais qui gèrent leur rafraichissement mémoire toutes seules. Le rafraichissement mémoire est alors totalement automatique, ni le processeur, ni le contrôleur de DRAM ne devant s'en charger. Le rafraichissement est purement le fait des circuits de la mémoire RAM et devient une simple opération de maintenance interne, gérée par la RAM elle-même. L'envoi des commandes de rafraichissement peuvent se faire de deux manières : soit on les envoie toutes en même temps, soit on les disperse le plus possible. Le premier cas est un '''rafraichissement en rafale''', le second un '''rafraichissement étalé'''. Le rafraichissement en rafale n'est pas utilisé dans les PC, car il bloque la mémoire pendant un temps assez long. Mais les anciennes consoles de jeu gagnaient parfois à utiliser eu rafraichissement en rafale. En effet, la mémoire était souvent effacée entre l'affichage de deux images, pour éviter certains problèmes dont on ne parlera pas ici. Le rafraichissement de la mémoire était effectué à ce moment là : l'effacement rafraichissait la mémoire. Le temps mis pour rafraichir la mémoire est le temps mis pour parcourir toute la mémoire. Il s'agit du temps de balayage vu dans le chapitre sur les performances d'un ordinateur. Pour les mémoires FPM et EDO, il est défini en divisant la capacité de la mémoire par son débit binaire. C'est le temps nécessaire pour lire ou réécrire tout le contenu de la mémoire. Sur les SDRAM, les choses sont un peu différentes, pour une raison qu'on expliquera plus bas. ===Le contrôleur de DRAM=== Le contrôleur de DRAM gère le bus mémoire et tout ce qui est envoyé dessus. Il envoie des commandes aux barrettes de mémoire, commandes qui peuvent être des lectures, des écritures, ou des demandes de rafraichissement, parfois d'autres commandes. La mémoire répond à ces commandes par l'action adéquate : lire la donnée et la placer sur le bus de données pour une commande de lecture, par exemple. Le rôle du contrôleur de DRAM varie grandement suivant le contrôleur en question, ainsi que selon le type de DRAM. Les anciens contrôleurs de DRAM étaient des composants séparés du processeur, du ''chipset'' ou du reste de la carte mère. Par exemple, les contrôleur de DRAM Intel 8202, Intel 8203 et Intel 8207 étaient vendus dans des boitiers DIP et étaient soudés sur la carte mère. Par la suite, ils ont été intégré au ''chipset'' de la carte mère pendant les décennies 90-2000. Après les années 2000, ils ont été intégrés dans les processeurs. Il est possible de connecter plusieurs barrettes sur le même bus mémoire, ou alors celles-ci sont connectées au contrôleur de DRAM avec un bus par barrette/boitier. C'est ce qui permet de placer plusieurs barrettes de mémoire sur la même carte mère : toutes les barrettes sont connectées au contrôleur de DRAM d'une manière ou d'une autre. ==Les rangées : l'arrangement horizontal et vertical== Il est rare d'utiliser un chip mémoire seul, car ceux-ci n'ont pas une capacité suffisante. Pour donner quelques chiffres, à l'heure où j'écris ces lignes, la norme pour un ordinateur est d'avoir entre 8 et 64 gibioctets de RAM. Mais les chips mémoire font entre 1 et 4 gibioctets, rarement plus. La raison est que les ordinateurs combinent ensemble plusieurs chips mémoire pour additionner leurs capacités. La concaténation de plusieurs chips mémoire peut se faire de deux manières différentes, appelées l'arrangement horizontal et l'arrangement vertical. Les deux additionnent la capacité des chips mémoire, mais se distinguent sur un point : ce qui arrive respectivement au bus de données, et au nombre d'adresses. Intuitivement, on se dit que doubler la capacité mémoire implique de doubler le nombre d'adresses mémoire. C'est effectivement ce qui se passe avec l'arrangement vertical. Mais avec l'arrangement horizontal, le nombre d'adresse ne varie pas. Voyons cela en détail, et commençons par le cas le plus simple, celui de l'arrangement vertical seul. ===L'arrangement vertical : cumuler des adresses mémoire=== Introduisons l'arrangement vertical par un exemple. Imaginez que je souhaite obtenir de 4 mébioctets de RAM, en combinant 4 chips mémoires de 1 mébioctet chacun. L'idée est que le premier mébioctet est placé dans le premier chip mémoire, le second mébioctet dans le second chip, etc. Des adresses consécutives se trouvent ainsi dans le même chip mémoire, sauf pour quelques adresses où on passe d'un chip à l'autre. Avec cette organisation, le bus de donnée fait un octet, et les chips mémoire ont aussi un bus de données d'un octet. Je peux alors combiner les capacités de plusieurs chips mémoire, sans toucher au bus de données. [[File:Répartition des adresses sans entrelacement.png|centre|vignette|upright=1.5|Répartition des adresses avec arrangement vertical.]] Pour sélectionner le chip mémoire adéquat, il faut que chaque chip mémoire dispose d'une entrée ''Chip Select'', qui permet de l'activer ou de le désactiver. L'idée est que selon l'adresse demandée, on active le chip mémoire associé à cette adresse. Les signaux ''Chip Select'' sont générés par le contrôleur de DRAM, à partir de l'adresse. On dit qu'il y a un '''décodage d'adresse'''. Avec cet arrangement, les bits de poids fort de l'adresse sont utilisées pour sélectionner la banque adéquate, et le reste de l'adresse est envoyé sur le bus d'adresse. Par exemple, avec 4 chips mémoire, les deux bits de poids fort de l'adresse sont utilisés pour sélectionner le chip mémoire adéquat. Les adresses mémoire sont alors découpées comme suit : {|class="wikitable" |+ Adresse mémoire |- ! Adresse de banque !! Adresse dans la banque |- | Quelques bits de poids fort || Reste de l'adresse |} ===L'arrangement horizontal : élargir le bus de données=== L'arrangement horizontal permet lui aussi d'additionner les capacités mémoire de plusieurs chips mémoire. Cependant, il les combine d'une autre manière. Le nombre d'adresses mémoire n'est pas changé en utilisant plusieurs chips, mais le bus de données est élargi. Le mieux pour comprendre l'idée est de partir d'un exemple, et nous allons prendre celui d'une mémoire SDRAM. Les ordinateurs actuels ont un bus de données de 64 bits (on met de côté le cas du double ou triple canal). Cependant, il n'existe pas de chip mémoire avec un bus aussi large. Les puces de SDRAM/DDR ont un bus de 4, 8 ou 16 bits, ce sont les tailles les plus courantes. L'arrangement horizontal résout ce problème en combinant plusieurs chips mémoire de manière à ce que leurs bus de données s'"additionnent", se concatènent. Par exemple, on peut regrouper 8 chips mémoires de 8 bits, obtenir un bus mémoire de 64 bits. Il est aussi possible d'obtenir ces 64 bits avec des puces de 16 chips mémoire de 4 bits, ou 4 chips mémoire de 16 bits. [[File:Arrangement horizontal SDRAM - un Rank.png|centre|vignette|upright=2|Arrangement horizontal SDRAM.]] Avec cette organisation, on accède à tous les bancs en parallèle à chaque accès, avec la même adresse. Vu que les chips mémoires contiennent tous une partie de la donnée demandée, ils doivent tous être activés en même temps. Pour cela, l'adresse à lire est envoyée à tous les chips mémoire d'un même ''rank'', idem pour les signaux de commande. Un ensemble de N chips reliés de cette manière forme une '''rangée''' (le terme anglais est ''rank''). [[File:Arrangement horizontal.jpg|centre|vignette|upright=2|Arrangement horizontal.]] ===L'arrangement horizontal et vertical combinés=== Nous venons de voir l'arrangement vertical et horizontal, pour ce qui est des barrettes de mémoire. Précisons que ce qui vient d'être dit marche aussi bien pour les barrettes de RAM que pour la mémoire soudée sur la carte mère. Du moment qu'on combine plusieurs chips mémoire ensemble, ces concepts restent valides. Et il en est de même pour la suite, encore que ce soit nettement moins fréquent avec de la mémoire soudée. Il est possible de combiner à la fois l'arrangement vertical et l'arrangement horizontal. Rien de plus simple : il suffit d'utiliser un arrangement vertical entre plusieurs rangées, chacun composée de plusieurs chips mémoire. C'est surtout utilisé sur les barrettes de mémoire SDRAM, qui contiennent 1, 2, 4 ou 8 rangées, rarement plus. Par exemple, une SDRAM peut combiner 16 chips de DRAM de 8 bits chacun, dans deux rangées de 64 bits chacun, chaque rangée regroupant 8 chips. [[File:SDRAM avec 4 ranks.png|centre|vignette|upright=2|SDRAM avec 4 ranks]] Le choix entre la première ou la seconde rangée se fait en configurant les bits ''Chip Select'' de chaque rangée. Il faut noter que les bits de ''Chip Select'' sont générés par le contrôleur mémoire, et envoyés sur le bus de commande. [[File:Td6bfig3.png|centre|vignette|upright=2|Comparaison entre arrangement horizontal (à gauche) et arrangement vertical (à droite).]] Le contrôleur de DRAM peut adresser un certain nombre de rangées, dispersés sur plusieurs barrettes. La limite maximale dépend du contrôleur de DRAM, elle est souvent proche de 8 ou 16 rangées. Si on combine plusieurs barrettes de mémoire, il est possible de dépasser cette limite. Par exemple, prenez un contrôleur de DRAM supportant maximum 8 rangées. Avec 4 barrettes contenant 4 rangées chacune, la limite est dépassée. : Il faut noter que tout ce qui vient d'être dit vaut aussi pour les mémoires ROM et SRAM. Mais en pratique, les arrangements verticaux et horizontaux sont surtout utilisés sur les mémoires DRAM. Il faut dire que de tels arrangements servent à augmenter la capacité mémoire, ce qui colle plus avec des DRAM que des SRAM ou des ROM. ==Les barrettes de mémoire DRAM== [[File:Ram-module.svg|droite|vignette|upright=0.5|Barrette de mémoire RAM.]] Il est possible de souder plusieurs boitiers de DRAM sur une cartre mère, et c'est ce qui est fait sur nombre d'ordinateurs portables. Mais dans les PC fixes, les puces de DRAM sont regroupées sur des ''barrettes mémoires'''. Les barrettes de mémoire se fixent à la carte mère sur un connecteur standardisé, appelé '''slot mémoire'''. Le dessin ci-contre montre une barrette de mémoire, celui-ci ci-dessous est celui d'un ''slot'' mémoire. [[File:Dual channel slots.jpg|centre|vignette|Slots mémoires.]] Sur le schéma de droite, on remarque facilement les boitiers de DRAM, rectangulaires, de couleur sombre. Chaque barrette combine ces puces de manière à additionner leurs capacités : on peut ainsi créer une mémoire de 8 gibioctets à partir de 8 puces d'un gibioctet, par exemple. Ils sont soudés sur un PCB en plastique vert sur lequel sont gravés des connexions métalliques. Les trucs dorés situés en bas des barrettes de mémoire sont des broches qui connectent la barrette au bus mémoire. Les barrettes des mémoires FPM/EDO/SDRAM/DDR n'ont pas le même nombre de broches, pour des raisons de compatibilité. {|class="wikitable" |- !Type de barrette !Type de mémoire !Nombre de broches |- | rowspan="2" | SIMM | rowspan="2" | FPM/EDO |30 |- |72 |- | rowspan="4" | DIMM |SDRAM |168 |- |DDR |184 |- |DDR2 |214, 240 ou 244, suivant la barrette ou la carte mère. |- |DDR3 |204 ou 240, suivant la barrette ou la carte mère. |} ===Le format des barrettes de mémoire=== Certaines barrettes ont des puces mémoire d'un seul côté alors que d'autres en ont sur les deux faces. Cela permet de distinguer les barrettes SIMM et DIMM. * Les '''barrettes SIMM''' ont des puces sur une seule face de la barrette. Elles étaient utilisées pour les mémoires FPM et EDO-RAM. * Les '''barrettes DIMM''' ont des puces sur les deux côtés. Elles sont utilisées sur les SDRAM et les DDR. {| class="flexible" |+ '''Barrette SIMM''' |- |[[File:SIMM FPM 4 MB - C0448721-7229.jpg|vignette|SIMM recto.]] |[[File:SIMM FPM 4 MB - C0448721-7230.jpg|vignette|SIMM verso.]] |} : Les modules DIMM tendent à avoir plus de rangées que les modules SIMM, mais ce n'est pas systématique. Il est souvent dit que les barrettes DIMM ont deux rangées, contre seulement 1 pour les SIMM, mais les contre-exemples sont nombreux. Les barrettes '''SO-DIMM''', pour ordinateurs portables, sont différentes des barrettes DIMM normales des DDR/SDRAM. La raison est qu'il n'y a pas beaucoup de place à l'intérieur d'un PC portable, ce qui demande de diminuer la taille des barrettes. {| |- |[[File:Desktop DDR Memory Comparison.svg|centre|vignette|upright=1.5|Barrettes de DDR pour PC de bureau.]] |[[File:Laptop SODIMM DDR Memory Comparison V2.svg|centre|vignette|upright=1.5|Barrettes de DDR pour PC portables.]] |} Les barrettes de Rambus ont parfois été appelées des '''barrettes RB-DIMM''', mais ce sont en réalité des DIMM comme les autres. La différence principale est que la position des broches n'était pas la même que celle des formats DIMM normaux, sans compter que le connecteur Rambus n'était pas compatible avec les connecteurs SDR/DDR normaux. ===Les interconnexions à l'intérieur d'une barrette de mémoire=== Les boîtiers de DRAM noirs sont connectés au bus par le biais de connexions métalliques. Toutes les puces d'une même rangée sont connectées aux bus d'adresse et de commande. Et les chips d'une même rangée reçoivent exactement les mêmes signaux de commande/adresses, ce qui permet d'envoyer la même adresse/commande à toutes les puces en même temps. La manière dont ces puces sont reliées au bus de commande dépend selon la mémoire utilisée. Les DDR1 et 2 utilisent ce qu'on appelle une '''topologie en T''', illustrée ci-dessous. On voit que le bus de commande forme une sorte d'arbre, dont chaque extrémité est connectée à une puce. La topologie en T permet d'égaliser le délai de transmission des commandes à travers le bus : la commande transmise arrive en même temps sur toutes les puces. Mais elle a de nombreux défauts, à savoir : elle fonctionne mal à haute fréquence, elle est difficile à router en raisons des embranchements. [[File:Organisation des bus de commandes sur les DDR1-2.png|centre|vignette|upright=3.0|Organisation des bus de commandes sur les DDR1-2, nommée topologie en T.]] En comparaison, les DDR3 utilisent une '''topologie ''fly-by''''', où les puces sont connectées en série sur le bus de commande/adresse. La topologie ''fly-by'' n'a pas les problèmes de la topologie en T : elle est simple à router et fonctionne très bien à haute fréquence. [[File:Organisation des bus de commandes sur les DDR3 - topologie fly-by.png|centre|vignette|upright=3.0|Organisation des bus de commandes sur les DDR3 - topologie ''fly-by'']] ===Les barrettes tamponnées (à registres)=== Certaines barrettes intègrent un registre tampon, qui fait l'interface entre le bus et la barrette de RAM. L'utilité est d'améliorer la transmission du signal sur le bus mémoire. Sans ce registre, les signaux électriques doivent traverser le bus, puis traverser les connexions à l'intérieur de la barrette, jusqu'aux puces de mémoire. Avec un registre tampon, les signaux traversent le bus, sont mémorisés dans le registre et c'est tout. Le registre envoie les commandes/données jusqu'aux puces mémoire, mais le signal a été régénéré par le registre. Le signal transmis est donc de meilleure qualité, ce qui augmente la fiabilité du système mémoire. Le défaut est que la présence de ce registre fait que les barrettes ont un temps de latence est plus important que celui des barrettes normales, du fait de la latence du registre. Les barrettes de ce genre sont appelées des '''barrettes RIMM'''. Il en existe deux types : * Avec les '''barrettes RDIMM''', le registre fait l'interface pour le bus d'adresse et le bus de commande, mais pas pour le bus de données. * Avec les '''barrettes LRDIMM''' (''Load Reduced DIMMs''), le registre fait tampon pour tous les bus, y compris le bus de données. [[File:Organisation des bus de commandes sur les RDIMM.png|centre|vignette|upright=3.0|Organisation des bus de commandes sur les RDIMM.]] ===Le ''Serial Presence Detect''=== [[File:SPD SDRAM.jpg|vignette|Localisation du SPD sur une barrette de SDRAM.]] Toute barrette de mémoire assez récente contient une petite mémoire ROM qui stocke les différentes informations sur la mémoire : délais mémoire, capacité, marque, etc. Cette mémoire s'appelle le '''''Serial Presence Detect''''', aussi communément appelé le SPD. Ce SPD contient non seulement les timings de la mémoire RAM, mais aussi diverses informations, comme le numéro de série de la barrette, sa marque, et diverses informations. Le SPD est lu au démarrage de l'ordinateur par le BIOS, afin de pourvoir configurer ce qu'il faut. Le contenu de ce fameux SPD est standardisé par un organisme nommé le JEDEC, qui s'est chargé de standardiser le contenu de cette mémoire, ainsi que les fréquences, timings, tensions et autres paramètres des mémoires SDRAM et DDR. Pour les curieux, vous pouvez lire la page wikipédia sur le SPD, qui donne son contenu pour les mémoires SDR et DDR : [https://en.wikipedia.org/wiki/Serial_presence_detect Serial Presence Detect]. ==Les mémoires asynchrones à RAS/CAS : FPM et EDO-RAM== Avant l'invention des mémoires SDRAM et DDR, il exista un grand nombre de mémoires différentes, les plus connues étant les mémoires fast page mode et EDO-RAM. Ces mémoires n'étaient pas synchronisées par un signal d'horloge, c'était des '''mémoires asynchrones'''. Quand ces mémoires ont été créées, cela ne posait aucun problème : les accès mémoire étaient très rapides et le processeur était certain que la mémoire aurait déjà fini sa lecture ou écriture au cycle suivant. Les mémoires asynchrones les plus connues étaient les '''mémoires FPM''' et '''mémoires EDO'''. Pour ce qui est de leur interface, il faut signaler qu'elles n'ont pas d'entrée ''Chip Select'' ou d'entrée ''Output Enable''. Les signaux RAS et CAS remplacent en quelque sorte ces deux signaux. Le bit RAS fait office de ''Chip Select'', le bit CAS fait office d'''Output Enable''. ===Les mémoires FPM=== Les '''mémoires FPM (''Fast Page Mode'')''' possédaient une petite amélioration, qui rendait l'adressage plus simple. Avec elles, il n'y a pas besoin de préciser deux fois la ligne si celle-ci ne changeait pas lors de deux accès consécutifs : on pouvait garder la ligne sélectionnée durant plusieurs accès. Par contre, il faut quand même préciser les adresses de colonnes à chaque changement d'adresse. Il existe une petite différence entre les mémoire ''Page Mode'' et les mémoires ''Fast-Page Mode'' proprement dit. Sur les premières, le signal CAS est censé passer à 0 avant qu'on fournisse l'adresse de colonne. Avec les ''Fast-Page Mode'', l'adresse de colonne pouvait être fournie avant que l'on configure le signal CAS. Cela faisait gagner un petit peu de temps, en réduisant quelque peu le temps d'accès total. [[File:Sélection d'une ligne sur une mémoire FPM ou EDO.png|centre|vignette|upright=2|Sélection d'une ligne sur une mémoire FPM ou EDO.]] Avec les '''mémoires en mode quartet''', il est possible de lire quatre octets consécutifs sans avoir à préciser la ligne ou la colonne à chaque accès. On envoie l'adresse de ligne et l'adresse de colonne pour le premier accès, mais les accès suivants sont fait automatiquement. La seule contrainte est que l'on doit générer un front descendant sur le signal CAS pour passer à l'adresse suivante. Vous aurez noté la ressemblance avec le mode rafale vu il y a quelques chapitres, mais il y a une différence notable : le mode rafale vrai n'aurait pas besoin qu'on précise quand passer à l'adresse suivante avec le signal CAS. [[File:Mode quartet.png|centre|vignette|upright=3|Mode quartet.]] Les '''mémoires FPM à colonne statique''' se passent même du signal CAS. Le changement de l'adresse de colonne est détecté automatiquement par la mémoire et suffit pour passer à la colonne suivante. Dans ces conditions, un délai supplémentaire a fait son apparition : le temps minimum entre deux sélections de deux colonnes différentes, appelé tCAS-to-CAS. [[File:Accès en colonne statique.jpg|centre|vignette|upright=2.5|Accès en colonne statique.]] ===Les mémoires EDO-RAM=== L''''EDO-RAM''' a été inventée quelques années après la mémoire FPM. Elle a été déclinée en deux versions : la EDO simple, et la EDO en rafale. L'EDO simple ajoutait une entrée ''Ouput Enable'' à une mémoire FPM. Pour rappel, l'entrée ''Ouput Enable'' permet de connecter/déconnecter la DRAM du bus de données. S'il est mis à 0, les lectures et écritures sont empêchées. Pour ajouter cette entrée, il a fallu rajouter un registre sur la sortie de donnée, celle qui sert pour les lectures. Et l'ajout de ce registre a introduit une capacité dite de ''pipelining'', sur le même modèle que pour les mémoires SRAM synchrones. La donnée pouvait être maintenue sur le bus de données durant un certain temps, même après la remontée du signal CAS. Le registre de sortie maintenait la donnée lu tant que le signal RAS restait à 0, et tant qu'un nouveau signal CAS n'a pas été envoyé. Faire remonter le signal CAS à 1 n'invalidait pas la donnée en sortie. La conséquence est qu'on pouvait démarrer une nouvelle lecture alors que la donnée de l'accès précédent était encore présent sur le bus de données. Le pipeline obtenu avait deux étages : un où on présentait l'adresse et sélectionnait la colonne, un autre où la donnée était lue depuis le registre de sortie. Les mémoires EDO étaient donc plus rapides. [[File:EDO RAM.png|centre|vignette|upright=3|EDO RAM]] Cependant, cela marchait surtout pour les lectures, pas pour les écritures. Une écriture ne démarre que quand la lecture ou écriture précédente est totalement terminée. De même, on ne peut pas démarrer un nouvel accès mémoire tant qu'une écriture est en cours. ===Les mémoires EDO-RAM avec mode rafale=== Les '''EDO en rafale''' effectuent les accès à 4 octets consécutifs automatiquement : il suffit d'adresser le premier octet à lire. Les 4 octets étaient envoyés sur le bus les uns après les autres, au rythme d'un par cycle d’horloge : ce genre d'accès mémoire s'appelle un accès en rafale. [[File:Accès en rafale.png|centre|vignette|upright=2|Accès en rafale sur une DRAM EDO.]] Implémenter cette technique nécessite d'ajouter un compteur, capable de faire passer d'une colonne à une autre quand on lui demande, et quelques circuits annexes pour commander le tout. [[File:Modifications du contrôleur mémoire liées aux accès en rafale.png|centre|vignette|upright=2|Modifications du contrôleur de DRAM liées aux accès en rafale.]] ===Le rafraichissement mémoire=== Les mémoires FPM et EDO doivent être rafraichies régulièrement. Au début, le rafraichissement se faisait ligne par ligne. Le rafraichissement avait lieu quand le RAS passait à l'état haut, alors que le CAS restait à l'état bas. Le processeur, ou le contrôleur mémoire, sélectionnait la ligne à rafraichir en fournissant son adresse mémoire. D'où le nom de '''rafraichissement par adresse''' qui est donné à cette méthode de commande du rafraichissement mémoire. Divers processeurs implémentaient de quoi faciliter le rafraichissement par adresse. Par exemple, le Zilog Z80 contenait un compteur de ligne, un registre qui contenait le numéro de la prochaine ligne à rafraichir. Il était incrémenté à chaque rafraichissement mémoire, automatiquement, par le processeur lui-même. Un ''timer'' interne permettait de savoir quand rafraichir la mémoire : quand ce ''timer'' atteignait 0, une commande de rafraichissement était envoyée à la mémoire, et le ''timer'' était ''reset''. [[File:Rafraichissement mémoire manuel.png|centre|vignette|upright=2|Rafraichissement mémoire manuel.]] Par la suite, certaines mémoires ont implémenté un compteur interne d'adresse, pour déterminer la prochaine adresse à rafraichir sans la préciser sur le bus d'adresse. Le déclenchement du rafraichissement se faisait toujours par une commande externe, provenant du contrôleur de DRAM ou du processeur. Cette commande faisait passer le CAS à 0 avant le RAS. Cette méthode de rafraichissement se nomme '''rafraichissement interne'''. [[File:Rafraichissement sur CAS précoce.png|centre|vignette|upright=2|Rafraichissement sur CAS précoce.]] On peut noter qu'il est possible de déclencher plusieurs rafraichissements à la suite en laissant le signal CAS dans le même état. Ce genre de choses pouvait avoir lieu après une lecture : on pouvait profiter du fait que le CAS soit mis à zéro par la lecture ou l'écriture pour ensuite effectuer des rafraichissements en touchant au signal RAS. Dans cette situation, la donnée lue était maintenue sur la sortie durant les différents rafraichissements. [[File:Rafraichissements multiples sur CAS précoce.png|centre|vignette|upright=2|Rafraichissements multiples sur CAS précoce.]] ==Les mémoires SDRAM== Dans les années 90, les mémoires asynchrones ont laissé la place aux '''mémoires SDRAM''', qui sont synchronisées avec le bus par une horloge. L'utilisation d'une horloge a comme avantage des temps d'accès fixes : le processeur sait qu'un accès mémoire prendra un nombre déterminé de cycles d'horloge. Avec les mémoires asynchrones, le processeur ne pouvait pas prévoir quand la donnée serait disponible et ne faisait rien tant que la mémoire n'avait pas répondu : il exécutait ce qu'on appelle des ''wait states'' en attendant que la mémoire ait fini. Les mémoires SDRAM sont standardisées par un organisme international, le JEDEC. Le standard SDRAM impose des spécifications électriques bien précise pour les barrettes de mémoire et le bus mémoire, décrit le protocole utilisé pour communiquer avec les barrettes de mémoire, et bien d'autres choses encore. Les SDRAM ont été déclinées en versions de performances différentes, décrites dans le tableau ci-dessous : {| class="wikitable" ! Nom standard ! Fréquence ! Bande passante |- | PC66 | 66 mhz | 528 Mio/s |- | PC66 | 100 mhz | 800 Mio/s |- | PC66 | 133 mhz | 1064 Mio/s |- | PC66 | 150 mhz | 1200 Mio/s |} ===Les banques internes aux chips mémoires SDRAM=== L'intérieur d'une mémoire SDRAM contient plusieurs '''banques''', aussi appelées des banc mémoire. Concrètement, une banque est... une mémoire. Ou plutôt, une sorte de mini-mémoire miniature. Chaque banque a son propre tampon de ligne, ses propres multiplexeurs de colonne et ses propres décodeurs. C'est comme si une SDRAM regroupait plusieurs mémoires séparées dans un même circuit intégré. [[File:Arrangement vertical.jpg|centre|vignette|upright=2.5|Mémoire multi-banques.]] Un point important est que chaque banque a son propre tampon de ligne. Il est donc possible d'ouvrir plusieurs lignes en même temps, chacune dans une banque différente. Par exemple, on peut ouvrir une ligne dans la banque numéro 1, et une autre ligne dans la banque numéro 2. Et c'est une source d'optimisations très intéressantes. La première optimisation est liée au rafraichissement mémoire. Au lieu de rafraichir chaque adresse une par une, il est possible de rafraichir des banques indépendantes en même temps, ce qui divise le temps de rafraichissement par le nombre de banques. C'est ce que je sous-entendais plus haut quand je disais que le temps de rafraichissement n'est pas égal au temps de balayage sur les SDRAM, alors que c'est le cas sur les DRAM FPM et EDO. De plus, et sans rentrer dans les détails, cela permet de faire plusieurs accès mémoire en même temps, dans des banques différentes. La possibilité est limitée, mais elle existe et elle améliore grandement la performance de la SDRAM. Mais nous en reparlerons dans un chapitre ultérieur, car cette histoire d'accès simultanés a plus sa place dans le chapitre sur le parallélisme mémoire. Pour le moment, nous ne pouvons pas expliquer pourquoi ni comment un processeur pourrait émettre plusieurs accès mémoire simultanés. Un processeur est censé travailler une instruction à la fois, à ce stade du cours, il ne peut pas en faire plusieurs en même temps. Mais nous allons cependant mentionner un cas où cette possibilité est intéressante : une mémoire SDRAM partagée entre un processeur et une carte graphique. Les deux accèdent à des données séparées, qui sont dans des banques différentes. On suppose que la carte graphique accède plus fréquemment à la mémoire que le processeur. Le contrôleur mémoire reçoit les accès mémoire du CPU et du GPU et il tente d'intercaler des accès CPU entre deux accès de la carte graphique. Vu qu'ils tombent dans des banques différentes, un accès CPU et un accès carte graphique peuvent se faire presque en même temps. La seule contrainte est que si on lance plusieurs accès mémoire simultanés, ceux-ci ne peuvent pas utiliser le bus de données en même temps. {|class="wikitable" |+ Pipelining basique sur les SDRAM |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 |- ! Banque Numéro 1 | || || || || || || || || || || |- ! Banque Numéro 2 | || colspan="3" bgcolor="#A0FFFF" | Accès CPU || || || colspan="3" bgcolor="#A0FFFF" | Accès CPU || || |- ! Banque Numéro 3 | || || || || || || || || || || |- ! Banque Numéro 4 | || || || colspan="3" bgcolor="#FFA0FF" | Accès carte graphique || || colspan="3" bgcolor="#FFA0FF" | Accès carte graphique || |} ===Le mode rafale des SDRAM=== Un point important est que les SDRAM reprennent les optimisations des mémoires FPM et EDO. Elles utilisent aussi un tampon de ligne, avec la possibilité de lire plusieurs colonnes à la suite sans avoir à préciser l'adresse de ligne à chaque fois. Mais surtout, elles gèrent nativement le mode rafale. les paramètres qui ont trait au mode rafale sont configurables. Il est possible de configurer la SDRAM pour activer les accès sans rafale, ou les désactiver. Il y a aussi la possibilité de configurer le nombre d'octets consécutifs à lire ou écrire en mode rafale. On peut ainsi accéder à 1, 2, 4, ou 8 octets en une seule fois, alors que les EDO ne permettaient que des accès à 4 octets consécutifs. Enfin, on peut décider s'il faut faire un accès en mode linéaire ou entrelacé. La configuration de la SDRAM est mémorisée dans un registre de 10 bits, le '''registre de mode'''. Il faisait 10 bits sur les mémoires SDRAM, mais a été étendu à 13 bits sur la DDR2. Voici les 10 bits originels de ce registre : {|class="wikitable" |+ Signification des bits du registre de mode des SDRAM |- ! Bit n°9 | Type d'accès : en rafale ou normal |- ! Bit n°8 et 7 | Doivent valoir 00, sont réservés pour une utilisation ultérieur dans de futurs standards. |- ! Bit n°6, 5, et 4 | Latence CAS (voir plus bas) |- ! Bit n°3 | Type de rafale : linéaire ou entrelacée |- ! Bit n°2, 3, et 0 | Longueur de la rafale : indique le nombre d'octets à lire/écrire lors d'une rafale. |} ===L'interface d'une mémoire SDRAM=== Le bus de commandes d'une SDRAM contient au moins 18 fils, dont celui pour le signal d'horloge. L'interface d'une SDRAM contient tous les bits présents sur une mémoire DRAM classique : une entrée RAS, une entrée CAS, une entrée R/W, et un bus d'adresse. A cela, il faut cependant ajouter une entrée ''Chip Select'' (CS), qui permet d'activer/désactiver la mémoire SDRAM. Je rappelle que le bit CS a été introduit sur les mémoires SDRAM, il n'était pas présent sur les mémoires FPM/EDO. Deux autres bits de commande sont vraiment spécifiques des mémoires SDRAM. Il s'agit des bits CKE et DQM. Le '''bit CKE''' est l'abréviation de ''Clock Enable'', qui qui trahit sa fonction. Lorsque ce signal est à 0, le chip de SDRAM voit son signal d'horloge gelè. S'il est à 0, le contrôleur de DRAM peut envoyer ce qu'il veut sur le bus de commande ou d'adresse, la SDRAM ne réagira pas du tout, il ne se passera rien. Le '''bit DQM''' est une sorte de bit ''Output Enable'', avec une nuance importante. Le terme DQM est l'abréviation de ''Data Mask'', ce qui trahit encore une fois sa fonction. Il y a un bit DQM pour chaque octet du bus de données. Une SDRAM ayant un bus de 64 bits, cela fait 8 bits DQM par mémoire SDRAM. Lorsque le bit DQM est à 1, l'octet en question n'est simplement pas lu ou écrit, le bus de donnée est déconnecté pour cet octet. Le bus d'adresse est particulier, car il tient compte de la présence de ''banques mémoires''. Le bus d'adresse est découpé en deux portions : une portion pour sélectionner la banque, une autre pour sélectionner l'adresse à l'intérieur d'une banque. L'interface de la SDRAM fait ainsi la différence entre une '''adresse de banque''' et une '''adresse intra-banque'''. L'adresse de banque est codée sur quelques bits, généralement deux ou trois suivant la SDRAM. Le reste de l'adresse est codé sur 11 bits sur les SDRAM, mais cela a augmenté avec les DDR 1, 2, 3, 4, 5. Le bus de données d'une SDRAM fait 4, 8, ou 16 bits. Je précise bien qu'il s'agit là des puces de SDRAM, les barrettes de SDRAM combinent plusieurs puces SDRAM avec un arrangement horizontal, qui peut combiner plusieurs puces de 8 bits pour alimenter un bus de données de 64 bits. La taille des puces utilisées souvent indiquée sur la barrette de RAM, avec une mention x4, x8 ou x16. Les puces de SDRAM les plus courantes ont une interface de 8 bits pour les données. Les SDRAM de 4 bits sont surtout utilisées pour les serveurs, c'est lié au support de l'ECC. les puces x16 sont moins utilisées car elles ont généralement moins de banques que les autres. ===Les commandes SDRAM=== Le bus de commande permet d'envoyer des commandes à la mémoire, chaque commande étant précisée par une combinaison précise des bits CS, RAS, CAS, R/W, et autres. Les commandes en question sont des demandes de lecture, d'écriture, de préchargement et autres. Elles sont codées par une valeur bien précise qui est envoyée sur les 18 fils du bus de commande. Ces commandes sont nommées READ, READA, WRITE, WRITEA, PRECHARGE, ACT, ... Les plus importantes sont les commandes PRECHARGE, ACT et READ/WRITE. La commande ACT sélectionne une ligne : elle met le bit RAS à zéro et présente une adresse de ligne. Les commandes READ et WRITE sélectionnent une colonne, et déclenchent respectivement une lecture ou une écriture. Elles précisent une adresse de colonne, mettent le bit CAS à 0 et le bit RAS à 1, et précise la valeur du bit R/W. Les commandes READ et WRITE ne peuvent se faire qu'une fois que la banque a été activée par une commande ACT. Il est possible d'envoyer plusieurs commandes READ ou WRITE successives à des colonnes différentes, ce qui permet d'implémenter les optimisations des mémoires FPM. La commande PRECHARGE ferme la ligne courante et prépare l'ouverture de la suivante. Elle précharge les lignes de bit de la RAM, d'où son nom. Il est nécessaire d'en envoyer une avant d'envoyer une commande ACT. Notons que la commande PRECHARGE agit sur une banque, dont l'adresse est indiquée dans la commande PRECHARGE. Il existe une commande PRECHARGE ALL, qui agit sur toutes les banques de la SDRAM à la fois. Elle est souvent utilisée de concert avec la commande de rafraichissement, car le rafraichissement mémoire rafraichit une ligne dans toutes les banques à la fois. Il faut donc fermer toutes les lignes ouvertes, dans chaque banque, ce que fait la commande PRECHARGE ALL. Les commandes READA et WRITEA fusionnent une commande READ/WRITE avec une commande PRECHARGE. Elles permettent d'éviter d'avoir à envoyer une commande PRECHARGE pour fermer la ligne courante. Au lieu d'envoyer une commande READ ou WRITE, puis une commande PRECHARGE pour fermer la ligne, on envoie une seule commande READA/WRITEA. Il s'agit d'une petite optimisation, qui permet de réduire le nombre de commandes envoyées sur le bus. Les commandes sont encodées comme indiquées dans ce tableau. Une commande est tout simplement encodée en précisant une adresse si nécessaire, et une combinaison des bits CS, RAS, CAS et R/W. La seule subtilité est que le bit numéro 10 du bus d'adresse sert à commander les opérations de PRECHARGE, y compris celles implicites dans les opérations READA et WRITEA. {| class="wikitable" style="text-align:center" ! Bit CS || Bit RAS || Bit CAS || Bit WE || Bits de sélection de banque (2 bits) || Bit du bus d'adresse A10 || Reste du bus d'adresse || Nom de la commande : Description |- | 1 | colspan="6" | X | Absence de commandes. |- | 0 || 1 || 1 || 1 || colspan="3" | X || No Operation : Pas d'opération |- | 0 || 1 || 1 || 0 || colspan="3" | X || Burst Terminante : Stoppe un accès en rafale (en cours). |- | 0 || 1 || 0 || 1 || Adresse de la banque || 0 || Adresse de la colonne || READ : lit une donnée depuis la ligne active. |- | 0 || 1 || 0 || 1 || Adresse de la banque || 1 || Adresse de la colonne || READA : lit une donnée depuis la ligne active, puis ferme la ligne. |- | 0 || 1 || 0 || 0 || Adresse de la banque || 0 || Adresse de la colonne || WRITE : écrit une donnée dans la ligne active. |- | 0 || 1 || 0 || 0 || Adresse de la banque || 1 || Adresse de la colonne || WRITEA : écrit une donnée dans la ligne active, puis ferme la ligne. |- | 0 || 0 || 1 || 1 || Adresse de la banque || colspan="2" | Adresse de la ligne || ACT : charge une ligne dans le tampon de ligne. |- | 0 || 0 || 1 || 0 || Adresse de la banque || 0 || X || PRECHARGE : précharge le tampon de ligne dans la banque voulue. |- | 0 || 0 || 1 || 0 || Adresse de la X || 1 || X || PRECHARGE ALL : précharge le tampon de ligne dans toutes les banques. |- | 0 || 0 || 0 || 1 || colspan="3" | X || Auto refresh : Demande de rafraichissement, gérée par la SDRAM. |- | 0 || 0 || 0 || 0 || 00 || colspan="2" | Nouveau contenu du registre de mode || LOAD MODE REGISTER : configure le registre de mode. |} Les commandes ACT se font à partir de l'état de repos, l'état où toutes les banques sont préchargées. Par contre, les commandes MODE REGISTER SET et AUTO REFRESH ne peuvent se faire que si toutes les banques sont désactivées. Le fonctionnement simplifié d'une SDRAM peut se résumer dans ce diagramme : [[File:Fonctionnement simplifié d'une SDRAM.jpg|centre|vignette|upright=2|Fonctionnement simplifié d'une SDRAM.]] ===Les délais mémoires=== Les mémoires SDRAM n'étant pas infiniment rapides, il y a toujours un certain délais à respecter entre deux commandes. Par exemple, quand on envoie une commande ACT pour activer une ligne, on ne peut pas envoyer une commande READ/WRITE au cycle suivant. La plupart des SDRAM ne sont pas assez rapides pour ça. Il faut respecter un délai de quelques cycles, qui dépend de la mémoire. Et il n'y a pas que ce délai entre une commande ACT et la commande suivante. Une SDRAM doit gérer d'autres temps d'attente, appelés des '''délais mémoires''', ou encore des ''timings'' mémoire. Les délais mémoire le plus importants sont résumés ci-dessous : {|class="wikitable" |- !Timing!!Description |- | colspan="2" | |- ! colspan="2" | Délais primaires |- ||tRP|| Temps entre une commande PRECHARGE et une commande ACT |- ||tRCD|| Temps entre une commande ACT et une commande READ/WRITE. |- ||tCL|| Temps entre une commande READ et l'envoi de la donnée lue sur le bus de données. |- ||tDQSS|| Temps entre une commande WRITE et l'écriture de la donnée. |- ||tCAS-to-CAS|| Temps minimum entre deux commandes READ. |- ! colspan="2" | Délais secondaires |- ||tWTR|| Temps entre une lecture et une écriture consécutives. |- ||tRAS || Temps entre une commande ACT et une commande PRECHARGE. |} La façon de mesurer ces délais varie : sur les mémoires FPM et EDO, on les mesure en unités de temps (secondes, millisecondes, micro-secondes, etc.), tandis qu'on les mesure en cycles d'horloge sur les mémoires SDRAM. Les délais/timings mémoire ne sont pas les mêmes suivant la barrette de mémoire que vous achetez. Certaines mémoires sont ainsi conçues pour avoir des timings assez bas et sont donc plus rapides, et surtout : beaucoup plus chères que les autres. Le gain en performances dépend beaucoup du processeur utilisé et est assez minime comparé au prix de ces barrettes. Les délais mémoires d'une barrette sont mémorisés dans le ''Serial Presence Detect'' de la barrette et sont lus par le BIOS au démarrage de l'ordinateur, et sont ensuite utilisés pour configurer le contrôleur de DRAM. ===Les commandes anticipées=== Les SDRAM sont parfois capables de démarrer une commande avant que la précédente soit terminée. Concrètement, pendant que la commande précédente envoie sa dernière donnée sur le bus de données, on peut envoyer la commande suivante avec quelques cycles d'avance. L'exemple ci-dessous devrait être assez clair : on envoie une seconde commande au neuvième cycle, alors qu'une rafale est en cours. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Bus de commande/adresse | || bgcolor="#A0FFFF" | ACT || || bgcolor="#A0FFFF" | READ (1) || || || || bgcolor="#FFA0FF" | READ (2) || || || || || |- ! Bus de données | || || || || || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 | bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 | |} Il s'agit d'une forme très limitée de pipeline, tellement limitée qu'on peut légitimement douter que c'est un vrai pipeline. Dans ce qui suit, j'ai décidé d'appeler cette possibilité sous le terme de '''commandes SDRAM anticipées'''. La possibilité est très limitée, car il faut tenir compte des délais mémoire. Elle améliore un peu les performances dans certaines circonstances où la RAM doit traiter plusieurs accès mémoire consécutifs, très rapprochés. L'exemple typique est celui du transfert d'un bloc de données entre mémoire cache et mémoire RAM, qui dépasse la taille d'une rafale, qui envoie plusieurs accès mémoire d'un seul coup au contrôleur mémoire. Mais d'autres exemples sont possibles, on ne peut juste pas les expliquer à ce stade du cours. Les commandes SDRAM anticipées sont possibles car les SDRAM sont formées en entourant une RAM asynchrone de registres, exactement comme les SRAM synchrones. Il est possible d'écrire dans les registres de données/commandes, pendant qu'un autre accès mémoire accède au cœur asynchrone. Les délais mémoire sont conçus pour éviter qu'une commande accède au cœur asynchrone en même temps que la suivante ou la précédente, idem pour l'usage des registres. C'est pour cela que les délais mémoire sont assez différents entre écritures et lectures, d'ailleurs. {|class="wikitable" |- ! !! Cycle 1 !! Cycle 2 !! Cycle 3 !! Cycle 4 !! Cycle 5 !! Cycle 6 !! Cycle 7 !! Cycle 8 !! Cycle 9 !! Cycle 10 !! Cycle 11 !! Cycle 12 !! Cycle 13 |- ! Enregistrement de la commande dans le registre d'adresse/commande | bgcolor="#A0FFFF" | READ (1) || || || || bgcolor="#FFA0FF" | READ (2) || || || || || |- ! Accès au cœur asynchrone | || bgcolor="#A0FFFF" | READ (1) || bgcolor="#A0FFFF" | READ (1) || bgcolor="#A0FFFF" | READ (1) || || bgcolor="#FFA0FF" | READ (2) || bgcolor="#FFA0FF" | READ (2) || bgcolor="#FFA0FF" | READ (2) || || || |- ! Lecture/écriture du registre de données | || || || || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 || bgcolor="#A0FFFF" | READ 1 | bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 || bgcolor="#FFA0FF" | READ 2 | |} Les DDR2 et 3 vont encore plus loin avec l'optimisation des '''CAS postés'''. L'idée est que le contrôleur mémoire peut envoyer une commande READ/WRITE sans se soucier des ''timings''. En théorie, les deux commandes doivent être séparées par quelques cycles, sur une SDRAM ou une DDR1. Mais avec la DDR2, le contrôleur mémoire peut envoyer les deux l'une après l'autre, au cycle suivant. C'est la mémoire qui mettra en attente la commande READ/WRITE pour respecter les ''timings'' mémoire. Cela complexifie le fonctionnement interne de la DDR, mais simplifie grandement le travail du contrôleur mémoire. ==Les mémoires DDR== Les mémoires SDRAM récentes sont des mémoires de type ''dual data rate'', ce qui fait qu'elles portent le nom de mémoires DDR. Pour rappel, les mémoires ''dual data rate'' ont un plan mémoire deux fois plus large que le bus mémoire, avec un bus mémoire allant à une fréquence double. Par double, on veut dire que les transferts sur le bus mémoire ont lieu sur les fronts montants et descendants de l'horloge. Il y a donc deux transferts de données sur le bus pour chaque cycle d'horloge, ce qui permet de doubler le débit sans toucher à la fréquence du plan mémoire lui-même. Les mémoires DDR sont standardisées par un organisme international, le JEDEC, et ont été déclinées en plusieurs générations : DDR1, DDR2, DDR3, et DDR4. La différence entre ces modèles sont très nombreuses, mais les plus évidentes sont la fréquence de la mémoire et du bus mémoire. D'autres différences mineures existent entre les SDRAM et les mémoires DDR. Par exemple, la tension d'alimentation des mémoires DDR est plus faible que pour les SDRAM. ET elle a diminué dans le temps, d'une génération de DDR à l'autre. Avec les mémoires DDR2,la tension d'alimentation est passée de 2,5/2,6 Volts à 1,8 Volts. Avec les mémoires DDR3, la tension d'alimentation est notamment passée à 1,5 Volts. ===Les performances des mémoires DDR=== Les mémoires SDRAM ont évolué dans le temps, mais leur temps d'accès/fréquence n'a pas beaucoup changé. Il valait environ 10 nanosecondes pour les SDRAM, approximativement 5 ns pour la DDR-400, il a peu évolué pendant la génération DDR et DDR3, avant d'augmenter pendant les générations DDR4 et de stagner à nouveau pour la génération DDR5. L'usage du DDR, puis du QDR, visait à augmenter les performances malgré la stagnation des temps d'accès. En conséquence, la fréquence du bus a augmenté plus vite que celle des puces mémoire pour compenser. {|class="wikitable" |- ! Année ! Type de mémoire ! Fréquence de la mémoire (haut de gamme) ! Fréquence du bus ! Coefficient multiplicateur entre les deux fréquences |- | 1998 | DDR 1 | 100 - 200 MHz | 200 - 400 MHz | 2 |- | 2003 | DDR 2 | 100 - 266 MHz | 400 - 1066 MHz | 4 |- | 2007 | DDR 3 | 100 - 266 MHz | 800 - 2133 MHz | 8 |- | 2014 | DDR 4 | 200 - 400 MHz | 1600 - 3200 MHz | 8 |- | 2020 | DDR 5 | 200 - 450 MHz | 3200 - 7200 MHz | 8 à 16 |} Une conséquence est que la latence CAS, exprimée en nombre de cycles, a augmenté avec le temps. Si vous comparez des mémoires DDR2 avec une DDR4, par exemple, vous allez voir que la latence CAS est plus élevée pour la DDR4. Mais c'est parce que la latence est exprimée en nombre de cycles d'horloge, et que la fréquence a augmentée. En comparant les temps d'accès exprimés en secondes, on voit une amélioration. ===Les commandes des mémoires DDR=== Les commandes des mémoires DDR sont globalement les mêmes que celles des mémoires SDRAM, vues plus haut. Les modifications entre SDRAM, DDR1, DDR2, DDR3, DDR4, et DDR5 sont assez mineures. Les seules différences sont l'addition de bits pour la transmission des adresses, des bits en plus pour la sélection des banques, etc. En clair, une simple augmentation quantitative. Le registre de mode a été un peu modifié. Il est passé de 10 bits pour les SDRAM et DDR1, à 13 bits sur la DDR 2 et les suivantes. Les DDR ont aussi ajouté le support de plusieurs registres de mode, qui sont sélectionnés en réutilisant l'adresse de banque. Dans une commande LOAD MODE REGISTER, l'adresse de banque indique quel registre de mode il faut altérer. Avant la DDR4, les modifications des commandes sont mineures. La DDR2 supprime la commande ''Burst Terminate'', la DDR3 et la DDR4 utilisent le bit A12 pour préciser s'il faut faire une rafale complète, ou une rafale de moitié moins de données. Une optimisation des DDR2 et 3 est celle des '''CAS postés'''. L'idée est que le contrôleur de DRAM peut envoyer une commande ACT et une commande READ/WRITE sans se soucier des ''timings'' nécessaires entre les deux. En théorie, les deux commandes doivent être séparées par quelques cycles, sur une SDRAM ou une DDR1. Mais avec la DDR2, le contrôleur de DRAM peut envoyer les deux l'une après l'autre, au cycle suivant. C'est la mémoire qui mettra en attente la commande READ/WRITE pour respecter les ''timings'' mémoire. Cela complexifie le fonctionnement interne de la DDR, mais simplifie grandement le travail du contrôleur de DRAM. Mais avec la DDR4, les choses changent, notamment au niveau de la commande ACT. Avec l'augmentation de la capacité des barrettes mémoires, la taille des adresses est devenue trop importante. Pour éviter de rajouter des bits d'adresses, les concepteurs du standard DDR4 ont décidé de ruser. Lors d'une commande ACT, les bits RAS, CAS et WE sont utilisés comme bits d'adresse, alors qu'ils ont leur signification normale pour les autres commandes. Pour éviter toute confusion, un nouveau bit ACT est ajouté pour indiquer la présence d'une commande ACT : il est à 1 pour une commande ACT, 0 pour les autres commandes. {| class="wikitable" style="text-align:center" |+ Commandes d'une mémoire DDR4, seule la commande colorée change par rapport aux SDRAM ! Bit CS || style="background: #CCFFCC" | Bit ACT || Bit RAS || Bit CAS || Bit WE || Bits de sélection de banque (4 bits) || Bit du bas d'adresse A10 || Reste du bus d'adresse || Nom de la commande : Description |- | 1 | colspan="6" | X | Absence de commandes. |- | 0 || style="background: #CCFFCC" | 0 || 1 || 1 || 1 || colspan="3" | X || No Operation : Pas d'opération |- | 0 || style="background: #CCFFCC" | 0 || 1 || 1 || 0 || colspan="3" | X || Burst Terminante : Arrêt d'un accès en rafale en cours. |- | 0 || style="background: #CCFFCC" | 0 || 1 || 0 || 1 || Adresse de la banque || 0 || Adresse de la colonne || READ : lire une donnée depuis la ligne active. |- | 0 || style="background: #CCFFCC" | 0 || 1 || 0 || 1 || Adresse de la banque || 1 || Adresse de la colonne || READA : lire une donnée depuis la ligne active, avec rafraichissement automatique de la ligne. |- | 0 || style="background: #CCFFCC" | 0 || 1 || 0 || 0 || Adresse de la banque || 0 || Adresse de la colonne || WRITE : écrire une donnée depuis la ligne active. |- | 0 || style="background: #CCFFCC" | 0 || 1 || 0 || 0 || Adresse de la banque || 1 || Adresse de la colonne || WRITEA : écrire une donnée depuis la ligne active, avec rafraichissement automatique de la ligne. |- style="background: #CCFFCC" | 0 || style="background: #CCFFCC" | 1 || colspan="3" | Adresse de la ligne (bits de poids forts) || Adresse de la banque || colspan="2" | Adresse de la ligne (bits de poids faible) || ACT : charge une ligne dans le tampon de ligne. |- | 0 || style="background: #CCFFCC" | 0 || 0 || 1 || 0 || Adresse de la banque || 0 || X || PRECHARGE : précharge le tampon de ligne dans la banque voulue. |- | 0 || style="background: #CCFFCC" | 0 || 0 || 1 || 0 || Adresse de la X || 1 || X || PRECHARGE ALL : précharge le tampon de ligne' dans toutes les banques. |- | 0 || style="background: #CCFFCC" | 0 || 0 || 0 || 1 || colspan="3" | X || Auto refresh : Demande de rafraichissement, gérée par la SDRAM. |- | 0 || style="background: #CCFFCC" | 0 || 0 || 0 || 0 || Numéro de registre de mode || colspan="2" | Nouveau contenu du registre de mode || LOAD MODE REGISTER : configure le registre de mode. |} ==Les VRAM des cartes vidéo== Les cartes graphiques ont des besoins légèrement différents des DRAM des processeurs, ce qui fait qu'il existe des mémoires DRAM qui leur sont dédiées. Elles sont appelés des '''''Graphics RAM''''' (GRAM). La plupart incorporent des fonctionnalités utiles uniquement pour les mémoires vidéos, comme des fonctionnalités de masquage (appliquer un masque aux données lue ou à écrire), ou le remplissage d'un bloc de mémoire avec une donnée unique. Les anciennes cartes graphiques et les anciennes consoles utilisaient de la DRAM normale, faute de mieux. La première GRAM utilisée était la NEC μPD481850, qui a été utilisée sur la console de jeu PlayStation, à partir de son modèle SCPH-5000. D'autres modèles de GRAM ont rapidement suivi. Les anciennes consoles de jeu, mais aussi des cartes graphiquesn utilisaient des GRAM spécifiques. ===Les mémoires vidéo double port=== Sur les premières consoles de jeu et les premières cartes graphiques, le ''framebuffer'' était mémorisé dans une mémoire vidéo spécialisée appelée une '''mémoire vidéo double port'''. Le premier port était connecté au processeur ou à la carte graphique, alors que le second port était connecté à un écran CRT. Aussi, nous appellerons ces deux port le ''port CPU/GPU'' et l'autre sera appelé le ''port CRT''. Le premier port était utilisé pour enregistrer l'image à calculer et faire les calculs, alors que le second port était utilisé pour envoyer à l'écran l'image à afficher. Le port CPU/GPU est tout ce qu'il y a de plus normal : on peut lire ou écrire des données, en précisant l'adresse mémoire de la donnée, rien de compliqué. Le port CRT est assez original : il permet d'envoyer un paquet de données bit par bit. De telles mémoires étaient des mémoires à tampon de ligne, dont le support de mémorisation était organisé en ligne et colonnes. Une ligne à l'intérieur de la mémoire correspond à une ligne de pixel à l'écran, ce qui se marie bien avec le fait que les anciens écrans CRT affichaient les images ligne par ligne. L'envoi d'une ligne à l'écran se fait bit par bit, sur un câble assez simple comme un câble VGA ou autre. Le second port permettait de faire cela automatiquement, en permettant de lire une ligne bit par bit, les bits étant envoyés l'un après l'autre automatiquement. Pour cela, les mémoires vidéo double port incorporaient un tampon de ligne spécialisé pour le port lié à l'écran. Ce tampon de ligne n'était autre qu'un registre à décalage, contrairement au tampon de ligne normal. Lors de l'accès au second port, la carte graphique fournissait un numéro de ligne et la ligne était chargée dans le tampon de ligne associé à l'écran. La carte graphique envoyait un signal d'horloge de même fréquence que l'écran, qui commandait le tampon de ligne à décalage : un bit sortait à chaque cycle d'écran et les bits étaient envoyé dans le bon ordre. ===Les mémoires SGRAM et GDDR=== De nos jours, les cartes graphiques n'utilisent plus de mémoires double port, mais des mémoires simple port. Les mémoires graphiques actuelles sont des SDRAM modifiées pour fonctionner en tant que ''Graphic RAM''. Les plus connues sont les '''mémoires GDDR''', pour ''graphics double data rate'', utilisées presque exclusivement sur les cartes graphiques. Il en existe plusieurs types pendant que j'écris ce tutoriel : GDDR, GDDR2, GDDR3, GDDR4, et GDDR5. Mais attention, il y a des différences avec les DDR normales. Par exemple, les GDDR ont une fréquence plus élevée que les DDR normales, avec des temps d'accès plus élevés (sauf pour le tCAS). De plus, elles sont capables de laisser ouvertes deux lignes en même temps. Par contre, ce sont des mémoires simple port. ==Les mémoires SLDRAM, RDRAM et associées== Les mémoires précédentes sont généralement associées à des bus larges. Les mémoires SDRAM et DDR modernes ont des bus de données de 64 bits de large, avec des d'adresse et de commande de largeur similaire. Le nombre de fils du bus mémoire dépasse facilement la centaine de fils, avec autant de broches sur les barrettes de mémoire. Largeur de ces bus pose de problèmes problèmes électriques, dont la résolution n'est pas triviale. En conséquence, la fréquence du bus mémoire est généralement moins performantes comparé à ce qu'on aurait avec un bus moins large. Mais d'autres mémoires DRAM ont exploré une solution alternative : avoir un bus peu large mais de haute fréquence, sur lequel on envoie les commandes/données en plusieurs fois. Elles sont regroupées sous le nom de '''DRAM à commutation par paquets'''. Elles utilisent des bus spéciaux, où les commandes/adresses/données sont transmises par paquets, par trames, en plusieurs fois. En théorie, ce qu'on a dit sur le codage des trames dans le chapitre sur le bus devrait s'appliquer à de telles mémoires. En pratique, les protocoles de transmission sur le bus mémoire sont simplifiés, pour gérer le fonctionnement à haute fréquence. Le processeur envoie des paquets de commandes, les mémoires répondent avec des paquets de données ou des accusés de réception. Les mémoires à commutation par paquets sont peu nombreuses. Les plus connues sont les mémoires conçues par la société Rambus, à savoir la ''RDRAM'' (''Rambus DRAM'') et ses deux successeurs ''XDR RAM'' et ''XDR RAM 2''. La ''Synchronous-link DRAM'' (''SLDRAM'') est un format concurrent conçu par un consortium de plusieurs concepteurs de mémoire. ===La SLDRAM (''Synchronous-link DRAM'')=== Les '''mémoires SLDRAM''' avaient un bus de données de 64 bits allant à 200-400 Hz, avec technologie DDR, ce qui était dans la norme de l'époque pour la fréquence (début des années 2000). Elle utilisait un bus de commande de 11 bits, qui était utilisé pour transmettre des commandes de 40 bits, transmises en quatre cycles d'horloge consécutifs (en réalité, quatre fronts d'horloge donc deux cycles en DDR). Le bus de données était de 18 bits, mais les transferts de donnée se faisaient par paquets de 4 à 8 octets (32-65 bits). Pour résumer, données et commandes sont chacunes transmises en plusieurs cycles consécutifs, sur un bus de commande/données plus court que les données/commandes elle-mêmes. Là où les SDRAM sélectionnent la bonne barrette grâce à des signaux de commande dédiés, ce n'est pas le cas avec la SLDRAM. A la place, chaque barrette de mémoire reçoit un identifiant, un numéro codé sur 7-8 bits. Les commandes de lecture/écriture précisent l'identifiant dans la commande. Toutes les barrettes reçoivent la commande, elles vérifient si l'identifiant de la commande est le leur, et elles la prennent en compte seulement si c'est le cas. Voici le format d'une commande SLDRAM. Elle contient l'adresse, qui regroupe le numéro de banque, le numéro de ligne et le numéro de colonne. On trouve aussi un code commande qui indique s'il faut faire une lecture ou une écriture, et qui configure l'accès mémoire. Il configure notamment le mode rafale, en indiquant s'il faut lire/écrire 4 ou 8 octets. Enfin, il indique s'il faut fermer la ligne accédée une fois l'accès terminé, ou s'il faut la laisser ouverte. Le code commande peut aussi préciser que la commande est un rafraichissement ou non, effectuer des opérations de configuration, etc. L'identifiant de barrette mémoire est envoyé en premier, histoire que les barrettes sachent précocement si l'accès les concerne ou non. {|class="wikitable" style="text-align:center" |+SLDRAM Read, write or row op request packet ! FLAG || CA9 || CA8 || CA7 || CA6 || CA5 || CA4 || CA3 || CA2 || CA1 || CA0 |- ! 1 | colspan=9 bgcolor=#ffcccc| Identifiant de barrette mémoire|| bgcolor=#ccffcc| Code de commande |- ! 0 | colspan=5 bgcolor=#ccffcc| Code de commande ||colspan=3 bgcolor=#ff88ff| Banque||colspan=2 bgcolor=#ffffcc| Ligne |- ! 0 | colspan=9 bgcolor=#ffffcc| Ligne || 0 |- ! 0 | 0 || 0 || 0 ||colspan=7 bgcolor=#ccffff| Colonne |} ===Les mémoires Rambus=== Les mémoires conçues par la société Rambus regroupent la '''RDRAM''' (''Rambus DRAM'') et ses deux successeurs '''XDR RAM''' et '''XDR RAM 2'''. Les toutes premières étaient les '''mémoires RDRAM''', où le bus permettait de transmettre soit des commandes (adresse inclue), soit des données, avec un multiplexage total. Le processeur envoie un paquet contenant commandes et adresse à la mémoire, qui répond avec un paquet d'acquittement. Lors d'une lecture, le paquet d'acquittement contient la donnée lue. Lors d'une écriture, le paquet d'acquittement est réduit au strict minimum. Le bus de commandes est réduit au strict minimum, à savoir l'horloge et quelques bits absolument essentiels, les bits RW est transmis dans un paquet et n'ont pas de ligne dédiée, pareil pour le bit OE. Toutes les barrettes de mémoire doivent vérifier toutes les transmissions et déterminer si elles sont concernées en analysant l'adresse transmise dans la trame. Elles ont été utilisées dans des PC ou d'anciennes consoles de jeu. Par exemple, la Nintendo 64 incorporait 4 mébioctets de mémoire RDRAM en tant que mémoire principale. La RDRAM de la Nintendo 64 était cadencée à 500 MHz, utilisait un bus de 9 bits, et avait un débit binaire maximal théorique de 500 MB/s. La Playstation 2 contenait quant à elle 32 mébioctets de RDRAM en ''dual-channel'', pour un débit binaire de 3.2 Gibioctets par seconde. Les processeurs Pentium 3 pouvaient être associés à de la RDRAM sur certaines mères. Les Pentium 4 étaient eux aussi associés à la de RDRAM, mais les cartes mères ne géraient que ce genre de mémoire. La Playstation 3 contenait quant à elle de la XDR RAM. ==Les eDRAM : des DRAM adaptées aux ''chiplets''== Les '''mémoires eDRAM''', pour ''embedded DRAM'', sont des mémoires RAM qui sont destinées à être intégrée au processeur. Pour comparer, les DRAM normales sont placées sur des barrettes de RAM ou soudées à la carte mère. Dans la quasi-totalité des cas, l'eDRAM est utilisée pour implémenter une mémoire cache, elle ne sert pas de mémoire principale (cache L4, le plus proche de la mémoire sur ces puces). De ce fait, elles sont conçues pour être très rapides, avoir une grande bande passante, au détriment de leur capacité mémoire. Pour être plus précis, l'eDRAM est une puce de DRAM conçue pour être intégrée dans un ''chiplet'', , à savoir des circuits imprimés qui regroupent plusieurs puces électroniques distinctes, regroupées sur le même PCB. Typiquement, un processeur de type ''chiplet'' avec de l'eDRAM comprend deux puces séparées : une pour le processeur, une autre pour une puce de communication avec la RAM. Avec la mémoire eDRAM, les deux puces sont complétées par une troisième puce spécialisée qui incorpore l'eDRAM. Elle a été utilisée sur quelques processeurs, mais aussi dans des consoles de jeu vidéo, pour la carte graphique des consoles suivantes : la PlayStation 2, la PlayStation Portable, la GameCube, la Wii, la Wii U, et la XBOX 360. Sur ces consoles, la RAM de la carte graphique était intégrée avec le processeur graphique dans le même circuit. La fameuse mémoire vidéo et le GPU n'étaient qu'une seule et même puce électronique, un seul circuit intégré. Ce n'est pas le cas sur une carte graphique moderne : regardez votre carte graphique avec attention et vous verrez que le GPU est une puce carrée située sous les ventilateurs, alors que les puces mémoires sont situées juste autour et soudées sur le PCB de la carte. Les processeurs Intel Core de microarchitecture Broadwell disposaient d'un cache L4 de 128 mébioctets, intégralement implémenté avec de la mémoire eDRAM. Quelques processeurs de la microarchitecture précédente (Haswell), disposaient aussi de ce cache. Le cache L4 eDRAM était implémenté sur un chiplet à part, à savoir que le processeur était composé de trois puces séparées : une pour le processeur, une autre pour la gestion des entrées-sorties, et une autre pour le cache L4. La puce pour le cache L4 était appelée ''Crystal Well''. La puce ''Crystal Well'' était une puce gravée en 22nm, ce qui était une finesse de gravure plus élevée que celle des processeurs associés. ''Crystal Well'' était très optimisé pour l'époque. Par exemple, elle disposait de bus séparées pour la lecture et l'écriture, chose qu'on retrouve fréquemment sur les SRAM mais qui est absent sur les mémoires DRAM actuelles. Pour le reste, elle ressemblait beaucoup aux mémoires DDR de l'époque (système de ''double data rate'', entres autres), mais elle allait à une fréquence plus élevée que les DRAM de l'époque et avait un débit bien plus élevé, pour une consommation moindre. ''Crystal Well'' consommait entre 1 à 5 watts (1 watt en veille, 5 à pleine utilisation), pour un débit binaire de 102 GB/s et fonctionnait à 3.2 GHz. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les mémoires SRAM synchrones | prevText=Les mémoires SRAM synchrones | next=Contrôleur mémoire externe | nextText=Le contrôleur mémoire externe }}{{autocat}} </noinclude> gq3y9se4pwkyz2sk4tf5rhekhimvp22 Mathc initiation/Fichiers h : c54 0 76654 764737 759939 2026-04-23T22:51:39Z Xhungab 23827 764737 wikitext text/x-wiki __NOTOC__ [[Catégorie:Mathc initiation (livre)]] : [[Mathc initiation/005i| Sommaire]] : {{Partie{{{type|}}}| Les fonctions green_dydx(); green_dydx();}} En mathématiques, le théorème de Green, ou théorème de Green-Riemann, donne la relation entre une intégrale curviligne le long d'une courbe simple fermée orientée C¹ par morceaux et l'intégrale double sur la région du plan délimitée par cette courbe. [https://fr.khanacademy.org/math/multivariable-calculus/greens-theorem-and-stokes-theorem/greens-theorem/v/green-s-theorem-proof-part-1 Khanacademy : green s theorem] Le théorème de Green peut transformer une intégrale curviligne en une intégrale double. Il suffit de prendre les dérivées partielles de N en fonction de x et de M en fonction de y et de les soustraires. ( (b (y1(x) int o M(x,y) dx + N(x,y) dy = int( int( (N_x - M_y) dy dx (c (a (y0(x) Nous allons résoudre ces problèmes en utilisant la fonction standard des intégrales doubles simpson_dydx(N_x_mns_M_y, y0,y1,LOOP, x0,x1,LOOP); qui va nous demander de calculer manuellement les dérivées partielles N_x et M_y. Nous allons aussi utiliser une variante de la fonction green_dydx(M,N, y0,y1,LOOP, x0,x1,LOOP); où l'on introduira directement les fonctions M et N et c'est la nouvelle fonction qui calculera les dérivées partielles. Copier la bibliothèque dans votre répertoire de travail : * [[Mathc initiation/Fichiers h : c54a1|x_hfile.h ............ Déclaration des fichiers h]] * [[Mathc initiation/Fichiers h : c30a2|x_def.h .............. Déclaration des utilitaires]] * [[Mathc initiation/Fichiers c : c47ca|x_strcp.h ........... Déclaration des structures (points, vecteurs)]] * [[Mathc initiation/Fichiers h : c23a3|x_fx.h ................ Calculer la dérivé première et seconde]] * [[Mathc initiation/Fichiers h : c25a4|x_fxy.h .............. Calculer les dérivées partielles]] * [[Mathc initiation/a286|x_dydx.h ............ Les intégrales de la forme dydx]] * [[Mathc initiation/a287|x_dxdy.h ............ Les intégrales de la forme dxdy]] * [[Mathc initiation/Fichiers h : c54a7|x_gdxdy.h ......... La fonction green_dxdy]] * [[Mathc initiation/Fichiers h : c54a8|x_gdydx.h ......... La fonction green_dydx]] : <br> les fonctions f : * [[Mathc initiation/Fichiers h : c54fa|f.h]] : <br> Calculer la double intégrale de Green directement : La fonction green_dydx(); * [[Mathc initiation/Fichiers c : c54ca|c18a.c ]] * [[Mathc initiation/Fichiers c : c54cb|c18b.c ]] * [[Mathc initiation/Fichiers c : c54cc|c18c.c ]] La fonction green_dxdy(); * [[Mathc initiation/Fichiers c : c54cd|c18d.c ]] : <br> Regardons la fonction qui effectue le travail : * [[Mathc initiation/Fichiers h : c54da| Étudions la fonction '''green_dydx();''']] : {{AutoCat}} emm3weh97p2pv8dzivwginlnq7m8z6f Mathc initiation/Fichiers h : c59 0 76713 764740 761328 2026-04-23T22:52:29Z Xhungab 23827 764740 wikitext text/x-wiki __NOTOC__ [[Catégorie:Mathc initiation (livre)]] : [[Mathc initiation/005i| Sommaire]] : {{Partie{{{type|}}}| Le théorème de Stoke (version II) }} En mathématiques, et plus particulièrement en géométrie différentielle, le théorème de Stokes est un résultat central sur l'intégration des formes différentielles, qui généralise le second théorème fondamental de l'analyse, ainsi que de nombreux théorèmes d'analyse vectorielle. [https://fr.khanacademy.org/math/multivariable-calculus/greens-theorem-and-stokes-theorem/stokes-theorem/v/stokes-theorem-intuition Khanacademy : stokes-theorem-intuition] ... [https://fr.khanacademy.org/math/multivariable-calculus/greens-theorem-and-stokes-theorem/stokes-proof/v/stokes-theorem-proof-part-1 Khanacademy : stokes-theorem-proof] Copier la bibliothèque dans votre répertoire de travail : * [[Mathc initiation/Fichiers h : c59a1|x_afile.h ............ Déclaration des fichiers h]] * [[Mathc initiation/Fichiers h : c30a2|x_def.h .............. Déclaration des utilitaires]] * [[Mathc initiation/Fichiers c : c47ca|x_strcp.h ........... Déclaration des structures (points, vecteurs)]] * [[Mathc initiation/Fichiers h : c23a3|x_fx.h ................ Calculer les dérivées]] * [[Mathc initiation/Fichiers h : c25a4|x_fxy.h]] * [[Mathc initiation/Fichiers h : c26a4|x_fxyz.h]] * [[Mathc initiation/Fichiers h : c59a7|x_l3d_dx.h ......... L'intégrale curviligne 3d]] * [[Mathc initiation/Fichiers h : c59a8|x_l3d_dy.h ]] * [[Mathc initiation/Fichiers h : c59a9|x_l3d_dz.h ]] * [[Mathc initiation/Fichiers h : c59aa|x_nxy.h ............. n = (-f_xi-f_yj+k) / [(f_x)^2+(f_y)^2+1]^1/2 ]] * [[Mathc initiation/Fichiers h : c59ab|x_curl.h ............. Calculer le rotationel ]] * [[Mathc initiation/Fichiers h : c59ac|x_stokxy.h ........... L'intégrale de Stoke ]] * [[Mathc initiation/c36a1|x_stokyx.h]] les fonctions f : * [[Mathc initiation/Fichiers h : c59fa|f.h]] Résolution avec : * [[Mathc initiation/Fichiers c : c59ca1|c0a1.c .............. L'intégrale de Stoke dxdy .... s = 113.081]] * [[Mathc initiation/a470|c0a2.c .............. Les intégrales curviligne ...... s = +113.097]] * [[Mathc initiation/Fichiers c : c59ca2|c0a3.c .............. L'intégrale de Stoke '''dydx''' .... s = 113.081]] * [[Mathc initiation/Fichiers c : c59cb1|c0b1.c .............. L'intégrale de Stoke dxdy .... s = -12.579]] * [[Mathc initiation/Fichiers c : c59cb2|c0b2.c .............. Les intégrales curviligne ...... s = -12.566]] * [[Mathc initiation/004x|c0b3.c .............. L'intégrale de Stoke '''dydx''' .... s = -12.579]] Regardons la fonction qui effectue le travail : * [[Mathc initiation/c57a1| Étudions la fonction '''stokes_dxdy();''']] {{AutoCat}} dhn2uij2hg2h03nic64i7anzeja5rlb Mathc initiation/Fichiers h : c64 0 76789 764734 759728 2026-04-23T22:50:00Z Xhungab 23827 764734 wikitext text/x-wiki __NOTOC__ [[Catégorie:Mathc initiation (livre)]] : [[Mathc initiation/004w| Sommaire]] : {{Partie{{{type|}}}| Théorème de flux-divergence (Partie:1)}} : En analyse vectorielle, le théorème de la divergence (également appelé théorème de Green-Ostrogradski ou théorème de flux-divergence), affirme l'égalité entre l'intégrale de la divergence d'un champ vectoriel sur un volume dans ℝ³ et le flux de ce champ à travers la frontière du volume (qui est une intégrale de surface). [https://fr.khanacademy.org/math/multivariable-calculus/greens-theorem-and-stokes-theorem/divergence-theorem/v/3-d-divergence-theorem-intuition Khanacademy : divergence-theorem-intuition] ... [https://fr.khanacademy.org/math/multivariable-calculus/greens-theorem-and-stokes-theorem/divergence-theorem-proof/v/divergence-theorem-proof-part-1 Khanacademy : divergence-theorem-proof] Dans ce travail nous allons modifier les fonctions d'intégrales triples standards, pour calculer l'intégrale de la divergence d'un champ vectoriel sur un volume dans ℝ³. Nous rentrerons la fonction vectorielle f(x,y,z) sous la forme de trois fonctions M, N, P, ou M, N, P, correspondront aux trois composants de la fonction vectorielle de f. La fonction calculera les dérivées partielles et résoudra l'intégrale. On pourra vérifier avec Mathematica le résultat. Nous calculerons à la main les dérivées partielles pour pouvoir les introduire dans Mathematica. Copier la bibliothèque dans votre répertoire de travail : * [[Mathc initiation/Fichiers h : c64a1|x_afile.h ............ Déclaration des fichiers h]] * [[Mathc initiation/Fichiers h : c30a2|x_def.h .............. Déclaration des utilitaires]] * [[Mathc initiation/Fichiers c : c47ca|x_strcp.h ........... Déclaration des structures (points, vecteurs)]] * [[Mathc initiation/Fichiers h : c26a4|x_fxyz.h ............. Calculer les dérivées partielles]] * [[Mathc initiation/Fichiers h : c51|x_fluxyz.h .......... Intégrale de flux en xyz]] * [[Mathc initiation/0037|x_fluxzy.h .......... En xzy]] * [[Mathc initiation/a316|x_fluyxz.h .......... En yxz]] * [[Mathc initiation/Fichiers h : c64a7|x_fluyzx.h .......... En yzx]] * [[Mathc initiation/Fichiers h : c64a8|x_fluzxy.h .......... En zxy]] * [[Mathc initiation/Fichiers h : c64a9|x_fluzyx.h .......... En zyx]] les fonctions f : * [[Mathc initiation/Fichiers h : c64fa|f.h]] Exemples d'application : * [[Mathc initiation/a320| dxdydz]] * [[Mathc initiation/0038| dxdzdy]] * [[Mathc initiation/a317| dydxdz]] * [[Mathc initiation/Fichiers c : c64cc|dydzdx]] * [[Mathc initiation/Fichiers c : c64cb| dzdxdy]] * [[Mathc initiation/Fichiers c : c64ca| dzdydx]] Étudions la fonction : * [[Mathc initiation/Fichiers c : c64ea|flux_dydzdx();]] {{AutoCat}} 3wyrepj8zoetcx2xg3zrqwyjr3tvq87 Mathc initiation/Fichiers h : c63 0 76803 764735 759729 2026-04-23T22:50:18Z Xhungab 23827 764735 wikitext text/x-wiki __NOTOC__ [[Catégorie:Mathc initiation (livre)]] : [[Mathc initiation/004w| Sommaire]] : {{Partie{{{type|}}}| Théorème de flux-divergence (Partie:2)}} En analyse vectorielle, le théorème de la divergence (également appelé théorème de Green-Ostrogradski ou théorème de flux-divergence), affirme l'égalité entre l'intégrale de la divergence d'un champ vectoriel sur un volume dans ℝ³ et le flux de ce champ à travers la frontière du volume (qui est une intégrale de surface). [https://fr.khanacademy.org/math/multivariable-calculus/greens-theorem-and-stokes-theorem/divergence-theorem/v/3-d-divergence-theorem-intuition Khanacademy : divergence-theorem-intuition] ... [https://fr.khanacademy.org/math/multivariable-calculus/greens-theorem-and-stokes-theorem/divergence-theorem-proof/v/divergence-theorem-proof-part-1 Khanacademy : divergence-theorem-proof] '''Nous allons faire le même travail que précédemment, mais en plusieurs étapes. Vous pourrez vérifier si nous obtenons le même résultat en utilisant le chapitre précédent.''' Copier la bibliothèque dans votre répertoire de travail : * [[Mathc initiation/Fichiers h : c63a1|x_afile.h ............ Déclaration des fichiers h]] * [[Mathc initiation/Fichiers h : c30a2|x_def.h .............. Déclaration des utilitaires]] * [[Mathc initiation/Fichiers c : c47ca|x_strcp.h ........... Déclaration des structures (points, vecteurs)]] * [[Mathc initiation/Fichiers h : c26a4|x_fxyz.h ............. Calculer les dérivées partielles]] * [[Mathc initiation/a318|x_pluxyz.h .......... Intégrale de flux en xyz]] * [[Mathc initiation/0039|x_pluxzy.h .......... En xzy]] * [[Mathc initiation/Fichiers h : c52|x_pluyxz.h .......... En yxz]] * [[Mathc initiation/Fichiers h : c63a7|x_pluyzx.h .......... En yzx]] * [[Mathc initiation/Fichiers h : c63a8|x_pluzxy.h .......... En zxy]] * [[Mathc initiation/Fichiers h : c63a9|x_pluzyx.h .......... En zyx]] les fonctions f : * [[Mathc initiation/Fichiers h : c64fa|f.h]]: Exemples d'application : * [[Mathc initiation/a319| dxdydz ]] * [[Mathc initiation/003a| dxdzdy ]] * [[Mathc initiation/Fichiers h : c53| dydxdz ]] * [[Mathc initiation/Fichiers c : c63cc| dydzdx ]] * [[Mathc initiation/Fichiers c : c63cb| dzdxdy ]] * [[Mathc initiation/Fichiers c : c63ca| dzdydx ]] Étudions la fonction : * [[Mathc initiation/Fichiers c : c63ea|flux_dydzdx(); ]] {{AutoCat}} 04glppak9y8o5j7ssap3igd2m2y8b54 Le mouvement Wikimédia/Version complète 0 77521 764758 764197 2026-04-24T08:43:51Z Lionel Scheepmans 20012 Wiki 764758 wikitext text/x-wiki {{DISPLAYTITLE:<span style="color:white;width:0;font-size:0;">{{FULLPAGENAME}}</span>}} __SECTIONNONEDITABLE__ __NOTOC__ {{Centrer|1=<div style="font-size:250%;">'''LE MOUVEMENT WIKIMÉDIA'''</div></br><div style="font-size:250%;">'''Comment rendre la connaissance fiable</br>par le partage, la liberté et l'équité ?'''</div>}} </br> [[Fichier:Photo de couverture du wikilivre Le mouvement Wikimédia.jpg|centré|sans_cadre|800x800px]] </br> <big><big>{{Centrer|De Lionel Scheepmans<br />Avec l'aide de la communauté Wikimédia}}</big></big> </br> [[Fichier:Wikibooks-logo-fr-noslogan-light.svg|centré|sans_cadre|100x100px]] </br> {{Seulement à l'impression|{{Centrer|Version du {{CURRENTDAY}} {{CURRENTMONTHNAME}} {{CURRENTYEAR}} à {{CURRENTTIME}}}}|div}} {{Nouvelle page imprimée}} == Quatrième de couverture == {{:Le mouvement Wikimédia/Quatrième de couverture}} {{Nouvelle page imprimée}} == Sommaire == {{:Le mouvement Wikimédia/Sommaire1}} {{Nouvelle page imprimée}} == Avant-propos == {{:Le mouvement Wikimédia/Avant-propos}} {{Nouvelle page imprimée}} == Introduction : Wikimédia n’est pas Wikipédia == {{:Le mouvement Wikimédia/Introduction : Wikimédia n'est pas Wikipédia}} {{Nouvelle page imprimée}} == Première partie : La naissance du mouvement Wikimédia == {{:Le mouvement Wikimédia/La naissance du mouvement Wikimédia}} === L'utopie Wikimédia === {{:Le mouvement Wikimédia/L'utopie Wikimédia}} === Le mouvement du logiciel libre === {{:Le mouvement Wikimédia/Le mouvement du logiciel libre}} === Les licences et la culture libres === {{:Le mouvement Wikimédia/Les licences et la culture libres}} === Le réseau Internet === {{:Le mouvement Wikimédia/Le réseau Internet}} === Le World Wide Web === {{:Le mouvement Wikimédia/Le World Wide Web}} === Les platesformes Wiki === {{:Le mouvement Wikimédia/Les platesformes Wiki}} === L’encyclopédie libre et universelle === {{:Le mouvement Wikimédia/L'encyclopédie libre et universelle}} === L'arrivée des projets frères === {{:Le mouvement Wikimédia/L'arrivée des projets frères}} === La conscientisation du mouvement === {{:Le mouvement Wikimédia/La conscientisation du mouvement}} === La création des organismes affiliés === {{:Le mouvement Wikimédia/La création des organismes affiliés}} === L'héritage d'une contre-culture === {{:Le mouvement Wikimédia/L'héritage d'une contre-culture}} {{Nouvelle page imprimée}} == Deuxième partie : Cosmographie du mouvement Wikimédia == {{:Le mouvement Wikimédia/Cosmographie du mouvement Wikimédia}} === La constellation des projets en ligne === {{:Le mouvement Wikimédia/La constellation des projets en ligne}} === Les projets de partage de la connaissance === {{:Le mouvement Wikimédia/Les projets de partage de la connaissance}} === Les projets de gouvernance, de gestion et de sensibilisation === {{:Le mouvement Wikimédia/Les projets de gouvernance, de gestion et de sensibilisation}} === Les projets de gestion technique === {{:Le mouvement Wikimédia/Les projets de gestion technique}} === Les espaces de communication et d’information === {{:Le mouvement Wikimédia/Les espaces de communication et d’information}} {{Nouvelle page imprimée}} === La constellation de la Fondation et de ses affiliés === {{:Le mouvement Wikimédia/La constellation de la Fondation et de ses affiliés}} === La Fondation Wikimédia === {{:Le mouvement Wikimédia/La Fondation Wikimédia}} === Le conseil d’administration de la Fondation === {{:Le mouvement Wikimédia/Le conseil d’administration}} === Les comités, groupes de travail et conseils === {{:Le mouvement Wikimédia/Les comités, groupes de travail et conseils}} === Les associations locales === {{:Le mouvement Wikimédia/Les associations locales}} === Les organisations thématiques, centrales et linguistiques === {{:Le mouvement Wikimédia/Les organisations thématiques, centrales et linguistiques }} === Les groupes d’usagers === {{:Le mouvement Wikimédia/Les groupes d’usagers}} === Les projets d’assistances === {{:Le mouvement Wikimédia/Les projets d’assistances}} === Les cycles de conférences et espaces de rencontres === {{:Le mouvement Wikimédia/Les cycles de conférences et espaces de rencontres}} === Les partenariats avec des entités externes au mouvement === {{:Le mouvement Wikimédia/Les partenariats externes}} {{Nouvelle page imprimée}} == Conclusion : Un mouvement culturel inspirant == {{:Le mouvement Wikimédia/Conclusion : Un mouvement culturel inspirant}} {{Nouvelle page imprimée}} == Remerciements == {{:Le mouvement Wikimédia/Remerciements}} {{Nouvelle page imprimée}} == Notes et références == [[Fichier:Qr code références livre Wikimédia.svg|alt=centrer|droite|sans_cadre|65x65px]] <small>{{Cacher à l'impression|<references />}}</small> == Bibliographie == {{:Le mouvement Wikimédia/Bibliographie}} [[Catégorie:Livres]] [[Catégorie:Livres]] [[Catégorie:Étude du cyber-mouvement du logiciel libre (livre)]] [[Catégorie:Anthropologie]] [[Catégorie:Sciences humaines]] [[Catégorie:Livres terminés]] [[Catégorie:Le mouvement Wikimédia]] [[Catégorie:Le mouvement Wikimédia (livre)]] [[Catégorie:Livres en vitrine]] {{AutoCat}} sfhbav9w0zy50yq63kl0ou3cx3phwqn 764760 764758 2026-04-24T08:56:07Z Lionel Scheepmans 20012 764760 wikitext text/x-wiki {{DISPLAYTITLE:<span style="color:white;width:0;font-size:0;">{{FULLPAGENAME}}</span>}} __SECTIONNONEDITABLE__ __NOTOC__ {{Centrer|1=<div style="font-size:250%;">'''LE MOUVEMENT WIKIMÉDIA'''</div></br><div style="font-size:250%;">'''Dernier refuge altruiste ?'''</div>}} </br> [[Fichier:Photo de couverture du wikilivre Le mouvement Wikimédia.jpg|centré|sans_cadre|800x800px]] </br> <big><big>{{Centrer|De Lionel Scheepmans<br />Avec l'aide de la communauté Wikimédia}}</big></big> </br> [[Fichier:Wikibooks-logo-fr-noslogan-light.svg|centré|sans_cadre|100x100px]] </br> {{Seulement à l'impression|{{Centrer|Version du {{CURRENTDAY}} {{CURRENTMONTHNAME}} {{CURRENTYEAR}} à {{CURRENTTIME}}}}|div}} {{Nouvelle page imprimée}} == Quatrième de couverture == {{:Le mouvement Wikimédia/Quatrième de couverture}} {{Nouvelle page imprimée}} == Sommaire == {{:Le mouvement Wikimédia/Sommaire1}} {{Nouvelle page imprimée}} == Avant-propos == {{:Le mouvement Wikimédia/Avant-propos}} {{Nouvelle page imprimée}} == Introduction : Wikimédia n’est pas Wikipédia == {{:Le mouvement Wikimédia/Introduction : Wikimédia n'est pas Wikipédia}} {{Nouvelle page imprimée}} == Première partie : La naissance du mouvement Wikimédia == {{:Le mouvement Wikimédia/La naissance du mouvement Wikimédia}} === L'utopie Wikimédia === {{:Le mouvement Wikimédia/L'utopie Wikimédia}} === Le mouvement du logiciel libre === {{:Le mouvement Wikimédia/Le mouvement du logiciel libre}} === Les licences et la culture libres === {{:Le mouvement Wikimédia/Les licences et la culture libres}} === Le réseau Internet === {{:Le mouvement Wikimédia/Le réseau Internet}} === Le World Wide Web === {{:Le mouvement Wikimédia/Le World Wide Web}} === Les platesformes Wiki === {{:Le mouvement Wikimédia/Les platesformes Wiki}} === L’encyclopédie libre et universelle === {{:Le mouvement Wikimédia/L'encyclopédie libre et universelle}} === L'arrivée des projets frères === {{:Le mouvement Wikimédia/L'arrivée des projets frères}} === La conscientisation du mouvement === {{:Le mouvement Wikimédia/La conscientisation du mouvement}} === La création des organismes affiliés === {{:Le mouvement Wikimédia/La création des organismes affiliés}} === L'héritage d'une contre-culture === {{:Le mouvement Wikimédia/L'héritage d'une contre-culture}} {{Nouvelle page imprimée}} == Deuxième partie : Cosmographie du mouvement Wikimédia == {{:Le mouvement Wikimédia/Cosmographie du mouvement Wikimédia}} === La constellation des projets en ligne === {{:Le mouvement Wikimédia/La constellation des projets en ligne}} === Les projets de partage de la connaissance === {{:Le mouvement Wikimédia/Les projets de partage de la connaissance}} === Les projets de gouvernance, de gestion et de sensibilisation === {{:Le mouvement Wikimédia/Les projets de gouvernance, de gestion et de sensibilisation}} === Les projets de gestion technique === {{:Le mouvement Wikimédia/Les projets de gestion technique}} === Les espaces de communication et d’information === {{:Le mouvement Wikimédia/Les espaces de communication et d’information}} {{Nouvelle page imprimée}} === La constellation de la Fondation et de ses affiliés === {{:Le mouvement Wikimédia/La constellation de la Fondation et de ses affiliés}} === La Fondation Wikimédia === {{:Le mouvement Wikimédia/La Fondation Wikimédia}} === Le conseil d’administration de la Fondation === {{:Le mouvement Wikimédia/Le conseil d’administration}} === Les comités, groupes de travail et conseils === {{:Le mouvement Wikimédia/Les comités, groupes de travail et conseils}} === Les associations locales === {{:Le mouvement Wikimédia/Les associations locales}} === Les organisations thématiques, centrales et linguistiques === {{:Le mouvement Wikimédia/Les organisations thématiques, centrales et linguistiques }} === Les groupes d’usagers === {{:Le mouvement Wikimédia/Les groupes d’usagers}} === Les projets d’assistances === {{:Le mouvement Wikimédia/Les projets d’assistances}} === Les cycles de conférences et espaces de rencontres === {{:Le mouvement Wikimédia/Les cycles de conférences et espaces de rencontres}} === Les partenariats avec des entités externes au mouvement === {{:Le mouvement Wikimédia/Les partenariats externes}} {{Nouvelle page imprimée}} == Conclusion : Un mouvement culturel inspirant == {{:Le mouvement Wikimédia/Conclusion : Un mouvement culturel inspirant}} {{Nouvelle page imprimée}} == Remerciements == {{:Le mouvement Wikimédia/Remerciements}} {{Nouvelle page imprimée}} == Notes et références == [[Fichier:Qr code références livre Wikimédia.svg|alt=centrer|droite|sans_cadre|65x65px]] <small>{{Cacher à l'impression|<references />}}</small> == Bibliographie == {{:Le mouvement Wikimédia/Bibliographie}} [[Catégorie:Livres]] [[Catégorie:Livres]] [[Catégorie:Étude du cyber-mouvement du logiciel libre (livre)]] [[Catégorie:Anthropologie]] [[Catégorie:Sciences humaines]] [[Catégorie:Livres terminés]] [[Catégorie:Le mouvement Wikimédia]] [[Catégorie:Le mouvement Wikimédia (livre)]] [[Catégorie:Livres en vitrine]] {{AutoCat}} az7uox6q2v9nr95pewu8gbpou0ziwsr 764762 764760 2026-04-24T09:00:49Z Lionel Scheepmans 20012 764762 wikitext text/x-wiki {{DISPLAYTITLE:<span style="color:white;width:0;font-size:0;">{{FULLPAGENAME}}</span>}} __SECTIONNONEDITABLE__ __NOTOC__ {{Centrer|1=<div style="font-size:250%;">'''LE MOUVEMENT WIKIMÉDIA'''</div></br><div style="font-size:250%;">'''Dernier refuge altruiste du savoir libre ?'''</div>}} </br> [[Fichier:Photo de couverture du wikilivre Le mouvement Wikimédia.jpg|centré|sans_cadre|800x800px]] </br> <big><big>{{Centrer|De Lionel Scheepmans<br />Avec l'aide de la communauté Wikimédia}}</big></big> </br> [[Fichier:Wikibooks-logo-fr-noslogan-light.svg|centré|sans_cadre|100x100px]] </br> {{Seulement à l'impression|{{Centrer|Version du {{CURRENTDAY}} {{CURRENTMONTHNAME}} {{CURRENTYEAR}} à {{CURRENTTIME}}}}|div}} {{Nouvelle page imprimée}} == Quatrième de couverture == {{:Le mouvement Wikimédia/Quatrième de couverture}} {{Nouvelle page imprimée}} == Sommaire == {{:Le mouvement Wikimédia/Sommaire1}} {{Nouvelle page imprimée}} == Avant-propos == {{:Le mouvement Wikimédia/Avant-propos}} {{Nouvelle page imprimée}} == Introduction : Wikimédia n’est pas Wikipédia == {{:Le mouvement Wikimédia/Introduction : Wikimédia n'est pas Wikipédia}} {{Nouvelle page imprimée}} == Première partie : La naissance du mouvement Wikimédia == {{:Le mouvement Wikimédia/La naissance du mouvement Wikimédia}} === L'utopie Wikimédia === {{:Le mouvement Wikimédia/L'utopie Wikimédia}} === Le mouvement du logiciel libre === {{:Le mouvement Wikimédia/Le mouvement du logiciel libre}} === Les licences et la culture libres === {{:Le mouvement Wikimédia/Les licences et la culture libres}} === Le réseau Internet === {{:Le mouvement Wikimédia/Le réseau Internet}} === Le World Wide Web === {{:Le mouvement Wikimédia/Le World Wide Web}} === Les platesformes Wiki === {{:Le mouvement Wikimédia/Les platesformes Wiki}} === L’encyclopédie libre et universelle === {{:Le mouvement Wikimédia/L'encyclopédie libre et universelle}} === L'arrivée des projets frères === {{:Le mouvement Wikimédia/L'arrivée des projets frères}} === La conscientisation du mouvement === {{:Le mouvement Wikimédia/La conscientisation du mouvement}} === La création des organismes affiliés === {{:Le mouvement Wikimédia/La création des organismes affiliés}} === L'héritage d'une contre-culture === {{:Le mouvement Wikimédia/L'héritage d'une contre-culture}} {{Nouvelle page imprimée}} == Deuxième partie : Cosmographie du mouvement Wikimédia == {{:Le mouvement Wikimédia/Cosmographie du mouvement Wikimédia}} === La constellation des projets en ligne === {{:Le mouvement Wikimédia/La constellation des projets en ligne}} === Les projets de partage de la connaissance === {{:Le mouvement Wikimédia/Les projets de partage de la connaissance}} === Les projets de gouvernance, de gestion et de sensibilisation === {{:Le mouvement Wikimédia/Les projets de gouvernance, de gestion et de sensibilisation}} === Les projets de gestion technique === {{:Le mouvement Wikimédia/Les projets de gestion technique}} === Les espaces de communication et d’information === {{:Le mouvement Wikimédia/Les espaces de communication et d’information}} {{Nouvelle page imprimée}} === La constellation de la Fondation et de ses affiliés === {{:Le mouvement Wikimédia/La constellation de la Fondation et de ses affiliés}} === La Fondation Wikimédia === {{:Le mouvement Wikimédia/La Fondation Wikimédia}} === Le conseil d’administration de la Fondation === {{:Le mouvement Wikimédia/Le conseil d’administration}} === Les comités, groupes de travail et conseils === {{:Le mouvement Wikimédia/Les comités, groupes de travail et conseils}} === Les associations locales === {{:Le mouvement Wikimédia/Les associations locales}} === Les organisations thématiques, centrales et linguistiques === {{:Le mouvement Wikimédia/Les organisations thématiques, centrales et linguistiques }} === Les groupes d’usagers === {{:Le mouvement Wikimédia/Les groupes d’usagers}} === Les projets d’assistances === {{:Le mouvement Wikimédia/Les projets d’assistances}} === Les cycles de conférences et espaces de rencontres === {{:Le mouvement Wikimédia/Les cycles de conférences et espaces de rencontres}} === Les partenariats avec des entités externes au mouvement === {{:Le mouvement Wikimédia/Les partenariats externes}} {{Nouvelle page imprimée}} == Conclusion : Un mouvement culturel inspirant == {{:Le mouvement Wikimédia/Conclusion : Un mouvement culturel inspirant}} {{Nouvelle page imprimée}} == Remerciements == {{:Le mouvement Wikimédia/Remerciements}} {{Nouvelle page imprimée}} == Notes et références == [[Fichier:Qr code références livre Wikimédia.svg|alt=centrer|droite|sans_cadre|65x65px]] <small>{{Cacher à l'impression|<references />}}</small> == Bibliographie == {{:Le mouvement Wikimédia/Bibliographie}} [[Catégorie:Livres]] [[Catégorie:Livres]] [[Catégorie:Étude du cyber-mouvement du logiciel libre (livre)]] [[Catégorie:Anthropologie]] [[Catégorie:Sciences humaines]] [[Catégorie:Livres terminés]] [[Catégorie:Le mouvement Wikimédia]] [[Catégorie:Le mouvement Wikimédia (livre)]] [[Catégorie:Livres en vitrine]] {{AutoCat}} 95ayxenbev5vr42yk65i23oij1eir17 764780 764762 2026-04-24T09:28:41Z Lionel Scheepmans 20012 764780 wikitext text/x-wiki {{DISPLAYTITLE:<span style="color:white;width:0;font-size:0;">{{FULLPAGENAME}}</span>}} __SECTIONNONEDITABLE__ __NOTOC__ {{Centrer|1=<div style="font-size:250%;">'''LE MOUVEMENT WIKIMÉDIA'''</div></br><div style="font-size:250%;">'''Dernier partage altruiste de la connaissance libre ?'''</div>}} </br> [[Fichier:Photo de couverture du wikilivre Le mouvement Wikimédia.jpg|centré|sans_cadre|800x800px]] </br> <big><big>{{Centrer|De Lionel Scheepmans<br />Avec l'aide de la communauté Wikimédia}}</big></big> </br> [[Fichier:Wikibooks-logo-fr-noslogan-light.svg|centré|sans_cadre|100x100px]] </br> {{Seulement à l'impression|{{Centrer|Version du {{CURRENTDAY}} {{CURRENTMONTHNAME}} {{CURRENTYEAR}} à {{CURRENTTIME}}}}|div}} {{Nouvelle page imprimée}} == Quatrième de couverture == {{:Le mouvement Wikimédia/Quatrième de couverture}} {{Nouvelle page imprimée}} == Sommaire == {{:Le mouvement Wikimédia/Sommaire1}} {{Nouvelle page imprimée}} == Avant-propos == {{:Le mouvement Wikimédia/Avant-propos}} {{Nouvelle page imprimée}} == Introduction : Wikimédia n’est pas Wikipédia == {{:Le mouvement Wikimédia/Introduction : Wikimédia n'est pas Wikipédia}} {{Nouvelle page imprimée}} == Première partie : La naissance du mouvement Wikimédia == {{:Le mouvement Wikimédia/La naissance du mouvement Wikimédia}} === L'utopie Wikimédia === {{:Le mouvement Wikimédia/L'utopie Wikimédia}} === Le mouvement du logiciel libre === {{:Le mouvement Wikimédia/Le mouvement du logiciel libre}} === Les licences et la culture libres === {{:Le mouvement Wikimédia/Les licences et la culture libres}} === Le réseau Internet === {{:Le mouvement Wikimédia/Le réseau Internet}} === Le World Wide Web === {{:Le mouvement Wikimédia/Le World Wide Web}} === Les platesformes Wiki === {{:Le mouvement Wikimédia/Les platesformes Wiki}} === L’encyclopédie libre et universelle === {{:Le mouvement Wikimédia/L'encyclopédie libre et universelle}} === L'arrivée des projets frères === {{:Le mouvement Wikimédia/L'arrivée des projets frères}} === La conscientisation du mouvement === {{:Le mouvement Wikimédia/La conscientisation du mouvement}} === La création des organismes affiliés === {{:Le mouvement Wikimédia/La création des organismes affiliés}} === L'héritage d'une contre-culture === {{:Le mouvement Wikimédia/L'héritage d'une contre-culture}} {{Nouvelle page imprimée}} == Deuxième partie : Cosmographie du mouvement Wikimédia == {{:Le mouvement Wikimédia/Cosmographie du mouvement Wikimédia}} === La constellation des projets en ligne === {{:Le mouvement Wikimédia/La constellation des projets en ligne}} === Les projets de partage de la connaissance === {{:Le mouvement Wikimédia/Les projets de partage de la connaissance}} === Les projets de gouvernance, de gestion et de sensibilisation === {{:Le mouvement Wikimédia/Les projets de gouvernance, de gestion et de sensibilisation}} === Les projets de gestion technique === {{:Le mouvement Wikimédia/Les projets de gestion technique}} === Les espaces de communication et d’information === {{:Le mouvement Wikimédia/Les espaces de communication et d’information}} {{Nouvelle page imprimée}} === La constellation de la Fondation et de ses affiliés === {{:Le mouvement Wikimédia/La constellation de la Fondation et de ses affiliés}} === La Fondation Wikimédia === {{:Le mouvement Wikimédia/La Fondation Wikimédia}} === Le conseil d’administration de la Fondation === {{:Le mouvement Wikimédia/Le conseil d’administration}} === Les comités, groupes de travail et conseils === {{:Le mouvement Wikimédia/Les comités, groupes de travail et conseils}} === Les associations locales === {{:Le mouvement Wikimédia/Les associations locales}} === Les organisations thématiques, centrales et linguistiques === {{:Le mouvement Wikimédia/Les organisations thématiques, centrales et linguistiques }} === Les groupes d’usagers === {{:Le mouvement Wikimédia/Les groupes d’usagers}} === Les projets d’assistances === {{:Le mouvement Wikimédia/Les projets d’assistances}} === Les cycles de conférences et espaces de rencontres === {{:Le mouvement Wikimédia/Les cycles de conférences et espaces de rencontres}} === Les partenariats avec des entités externes au mouvement === {{:Le mouvement Wikimédia/Les partenariats externes}} {{Nouvelle page imprimée}} == Conclusion : Un mouvement culturel inspirant == {{:Le mouvement Wikimédia/Conclusion : Un mouvement culturel inspirant}} {{Nouvelle page imprimée}} == Remerciements == {{:Le mouvement Wikimédia/Remerciements}} {{Nouvelle page imprimée}} == Notes et références == [[Fichier:Qr code références livre Wikimédia.svg|alt=centrer|droite|sans_cadre|65x65px]] <small>{{Cacher à l'impression|<references />}}</small> == Bibliographie == {{:Le mouvement Wikimédia/Bibliographie}} [[Catégorie:Livres]] [[Catégorie:Livres]] [[Catégorie:Étude du cyber-mouvement du logiciel libre (livre)]] [[Catégorie:Anthropologie]] [[Catégorie:Sciences humaines]] [[Catégorie:Livres terminés]] [[Catégorie:Le mouvement Wikimédia]] [[Catégorie:Le mouvement Wikimédia (livre)]] [[Catégorie:Livres en vitrine]] {{AutoCat}} jh57o0mjopjt8h0bnlgb6blhpdwaxcb Le mouvement Wikimédia/Introduction : Wikimédia n'est pas Wikipédia 0 78999 764783 764209 2026-04-24T09:36:04Z Lionel Scheepmans 20012 764783 wikitext text/x-wiki <noinclude>{{Le mouvement Wikimédia}} </noinclude> Depuis le succès initial de Wikipédia, une myriade de projets de partage des connaissances, d’organisations et de groupes de soutien ont émergé pour former ce qu’on appelle aujourd’hui le mouvement Wikimédia. Même si, à ce jour, l’encyclopédie libre reste ll'activité phare du mouvement, il serait regrettable de réduire l’ensemble du mouvement à cet unique projet pédagogique. Malheureusement, il arrive bien trop souvent qu'une seule version linguistique de Wikipédia suffise pour cacher l’étendue de la forêt Wikimédia. En réalité, Wikimédia représente un mouvement social, international et interculturel complexe, au sein duquel Wikipédia n’est qu’une composante parmi d’autres. Dans le cadre d'un mémoire de master, on peut ainsi fournir en quelques mois une [[v:recherche:Culture_fr_Wikipédia|ethnographie du projet Wikipédia en français]]<ref>{{Lien web|titre=Culture fr Wikipédia, ethnographie du projet Wikipédia en français|url=https://web.archive.org/web/20250905132014/https://fr.wikiversity.org/wiki/Recherche:Culture_fr_Wikip%C3%A9dia|auteur=Lionel Scheepmans|site=Wikiversité|année=2011}}.</ref>. Alors que pour synthétiser les origines, l’organisation et les dynamiques globales du mouvement Wikimédia, une [[v:fr:Recherche:Imagine_un_monde|thèse de doctorat]]<ref>{{Ouvrage|langue=fr|prénom1=Lionel|nom1=Scheepmans|lien auteur1=user:Lionel Scheepmans|titre=Imagine un monde : quand le mouvement Wikimédia nous aide à penser de manière prospective la société globale et numérique de demain|éditeur=UCL - Université Catholique de Louvain|année=2022|date=17/06/2022|lire en ligne=https://dial.uclouvain.be/pr/boreal/object/boreal:264603|consulté le=2024-03-10|nature article=Thèse de doctorat}}</ref> et cinq années de recul supplémentaires furent nécessaires pour finaliser le projet. [[Fichier:Wikimedia project stickers.jpg|vignette|<small>Figure 1. Série d’autocollants reprenant les logos des principaux projets collaboratifs actifs au sein du mouvement Wikimédia.</small>]] À la fin de l'année 2025, l’ampleur numérique du mouvement est effectivement impressionnante. Chaque mois, des millions de modifications bénévoles sont effectuées sur plus de 500 millions de pages<ref>{{Lien web|auteur=Stat.wikimedia.org|titre=Statistiques de Wikimédia|url=https://web.archive.org/web/20251209230902/https://stats.wikimedia.org/#/all-projects}}.</ref>, réparties sur plus d’un millier de sites web<ref>{{Lien web|url=https://web.archive.org/web/20251217134333/https://wikistats.wmcloud.org/wikimedias_html.php?s=users_asc&th=0&lines=2000|titre=All Wikimedia Projects by Size|auteur=Wikistats wmcloud}}.</ref>, dont 358 seulement, correspondent aux [[m:List_of_Wikipedias|versions linguistiques de Wikipédia]]<ref>{{Lien web|url=https://web.archive.org/web/20251208041814/https://meta.wikimedia.org/wiki/List_of_Wikipedias|titre=List_of_Wikipedias|auteur=Méta-Wiki}}.</ref>. Ce qui prouve donc clairement que les activités en ligne portées par l'ensemble du mouvement Wikimédia dépassent largement ce qui se passe au sein de l’encyclopédie. Il existe ainsi huit autres projets pédagogiques susceptibles d'atteindre un jour l'envergure et la notoriété de Wikipédia, avec un objectif et un fonctionnement spécifiques à chacun. De manière détaillée, Wikilivres crée des livres pédagogiques, Wikiversité rassemble des supports d'enseignement et des travaux de recherche, Wikinews fait du journalisme collaboratif, Wikivoyage développe un guide touristique, pendant que Wiktionnaire apporte des définitions des mots de toutes les langues et dans toutes les langues. Contrairement à Wikipédia, tous ces projets ne sont pas soumis à une neutralité de point de vue, ni limités à l'usage de sources secondaires reconnues, au niveau de la rédaction des articles. La plupart d'entre eux acceptent aussi la publication de travaux de recherche ou de productions personnelles, alors que cela est tout à fait interdit dans Wikipédia. Selon l'étymologie du mot encyclopédie, le but de Wikipédia est en effet de synthétiser ou, plus précisément, d'encercler le savoir humain déjà préexistant. Cette contrainte éditoriale limite donc les contributeurs et contributrices à l'usage de sources secondaires et tertiaires présentes dans des publications externes au projet. De ce fait, Wikipédia reproduit fatalement les biais systémiques, tels que les déséquilibres et les surreprésentations de genre et de culture, présents dans un monde de l'édition majoritairement occidental. Or, cette impasse éditoriale propre à Wikipédia n'existe pas dans les autres projets pédagogiques. Comme ces projets frères, Wikipédia n'est pas non plus un projet complètement autonome des autres projets Wikimédia. L'encyclopédie compte en effet sur le projet Wikimedia Commons pour héberger l'ensemble de sa médiathèque. Elle utilise ensuite le contenu du projet Wikidata comme base de données structurée. Quant aux personnes qui produisent l'encyclopédie, elles peuvent aussi puiser leurs sources dans la bibliothèque Wikisource, ou se référer à des citations d'auteurs collectées dans le projet Wikiquote. Tout cela sans oublier que des dizaines de sites web traitent l'archivage permanent de Wikipédia et des autres projets Wikimédia, dans le but de fournir des analyses précieuses et totalement libres d’accès. Enfin, il faut aussi garder à l'esprit qu'au-delà de tous ces sites web, le mouvement Wikimédia, c'est aussi de nombreuses institutions et organisations affiliées dispersées dans le monde. Autour de la [[w:Fondation_Wikimédia|Fondation Wikimédia]] chargée de la gestion et de l’organisation internationales, avec près de 650 salariés de nationalités diverses<ref>{{Lien web|langue=|auteur=Wikimedia Foundation|titre=Les personnes derrière notre connaissance|url=https://web.archive.org/web/20250904032058/https://wikimediafoundation.org/fr/who-we-are/people/|site=|date=|consulté le=}}</ref>, se regroupent des centaines d'organisations satellites. Parmi celles-ci, on retrouve 2 [[m:Wikimedia_thematic_organizations/fr|associations thématiques]]<ref>{{Lien web|langue=|auteur=Méta-Wiki|titre=Wikimedia thematic organisations|url=https://web.archive.org/web/20250926235310/https://meta.wikimedia.org/wiki/Wikimedia_thematic_organizations|site=|date=|consulté le=}}.</ref>, 40 [[m:Wikimedia_chapters/fr|associations locales]]<ref>{{Lien web|langue=|auteur=Méta-Wiki|titre=Wikimedia chapters|url=https://web.archive.org/web/20251006092129/https://meta.wikimedia.org/wiki/Wikimedia_chapters}}</ref>, dont ''Wikimedia Deutschland'' qui regroupe plus de 170 emplyés<ref>{{Lien web|langue=|auteur=RocketReach|titre=Wikimedia Deutschlande. V. Information|url=https://web.archive.org/web/20251218034616/https://rocketreach.co/wikimedia-deutschland-e-v-profile_b5caf600f42e140f|site=|date=|consulté le=}}.</ref>, et finalement 141 [[metawiki:Wikimedia_user_groups/fr|groupes d’utilisateurs et utilisatrices]]<ref>{{Lien web|langue=|auteur=Méta-Wiki|titre=Wikimedia user groups|url=https://web.archive.org/web/20251008041452/https://meta.wikimedia.org/wiki/Wikimedia_User_Groups|site=|date=|consulté le=}}</ref>. Tout ce qui vient d'être exposé dans cette introduction justifie donc la nécessité de distinguer le mouvement Wikimédia du projet Wikipédia. Imaginons seulement que l’on se limite à citer Paris pour décrire et comprendre un pays aussi vaste que la France. Certes, Paris est une ville mondialement connue et qui compte plus de deux millions d’habitants et un patrimoine culturel impressionnant. Mais est-ce pour autant qu'il faudrait oublier les autres villes, villages et métropoles françaises ? Sans compter que la France regroupe aussi des départements et des territoires d’outre-mer et qu'elle entretient des relations et des partenariats internationaux qui dépassent de loin ce qui se passe entre Paris et le reste du monde. Ne pas confondre le mouvement Wikimédia avec le projet Wikipédia relève donc du bon sens. En 2019 cependant, la Fondation Wikimédia a envisagé de se renommer en Fondation Wikipédia et de remplacer le terme « Wikimédia » par celui de « Wikipédia », partout où ce terme est utilisé dans la sphère hors ligne du mouvement. Le but était d’acquérir une plus grande visibilité et d’attirer des milliards de personnes, grâce au nom de marque Wikipédia, considéré comme l’un des plus connus au monde<ref>{{Lien web|langue=|auteur=Zack McCune|titre=Leading with Wikipedia: A brand proposal for 2030|url=https://web.archive.org/web/20210117025153/https://wikimediafoundation.org/news/2019/02/26/leading-with-wikipedia-a-brand-proposal-for-2030/|site=Wikimedia Foundation News|éditeur=|date=26 February 2019|consulté le=}}.</ref>. Ce changement n’a toutefois pas été accepté par de nombreuses personnes actives au sein du mouvement Wikimédia. En janvier 2020, ces opposants ont ainsi créé une page d’[[m:Requests_for_comment/Should_the_Foundation_call_itself_Wikipedia|appel à commentaires]], qui fut le siège d’un long débat<ref name="Requests for comment">{{Lien web|langue=|auteur=Méta-Wiki|titre=Requests for comment/Should the Foundation call itself Wikipedia|url=https://web.archive.org/web/20210905054842/https://meta.wikimedia.org/wiki/Requests_for_comment/Should_the_Foundation_call_itself_Wikipedia|consulté le=}}.</ref>. À l’issue de ce dernier, 73 représentants d’organisations affiliées et 984 personnes ont signé une [[m:Community_open_letter_on_renaming/fr|lettre ouverte]] adressée à la Fondation qui comprenait le paragraphe suivant<ref>{{Lien web|langue=|auteur=Méta-Wiki|titre=Lettre ouverte de la Communauté sur le changement de nom|url=https://web.archive.org/web/20210122162652/https://meta.wikimedia.org/wiki/Community_open_letter_on_renaming/fr|consulté le=}}.</ref> : <blockquote> Depuis 20 ans, les bénévoles ont bâti la réputation de Wikipédia en tant que ressource indépendante et communautaire. Les projets du mouvement Wikimédia, dont Wikipédia, se développent autour de la décentralisation et du consensus. Il est essentiel d’établir des distinctions claires entre la Fondation Wikimédia, les affiliés et les contributeurs individuels. Tout changement qui affecte cet équilibre exige le consentement éclairé et la collaboration des communautés. Il est donc très préoccupant de voir « Wikipédia » présenté pour le nom de l’organisation et du mouvement malgré le mécontentement général de la communauté. </blockquote> En s’opposant aux idées de la Fondation, ces membres de la communauté Wikimédia ont ainsi fait preuve de sagesse. De plus, ils ont signalé dans de nombreux commentaires que beaucoup de personnes connaissent le mouvement Wikimédia uniquement au travers de son encyclopédie. Il est même étonnant d'observer que la méconnaissance du mouvement existe aussi en interne. L'[[w:en:Wikimedia_movement|article du projet Wikipédia en anglais consacré au mouvement Wikimédia]], par exemple, ne s’est développé qu’à partir de 2016<ref>{{Lien web|auteur=Wikipedia|titre=Wikimedia mouvement - old revision|url=https://en.wikipedia.org/w/index.php?title=Wikimedia_movement&oldid=716240586|consulté le=}}.</ref>, tandis que ce même article dans la [[w:fr:Mouvement_Wikimédia|version francophone de l'encyclopédie]] n’est apparu qu’en 2019<ref>{{Lien web|auteur=Wikipédia|titre=Mouvement Wikimédia - version archivée|url=https://fr.wikipedia.org/w/index.php?title=Mouvement_Wikim%C3%A9dia&oldid=158268859|consulté le=}}.</ref>. Quant aux autres versions linguistiques, il est tout aussi étonnant de constater qu'en octobre 2025, seulement [[wikidata:Q3568028|39 d'entre elles sur un total de 358]] possédaient un article dédié au mouvement Wikimédia<ref>{{Lien web|auteur=Wikidata|titre=Wikimedia Movement|url=https://web.archive.org/web/20251004091239/https://www.wikidata.org/wiki/Q3568028}}.</ref>. Tous ces éléments justifient donc la nécessité d’offrir au monde une meilleure connaissance du mouvement Wikimédia et des nombreux projets et organisations qui soutiennent leurs missions de partage du savoir. En ce sens, ce livre est une contribution importante aux défis stratégiques que doit relever le mouvement Wikimédia à l’approche de 2030. Car au-delà des [[wmf:Resolution:Next_Steps_for_Brand_Work,_2021|résolutions]] prises pour développer de nouveaux processus participatifs et délibératifs concernant les questions de marque<ref>{{Lien web|auteur=Wikimedia Foundation Wiki|titre=Resolution:Next Steps for Brand Work, 2021|url=https://web.archive.org/web/20211020232912/https://foundation.wikimedia.org/wiki/Resolution:Next_Steps_for_Brand_Work,_2021|date=|consulté le=}}.</ref>, c’est avant tout un travail d’information et de sensibilisation à destination du grand public qu'il reste à faire. {| class="wikitable"style="margin: auto;" "text-align:center;" |+ |[[Fichier:Qrcode Culture fr Wikipédia.svg|lien=https://fr.wikiversity.org/wiki/Recherche:Culture_fr_Wikip%C3%A9dia|100x100px|centré|sans_cadre]] |[[Fichier:Qrcode Imagine un monde.svg|lien=https://fr.wikiversity.org/wiki/Recherche:Imagine_un_monde|centré|sans_cadre|100x100px]] |[[Fichier:Code qr Wikiscan.svg|lien=http://wikiscan.org|100x100px|centré|sans_cadre]] |[[Fichier:QR code Statistiques Wikimédia.png|lien=https://stats.wikimedia.org|centré|sans_cadre|100x100px]] |[[Fichier:QR-code Wikistats wmcloud.png|lien=https://wikistats.wmcloud.org/wikimedias_html.php?s=users_asc&th=0&lines=2000|centré|sans cadre|100x100px]] |- |<small>Q 5 : Ethnographie fr Wikipédia</small> |<small>Q 2 : Thèse ''Imagine un monde''</small> |{{Centrer|<small>Q 2 : Wikiscan</small>}} |<small>QStatistiques de Wikimédia</small> |<small>Wikistats wmcloud</small> |} ‎<includeonly></includeonly> {{AutoCat}} aaysmrqbyj9y1hjh7c6v2ka6f2qzfhi 764784 764783 2026-04-24T09:37:21Z Lionel Scheepmans 20012 764784 wikitext text/x-wiki <noinclude>{{Le mouvement Wikimédia}} </noinclude> Depuis le succès initial de Wikipédia, une myriade de projets de partage des connaissances, d’organisations et de groupes de soutien ont émergé pour former ce qu’on appelle aujourd’hui le mouvement Wikimédia. Même si, à ce jour, l’encyclopédie libre reste l'activité phare du mouvement, il serait regrettable de réduire l’ensemble du mouvement à cet unique projet pédagogique. Malheureusement, il arrive bien trop souvent qu'une seule version linguistique de Wikipédia suffise pour cacher l’étendue de la forêt Wikimédia. En réalité, Wikimédia représente un mouvement social, international et interculturel complexe, au sein duquel Wikipédia n’est qu’une composante parmi d’autres. Dans le cadre d'un mémoire de master, on peut ainsi fournir en quelques mois une [[v:recherche:Culture_fr_Wikipédia|ethnographie du projet Wikipédia en français]]<ref>{{Lien web|titre=Culture fr Wikipédia, ethnographie du projet Wikipédia en français|url=https://web.archive.org/web/20250905132014/https://fr.wikiversity.org/wiki/Recherche:Culture_fr_Wikip%C3%A9dia|auteur=Lionel Scheepmans|site=Wikiversité|année=2011}}.</ref>. Alors que pour synthétiser les origines, l’organisation et les dynamiques globales du mouvement Wikimédia, une [[v:fr:Recherche:Imagine_un_monde|thèse de doctorat]]<ref>{{Ouvrage|langue=fr|prénom1=Lionel|nom1=Scheepmans|lien auteur1=user:Lionel Scheepmans|titre=Imagine un monde : quand le mouvement Wikimédia nous aide à penser de manière prospective la société globale et numérique de demain|éditeur=UCL - Université Catholique de Louvain|année=2022|date=17/06/2022|lire en ligne=https://dial.uclouvain.be/pr/boreal/object/boreal:264603|consulté le=2024-03-10|nature article=Thèse de doctorat}}</ref> et cinq années de recul supplémentaires furent nécessaires pour finaliser le projet. [[Fichier:Wikimedia project stickers.jpg|vignette|<small>Figure 1. Série d’autocollants reprenant les logos des principaux projets collaboratifs actifs au sein du mouvement Wikimédia.</small>]] À la fin de l'année 2025, l’ampleur numérique du mouvement est effectivement impressionnante. Chaque mois, des millions de modifications bénévoles sont effectuées sur plus de 500 millions de pages<ref>{{Lien web|auteur=Stat.wikimedia.org|titre=Statistiques de Wikimédia|url=https://web.archive.org/web/20251209230902/https://stats.wikimedia.org/#/all-projects}}.</ref>, réparties sur plus d’un millier de sites web<ref>{{Lien web|url=https://web.archive.org/web/20251217134333/https://wikistats.wmcloud.org/wikimedias_html.php?s=users_asc&th=0&lines=2000|titre=All Wikimedia Projects by Size|auteur=Wikistats wmcloud}}.</ref>, dont 358 seulement, correspondent aux [[m:List_of_Wikipedias|versions linguistiques de Wikipédia]]<ref>{{Lien web|url=https://web.archive.org/web/20251208041814/https://meta.wikimedia.org/wiki/List_of_Wikipedias|titre=List_of_Wikipedias|auteur=Méta-Wiki}}.</ref>. Ce qui prouve donc clairement que les activités en ligne portées par l'ensemble du mouvement Wikimédia dépassent largement ce qui se passe au sein de l’encyclopédie. Il existe ainsi huit autres projets pédagogiques susceptibles d'atteindre un jour l'envergure et la notoriété de Wikipédia, avec un objectif et un fonctionnement spécifiques à chacun. De manière détaillée, Wikilivres crée des livres pédagogiques, Wikiversité rassemble des supports d'enseignement et des travaux de recherche, Wikinews fait du journalisme collaboratif, Wikivoyage développe un guide touristique, pendant que Wiktionnaire apporte des définitions des mots de toutes les langues et dans toutes les langues. Contrairement à Wikipédia, tous ces projets ne sont pas soumis à une neutralité de point de vue, ni limités à l'usage de sources secondaires reconnues, au niveau de la rédaction des articles. La plupart d'entre eux acceptent aussi la publication de travaux de recherche ou de productions personnelles, alors que cela est tout à fait interdit dans Wikipédia. Selon l'étymologie du mot encyclopédie, le but de Wikipédia est en effet de synthétiser ou, plus précisément, d'encercler le savoir humain déjà préexistant. Cette contrainte éditoriale limite donc les contributeurs et contributrices à l'usage de sources secondaires et tertiaires présentes dans des publications externes au projet. De ce fait, Wikipédia reproduit fatalement les biais systémiques, tels que les déséquilibres et les surreprésentations de genre et de culture, présents dans un monde de l'édition majoritairement occidental. Or, cette impasse éditoriale propre à Wikipédia n'existe pas dans les autres projets pédagogiques. Comme ces projets frères, Wikipédia n'est pas non plus un projet complètement autonome des autres projets Wikimédia. L'encyclopédie compte en effet sur le projet Wikimedia Commons pour héberger l'ensemble de sa médiathèque. Elle utilise ensuite le contenu du projet Wikidata comme base de données structurée. Quant aux personnes qui produisent l'encyclopédie, elles peuvent aussi puiser leurs sources dans la bibliothèque Wikisource, ou se référer à des citations d'auteurs collectées dans le projet Wikiquote. Tout cela sans oublier que des dizaines de sites web traitent l'archivage permanent de Wikipédia et des autres projets Wikimédia, dans le but de fournir des analyses précieuses et totalement libres d’accès. Enfin, il faut aussi garder à l'esprit qu'au-delà de tous ces sites web, le mouvement Wikimédia, c'est aussi de nombreuses institutions et organisations affiliées dispersées dans le monde. Autour de la [[w:Fondation_Wikimédia|Fondation Wikimédia]] chargée de la gestion et de l’organisation internationales, avec près de 650 salariés de nationalités diverses<ref>{{Lien web|langue=|auteur=Wikimedia Foundation|titre=Les personnes derrière notre connaissance|url=https://web.archive.org/web/20250904032058/https://wikimediafoundation.org/fr/who-we-are/people/|site=|date=|consulté le=}}</ref>, se regroupent des centaines d'organisations satellites. Parmi celles-ci, on retrouve 2 [[m:Wikimedia_thematic_organizations/fr|associations thématiques]]<ref>{{Lien web|langue=|auteur=Méta-Wiki|titre=Wikimedia thematic organisations|url=https://web.archive.org/web/20250926235310/https://meta.wikimedia.org/wiki/Wikimedia_thematic_organizations|site=|date=|consulté le=}}.</ref>, 40 [[m:Wikimedia_chapters/fr|associations locales]]<ref>{{Lien web|langue=|auteur=Méta-Wiki|titre=Wikimedia chapters|url=https://web.archive.org/web/20251006092129/https://meta.wikimedia.org/wiki/Wikimedia_chapters}}</ref>, dont ''Wikimedia Deutschland'' qui regroupe plus de 170 emplyés<ref>{{Lien web|langue=|auteur=RocketReach|titre=Wikimedia Deutschlande. V. Information|url=https://web.archive.org/web/20251218034616/https://rocketreach.co/wikimedia-deutschland-e-v-profile_b5caf600f42e140f|site=|date=|consulté le=}}.</ref>, et finalement 141 [[metawiki:Wikimedia_user_groups/fr|groupes d’utilisateurs et utilisatrices]]<ref>{{Lien web|langue=|auteur=Méta-Wiki|titre=Wikimedia user groups|url=https://web.archive.org/web/20251008041452/https://meta.wikimedia.org/wiki/Wikimedia_User_Groups|site=|date=|consulté le=}}</ref>. Tout ce qui vient d'être exposé dans cette introduction justifie donc la nécessité de distinguer le mouvement Wikimédia du projet Wikipédia. Imaginons seulement que l’on se limite à citer Paris pour décrire et comprendre un pays aussi vaste que la France. Certes, Paris est une ville mondialement connue et qui compte plus de deux millions d’habitants et un patrimoine culturel impressionnant. Mais est-ce pour autant qu'il faudrait oublier les autres villes, villages et métropoles françaises ? Sans compter que la France regroupe aussi des départements et des territoires d’outre-mer et qu'elle entretient des relations et des partenariats internationaux qui dépassent de loin ce qui se passe entre Paris et le reste du monde. Ne pas confondre le mouvement Wikimédia avec le projet Wikipédia relève donc du bon sens. En 2019 cependant, la Fondation Wikimédia a envisagé de se renommer en Fondation Wikipédia et de remplacer le terme « Wikimédia » par celui de « Wikipédia », partout où ce terme est utilisé dans la sphère hors ligne du mouvement. Le but était d’acquérir une plus grande visibilité et d’attirer des milliards de personnes, grâce au nom de marque Wikipédia, considéré comme l’un des plus connus au monde<ref>{{Lien web|langue=|auteur=Zack McCune|titre=Leading with Wikipedia: A brand proposal for 2030|url=https://web.archive.org/web/20210117025153/https://wikimediafoundation.org/news/2019/02/26/leading-with-wikipedia-a-brand-proposal-for-2030/|site=Wikimedia Foundation News|éditeur=|date=26 February 2019|consulté le=}}.</ref>. Ce changement n’a toutefois pas été accepté par de nombreuses personnes actives au sein du mouvement Wikimédia. En janvier 2020, ces opposants ont ainsi créé une page d’[[m:Requests_for_comment/Should_the_Foundation_call_itself_Wikipedia|appel à commentaires]], qui fut le siège d’un long débat<ref name="Requests for comment">{{Lien web|langue=|auteur=Méta-Wiki|titre=Requests for comment/Should the Foundation call itself Wikipedia|url=https://web.archive.org/web/20210905054842/https://meta.wikimedia.org/wiki/Requests_for_comment/Should_the_Foundation_call_itself_Wikipedia|consulté le=}}.</ref>. À l’issue de ce dernier, 73 représentants d’organisations affiliées et 984 personnes ont signé une [[m:Community_open_letter_on_renaming/fr|lettre ouverte]] adressée à la Fondation qui comprenait le paragraphe suivant<ref>{{Lien web|langue=|auteur=Méta-Wiki|titre=Lettre ouverte de la Communauté sur le changement de nom|url=https://web.archive.org/web/20210122162652/https://meta.wikimedia.org/wiki/Community_open_letter_on_renaming/fr|consulté le=}}.</ref> : <blockquote> Depuis 20 ans, les bénévoles ont bâti la réputation de Wikipédia en tant que ressource indépendante et communautaire. Les projets du mouvement Wikimédia, dont Wikipédia, se développent autour de la décentralisation et du consensus. Il est essentiel d’établir des distinctions claires entre la Fondation Wikimédia, les affiliés et les contributeurs individuels. Tout changement qui affecte cet équilibre exige le consentement éclairé et la collaboration des communautés. Il est donc très préoccupant de voir « Wikipédia » présenté pour le nom de l’organisation et du mouvement malgré le mécontentement général de la communauté. </blockquote> En s’opposant aux idées de la Fondation, ces membres de la communauté Wikimédia ont ainsi fait preuve de sagesse. De plus, ils ont signalé dans de nombreux commentaires que beaucoup de personnes connaissent le mouvement Wikimédia uniquement au travers de son encyclopédie. Il est même étonnant d'observer que la méconnaissance du mouvement existe aussi en interne. L'[[w:en:Wikimedia_movement|article du projet Wikipédia en anglais consacré au mouvement Wikimédia]], par exemple, ne s’est développé qu’à partir de 2016<ref>{{Lien web|auteur=Wikipedia|titre=Wikimedia mouvement - old revision|url=https://en.wikipedia.org/w/index.php?title=Wikimedia_movement&oldid=716240586|consulté le=}}.</ref>, tandis que ce même article dans la [[w:fr:Mouvement_Wikimédia|version francophone de l'encyclopédie]] n’est apparu qu’en 2019<ref>{{Lien web|auteur=Wikipédia|titre=Mouvement Wikimédia - version archivée|url=https://fr.wikipedia.org/w/index.php?title=Mouvement_Wikim%C3%A9dia&oldid=158268859|consulté le=}}.</ref>. Quant aux autres versions linguistiques, il est tout aussi étonnant de constater qu'en octobre 2025, seulement [[wikidata:Q3568028|39 d'entre elles sur un total de 358]] possédaient un article dédié au mouvement Wikimédia<ref>{{Lien web|auteur=Wikidata|titre=Wikimedia Movement|url=https://web.archive.org/web/20251004091239/https://www.wikidata.org/wiki/Q3568028}}.</ref>. Tous ces éléments justifient donc la nécessité d’offrir au monde une meilleure connaissance du mouvement Wikimédia et des nombreux projets et organisations qui soutiennent leurs missions de partage du savoir. En ce sens, ce livre est une contribution importante aux défis stratégiques que doit relever le mouvement Wikimédia à l’approche de 2030. Car au-delà des [[wmf:Resolution:Next_Steps_for_Brand_Work,_2021|résolutions]] prises pour développer de nouveaux processus participatifs et délibératifs concernant les questions de marque<ref>{{Lien web|auteur=Wikimedia Foundation Wiki|titre=Resolution:Next Steps for Brand Work, 2021|url=https://web.archive.org/web/20211020232912/https://foundation.wikimedia.org/wiki/Resolution:Next_Steps_for_Brand_Work,_2021|date=|consulté le=}}.</ref>, c’est avant tout un travail d’information et de sensibilisation à destination du grand public qu'il reste à faire. {| class="wikitable"style="margin: auto;" "text-align:center;" |+ |[[Fichier:Qrcode Culture fr Wikipédia.svg|lien=https://fr.wikiversity.org/wiki/Recherche:Culture_fr_Wikip%C3%A9dia|100x100px|centré|sans_cadre]] |[[Fichier:Qrcode Imagine un monde.svg|lien=https://fr.wikiversity.org/wiki/Recherche:Imagine_un_monde|centré|sans_cadre|100x100px]] |[[Fichier:Code qr Wikiscan.svg|lien=http://wikiscan.org|100x100px|centré|sans_cadre]] |[[Fichier:QR code Statistiques Wikimédia.png|lien=https://stats.wikimedia.org|centré|sans_cadre|100x100px]] |[[Fichier:QR-code Wikistats wmcloud.png|lien=https://wikistats.wmcloud.org/wikimedias_html.php?s=users_asc&th=0&lines=2000|centré|sans cadre|100x100px]] |- |<small>Q 5 : Ethnographie fr Wikipédia</small> |<small>Q 2 : Thèse ''Imagine un monde''</small> |{{Centrer|<small>Q 2 : Wikiscan</small>}} |<small>QStatistiques de Wikimédia</small> |<small>Wikistats wmcloud</small> |} ‎<includeonly></includeonly> {{AutoCat}} aq8uy6p0t0vizc60wnx00fnl34642i8 764785 764784 2026-04-24T09:39:26Z Lionel Scheepmans 20012 764785 wikitext text/x-wiki <noinclude>{{Le mouvement Wikimédia}} </noinclude> Depuis le succès initial de Wikipédia, une myriade de projets de partage des connaissances, d’organisations et de groupes de soutien ont émergé pour former ce qu’on appelle aujourd’hui le mouvement Wikimédia. Même si, à ce jour, l’encyclopédie libre reste l'activité phare du mouvement, il serait regrettable de réduire l’ensemble du mouvement à cet unique projet pédagogique. Malheureusement, il arrive bien trop souvent qu'une seule version linguistique de Wikipédia suffise pour cacher l’étendue de la forêt Wikimédia. En réalité, Wikimédia représente un mouvement social, international et interculturel complexe, au sein duquel Wikipédia n’est qu’une composante parmi d’autres. Dans le cadre d'un mémoire de master, on peut ainsi fournir en quelques mois une [[v:recherche:Culture_fr_Wikipédia|ethnographie du projet Wikipédia en français]]<ref>{{Lien web|titre=Culture fr Wikipédia, ethnographie du projet Wikipédia en français|url=https://web.archive.org/web/20250905132014/https://fr.wikiversity.org/wiki/Recherche:Culture_fr_Wikip%C3%A9dia|auteur=Lionel Scheepmans|site=Wikiversité|année=2011}}.</ref>. Alors que pour synthétiser les origines, l’organisation et les dynamiques globales du mouvement Wikimédia, une [[v:fr:Recherche:Imagine_un_monde|thèse de doctorat]]<ref>{{Ouvrage|langue=fr|prénom1=Lionel|nom1=Scheepmans|lien auteur1=user:Lionel Scheepmans|titre=Imagine un monde : quand le mouvement Wikimédia nous aide à penser de manière prospective la société globale et numérique de demain|éditeur=UCL - Université Catholique de Louvain|année=2022|date=17/06/2022|lire en ligne=https://dial.uclouvain.be/pr/boreal/object/boreal:264603|consulté le=2024-03-10|nature article=Thèse de doctorat}}</ref> et cinq années de recul supplémentaires furent nécessaires. [[Fichier:Wikimedia project stickers.jpg|vignette|<small>Figure 1. Série d’autocollants reprenant les logos des principaux projets collaboratifs actifs au sein du mouvement Wikimédia.</small>]] À la fin de l'année 2025, l’ampleur numérique du mouvement est effectivement impressionnante. Chaque mois, des millions de modifications bénévoles sont effectuées sur plus de 500 millions de pages<ref>{{Lien web|auteur=Stat.wikimedia.org|titre=Statistiques de Wikimédia|url=https://web.archive.org/web/20251209230902/https://stats.wikimedia.org/#/all-projects}}.</ref>, réparties sur plus d’un millier de sites web<ref>{{Lien web|url=https://web.archive.org/web/20251217134333/https://wikistats.wmcloud.org/wikimedias_html.php?s=users_asc&th=0&lines=2000|titre=All Wikimedia Projects by Size|auteur=Wikistats wmcloud}}.</ref>, dont 358 seulement, correspondent aux [[m:List_of_Wikipedias|versions linguistiques de Wikipédia]]<ref>{{Lien web|url=https://web.archive.org/web/20251208041814/https://meta.wikimedia.org/wiki/List_of_Wikipedias|titre=List_of_Wikipedias|auteur=Méta-Wiki}}.</ref>. Ce qui prouve donc clairement que les activités en ligne portées par l'ensemble du mouvement Wikimédia dépassent largement ce qui se passe au sein de l’encyclopédie. Il existe ainsi huit autres projets pédagogiques susceptibles d'atteindre un jour l'envergure et la notoriété de Wikipédia, avec un objectif et un fonctionnement spécifiques à chacun. De manière détaillée, Wikilivres crée des livres pédagogiques, Wikiversité rassemble des supports d'enseignement et des travaux de recherche, Wikinews fait du journalisme collaboratif, Wikivoyage développe un guide touristique, pendant que Wiktionnaire apporte des définitions des mots de toutes les langues et dans toutes les langues. Contrairement à Wikipédia, tous ces projets ne sont pas soumis à une neutralité de point de vue, ni limités à l'usage de sources secondaires reconnues, au niveau de la rédaction des articles. La plupart d'entre eux acceptent aussi la publication de travaux de recherche ou de productions personnelles, alors que cela est tout à fait interdit dans Wikipédia. Selon l'étymologie du mot encyclopédie, le but de Wikipédia est en effet de synthétiser ou, plus précisément, d'encercler le savoir humain déjà préexistant. Cette contrainte éditoriale limite donc les contributeurs et contributrices à l'usage de sources secondaires et tertiaires présentes dans des publications externes au projet. De ce fait, Wikipédia reproduit fatalement les biais systémiques, tels que les déséquilibres et les surreprésentations de genre et de culture, présents dans un monde de l'édition majoritairement occidental. Or, cette impasse éditoriale propre à Wikipédia n'existe pas dans les autres projets pédagogiques. Comme ces projets frères, Wikipédia n'est pas non plus un projet complètement autonome des autres projets Wikimédia. L'encyclopédie compte en effet sur le projet Wikimedia Commons pour héberger l'ensemble de sa médiathèque. Elle utilise ensuite le contenu du projet Wikidata comme base de données structurée. Quant aux personnes qui produisent l'encyclopédie, elles peuvent aussi puiser leurs sources dans la bibliothèque Wikisource, ou se référer à des citations d'auteurs collectées dans le projet Wikiquote. Tout cela sans oublier que des dizaines de sites web traitent l'archivage permanent de Wikipédia et des autres projets Wikimédia, dans le but de fournir des analyses précieuses et totalement libres d’accès. Enfin, il faut aussi garder à l'esprit qu'au-delà de tous ces sites web, le mouvement Wikimédia, c'est aussi de nombreuses institutions et organisations affiliées dispersées dans le monde. Autour de la [[w:Fondation_Wikimédia|Fondation Wikimédia]] chargée de la gestion et de l’organisation internationales, avec près de 650 salariés de nationalités diverses<ref>{{Lien web|langue=|auteur=Wikimedia Foundation|titre=Les personnes derrière notre connaissance|url=https://web.archive.org/web/20250904032058/https://wikimediafoundation.org/fr/who-we-are/people/|site=|date=|consulté le=}}</ref>, se regroupent des centaines d'organisations satellites. Parmi celles-ci, on retrouve 2 [[m:Wikimedia_thematic_organizations/fr|associations thématiques]]<ref>{{Lien web|langue=|auteur=Méta-Wiki|titre=Wikimedia thematic organisations|url=https://web.archive.org/web/20250926235310/https://meta.wikimedia.org/wiki/Wikimedia_thematic_organizations|site=|date=|consulté le=}}.</ref>, 40 [[m:Wikimedia_chapters/fr|associations locales]]<ref>{{Lien web|langue=|auteur=Méta-Wiki|titre=Wikimedia chapters|url=https://web.archive.org/web/20251006092129/https://meta.wikimedia.org/wiki/Wikimedia_chapters}}</ref>, dont ''Wikimedia Deutschland'' qui regroupe plus de 170 emplyés<ref>{{Lien web|langue=|auteur=RocketReach|titre=Wikimedia Deutschlande. V. Information|url=https://web.archive.org/web/20251218034616/https://rocketreach.co/wikimedia-deutschland-e-v-profile_b5caf600f42e140f|site=|date=|consulté le=}}.</ref>, et finalement 141 [[metawiki:Wikimedia_user_groups/fr|groupes d’utilisateurs et utilisatrices]]<ref>{{Lien web|langue=|auteur=Méta-Wiki|titre=Wikimedia user groups|url=https://web.archive.org/web/20251008041452/https://meta.wikimedia.org/wiki/Wikimedia_User_Groups|site=|date=|consulté le=}}</ref>. Tout ce qui vient d'être exposé dans cette introduction justifie donc la nécessité de distinguer le mouvement Wikimédia du projet Wikipédia. Imaginons seulement que l’on se limite à citer Paris pour décrire et comprendre un pays aussi vaste que la France. Certes, Paris est une ville mondialement connue et qui compte plus de deux millions d’habitants et un patrimoine culturel impressionnant. Mais est-ce pour autant qu'il faudrait oublier les autres villes, villages et métropoles françaises ? Sans compter que la France regroupe aussi des départements et des territoires d’outre-mer et qu'elle entretient des relations et des partenariats internationaux qui dépassent de loin ce qui se passe entre Paris et le reste du monde. Ne pas confondre le mouvement Wikimédia avec le projet Wikipédia relève donc du bon sens. En 2019 cependant, la Fondation Wikimédia a envisagé de se renommer en Fondation Wikipédia et de remplacer le terme « Wikimédia » par celui de « Wikipédia », partout où ce terme est utilisé dans la sphère hors ligne du mouvement. Le but était d’acquérir une plus grande visibilité et d’attirer des milliards de personnes, grâce au nom de marque Wikipédia, considéré comme l’un des plus connus au monde<ref>{{Lien web|langue=|auteur=Zack McCune|titre=Leading with Wikipedia: A brand proposal for 2030|url=https://web.archive.org/web/20210117025153/https://wikimediafoundation.org/news/2019/02/26/leading-with-wikipedia-a-brand-proposal-for-2030/|site=Wikimedia Foundation News|éditeur=|date=26 February 2019|consulté le=}}.</ref>. Ce changement n’a toutefois pas été accepté par de nombreuses personnes actives au sein du mouvement Wikimédia. En janvier 2020, ces opposants ont ainsi créé une page d’[[m:Requests_for_comment/Should_the_Foundation_call_itself_Wikipedia|appel à commentaires]], qui fut le siège d’un long débat<ref name="Requests for comment">{{Lien web|langue=|auteur=Méta-Wiki|titre=Requests for comment/Should the Foundation call itself Wikipedia|url=https://web.archive.org/web/20210905054842/https://meta.wikimedia.org/wiki/Requests_for_comment/Should_the_Foundation_call_itself_Wikipedia|consulté le=}}.</ref>. À l’issue de ce dernier, 73 représentants d’organisations affiliées et 984 personnes ont signé une [[m:Community_open_letter_on_renaming/fr|lettre ouverte]] adressée à la Fondation qui comprenait le paragraphe suivant<ref>{{Lien web|langue=|auteur=Méta-Wiki|titre=Lettre ouverte de la Communauté sur le changement de nom|url=https://web.archive.org/web/20210122162652/https://meta.wikimedia.org/wiki/Community_open_letter_on_renaming/fr|consulté le=}}.</ref> : <blockquote> Depuis 20 ans, les bénévoles ont bâti la réputation de Wikipédia en tant que ressource indépendante et communautaire. Les projets du mouvement Wikimédia, dont Wikipédia, se développent autour de la décentralisation et du consensus. Il est essentiel d’établir des distinctions claires entre la Fondation Wikimédia, les affiliés et les contributeurs individuels. Tout changement qui affecte cet équilibre exige le consentement éclairé et la collaboration des communautés. Il est donc très préoccupant de voir « Wikipédia » présenté pour le nom de l’organisation et du mouvement malgré le mécontentement général de la communauté. </blockquote> En s’opposant aux idées de la Fondation, ces membres de la communauté Wikimédia ont ainsi fait preuve de sagesse. De plus, ils ont signalé dans de nombreux commentaires que beaucoup de personnes connaissent le mouvement Wikimédia uniquement au travers de son encyclopédie. Il est même étonnant d'observer que la méconnaissance du mouvement existe aussi en interne. L'[[w:en:Wikimedia_movement|article du projet Wikipédia en anglais consacré au mouvement Wikimédia]], par exemple, ne s’est développé qu’à partir de 2016<ref>{{Lien web|auteur=Wikipedia|titre=Wikimedia mouvement - old revision|url=https://en.wikipedia.org/w/index.php?title=Wikimedia_movement&oldid=716240586|consulté le=}}.</ref>, tandis que ce même article dans la [[w:fr:Mouvement_Wikimédia|version francophone de l'encyclopédie]] n’est apparu qu’en 2019<ref>{{Lien web|auteur=Wikipédia|titre=Mouvement Wikimédia - version archivée|url=https://fr.wikipedia.org/w/index.php?title=Mouvement_Wikim%C3%A9dia&oldid=158268859|consulté le=}}.</ref>. Quant aux autres versions linguistiques, il est tout aussi étonnant de constater qu'en octobre 2025, seulement [[wikidata:Q3568028|39 d'entre elles sur un total de 358]] possédaient un article dédié au mouvement Wikimédia<ref>{{Lien web|auteur=Wikidata|titre=Wikimedia Movement|url=https://web.archive.org/web/20251004091239/https://www.wikidata.org/wiki/Q3568028}}.</ref>. Tous ces éléments justifient donc la nécessité d’offrir au monde une meilleure connaissance du mouvement Wikimédia et des nombreux projets et organisations qui soutiennent leurs missions de partage du savoir. En ce sens, ce livre est une contribution importante aux défis stratégiques que doit relever le mouvement Wikimédia à l’approche de 2030. Car au-delà des [[wmf:Resolution:Next_Steps_for_Brand_Work,_2021|résolutions]] prises pour développer de nouveaux processus participatifs et délibératifs concernant les questions de marque<ref>{{Lien web|auteur=Wikimedia Foundation Wiki|titre=Resolution:Next Steps for Brand Work, 2021|url=https://web.archive.org/web/20211020232912/https://foundation.wikimedia.org/wiki/Resolution:Next_Steps_for_Brand_Work,_2021|date=|consulté le=}}.</ref>, c’est avant tout un travail d’information et de sensibilisation à destination du grand public qu'il reste à faire. {| class="wikitable"style="margin: auto;" "text-align:center;" |+ |[[Fichier:Qrcode Culture fr Wikipédia.svg|lien=https://fr.wikiversity.org/wiki/Recherche:Culture_fr_Wikip%C3%A9dia|100x100px|centré|sans_cadre]] |[[Fichier:Qrcode Imagine un monde.svg|lien=https://fr.wikiversity.org/wiki/Recherche:Imagine_un_monde|centré|sans_cadre|100x100px]] |[[Fichier:Code qr Wikiscan.svg|lien=http://wikiscan.org|100x100px|centré|sans_cadre]] |[[Fichier:QR code Statistiques Wikimédia.png|lien=https://stats.wikimedia.org|centré|sans_cadre|100x100px]] |[[Fichier:QR-code Wikistats wmcloud.png|lien=https://wikistats.wmcloud.org/wikimedias_html.php?s=users_asc&th=0&lines=2000|centré|sans cadre|100x100px]] |- |<small>Q 5 : Ethnographie fr Wikipédia</small> |<small>Q 2 : Thèse ''Imagine un monde''</small> |{{Centrer|<small>Q 2 : Wikiscan</small>}} |<small>QStatistiques de Wikimédia</small> |<small>Wikistats wmcloud</small> |} ‎<includeonly></includeonly> {{AutoCat}} qs2ag50i6wxl898ss7t9k7osyd567jw 764787 764785 2026-04-24T09:49:21Z Lionel Scheepmans 20012 764787 wikitext text/x-wiki <noinclude>{{Le mouvement Wikimédia}} </noinclude> Depuis le succès initial de Wikipédia, une myriade de projets de partage des connaissances, d’organisations et de groupes de soutien ont émergé pour former ce qu’on appelle aujourd’hui le mouvement Wikimédia. Même si, à ce jour, l’encyclopédie libre reste l'activité phare du mouvement, il serait regrettable de réduire l’ensemble du mouvement à cet unique projet pédagogique. Malheureusement, il arrive bien trop souvent qu'une seule version linguistique de Wikipédia suffise pour cacher l’étendue de la forêt Wikimédia. En réalité, Wikimédia représente un mouvement social, international et interculturel complexe, au sein duquel Wikipédia n’est qu’une composante parmi d’autres. Dans le cadre d'un mémoire de master, on peut ainsi fournir en quelques mois une [[v:recherche:Culture_fr_Wikipédia|ethnographie du projet Wikipédia en français]]<ref>{{Lien web|titre=Culture fr Wikipédia, ethnographie du projet Wikipédia en français|url=https://web.archive.org/web/20250905132014/https://fr.wikiversity.org/wiki/Recherche:Culture_fr_Wikip%C3%A9dia|auteur=Lionel Scheepmans|site=Wikiversité|année=2011}}.</ref>. Alors que pour synthétiser les origines, l’organisation et les dynamiques globales du mouvement Wikimédia, une [[v:fr:Recherche:Imagine_un_monde|thèse de doctorat]]<ref>{{Ouvrage|langue=fr|prénom1=Lionel|nom1=Scheepmans|lien auteur1=user:Lionel Scheepmans|titre=Imagine un monde : quand le mouvement Wikimédia nous aide à penser de manière prospective la société globale et numérique de demain|éditeur=UCL - Université Catholique de Louvain|année=2022|date=17/06/2022|lire en ligne=https://dial.uclouvain.be/pr/boreal/object/boreal:264603|consulté le=2024-03-10|nature article=Thèse de doctorat}}</ref> et cinq années de recul supplémentaires furent nécessaires. [[Fichier:Wikimedia project stickers.jpg|vignette|<small>Figure 1. Série d’autocollants reprenant les logos des principaux projets collaboratifs actifs au sein du mouvement Wikimédia.</small>]] À la fin de l'année 2025, l’ampleur numérique du mouvement est effectivement impressionnante. Chaque mois, des millions de modifications bénévoles sont effectuées sur plus de 500 millions de pages<ref>{{Lien web|auteur=Stat.wikimedia.org|titre=Statistiques de Wikimédia|url=https://web.archive.org/web/20251209230902/https://stats.wikimedia.org/#/all-projects}}.</ref>, réparties sur plus d’un millier de sites web<ref>{{Lien web|url=https://web.archive.org/web/20251217134333/https://wikistats.wmcloud.org/wikimedias_html.php?s=users_asc&th=0&lines=2000|titre=All Wikimedia Projects by Size|auteur=Wikistats wmcloud}}.</ref>, dont 358 seulement, correspondent aux [[m:List_of_Wikipedias|versions linguistiques de Wikipédia]]<ref>{{Lien web|url=https://web.archive.org/web/20251208041814/https://meta.wikimedia.org/wiki/List_of_Wikipedias|titre=List_of_Wikipedias|auteur=Méta-Wiki}}.</ref>. Ce qui prouve donc clairement que les activités en ligne portées par l'ensemble du mouvement Wikimédia dépassent largement ce qui se passe au sein de l’encyclopédie. Il existe ainsi huit autres projets pédagogiques susceptibles d'atteindre un jour l'envergure et la notoriété de Wikipédia, avec un objectif et un fonctionnement spécifiques à chacun. De manière détaillée, Wikilivres crée des livres pédagogiques, Wikiversité rassemble des supports d'enseignement et des travaux de recherche, Wikinews fait du journalisme collaboratif, Wikivoyage développe un guide touristique, pendant que Wiktionnaire apporte des définitions des mots de toutes les langues et dans toutes les langues. Contrairement à Wikipédia, tous ces projets ne sont pas soumis à une neutralité de point de vue, ni limités à l'usage de sources secondaires reconnues, au niveau de la rédaction des articles. La plupart d'entre eux acceptent aussi la publication de travaux de recherche ou de productions personnelles, alors que cela est tout à fait interdit dans Wikipédia. Selon l'étymologie du mot encyclopédie, le but de Wikipédia est en effet de synthétiser ou, plus précisément, d'encercler le savoir humain déjà préexistant. Cette contrainte éditoriale limite donc les contributeurs et contributrices à l'usage de sources secondaires et tertiaires présentes dans des publications externes au projet. De ce fait, Wikipédia reproduit fatalement les biais systémiques, tels que les déséquilibres et les surreprésentations de genre et de culture, présents dans un monde de l'édition majoritairement occidental. Or, cette impasse éditoriale propre à Wikipédia n'existe pas dans les autres projets pédagogiques. Comme ces projets frères, Wikipédia n'est pas non plus un projet complètement autonome des autres projets Wikimédia. L'encyclopédie compte en effet sur le projet Wikimedia Commons pour héberger l'ensemble de sa médiathèque. Elle utilise ensuite le contenu du projet Wikidata comme base de données structurée. Quant aux personnes qui produisent l'encyclopédie, elles peuvent aussi puiser leurs sources dans la bibliothèque Wikisource, ou se référer à des citations d'auteurs collectées dans le projet Wikiquote. Tout cela sans oublier que des dizaines de sites web traitent l'archivage permanent de Wikipédia et des autres projets Wikimédia, dans le but de fournir des analyses précieuses et totalement libres d’accès. Enfin, il faut aussi garder à l'esprit qu'au-delà de tous ces sites web, le mouvement Wikimédia, c'est aussi de nombreuses institutions et organisations affiliées dispersées dans le monde. Autour de la [[w:Fondation_Wikimédia|Fondation Wikimédia]] chargée de la gestion et de l’organisation internationales, avec près de 650 salariés de nationalités diverses<ref>{{Lien web|langue=|auteur=Wikimedia Foundation|titre=Les personnes derrière notre connaissance|url=https://web.archive.org/web/20250904032058/https://wikimediafoundation.org/fr/who-we-are/people/|site=|date=|consulté le=}}</ref>, se regroupent des centaines d'organisations satellites. Parmi celles-ci, on retrouve 2 [[m:Wikimedia_thematic_organizations/fr|associations thématiques]]<ref>{{Lien web|langue=|auteur=Méta-Wiki|titre=Wikimedia thematic organisations|url=https://web.archive.org/web/20250926235310/https://meta.wikimedia.org/wiki/Wikimedia_thematic_organizations|site=|date=|consulté le=}}.</ref>, 40 [[m:Wikimedia_chapters/fr|associations locales]]<ref>{{Lien web|langue=|auteur=Méta-Wiki|titre=Wikimedia chapters|url=https://web.archive.org/web/20251006092129/https://meta.wikimedia.org/wiki/Wikimedia_chapters}}</ref>, dont ''Wikimedia Deutschland'' qui regroupe plus de 170 emplyés<ref>{{Lien web|langue=|auteur=RocketReach|titre=Wikimedia Deutschlande. V. Information|url=https://web.archive.org/web/20251218034616/https://rocketreach.co/wikimedia-deutschland-e-v-profile_b5caf600f42e140f|site=|date=|consulté le=}}.</ref>, et finalement 141 [[metawiki:Wikimedia_user_groups/fr|groupes d’utilisateurs et utilisatrices]]<ref>{{Lien web|langue=|auteur=Méta-Wiki|titre=Wikimedia user groups|url=https://web.archive.org/web/20251008041452/https://meta.wikimedia.org/wiki/Wikimedia_User_Groups|site=|date=|consulté le=}}</ref>. Tout ce qui vient d'être exposé dans cette introduction justifie donc la nécessité de distinguer le mouvement Wikimédia du projet Wikipédia. Imaginons seulement que l’on se limite à citer Paris pour décrire et comprendre un pays aussi vaste que la France. Certes, Paris est une ville mondialement connue et qui compte plus de deux millions d’habitants et un patrimoine culturel impressionnant. Mais est-ce pour autant qu'il faudrait oublier les autres villes, villages et métropoles françaises ? Sans compter que la France regroupe aussi des départements et des territoires d’outre-mer et qu'elle entretient des relations et des partenariats internationaux qui dépassent de loin ce qui se passe entre Paris et le reste du monde. Ne pas confondre le mouvement Wikimédia avec le projet Wikipédia relève donc du bon sens. En 2019 cependant, la Fondation Wikimédia a envisagé de se renommer en Fondation Wikipédia et de remplacer le terme « Wikimédia » par celui de « Wikipédia », partout où ce terme est utilisé dans la sphère hors ligne du mouvement. Le but était d’acquérir une plus grande visibilité et d’attirer des milliards de personnes, grâce au nom de marque Wikipédia, considéré comme l’un des plus connus au monde<ref>{{Lien web|langue=|auteur=Zack McCune|titre=Leading with Wikipedia: A brand proposal for 2030|url=https://web.archive.org/web/20210117025153/https://wikimediafoundation.org/news/2019/02/26/leading-with-wikipedia-a-brand-proposal-for-2030/|site=Wikimedia Foundation News|éditeur=|date=26 February 2019|consulté le=}}.</ref>. Ce changement n’a toutefois pas été accepté par de nombreuses personnes actives au sein du mouvement Wikimédia. En janvier 2020, ces opposants ont ainsi créé une page d’[[m:Requests_for_comment/Should_the_Foundation_call_itself_Wikipedia|appel à commentaires]], qui fut le siège d’un long débat<ref name="Requests for comment">{{Lien web|langue=|auteur=Méta-Wiki|titre=Requests for comment/Should the Foundation call itself Wikipedia|url=https://web.archive.org/web/20210905054842/https://meta.wikimedia.org/wiki/Requests_for_comment/Should_the_Foundation_call_itself_Wikipedia|consulté le=}}.</ref>. À l’issue de ce dernier, 73 représentants d’organisations affiliées et 984 personnes ont signé une [[m:Community_open_letter_on_renaming/fr|lettre ouverte]] adressée à la Fondation, qui comprenait le paragraphe suivant<ref>{{Lien web|langue=|auteur=Méta-Wiki|titre=Lettre ouverte de la Communauté sur le changement de nom|url=https://web.archive.org/web/20210122162652/https://meta.wikimedia.org/wiki/Community_open_letter_on_renaming/fr|consulté le=}}.</ref> : <blockquote> Depuis 20 ans, les bénévoles ont bâti la réputation de Wikipédia en tant que ressource indépendante et communautaire. Les projets du mouvement Wikimédia, dont Wikipédia, se développent autour de la décentralisation et du consensus. Il est essentiel d’établir des distinctions claires entre la Fondation Wikimédia, les affiliés et les contributeurs individuels. Tout changement qui affecte cet équilibre exige le consentement éclairé et la collaboration des communautés. Il est donc très préoccupant de voir « Wikipédia » présenté pour le nom de l’organisation et du mouvement malgré le mécontentement général de la communauté. </blockquote> En s’opposant aux idées de la Fondation, ces membres de la communauté Wikimédia ont ainsi fait preuve de sagesse. De plus, ils ont signalé dans de nombreux commentaires que beaucoup de personnes connaissent le mouvement Wikimédia uniquement au travers de son encyclopédie. Il est même étonnant d'observer que la méconnaissance du mouvement existe aussi en interne. Parar exemple, on peut observer que l'[[w:en:Wikimedia_movement|article du projet Wikipédia en anglais consacré au mouvement Wikimédia]] ne s’est développé qu’à partir de 2016<ref>{{Lien web|auteur=Wikipedia|titre=Wikimedia mouvement - old revision|url=https://en.wikipedia.org/w/index.php?title=Wikimedia_movement&oldid=716240586|consulté le=}}.</ref>, tandis que ce même article dans la [[w:fr:Mouvement_Wikimédia|version francophone de l'encyclopédie]] n’est apparu qu’en 2019<ref>{{Lien web|auteur=Wikipédia|titre=Mouvement Wikimédia - version archivée|url=https://fr.wikipedia.org/w/index.php?title=Mouvement_Wikim%C3%A9dia&oldid=158268859|consulté le=}}.</ref>. Quant aux autres versions linguistiques, il est tout aussi étonnant de constater qu'en octobre 2025, seulement [[wikidata:Q3568028|39 d'entre elles sur un total de 358]] possédaient un article dédié au mouvement Wikimédia<ref>{{Lien web|auteur=Wikidata|titre=Wikimedia Movement|url=https://web.archive.org/web/20251004091239/https://www.wikidata.org/wiki/Q3568028}}.</ref>. Tous ces éléments justifient donc la nécessité d’offrir au monde une meilleure connaissance du mouvement Wikimédia et des nombreux projets et organisations qui soutiennent leurs missions de partage du savoir. En ce sens, ce livre est une contribution importante aux défis stratégiques que doit relever le mouvement Wikimédia à l’approche de 2030. Car au-delà des [[wmf:Resolution:Next_Steps_for_Brand_Work,_2021|résolutions]] prises pour développer de nouveaux processus participatifs et délibératifs concernant les questions de marque<ref>{{Lien web|auteur=Wikimedia Foundation Wiki|titre=Resolution:Next Steps for Brand Work, 2021|url=https://web.archive.org/web/20211020232912/https://foundation.wikimedia.org/wiki/Resolution:Next_Steps_for_Brand_Work,_2021|date=|consulté le=}}.</ref>, c’est avant tout un travail d’information et de sensibilisation à destination du grand public qu'il reste à faire. {| class="wikitable"style="margin: auto;" "text-align:center;" |+ |[[Fichier:Qrcode Culture fr Wikipédia.svg|lien=https://fr.wikiversity.org/wiki/Recherche:Culture_fr_Wikip%C3%A9dia|100x100px|centré|sans_cadre]] |[[Fichier:Qrcode Imagine un monde.svg|lien=https://fr.wikiversity.org/wiki/Recherche:Imagine_un_monde|centré|sans_cadre|100x100px]] |[[Fichier:Code qr Wikiscan.svg|lien=http://wikiscan.org|100x100px|centré|sans_cadre]] |[[Fichier:QR code Statistiques Wikimédia.png|lien=https://stats.wikimedia.org|centré|sans_cadre|100x100px]] |[[Fichier:QR-code Wikistats wmcloud.png|lien=https://wikistats.wmcloud.org/wikimedias_html.php?s=users_asc&th=0&lines=2000|centré|sans cadre|100x100px]] |- |<small>Q 5 : Ethnographie fr Wikipédia</small> |<small>Q 2 : Thèse ''Imagine un monde''</small> |{{Centrer|<small>Q 2 : Wikiscan</small>}} |<small>QStatistiques de Wikimédia</small> |<small>Wikistats wmcloud</small> |} ‎<includeonly></includeonly> {{AutoCat}} emgwcvn0btcxouk50myw9hdvk6amqkp 764788 764787 2026-04-24T09:52:18Z Lionel Scheepmans 20012 764788 wikitext text/x-wiki <noinclude>{{Le mouvement Wikimédia}} </noinclude> Depuis le succès initial de Wikipédia, une myriade de projets de partage des connaissances, d’organisations et de groupes de soutien ont émergé pour former ce qu’on appelle aujourd’hui le mouvement Wikimédia. Même si, à ce jour, l’encyclopédie libre reste l'activité phare du mouvement, il serait regrettable de réduire l’ensemble du mouvement à cet unique projet pédagogique. Malheureusement, il arrive bien trop souvent qu'une seule version linguistique de Wikipédia suffise pour cacher l’étendue de la forêt Wikimédia. En réalité, Wikimédia représente un mouvement social, international et interculturel complexe, au sein duquel Wikipédia n’est qu’une composante parmi d’autres. Dans le cadre d'un mémoire de master, on peut ainsi fournir en quelques mois une [[v:recherche:Culture_fr_Wikipédia|ethnographie du projet Wikipédia en français]]<ref>{{Lien web|titre=Culture fr Wikipédia, ethnographie du projet Wikipédia en français|url=https://web.archive.org/web/20250905132014/https://fr.wikiversity.org/wiki/Recherche:Culture_fr_Wikip%C3%A9dia|auteur=Lionel Scheepmans|site=Wikiversité|année=2011}}.</ref>. Alors que pour synthétiser les origines, l’organisation et les dynamiques globales du mouvement Wikimédia, une [[v:fr:Recherche:Imagine_un_monde|thèse de doctorat]]<ref>{{Ouvrage|langue=fr|prénom1=Lionel|nom1=Scheepmans|lien auteur1=user:Lionel Scheepmans|titre=Imagine un monde : quand le mouvement Wikimédia nous aide à penser de manière prospective la société globale et numérique de demain|éditeur=UCL - Université Catholique de Louvain|année=2022|date=17/06/2022|lire en ligne=https://dial.uclouvain.be/pr/boreal/object/boreal:264603|consulté le=2024-03-10|nature article=Thèse de doctorat}}</ref> et cinq années de recul supplémentaires furent nécessaires. [[Fichier:Wikimedia project stickers.jpg|vignette|<small>Figure 1. Série d’autocollants reprenant les logos des principaux projets collaboratifs actifs au sein du mouvement Wikimédia.</small>]] À la fin de l'année 2025, l’ampleur numérique du mouvement est effectivement impressionnante. Chaque mois, des millions de modifications bénévoles sont effectuées sur plus de 500 millions de pages<ref>{{Lien web|auteur=Stat.wikimedia.org|titre=Statistiques de Wikimédia|url=https://web.archive.org/web/20251209230902/https://stats.wikimedia.org/#/all-projects}}.</ref>, réparties sur plus d’un millier de sites web<ref>{{Lien web|url=https://web.archive.org/web/20251217134333/https://wikistats.wmcloud.org/wikimedias_html.php?s=users_asc&th=0&lines=2000|titre=All Wikimedia Projects by Size|auteur=Wikistats wmcloud}}.</ref>, dont 358 seulement, correspondent aux [[m:List_of_Wikipedias|versions linguistiques de Wikipédia]]<ref>{{Lien web|url=https://web.archive.org/web/20251208041814/https://meta.wikimedia.org/wiki/List_of_Wikipedias|titre=List_of_Wikipedias|auteur=Méta-Wiki}}.</ref>. Ce qui prouve donc clairement que les activités en ligne portées par l'ensemble du mouvement Wikimédia dépassent largement ce qui se passe au sein de l’encyclopédie. Il existe ainsi huit autres projets pédagogiques susceptibles d'atteindre un jour l'envergure et la notoriété de Wikipédia, avec un objectif et un fonctionnement spécifiques à chacun. De manière détaillée, Wikilivres crée des livres pédagogiques, Wikiversité rassemble des supports d'enseignement et des travaux de recherche, Wikinews fait du journalisme collaboratif, Wikivoyage développe un guide touristique, pendant que Wiktionnaire apporte des définitions des mots de toutes les langues et dans toutes les langues. Contrairement à Wikipédia, tous ces projets ne sont pas soumis à une neutralité de point de vue, ni limités à l'usage de sources secondaires reconnues, au niveau de la rédaction des articles. La plupart d'entre eux acceptent aussi la publication de travaux de recherche ou de productions personnelles, alors que cela est tout à fait interdit dans Wikipédia. Selon l'étymologie du mot encyclopédie, le but de Wikipédia est en effet de synthétiser ou, plus précisément, d'encercler le savoir humain déjà préexistant. Cette contrainte éditoriale limite donc les contributeurs et contributrices à l'usage de sources secondaires et tertiaires présentes dans des publications externes au projet. De ce fait, Wikipédia reproduit fatalement les biais systémiques, tels que les déséquilibres et les surreprésentations de genre et de culture, présents dans un monde de l'édition majoritairement occidental. Or, cette impasse éditoriale propre à Wikipédia n'existe pas dans les autres projets pédagogiques. Comme ces projets frères, Wikipédia n'est pas non plus un projet complètement autonome des autres projets Wikimédia. L'encyclopédie compte en effet sur le projet Wikimedia Commons pour héberger l'ensemble de sa médiathèque. Elle utilise ensuite le contenu du projet Wikidata comme base de données structurée. Quant aux personnes qui produisent l'encyclopédie, elles peuvent aussi puiser leurs sources dans la bibliothèque Wikisource, ou se référer à des citations d'auteurs collectées dans le projet Wikiquote. Tout cela sans oublier que des dizaines de sites web traitent l'archivage permanent de Wikipédia et des autres projets Wikimédia, dans le but de fournir des analyses précieuses et totalement libres d’accès. Enfin, il faut aussi garder à l'esprit qu'au-delà de tous ces sites web, le mouvement Wikimédia, c'est aussi de nombreuses institutions et organisations affiliées dispersées dans le monde. Autour de la [[w:Fondation_Wikimédia|Fondation Wikimédia]] chargée de la gestion et de l’organisation internationales, avec près de 650 salariés de nationalités diverses<ref>{{Lien web|langue=|auteur=Wikimedia Foundation|titre=Les personnes derrière notre connaissance|url=https://web.archive.org/web/20250904032058/https://wikimediafoundation.org/fr/who-we-are/people/|site=|date=|consulté le=}}</ref>, se regroupent des centaines d'organisations satellites. Parmi celles-ci, on retrouve 2 [[m:Wikimedia_thematic_organizations/fr|associations thématiques]]<ref>{{Lien web|langue=|auteur=Méta-Wiki|titre=Wikimedia thematic organisations|url=https://web.archive.org/web/20250926235310/https://meta.wikimedia.org/wiki/Wikimedia_thematic_organizations|site=|date=|consulté le=}}.</ref>, 40 [[m:Wikimedia_chapters/fr|associations locales]]<ref>{{Lien web|langue=|auteur=Méta-Wiki|titre=Wikimedia chapters|url=https://web.archive.org/web/20251006092129/https://meta.wikimedia.org/wiki/Wikimedia_chapters}}</ref>, dont ''Wikimedia Deutschland'' qui regroupe plus de 170 emplyés<ref>{{Lien web|langue=|auteur=RocketReach|titre=Wikimedia Deutschlande. V. Information|url=https://web.archive.org/web/20251218034616/https://rocketreach.co/wikimedia-deutschland-e-v-profile_b5caf600f42e140f|site=|date=|consulté le=}}.</ref>, et finalement 141 [[metawiki:Wikimedia_user_groups/fr|groupes d’utilisateurs et utilisatrices]]<ref>{{Lien web|langue=|auteur=Méta-Wiki|titre=Wikimedia user groups|url=https://web.archive.org/web/20251008041452/https://meta.wikimedia.org/wiki/Wikimedia_User_Groups|site=|date=|consulté le=}}</ref>. Tout ce qui vient d'être exposé dans cette introduction justifie donc la nécessité de distinguer le mouvement Wikimédia du projet Wikipédia. Imaginons seulement que l’on se limite à citer Paris pour décrire et comprendre un pays aussi vaste que la France. Certes, Paris est une ville mondialement connue et qui compte plus de deux millions d’habitants et un patrimoine culturel impressionnant. Mais est-ce pour autant qu'il faudrait oublier les autres villes, villages et métropoles françaises ? Sans compter que la France regroupe aussi des départements et des territoires d’outre-mer et qu'elle entretient des relations et des partenariats internationaux qui dépassent de loin ce qui se passe entre Paris et le reste du monde. Ne pas confondre le mouvement Wikimédia avec le projet Wikipédia relève donc du bon sens. En 2019 cependant, la Fondation Wikimédia a envisagé de se renommer en Fondation Wikipédia et de remplacer le terme « Wikimédia » par celui de « Wikipédia », partout où ce terme est utilisé dans la sphère hors ligne du mouvement. Le but était d’acquérir une plus grande visibilité et d’attirer des milliards de personnes, grâce au nom de marque Wikipédia, considéré comme l’un des plus connus au monde<ref>{{Lien web|langue=|auteur=Zack McCune|titre=Leading with Wikipedia: A brand proposal for 2030|url=https://web.archive.org/web/20210117025153/https://wikimediafoundation.org/news/2019/02/26/leading-with-wikipedia-a-brand-proposal-for-2030/|site=Wikimedia Foundation News|éditeur=|date=26 February 2019|consulté le=}}.</ref>. Ce changement n’a toutefois pas été accepté par de nombreuses personnes actives au sein du mouvement Wikimédia. En janvier 2020, ces opposants ont ainsi créé une page d’[[m:Requests_for_comment/Should_the_Foundation_call_itself_Wikipedia|appel à commentaires]], qui fut le siège d’un long débat<ref name="Requests for comment">{{Lien web|langue=|auteur=Méta-Wiki|titre=Requests for comment/Should the Foundation call itself Wikipedia|url=https://web.archive.org/web/20210905054842/https://meta.wikimedia.org/wiki/Requests_for_comment/Should_the_Foundation_call_itself_Wikipedia|consulté le=}}.</ref>. À l’issue de ce dernier, 73 représentants d’organisations affiliées et 984 personnes ont signé une [[m:Community_open_letter_on_renaming/fr|lettre ouverte]] adressée à la Fondation, qui comprenait le paragraphe suivant<ref>{{Lien web|langue=|auteur=Méta-Wiki|titre=Lettre ouverte de la Communauté sur le changement de nom|url=https://web.archive.org/web/20210122162652/https://meta.wikimedia.org/wiki/Community_open_letter_on_renaming/fr|consulté le=}}.</ref> : <blockquote> Depuis 20 ans, les bénévoles ont bâti la réputation de Wikipédia en tant que ressource indépendante et communautaire. Les projets du mouvement Wikimédia, dont Wikipédia, se développent autour de la décentralisation et du consensus. Il est essentiel d’établir des distinctions claires entre la Fondation Wikimédia, les affiliés et les contributeurs individuels. Tout changement qui affecte cet équilibre exige le consentement éclairé et la collaboration des communautés. Il est donc très préoccupant de voir « Wikipédia » présenté pour le nom de l’organisation et du mouvement malgré le mécontentement général de la communauté. </blockquote> En s’opposant aux idées de la Fondation, ces membres de la communauté Wikimédia ont ainsi fait preuve de sagesse. De plus, ils ont signalé dans de nombreux commentaires que beaucoup de personnes connaissent le mouvement Wikimédia uniquement au travers de son encyclopédie. Il est même étonnant d'observer que la méconnaissance du mouvement existe aussi au sein même de celui-ci. Pour exemple, on peut observer que l'[[w:en:Wikimedia_movement|article du projet Wikipédia en anglais consacré au mouvement Wikimédia]] ne s’est développé qu’à partir de 2016<ref>{{Lien web|auteur=Wikipedia|titre=Wikimedia mouvement - old revision|url=https://en.wikipedia.org/w/index.php?title=Wikimedia_movement&oldid=716240586|consulté le=}}.</ref>, tandis que ce même article dans la [[w:fr:Mouvement_Wikimédia|version francophone de l'encyclopédie]] n’est apparu qu’en 2019<ref>{{Lien web|auteur=Wikipédia|titre=Mouvement Wikimédia - version archivée|url=https://fr.wikipedia.org/w/index.php?title=Mouvement_Wikim%C3%A9dia&oldid=158268859|consulté le=}}.</ref>. Quant aux autres versions linguistiques, il est tout aussi étonnant de constater qu'en octobre 2025, seulement [[wikidata:Q3568028|39 d'entre elles sur un total de 358]] possédaient un article dédié au mouvement Wikimédia<ref>{{Lien web|auteur=Wikidata|titre=Wikimedia Movement|url=https://web.archive.org/web/20251004091239/https://www.wikidata.org/wiki/Q3568028}}.</ref>. Tous ces éléments justifient donc la nécessité d’offrir au monde une meilleure connaissance du mouvement Wikimédia et des nombreux projets et organisations qui soutiennent leurs missions de partage du savoir. En ce sens, ce livre est une contribution importante aux défis stratégiques que doit relever le mouvement Wikimédia à l’approche de 2030. Car au-delà des [[wmf:Resolution:Next_Steps_for_Brand_Work,_2021|résolutions]] prises pour développer de nouveaux processus participatifs et délibératifs concernant les questions de marque<ref>{{Lien web|auteur=Wikimedia Foundation Wiki|titre=Resolution:Next Steps for Brand Work, 2021|url=https://web.archive.org/web/20211020232912/https://foundation.wikimedia.org/wiki/Resolution:Next_Steps_for_Brand_Work,_2021|date=|consulté le=}}.</ref>, c’est avant tout un travail d’information et de sensibilisation à destination du grand public qu'il reste à faire. {| class="wikitable"style="margin: auto;" "text-align:center;" |+ |[[Fichier:Qrcode Culture fr Wikipédia.svg|lien=https://fr.wikiversity.org/wiki/Recherche:Culture_fr_Wikip%C3%A9dia|100x100px|centré|sans_cadre]] |[[Fichier:Qrcode Imagine un monde.svg|lien=https://fr.wikiversity.org/wiki/Recherche:Imagine_un_monde|centré|sans_cadre|100x100px]] |[[Fichier:Code qr Wikiscan.svg|lien=http://wikiscan.org|100x100px|centré|sans_cadre]] |[[Fichier:QR code Statistiques Wikimédia.png|lien=https://stats.wikimedia.org|centré|sans_cadre|100x100px]] |[[Fichier:QR-code Wikistats wmcloud.png|lien=https://wikistats.wmcloud.org/wikimedias_html.php?s=users_asc&th=0&lines=2000|centré|sans cadre|100x100px]] |- |<small>Q 5 : Ethnographie fr Wikipédia</small> |<small>Q 2 : Thèse ''Imagine un monde''</small> |{{Centrer|<small>Q 2 : Wikiscan</small>}} |<small>QStatistiques de Wikimédia</small> |<small>Wikistats wmcloud</small> |} ‎<includeonly></includeonly> {{AutoCat}} 5l40tzt0gbswefzjyim8h4j3h6pz14j 764789 764788 2026-04-24T09:55:17Z Lionel Scheepmans 20012 764789 wikitext text/x-wiki <noinclude>{{Le mouvement Wikimédia}} </noinclude> Depuis le succès initial de Wikipédia, une myriade de projets de partage des connaissances, d’organisations et de groupes de soutien ont émergé pour former ce qu’on appelle aujourd’hui le mouvement Wikimédia. Même si, à ce jour, l’encyclopédie libre reste l'activité phare du mouvement, il serait regrettable de réduire l’ensemble du mouvement à cet unique projet pédagogique. Malheureusement, il arrive bien trop souvent qu'une seule version linguistique de Wikipédia suffise pour cacher l’étendue de la forêt Wikimédia. En réalité, Wikimédia représente un mouvement social, international et interculturel complexe, au sein duquel Wikipédia n’est qu’une composante parmi d’autres. Dans le cadre d'un mémoire de master, on peut ainsi fournir en quelques mois une [[v:recherche:Culture_fr_Wikipédia|ethnographie du projet Wikipédia en français]]<ref>{{Lien web|titre=Culture fr Wikipédia, ethnographie du projet Wikipédia en français|url=https://web.archive.org/web/20250905132014/https://fr.wikiversity.org/wiki/Recherche:Culture_fr_Wikip%C3%A9dia|auteur=Lionel Scheepmans|site=Wikiversité|année=2011}}.</ref>. Alors que pour synthétiser les origines, l’organisation et les dynamiques globales du mouvement Wikimédia, une [[v:fr:Recherche:Imagine_un_monde|thèse de doctorat]]<ref>{{Ouvrage|langue=fr|prénom1=Lionel|nom1=Scheepmans|lien auteur1=user:Lionel Scheepmans|titre=Imagine un monde : quand le mouvement Wikimédia nous aide à penser de manière prospective la société globale et numérique de demain|éditeur=UCL - Université Catholique de Louvain|année=2022|date=17/06/2022|lire en ligne=https://dial.uclouvain.be/pr/boreal/object/boreal:264603|consulté le=2024-03-10|nature article=Thèse de doctorat}}</ref> et cinq années de recul supplémentaires furent nécessaires. [[Fichier:Wikimedia project stickers.jpg|vignette|<small>Figure 1. Série d’autocollants reprenant les logos des principaux projets collaboratifs actifs au sein du mouvement Wikimédia.</small>]] À la fin de l'année 2025, l’ampleur numérique du mouvement est effectivement impressionnante. Chaque mois, des millions de modifications bénévoles sont effectuées sur plus de 500 millions de pages<ref>{{Lien web|auteur=Stat.wikimedia.org|titre=Statistiques de Wikimédia|url=https://web.archive.org/web/20251209230902/https://stats.wikimedia.org/#/all-projects}}.</ref>, réparties sur plus d’un millier de sites web<ref>{{Lien web|url=https://web.archive.org/web/20251217134333/https://wikistats.wmcloud.org/wikimedias_html.php?s=users_asc&th=0&lines=2000|titre=All Wikimedia Projects by Size|auteur=Wikistats wmcloud}}.</ref>, dont 358 seulement, correspondent aux [[m:List_of_Wikipedias|versions linguistiques de Wikipédia]]<ref>{{Lien web|url=https://web.archive.org/web/20251208041814/https://meta.wikimedia.org/wiki/List_of_Wikipedias|titre=List_of_Wikipedias|auteur=Méta-Wiki}}.</ref>. Ce qui prouve donc clairement que les activités en ligne portées par l'ensemble du mouvement Wikimédia dépassent largement ce qui se passe au sein de l’encyclopédie. Il existe ainsi huit autres projets pédagogiques susceptibles d'atteindre un jour l'envergure et la notoriété de Wikipédia, avec un objectif et un fonctionnement spécifiques à chacun. De manière détaillée, Wikilivres crée des livres pédagogiques, Wikiversité rassemble des supports d'enseignement et des travaux de recherche, Wikinews fait du journalisme collaboratif, Wikivoyage développe un guide touristique, pendant que Wiktionnaire apporte des définitions des mots de toutes les langues et dans toutes les langues. Contrairement à Wikipédia, tous ces projets ne sont pas soumis à une neutralité de point de vue, ni limités à l'usage de sources secondaires reconnues, au niveau de la rédaction des articles. La plupart d'entre eux acceptent aussi la publication de travaux de recherche ou de productions personnelles, alors que cela est tout à fait interdit dans Wikipédia. Selon l'étymologie du mot encyclopédie, le but de Wikipédia est en effet de synthétiser ou, plus précisément, d'encercler le savoir humain déjà préexistant. Cette contrainte éditoriale limite donc les contributeurs et contributrices à l'usage de sources secondaires et tertiaires présentes dans des publications externes au projet. De ce fait, Wikipédia reproduit fatalement les biais systémiques, tels que les déséquilibres et les surreprésentations de genre et de culture, présents dans un monde de l'édition majoritairement occidental. Or, cette impasse éditoriale propre à Wikipédia n'existe pas dans les autres projets pédagogiques. Comme ces projets frères, Wikipédia n'est pas non plus un projet complètement autonome des autres projets Wikimédia. L'encyclopédie compte en effet sur le projet Wikimedia Commons pour héberger l'ensemble de sa médiathèque. Elle utilise ensuite le contenu du projet Wikidata comme base de données structurée. Quant aux personnes qui produisent l'encyclopédie, elles peuvent aussi puiser leurs sources dans la bibliothèque Wikisource, ou se référer à des citations d'auteurs collectées dans le projet Wikiquote. Tout cela sans oublier que des dizaines de sites web traitent l'archivage permanent de Wikipédia et des autres projets Wikimédia, dans le but de fournir des analyses précieuses et totalement libres d’accès. Enfin, il faut aussi garder à l'esprit qu'au-delà de tous ces sites web, le mouvement Wikimédia, c'est aussi de nombreuses institutions et organisations affiliées dispersées dans le monde. Autour de la [[w:Fondation_Wikimédia|Fondation Wikimédia]] chargée de la gestion et de l’organisation internationales, avec près de 650 salariés de nationalités diverses<ref>{{Lien web|langue=|auteur=Wikimedia Foundation|titre=Les personnes derrière notre connaissance|url=https://web.archive.org/web/20250904032058/https://wikimediafoundation.org/fr/who-we-are/people/|site=|date=|consulté le=}}</ref>, se regroupent des centaines d'organisations satellites. Parmi celles-ci, on retrouve 2 [[m:Wikimedia_thematic_organizations/fr|associations thématiques]]<ref>{{Lien web|langue=|auteur=Méta-Wiki|titre=Wikimedia thematic organisations|url=https://web.archive.org/web/20250926235310/https://meta.wikimedia.org/wiki/Wikimedia_thematic_organizations|site=|date=|consulté le=}}.</ref>, 40 [[m:Wikimedia_chapters/fr|associations locales]]<ref>{{Lien web|langue=|auteur=Méta-Wiki|titre=Wikimedia chapters|url=https://web.archive.org/web/20251006092129/https://meta.wikimedia.org/wiki/Wikimedia_chapters}}</ref>, dont ''Wikimedia Deutschland'' qui regroupe plus de 170 emplyés<ref>{{Lien web|langue=|auteur=RocketReach|titre=Wikimedia Deutschlande. V. Information|url=https://web.archive.org/web/20251218034616/https://rocketreach.co/wikimedia-deutschland-e-v-profile_b5caf600f42e140f|site=|date=|consulté le=}}.</ref>, et finalement 141 [[metawiki:Wikimedia_user_groups/fr|groupes d’utilisateurs et utilisatrices]]<ref>{{Lien web|langue=|auteur=Méta-Wiki|titre=Wikimedia user groups|url=https://web.archive.org/web/20251008041452/https://meta.wikimedia.org/wiki/Wikimedia_User_Groups|site=|date=|consulté le=}}</ref>. Tout ce qui vient d'être exposé dans cette introduction justifie donc la nécessité de distinguer le mouvement Wikimédia du projet Wikipédia. Imaginons seulement que l’on se limite à citer Paris pour décrire et comprendre un pays aussi vaste que la France. Certes, Paris est une ville mondialement connue et qui compte plus de deux millions d’habitants et un patrimoine culturel impressionnant. Mais est-ce pour autant qu'il faudrait oublier les autres villes, villages et métropoles françaises ? Sans compter que la France regroupe aussi des départements et des territoires d’outre-mer et qu'elle entretient des relations et des partenariats internationaux qui dépassent de loin ce qui se passe entre Paris et le reste du monde. Ne pas confondre le mouvement Wikimédia avec le projet Wikipédia relève donc du bon sens. En 2019 cependant, la Fondation Wikimédia a envisagé de se renommer en Fondation Wikipédia et de remplacer le terme « Wikimédia » par celui de « Wikipédia », partout où ce terme est utilisé dans la sphère hors ligne du mouvement. Le but était d’acquérir une plus grande visibilité et d’attirer des milliards de personnes, grâce au nom de marque Wikipédia, considéré comme l’un des plus connus au monde<ref>{{Lien web|langue=|auteur=Zack McCune|titre=Leading with Wikipedia: A brand proposal for 2030|url=https://web.archive.org/web/20210117025153/https://wikimediafoundation.org/news/2019/02/26/leading-with-wikipedia-a-brand-proposal-for-2030/|site=Wikimedia Foundation News|éditeur=|date=26 February 2019|consulté le=}}.</ref>. Ce changement n’a toutefois pas été accepté par de nombreuses personnes actives au sein du mouvement Wikimédia. En janvier 2020, ces opposants ont ainsi créé une page d’[[m:Requests_for_comment/Should_the_Foundation_call_itself_Wikipedia|appel à commentaires]], qui fut le siège d’un long débat<ref name="Requests for comment">{{Lien web|langue=|auteur=Méta-Wiki|titre=Requests for comment/Should the Foundation call itself Wikipedia|url=https://web.archive.org/web/20210905054842/https://meta.wikimedia.org/wiki/Requests_for_comment/Should_the_Foundation_call_itself_Wikipedia|consulté le=}}.</ref>. À l’issue de ce dernier, 73 représentants d’organisations affiliées et 984 personnes ont signé une [[m:Community_open_letter_on_renaming/fr|lettre ouverte]] adressée à la Fondation, qui comprenait le paragraphe suivant<ref>{{Lien web|langue=|auteur=Méta-Wiki|titre=Lettre ouverte de la Communauté sur le changement de nom|url=https://web.archive.org/web/20210122162652/https://meta.wikimedia.org/wiki/Community_open_letter_on_renaming/fr|consulté le=}}.</ref> : <blockquote> Depuis 20 ans, les bénévoles ont bâti la réputation de Wikipédia en tant que ressource indépendante et communautaire. Les projets du mouvement Wikimédia, dont Wikipédia, se développent autour de la décentralisation et du consensus. Il est essentiel d’établir des distinctions claires entre la Fondation Wikimédia, les affiliés et les contributeurs individuels. Tout changement qui affecte cet équilibre exige le consentement éclairé et la collaboration des communautés. Il est donc très préoccupant de voir « Wikipédia » présenté pour le nom de l’organisation et du mouvement malgré le mécontentement général de la communauté. </blockquote> En s’opposant aux idées de la Fondation, ces membres de la communauté Wikimédia ont ainsi fait preuve de sagesse. De plus, ils ont signalé dans de nombreux commentaires que beaucoup de personnes connaissent le mouvement Wikimédia uniquement au travers de son encyclopédie. Il est même étonnant d'observer que la méconnaissance du mouvement existe aussi au sein même de sa propre communauté. Comme exemple, on peut observer que l'[[w:en:Wikimedia_movement|article du projet Wikipédia en anglais consacré au mouvement Wikimédia]] ne s’est développé qu’à partir de 2016<ref>{{Lien web|auteur=Wikipedia|titre=Wikimedia mouvement - old revision|url=https://en.wikipedia.org/w/index.php?title=Wikimedia_movement&oldid=716240586|consulté le=}}.</ref>, tandis que ce même article dans la [[w:fr:Mouvement_Wikimédia|version francophone de l'encyclopédie]] n’est apparu qu’en 2019<ref>{{Lien web|auteur=Wikipédia|titre=Mouvement Wikimédia - version archivée|url=https://fr.wikipedia.org/w/index.php?title=Mouvement_Wikim%C3%A9dia&oldid=158268859|consulté le=}}.</ref>. Quant aux autres versions linguistiques, il est tout aussi étonnant de constater qu'en octobre 2025, seulement [[wikidata:Q3568028|39 d'entre elles sur un total de 358]] possédaient un article dédié au mouvement Wikimédia<ref>{{Lien web|auteur=Wikidata|titre=Wikimedia Movement|url=https://web.archive.org/web/20251004091239/https://www.wikidata.org/wiki/Q3568028}}.</ref>. Tous ces éléments justifient donc la nécessité d’offrir au monde une meilleure connaissance du mouvement Wikimédia et des nombreux projets et organisations qui soutiennent leurs missions de partage du savoir. En ce sens, ce livre est une contribution importante aux défis stratégiques que doit relever le mouvement Wikimédia à l’approche de 2030. Car au-delà des [[wmf:Resolution:Next_Steps_for_Brand_Work,_2021|résolutions]] prises pour développer de nouveaux processus participatifs et délibératifs concernant les questions de marque<ref>{{Lien web|auteur=Wikimedia Foundation Wiki|titre=Resolution:Next Steps for Brand Work, 2021|url=https://web.archive.org/web/20211020232912/https://foundation.wikimedia.org/wiki/Resolution:Next_Steps_for_Brand_Work,_2021|date=|consulté le=}}.</ref>, c’est avant tout un travail d’information et de sensibilisation à destination du grand public qu'il reste à faire. {| class="wikitable"style="margin: auto;" "text-align:center;" |+ |[[Fichier:Qrcode Culture fr Wikipédia.svg|lien=https://fr.wikiversity.org/wiki/Recherche:Culture_fr_Wikip%C3%A9dia|100x100px|centré|sans_cadre]] |[[Fichier:Qrcode Imagine un monde.svg|lien=https://fr.wikiversity.org/wiki/Recherche:Imagine_un_monde|centré|sans_cadre|100x100px]] |[[Fichier:Code qr Wikiscan.svg|lien=http://wikiscan.org|100x100px|centré|sans_cadre]] |[[Fichier:QR code Statistiques Wikimédia.png|lien=https://stats.wikimedia.org|centré|sans_cadre|100x100px]] |[[Fichier:QR-code Wikistats wmcloud.png|lien=https://wikistats.wmcloud.org/wikimedias_html.php?s=users_asc&th=0&lines=2000|centré|sans cadre|100x100px]] |- |<small>Q 5 : Ethnographie fr Wikipédia</small> |<small>Q 2 : Thèse ''Imagine un monde''</small> |{{Centrer|<small>Q 2 : Wikiscan</small>}} |<small>QStatistiques de Wikimédia</small> |<small>Wikistats wmcloud</small> |} ‎<includeonly></includeonly> {{AutoCat}} 2ew73fofhlskvjjq2mi0h627stnandf 764790 764789 2026-04-24T09:57:14Z Lionel Scheepmans 20012 764790 wikitext text/x-wiki <noinclude>{{Le mouvement Wikimédia}} </noinclude> Depuis le succès initial de Wikipédia, une myriade de projets de partage des connaissances, d’organisations et de groupes de soutien ont émergé pour former ce qu’on appelle aujourd’hui le mouvement Wikimédia. Même si, à ce jour, l’encyclopédie libre reste l'activité phare du mouvement, il serait regrettable de réduire l’ensemble du mouvement à cet unique projet pédagogique. Malheureusement, il arrive bien trop souvent qu'une seule version linguistique de Wikipédia suffise pour cacher l’étendue de la forêt Wikimédia. En réalité, Wikimédia représente un mouvement social, international et interculturel complexe, au sein duquel Wikipédia n’est qu’une composante parmi d’autres. Dans le cadre d'un mémoire de master, on peut ainsi fournir en quelques mois une [[v:recherche:Culture_fr_Wikipédia|ethnographie du projet Wikipédia en français]]<ref>{{Lien web|titre=Culture fr Wikipédia, ethnographie du projet Wikipédia en français|url=https://web.archive.org/web/20250905132014/https://fr.wikiversity.org/wiki/Recherche:Culture_fr_Wikip%C3%A9dia|auteur=Lionel Scheepmans|site=Wikiversité|année=2011}}.</ref>. Alors que pour synthétiser les origines, l’organisation et les dynamiques globales du mouvement Wikimédia, une [[v:fr:Recherche:Imagine_un_monde|thèse de doctorat]]<ref>{{Ouvrage|langue=fr|prénom1=Lionel|nom1=Scheepmans|lien auteur1=user:Lionel Scheepmans|titre=Imagine un monde : quand le mouvement Wikimédia nous aide à penser de manière prospective la société globale et numérique de demain|éditeur=UCL - Université Catholique de Louvain|année=2022|date=17/06/2022|lire en ligne=https://dial.uclouvain.be/pr/boreal/object/boreal:264603|consulté le=2024-03-10|nature article=Thèse de doctorat}}</ref> et cinq années de recul supplémentaires furent nécessaires. [[Fichier:Wikimedia project stickers.jpg|vignette|<small>Figure 1. Série d’autocollants reprenant les logos des principaux projets collaboratifs actifs au sein du mouvement Wikimédia.</small>]] À la fin de l'année 2025, l’ampleur numérique du mouvement est effectivement impressionnante. Chaque mois, des millions de modifications bénévoles sont effectuées sur plus de 500 millions de pages<ref>{{Lien web|auteur=Stat.wikimedia.org|titre=Statistiques de Wikimédia|url=https://web.archive.org/web/20251209230902/https://stats.wikimedia.org/#/all-projects}}.</ref>, réparties sur plus d’un millier de sites web<ref>{{Lien web|url=https://web.archive.org/web/20251217134333/https://wikistats.wmcloud.org/wikimedias_html.php?s=users_asc&th=0&lines=2000|titre=All Wikimedia Projects by Size|auteur=Wikistats wmcloud}}.</ref>, dont 358 seulement, correspondent aux [[m:List_of_Wikipedias|versions linguistiques de Wikipédia]]<ref>{{Lien web|url=https://web.archive.org/web/20251208041814/https://meta.wikimedia.org/wiki/List_of_Wikipedias|titre=List_of_Wikipedias|auteur=Méta-Wiki}}.</ref>. Ce qui prouve donc clairement que les activités en ligne portées par l'ensemble du mouvement Wikimédia dépassent largement ce qui se passe au sein de l’encyclopédie. Il existe ainsi huit autres projets pédagogiques susceptibles d'atteindre un jour l'envergure et la notoriété de Wikipédia, avec un objectif et un fonctionnement spécifiques à chacun. De manière détaillée, Wikilivres crée des livres pédagogiques, Wikiversité rassemble des supports d'enseignement et des travaux de recherche, Wikinews fait du journalisme collaboratif, Wikivoyage développe un guide touristique, pendant que Wiktionnaire apporte des définitions des mots de toutes les langues et dans toutes les langues. Contrairement à Wikipédia, tous ces projets ne sont pas soumis à une neutralité de point de vue, ni limités à l'usage de sources secondaires reconnues, au niveau de la rédaction des articles. La plupart d'entre eux acceptent aussi la publication de travaux de recherche ou de productions personnelles, alors que cela est tout à fait interdit dans Wikipédia. Selon l'étymologie du mot encyclopédie, le but de Wikipédia est en effet de synthétiser ou, plus précisément, d'encercler le savoir humain déjà préexistant. Cette contrainte éditoriale limite donc les contributeurs et contributrices à l'usage de sources secondaires et tertiaires présentes dans des publications externes au projet. De ce fait, Wikipédia reproduit fatalement les biais systémiques, tels que les déséquilibres et les surreprésentations de genre et de culture, présents dans un monde de l'édition majoritairement occidental. Or, cette impasse éditoriale propre à Wikipédia n'existe pas dans les autres projets pédagogiques. Comme ces projets frères, Wikipédia n'est pas non plus un projet complètement autonome des autres projets Wikimédia. L'encyclopédie compte en effet sur le projet Wikimedia Commons pour héberger l'ensemble de sa médiathèque. Elle utilise ensuite le contenu du projet Wikidata comme base de données structurée. Quant aux personnes qui produisent l'encyclopédie, elles peuvent aussi puiser leurs sources dans la bibliothèque Wikisource, ou se référer à des citations d'auteurs collectées dans le projet Wikiquote. Tout cela sans oublier que des dizaines de sites web traitent l'archivage permanent de Wikipédia et des autres projets Wikimédia, dans le but de fournir des analyses précieuses et totalement libres d’accès. Enfin, il faut aussi garder à l'esprit qu'au-delà de tous ces sites web, le mouvement Wikimédia, c'est aussi de nombreuses institutions et organisations affiliées dispersées dans le monde. Autour de la [[w:Fondation_Wikimédia|Fondation Wikimédia]] chargée de la gestion et de l’organisation internationales, avec près de 650 salariés de nationalités diverses<ref>{{Lien web|langue=|auteur=Wikimedia Foundation|titre=Les personnes derrière notre connaissance|url=https://web.archive.org/web/20250904032058/https://wikimediafoundation.org/fr/who-we-are/people/|site=|date=|consulté le=}}</ref>, se regroupent des centaines d'organisations satellites. Parmi celles-ci, on retrouve 2 [[m:Wikimedia_thematic_organizations/fr|associations thématiques]]<ref>{{Lien web|langue=|auteur=Méta-Wiki|titre=Wikimedia thematic organisations|url=https://web.archive.org/web/20250926235310/https://meta.wikimedia.org/wiki/Wikimedia_thematic_organizations|site=|date=|consulté le=}}.</ref>, 40 [[m:Wikimedia_chapters/fr|associations locales]]<ref>{{Lien web|langue=|auteur=Méta-Wiki|titre=Wikimedia chapters|url=https://web.archive.org/web/20251006092129/https://meta.wikimedia.org/wiki/Wikimedia_chapters}}</ref>, dont ''Wikimedia Deutschland'' qui regroupe plus de 170 emplyés<ref>{{Lien web|langue=|auteur=RocketReach|titre=Wikimedia Deutschlande. V. Information|url=https://web.archive.org/web/20251218034616/https://rocketreach.co/wikimedia-deutschland-e-v-profile_b5caf600f42e140f|site=|date=|consulté le=}}.</ref>, et finalement 141 [[metawiki:Wikimedia_user_groups/fr|groupes d’utilisateurs et utilisatrices]]<ref>{{Lien web|langue=|auteur=Méta-Wiki|titre=Wikimedia user groups|url=https://web.archive.org/web/20251008041452/https://meta.wikimedia.org/wiki/Wikimedia_User_Groups|site=|date=|consulté le=}}</ref>. Tout ce qui vient d'être exposé dans cette introduction justifie donc la nécessité de distinguer le mouvement Wikimédia du projet Wikipédia. Imaginons seulement que l’on se limite à citer Paris pour décrire et comprendre un pays aussi vaste que la France. Certes, Paris est une ville mondialement connue et qui compte plus de deux millions d’habitants et un patrimoine culturel impressionnant. Mais est-ce pour autant qu'il faudrait oublier les autres villes, villages et métropoles françaises ? Sans compter que la France regroupe aussi des départements et des territoires d’outre-mer et qu'elle entretient des relations et des partenariats internationaux qui dépassent de loin ce qui se passe entre Paris et le reste du monde. Ne pas confondre le mouvement Wikimédia avec le projet Wikipédia relève donc du bon sens. En 2019 cependant, la Fondation Wikimédia a envisagé de se renommer en Fondation Wikipédia et de remplacer le terme « Wikimédia » par celui de « Wikipédia », partout où ce terme est utilisé dans la sphère hors ligne du mouvement. Le but était d’acquérir une plus grande visibilité et d’attirer des milliards de personnes, grâce au nom de marque Wikipédia, considéré comme l’un des plus connus au monde<ref>{{Lien web|langue=|auteur=Zack McCune|titre=Leading with Wikipedia: A brand proposal for 2030|url=https://web.archive.org/web/20210117025153/https://wikimediafoundation.org/news/2019/02/26/leading-with-wikipedia-a-brand-proposal-for-2030/|site=Wikimedia Foundation News|éditeur=|date=26 February 2019|consulté le=}}.</ref>. Ce changement n’a toutefois pas été accepté par de nombreuses personnes actives au sein du mouvement Wikimédia. En janvier 2020, ces opposants ont ainsi créé une page d’[[m:Requests_for_comment/Should_the_Foundation_call_itself_Wikipedia|appel à commentaires]], qui fut le siège d’un long débat<ref name="Requests for comment">{{Lien web|langue=|auteur=Méta-Wiki|titre=Requests for comment/Should the Foundation call itself Wikipedia|url=https://web.archive.org/web/20210905054842/https://meta.wikimedia.org/wiki/Requests_for_comment/Should_the_Foundation_call_itself_Wikipedia|consulté le=}}.</ref>. À l’issue de ce dernier, 73 représentants d’organisations affiliées et 984 personnes ont signé une [[m:Community_open_letter_on_renaming/fr|lettre ouverte]] adressée à la Fondation, qui comprenait le paragraphe suivant<ref>{{Lien web|langue=|auteur=Méta-Wiki|titre=Lettre ouverte de la Communauté sur le changement de nom|url=https://web.archive.org/web/20210122162652/https://meta.wikimedia.org/wiki/Community_open_letter_on_renaming/fr|consulté le=}}.</ref> : <blockquote> Depuis 20 ans, les bénévoles ont bâti la réputation de Wikipédia en tant que ressource indépendante et communautaire. Les projets du mouvement Wikimédia, dont Wikipédia, se développent autour de la décentralisation et du consensus. Il est essentiel d’établir des distinctions claires entre la Fondation Wikimédia, les affiliés et les contributeurs individuels. Tout changement qui affecte cet équilibre exige le consentement éclairé et la collaboration des communautés. Il est donc très préoccupant de voir « Wikipédia » présenté pour le nom de l’organisation et du mouvement malgré le mécontentement général de la communauté. </blockquote> En s’opposant aux idées de la Fondation, ces membres de la communauté Wikimédia ont ainsi fait preuve de sagesse. De plus, ils ont signalé dans de nombreux commentaires que beaucoup de personnes connaissent le mouvement Wikimédia uniquement au travers de son encyclopédie. Il est même étonnant d'observer que la méconnaissance du mouvement existe au sein même de sa propre communauté. Comme exemple, on peut observer que l'[[w:en:Wikimedia_movement|article du projet Wikipédia en anglais consacré au mouvement Wikimédia]] ne s’est développé qu’à partir de 2016<ref>{{Lien web|auteur=Wikipedia|titre=Wikimedia mouvement - old revision|url=https://en.wikipedia.org/w/index.php?title=Wikimedia_movement&oldid=716240586|consulté le=}}.</ref>, tandis que ce même article dans la [[w:fr:Mouvement_Wikimédia|version francophone de l'encyclopédie]] n’est apparu qu’en 2019<ref>{{Lien web|auteur=Wikipédia|titre=Mouvement Wikimédia - version archivée|url=https://fr.wikipedia.org/w/index.php?title=Mouvement_Wikim%C3%A9dia&oldid=158268859|consulté le=}}.</ref>. Quant aux autres versions linguistiques, il est tout aussi étonnant de constater qu'en octobre 2025, seulement [[wikidata:Q3568028|39 d'entre elles sur un total de 358]] possédaient un article dédié au mouvement Wikimédia<ref>{{Lien web|auteur=Wikidata|titre=Wikimedia Movement|url=https://web.archive.org/web/20251004091239/https://www.wikidata.org/wiki/Q3568028}}.</ref>. Tous ces éléments justifient donc la nécessité d’offrir au monde une meilleure connaissance du mouvement Wikimédia et des nombreux projets et organisations qui soutiennent leurs missions de partage du savoir. En ce sens, ce livre est une contribution importante aux défis stratégiques que doit relever le mouvement Wikimédia à l’approche de 2030. Car au-delà des [[wmf:Resolution:Next_Steps_for_Brand_Work,_2021|résolutions]] prises pour développer de nouveaux processus participatifs et délibératifs concernant les questions de marque<ref>{{Lien web|auteur=Wikimedia Foundation Wiki|titre=Resolution:Next Steps for Brand Work, 2021|url=https://web.archive.org/web/20211020232912/https://foundation.wikimedia.org/wiki/Resolution:Next_Steps_for_Brand_Work,_2021|date=|consulté le=}}.</ref>, c’est avant tout un travail d’information et de sensibilisation à destination du grand public qu'il reste à faire. {| class="wikitable"style="margin: auto;" "text-align:center;" |+ |[[Fichier:Qrcode Culture fr Wikipédia.svg|lien=https://fr.wikiversity.org/wiki/Recherche:Culture_fr_Wikip%C3%A9dia|100x100px|centré|sans_cadre]] |[[Fichier:Qrcode Imagine un monde.svg|lien=https://fr.wikiversity.org/wiki/Recherche:Imagine_un_monde|centré|sans_cadre|100x100px]] |[[Fichier:Code qr Wikiscan.svg|lien=http://wikiscan.org|100x100px|centré|sans_cadre]] |[[Fichier:QR code Statistiques Wikimédia.png|lien=https://stats.wikimedia.org|centré|sans_cadre|100x100px]] |[[Fichier:QR-code Wikistats wmcloud.png|lien=https://wikistats.wmcloud.org/wikimedias_html.php?s=users_asc&th=0&lines=2000|centré|sans cadre|100x100px]] |- |<small>Q 5 : Ethnographie fr Wikipédia</small> |<small>Q 2 : Thèse ''Imagine un monde''</small> |{{Centrer|<small>Q 2 : Wikiscan</small>}} |<small>QStatistiques de Wikimédia</small> |<small>Wikistats wmcloud</small> |} ‎<includeonly></includeonly> {{AutoCat}} lwlmszcsn4hxgdygxtvnfn6eldqs6lh Le mouvement Wikimédia/Quatrième de couverture 0 79251 764770 764199 2026-04-24T09:17:51Z Lionel Scheepmans 20012 764770 wikitext text/x-wiki '''Le mouvement Wikimédia, une improbable aventure altruiste et mondiale, au service d'un savoir libre et fiable''' Quel est ce seul acteur à but non lucratif présent dans le top 100 des sites les plus visités sur le Web ? Comment incarne-t-il l’expression la plus visible des valeurs de liberté, d’équité et de partage, héritées de la révolution numérique et des mouvements sociaux des années 1960 ? Comment, à partir de Wikipédia et suite à la création d’une quinzaine de projets frères distribués en centaines de versions linguistiques, le mouvement social Wikimédia a imaginé un monde dans lequel le savoir se produit et se partage librement ? Et comment, en toute autonomie, des dizaines de projets pédagogiques, édités par des millions de bénévoles, soutenus par une fondation et près de 200 associations et groupes locaux, produisent-ils la plus grande intelligence collective au monde ? Avec de nombreux codes QR, cet ouvrage répond à ces questions, tout en permettant de mieux comprendre le monde global et numérique qui nous entoure. ---- [[Utilisateur:Lionel Scheepmans|Lionel Scheepmans]] est docteur en sciences politiques et sociales, militant de la culture libre et professeur d’anthropologie numérique. Il occupe plusieurs postes d’administrateur au sein du mouvement Wikimédia qu’il observe de manière participative depuis 2011. Ses travaux universitaires, du master à la thèse de doctorat, furent consacrés à l’organisation et aux enjeux de Wikipédia et du mouvement Wikimédia. {{AutoCat}}<br /><br /> {{Centrer|'''Accès aux formats de publications'''}} <br /> {| width="100%" style="margin: auto;" ![[Fichier:Code qr version complète Le mouvement Wikimédia.svg|lien=https://fr.wikibooks.org/wiki/Le_mouvement_Wikim%C3%A9dia|alt=|centré|sans_cadre|100x100px]] ![[Fichier:Code QR pdf Le mouvement Wikimédia.svg|lien=https://upload.wikimedia.org/wikipedia/commons/8/88/Le_mouvement_Wikim%C3%A9dia_%E2%80%94_Wikilivres.pdf|alt=|centré|sans_cadre|100x100px]] ![[Fichier:Code QR audio Le mouvement Wikimédia.svg|lien=https://upload.wikimedia.org/wikipedia/commons/1/15/Le_mouvement_Wikim%C3%A9dia.oga|alt=Audio|centré|sans_cadre|100x100px]] |- !Page web !PDF !Livre audio |} {{Autocat}} 73gna6i9fhw56x8pw15tuvj3k9rh5uq 764778 764770 2026-04-24T09:24:14Z Lionel Scheepmans 20012 764778 wikitext text/x-wiki '''Le mouvement Wikimédia, une improbable aventure altruiste et mondiale, au service d'un savoir libre et fiable''' Quel est ce seul acteur à but non lucratif présent dans le top 100 des sites les plus visités sur le Web ? Comment incarne-t-il l’expression la plus visible des valeurs de liberté, d’égalité et de partage, héritées de la révolution numérique et des mouvements sociaux des années 1960 ? Comment, à partir de Wikipédia et suite à la création d’une quinzaine de projets frères distribués en centaines de versions linguistiques, le mouvement social Wikimédia a imaginé un monde dans lequel le savoir se produit et se partage librement ? Et comment, en toute autonomie, des dizaines de projets pédagogiques, édités par des millions de bénévoles, soutenus par une fondation et près de 200 associations et groupes locaux, produisent-ils la plus grande intelligence collective au monde ? Avec de nombreux codes QR, cet ouvrage répond à ces questions, tout en permettant de mieux comprendre le monde global et numérique qui nous entoure. ---- [[Utilisateur:Lionel Scheepmans|Lionel Scheepmans]] est docteur en sciences politiques et sociales, militant de la culture libre et professeur d’anthropologie numérique. Il occupe plusieurs postes d’administrateur au sein du mouvement Wikimédia qu’il observe de manière participative depuis 2011. Ses travaux universitaires, du master à la thèse de doctorat, furent consacrés à l’organisation et aux enjeux de Wikipédia et du mouvement Wikimédia. {{AutoCat}}<br /><br /> {{Centrer|'''Accès aux formats de publications'''}} <br /> {| width="100%" style="margin: auto;" ![[Fichier:Code qr version complète Le mouvement Wikimédia.svg|lien=https://fr.wikibooks.org/wiki/Le_mouvement_Wikim%C3%A9dia|alt=|centré|sans_cadre|100x100px]] ![[Fichier:Code QR pdf Le mouvement Wikimédia.svg|lien=https://upload.wikimedia.org/wikipedia/commons/8/88/Le_mouvement_Wikim%C3%A9dia_%E2%80%94_Wikilivres.pdf|alt=|centré|sans_cadre|100x100px]] ![[Fichier:Code QR audio Le mouvement Wikimédia.svg|lien=https://upload.wikimedia.org/wikipedia/commons/1/15/Le_mouvement_Wikim%C3%A9dia.oga|alt=Audio|centré|sans_cadre|100x100px]] |- !Page web !PDF !Livre audio |} {{Autocat}} 053vhgf77xv77rymwua7gibiwxpg5m3 Le mouvement Wikimédia/L'utopie Wikimédia 0 79253 764791 764224 2026-04-24T10:01:22Z Lionel Scheepmans 20012 764791 wikitext text/x-wiki <noinclude>{{Le mouvement Wikimédia}}</noinclude> Au fil du temps, Wikipédia fut perçu comme une utopie en marche<ref>{{Article|langue=|prénom1=Christian|nom1=Vandendorpe|titre=Le phénomène Wikipédia: une utopie en marche|périodique=Le Débat|volume=148|numéro=1|éditeur=Gallimard|date=2008|issn=0246-2346|pages=17}}.</ref>, puis comme une utopie réalisée<ref>{{Ouvrage|prénom1=Théo|nom1=Henri|directeur1=|titre=Wikipédia : une utopie réalisée ?|lieu=Université de Poitier|date=juillet 2013|pages totales=98|lire en ligne=https://web.archive.org/web/20211103122912/https://www.seies.net/sites/theo/doc/HENRI_theo_-_master_1_-_memoire.pdf}}.</ref>, et finalement comme la dernière utopie collective du Web<ref>{{Lien web|langue=fr-FR|nom1=Dupont-Besnard|prénom1=Marcus|titre=Wikipédia, la dernière utopie collective du web ?|url=https://web.archive.org/web/20250516050814/https://www.numerama.com/tech/805447-wikipedia-la-derniere-utopie-collective-du-web.html|site=Numerama|date=2021-12-29|consulté le=2025-12-21}}</ref>. Mais qu'en est-il de l'ensemble du mouvement Wikimédia ? Pour nous aider à comprendre ce qui se passe dans la dimension numérique de ce mouvement, voici une métaphore qui décrit un quartier établi au sein de la ville « Espace Web ». Dans cette ville imaginaire, Internet représenterait le réseau routier, pendant que des serveurs informatiques feraient office de bâtiments, tandis que les pages web qu'ils hébergent représenteraient les différentes pièces habitables de ces édifices. Le quartier Wikimédia rassemblerait ainsi plus d’un millier de bâtiments. Au sein de ceux-ci et à l’exception de quelques lieux administratifs, chaque pièce peut être visitée gratuitement et même modifiée au niveau de son contenu. On peut dès lors y ajouter de nouvelles choses, telles que du texte, des photos, des vidéos ou des documents sonores, et changer ou supprimer ce qui a été créé ou modifié par d’autres. Tout cela, bien sûr, dans le but de rendre ces endroits plus esthétiques et plus authentiques et en tenant compte des différentes idées et des éventuelles oppositions de point de vue concernant les aménagements. Pour faciliter l'entente entre les personnes qui s'investissent dans ces changements, chaque pièce des bâtiments Wikimédia possède un espace annexe dédié à la discussion. Dans la plupart des bâtiments Wikimédia, une personne malintentionnée peut même faire disparaitre tout le contenu d'une pièce. Néanmoins, dans la seconde qui suit, un robot remettra tout en place, avant de transmettre un message concernant le traitement du vandalisme. Lorsqu'une action plus discrète n'est pas détectée par un robot, c'est alors quelqu'un qui surveille la pièce qui prendra le relais pour annuler les changements malveillants et contacter la personne responsable. En cas de multirécidive, celle-ci peut se voir privée de sa capacité de modifier les pièces, soit dans le bâtiment vandalisé, soit dans tout le quartier quand cela se justifie. Après discussion, cette sanction sera mise en application par un administrateur ou une administratrice bénévole, choisi ou choisie par l'ensemble des autres bénévoles qui prennent soin des bâtiments. [[Fichier:The Digital City, Riyadh 191957.jpg|vignette|<small>Figure 3. Photo de la ''Digital City'' de [[w:Riyad|Riyadh]] et son aspect visuel en lien avec la métaphore du quartier Wikimédia.</small>|300x300px]] On comprend donc que tout le monde peut enrichir, mais également surveiller et protéger les richesses partagées dans le quartier Wikimédia. Il suffit pour cela de rejoindre le mouvement en se créant un compte. Cela permet de profiter de nombreux outils, dont notamment un système qui envoie des notifications, dès qu'une pièce que l'on veut surveiller est modifiée. Pour créer ce compte, pas besoin de fournir une adresse ou un numéro de téléphone. Les seules informations personnelles indispensables au bon fonctionnement du quartier Wikimédia sont les [[w:Adresses_IP|adresses IP]] des visiteurs. Lors des visites et contrairement à ce qui se passe dans les quartiers commerciaux de la grande ville numérique, tels que les [[w:GAFAM|GAFAM]], [[w:NATU_(Netflix,_Airbnb,_Tesla_et_Uber)|NATU]], [[w:BATX|BATX]], le quartier Wikimédia ne récolte et ne vend aucune donnée à des fins d'exploitation. Même les adresses IP enregistrées par le système ne sont pas visibles par les autres visiteurs. Elles sont remplacées par les noms et les pseudonymes fournis lors de la création des comptes, ou masquées par des comptes temporaires pour les modifications faites par des personnes non connectées. Seules quelques personnes accréditées par la communauté pour effectuer des contrôles d’usurpation d’identité ont accès à ces informations<ref>{{Lien web|auteur=MédiaWiki|titre=Produit de confiance et de sécurité/Comptes temporaires|url=https://web.archive.org/web/20250813140004/https://www.mediawiki.org/wiki/Trust_and_Safety_Product/Temporary_Accounts/fr}}.</ref>. C’est là une précaution nécessaire au bon déroulement des votes qui succèdent parfois aux recherches de [[w:Consensus|consensus]] concernant l'aménagement du quartier Wikimédia. Dans cette ville numérique que constituerait l'espace web, Wikimédia apparait ainsi comme le plus grand quartier dédié au partage de la connaissance. Tout d'abord, il y a les plus de 350 bâtiments [[w:Wikipédia:Accueil_principal|Wikipédia]], chacun dédié à une version linguistique de l'encyclopédie. Puis, toujours séparés en versions linguistiques, on trouve ensuite les bibliothèques [[:en:fr:accueil|Wikilivres]] et [[s:fr:Wikisource:Accueil|Wikisource]], les bâtiments lexicaux multilingues [[wikt:fr:Wiktionnaire:Page_d’accueil|Wiktionnaire]], le centre journalistique [[n:fr:accueil|Wikinews]], le centre pédagogique et de recherche [[v:fr:accueil|Wikiversité]], le centre d'informations touristique [[voy:fr:accueil|Wikivoyage]], le répertoire des êtres vivants [[species:main page|Wikispecies]] et enfin l'institut des citations d’auteurs [[q:fr:accueil|Wikiquote]]. Cela sans oublier le musée médiatique [[commons:main page|Wikimedia Commons]] et la banque [[wikidata:wikidata:main_page|Wikidata]], reconnue comme étant la plus grande banque d’informations structurées au monde. Deux bâtiments dont l'une des fonctions principales communes est d’enrichir les pièces situées dans les autres buildings du quartier Wikimédia. Dans tous ces immeubles, il arrive souvent que plus de la moitié des étages soient uniquement attribués à l'organisation des activités qui s'y déroulent. Chaque bâtiment peut aussi compter sur le soutien d'autres édifices tels que [[mw:main page|MediaWiki]], [[wikitech:Main_Page|Wikitech]], [[w:fr:phabricator|Phabricator]], qui sont trois lieux entièrement dédiés aux maintenances techniques sur l'ensemble du quartier. Concernant les aspects administratifs, c'est dans le bâtiment [[metawiki:main page|Méta-Wiki]] que s'opère la gouvernance générale du quartier, alors que les courriers adressés à ce dernier sont traités en première ligne dans le bâtiment [[otrswiki:Main page|Wikimedia VRT]]. À la suite de quoi, il ne reste plus qu'à citer le bâtiment ''[[outreach:main page|Wikimedia outreach]],'' pour des initiatives de sensibilisation, et le bâtiment du journal [https://diff.wikimedia.org Diff Wikimedia], comme lieu de publication d'actualités sur le mouvement. En dehors de certains aspects techniques, tous ces bâtiments sont exclusivement régis par des communautés bénévoles, qui sont toujours prêtes à accueillir de nouveaux membres. Les seuls immeubles du quartier qui diffèrent de ce principe sont les bâtiments vitrines de la Fondation Wikimédia et des autres associations Wikimédia qui engagent du personnel. Quant au bâtiment du conseil d'administration de la Fondation, des raisons officielles justifient le fait que la modification de ses pièces est réservée à ces membres et aux employés qui les soutiennent. Face à tant d'utopies, il devient légitime de vouloir comprendre comment tout cela fut rendu possible. Or, pour répondre à cette question, il faut alors parcourir tout un pan de l'histoire de la révolution numérique, depuis la [[w:Contre-culture_des_années_1960|contre-culture des années 1960]] jusqu'à nos jours. On y découvre que les pionniers du réseau Internet étaient des chercheurs et étudiants en informatique, fortement influencés par de nouvelles idéologies, telles que celles qui furent à la source des évènements de [[w:mai_68|mai 68]] en France. C'est donc de là que naîtra la philosophie de partage, de liberté, de décentralisation et ce mode d’organisation tout à fait spécifique, que l'on observe aujourd'hui au sein du mouvement Wikimédia. Il y eut tout d'abord la création d'[[w:Internet|Internet]], comme réseau mondial de communication en libre accès, puis le développement du ''[[w:World_Wide_Web|World Wide Web]]'', qui a grandement facilité les interactions humaines à l’échelle planétaire. Par la suite, ce fut l'arrivée du [[w:Web_2.0|Web 2.0]], caractérisé par l'apparition de nouveaux sites web directement modifiables à l'aide d’un simple navigateur. Or, parmi ceux-ci se trouvent les [[w:fr:Moteur de Wiki|moteurs de Wiki]], dont le plus puissant d’entre eux, [[w:MediaWiki|MediaWiki]], est un [[Logiciels libres|logiciel libre]] développé par la Fondation Wikimédia. Le moment est donc venu d'en savoir plus sur ce type de programme informatique, ainsi que sur le [[w:Mouvement_du_logiciel_libre|mouvement du logiciel libre]], qui a fortement influencé la philosophie et les valeurs véhiculées au sein du mouvement Wikimédia.{{AutoCat}} sqpvikykzhoe74cjvwek1q7jv1hpe4i 764792 764791 2026-04-24T10:05:59Z Lionel Scheepmans 20012 764792 wikitext text/x-wiki <noinclude>{{Le mouvement Wikimédia}}</noinclude> Au fil du temps, Wikipédia fut perçu comme une utopie en marche<ref>{{Article|langue=|prénom1=Christian|nom1=Vandendorpe|titre=Le phénomène Wikipédia: une utopie en marche|périodique=Le Débat|volume=148|numéro=1|éditeur=Gallimard|date=2008|issn=0246-2346|pages=17}}.</ref>, puis comme une utopie réalisée<ref>{{Ouvrage|prénom1=Théo|nom1=Henri|directeur1=|titre=Wikipédia : une utopie réalisée ?|lieu=Université de Poitier|date=juillet 2013|pages totales=98|lire en ligne=https://web.archive.org/web/20211103122912/https://www.seies.net/sites/theo/doc/HENRI_theo_-_master_1_-_memoire.pdf}}.</ref>, et finalement comme la dernière utopie collective du Web<ref>{{Lien web|langue=fr-FR|nom1=Dupont-Besnard|prénom1=Marcus|titre=Wikipédia, la dernière utopie collective du web ?|url=https://web.archive.org/web/20250516050814/https://www.numerama.com/tech/805447-wikipedia-la-derniere-utopie-collective-du-web.html|site=Numerama|date=2021-12-29|consulté le=2025-12-21}}</ref>. Mais qu'en est-il de l'ensemble du mouvement Wikimédia ? Pour nous aider à comprendre ce qui se passe dans la dimension numérique de ce mouvement, voici une métaphore qui décrit un quartier établi au sein de la ville « Espace Web ». Dans cette ville imaginaire, Internet représenterait le réseau routier, pendant que des serveurs informatiques feraient office de bâtiments, et que les pages web qu'ils hébergent, constitueraient les différentess pièces de ces édifices. Le quartier Wikimédia rassemblerait ainsi plus d’un millier de bâtiments. Au sein de ceux-ci et à l’exception de quelques lieux administratifs, chaque pièce peut être visitée gratuitement et même modifiée au niveau de son contenu. On peut dès lors y ajouter de nouvelles choses, telles que du texte, des photos, des vidéos ou des documents sonores, et changer ou supprimer ce qui a été créé ou modifié par d’autres. Tout cela, bien sûr, dans le but de rendre ces endroits plus esthétiques et plus authentiques et en tenant compte des différentes idées et des éventuelles oppositions de point de vue concernant les aménagements. Pour faciliter l'entente entre les personnes qui s'investissent dans ces changements, chaque pièce des bâtiments Wikimédia possède un espace annexe dédié à la discussion. Dans la plupart des bâtiments Wikimédia, une personne malintentionnée peut même faire disparaitre tout le contenu d'une pièce. Néanmoins, dans la seconde qui suit, un robot remettra tout en place, avant de transmettre un message concernant le traitement du vandalisme. Lorsqu'une action plus discrète n'est pas détectée par un robot, c'est alors quelqu'un qui surveille la pièce qui prendra le relais pour annuler les changements malveillants et contacter la personne responsable. En cas de multirécidive, celle-ci peut se voir privée de sa capacité de modifier les pièces, soit dans le bâtiment vandalisé, soit dans tout le quartier quand cela se justifie. Après discussion, cette sanction sera mise en application par un administrateur ou une administratrice bénévole, choisi ou choisie par l'ensemble des autres bénévoles qui prennent soin des bâtiments. [[Fichier:The Digital City, Riyadh 191957.jpg|vignette|<small>Figure 3. Photo de la ''Digital City'' de [[w:Riyad|Riyadh]] et son aspect visuel en lien avec la métaphore du quartier Wikimédia.</small>|300x300px]] On comprend donc que tout le monde peut enrichir, mais également surveiller et protéger les richesses partagées dans le quartier Wikimédia. Il suffit pour cela de rejoindre le mouvement en se créant un compte. Cela permet de profiter de nombreux outils, dont notamment un système qui envoie des notifications, dès qu'une pièce que l'on veut surveiller est modifiée. Pour créer ce compte, pas besoin de fournir une adresse ou un numéro de téléphone. Les seules informations personnelles indispensables au bon fonctionnement du quartier Wikimédia sont les [[w:Adresses_IP|adresses IP]] des visiteurs. Lors des visites et contrairement à ce qui se passe dans les quartiers commerciaux de la grande ville numérique, tels que les [[w:GAFAM|GAFAM]], [[w:NATU_(Netflix,_Airbnb,_Tesla_et_Uber)|NATU]], [[w:BATX|BATX]], le quartier Wikimédia ne récolte et ne vend aucune donnée à des fins d'exploitation. Même les adresses IP enregistrées par le système ne sont pas visibles par les autres visiteurs. Elles sont remplacées par les noms et les pseudonymes fournis lors de la création des comptes, ou masquées par des comptes temporaires pour les modifications faites par des personnes non connectées. Seules quelques personnes accréditées par la communauté pour effectuer des contrôles d’usurpation d’identité ont accès à ces informations<ref>{{Lien web|auteur=MédiaWiki|titre=Produit de confiance et de sécurité/Comptes temporaires|url=https://web.archive.org/web/20250813140004/https://www.mediawiki.org/wiki/Trust_and_Safety_Product/Temporary_Accounts/fr}}.</ref>. C’est là une précaution nécessaire au bon déroulement des votes qui succèdent parfois aux recherches de [[w:Consensus|consensus]] concernant l'aménagement du quartier Wikimédia. Dans cette ville numérique que constituerait l'espace web, Wikimédia apparait ainsi comme le plus grand quartier dédié au partage de la connaissance. Tout d'abord, il y a les plus de 350 bâtiments [[w:Wikipédia:Accueil_principal|Wikipédia]], chacun dédié à une version linguistique de l'encyclopédie. Puis, toujours séparés en versions linguistiques, on trouve ensuite les bibliothèques [[:en:fr:accueil|Wikilivres]] et [[s:fr:Wikisource:Accueil|Wikisource]], les bâtiments lexicaux multilingues [[wikt:fr:Wiktionnaire:Page_d’accueil|Wiktionnaire]], le centre journalistique [[n:fr:accueil|Wikinews]], le centre pédagogique et de recherche [[v:fr:accueil|Wikiversité]], le centre d'informations touristique [[voy:fr:accueil|Wikivoyage]], le répertoire des êtres vivants [[species:main page|Wikispecies]] et enfin l'institut des citations d’auteurs [[q:fr:accueil|Wikiquote]]. Cela sans oublier le musée médiatique [[commons:main page|Wikimedia Commons]] et la banque [[wikidata:wikidata:main_page|Wikidata]], reconnue comme étant la plus grande banque d’informations structurées au monde. Deux bâtiments dont l'une des fonctions principales communes est d’enrichir les pièces situées dans les autres buildings du quartier Wikimédia. Dans tous ces immeubles, il arrive souvent que plus de la moitié des étages soient uniquement attribués à l'organisation des activités qui s'y déroulent. Chaque bâtiment peut aussi compter sur le soutien d'autres édifices tels que [[mw:main page|MediaWiki]], [[wikitech:Main_Page|Wikitech]], [[w:fr:phabricator|Phabricator]], qui sont trois lieux entièrement dédiés aux maintenances techniques sur l'ensemble du quartier. Concernant les aspects administratifs, c'est dans le bâtiment [[metawiki:main page|Méta-Wiki]] que s'opère la gouvernance générale du quartier, alors que les courriers adressés à ce dernier sont traités en première ligne dans le bâtiment [[otrswiki:Main page|Wikimedia VRT]]. À la suite de quoi, il ne reste plus qu'à citer le bâtiment ''[[outreach:main page|Wikimedia outreach]],'' pour des initiatives de sensibilisation, et le bâtiment du journal [https://diff.wikimedia.org Diff Wikimedia], comme lieu de publication d'actualités sur le mouvement. En dehors de certains aspects techniques, tous ces bâtiments sont exclusivement régis par des communautés bénévoles, qui sont toujours prêtes à accueillir de nouveaux membres. Les seuls immeubles du quartier qui diffèrent de ce principe sont les bâtiments vitrines de la Fondation Wikimédia et des autres associations Wikimédia qui engagent du personnel. Quant au bâtiment du conseil d'administration de la Fondation, des raisons officielles justifient le fait que la modification de ses pièces est réservée à ces membres et aux employés qui les soutiennent. Face à tant d'utopies, il devient légitime de vouloir comprendre comment tout cela fut rendu possible. Or, pour répondre à cette question, il faut alors parcourir tout un pan de l'histoire de la révolution numérique, depuis la [[w:Contre-culture_des_années_1960|contre-culture des années 1960]] jusqu'à nos jours. On y découvre que les pionniers du réseau Internet étaient des chercheurs et étudiants en informatique, fortement influencés par de nouvelles idéologies, telles que celles qui furent à la source des évènements de [[w:mai_68|mai 68]] en France. C'est donc de là que naîtra la philosophie de partage, de liberté, de décentralisation et ce mode d’organisation tout à fait spécifique, que l'on observe aujourd'hui au sein du mouvement Wikimédia. Il y eut tout d'abord la création d'[[w:Internet|Internet]], comme réseau mondial de communication en libre accès, puis le développement du ''[[w:World_Wide_Web|World Wide Web]]'', qui a grandement facilité les interactions humaines à l’échelle planétaire. Par la suite, ce fut l'arrivée du [[w:Web_2.0|Web 2.0]], caractérisé par l'apparition de nouveaux sites web directement modifiables à l'aide d’un simple navigateur. Or, parmi ceux-ci se trouvent les [[w:fr:Moteur de Wiki|moteurs de Wiki]], dont le plus puissant d’entre eux, [[w:MediaWiki|MediaWiki]], est un [[Logiciels libres|logiciel libre]] développé par la Fondation Wikimédia. Le moment est donc venu d'en savoir plus sur ce type de programme informatique, ainsi que sur le [[w:Mouvement_du_logiciel_libre|mouvement du logiciel libre]], qui a fortement influencé la philosophie et les valeurs véhiculées au sein du mouvement Wikimédia.{{AutoCat}} feg9yqgxm9zgnrnqon2cjd53rk7kai1 764793 764792 2026-04-24T10:09:32Z Lionel Scheepmans 20012 764793 wikitext text/x-wiki <noinclude>{{Le mouvement Wikimédia}}</noinclude> Au fil du temps, Wikipédia fut perçu comme une utopie en marche<ref>{{Article|langue=|prénom1=Christian|nom1=Vandendorpe|titre=Le phénomène Wikipédia: une utopie en marche|périodique=Le Débat|volume=148|numéro=1|éditeur=Gallimard|date=2008|issn=0246-2346|pages=17}}.</ref>, puis comme une utopie réalisée<ref>{{Ouvrage|prénom1=Théo|nom1=Henri|directeur1=|titre=Wikipédia : une utopie réalisée ?|lieu=Université de Poitier|date=juillet 2013|pages totales=98|lire en ligne=https://web.archive.org/web/20211103122912/https://www.seies.net/sites/theo/doc/HENRI_theo_-_master_1_-_memoire.pdf}}.</ref>, et finalement comme la dernière utopie collective du Web<ref>{{Lien web|langue=fr-FR|nom1=Dupont-Besnard|prénom1=Marcus|titre=Wikipédia, la dernière utopie collective du web ?|url=https://web.archive.org/web/20250516050814/https://www.numerama.com/tech/805447-wikipedia-la-derniere-utopie-collective-du-web.html|site=Numerama|date=2021-12-29|consulté le=2025-12-21}}</ref>. Mais qu'en est-il de l'ensemble du mouvement Wikimédia ? Pour nous aider à comprendre ce qui se passe dans la dimension numérique de ce mouvement, voici une métaphore qui décrit un quartier établi au sein d'une ville, imaginée au départ de l'espace web ». Dans cette ville imaginaire, Internet représenterait le réseau routier, pendant que des serveurs informatiques feraient office de bâtiments, et que les pages web qu'ils hébergent, constitueraient les différentess pièces de ces édifices. Le quartier Wikimédia rassemblerait ainsi plus d’un millier de bâtiments. Au sein de ceux-ci et à l’exception de quelques lieux administratifs, chaque pièce peut être visitée gratuitement et même modifiée au niveau de son contenu. On peut dès lors y ajouter de nouvelles choses, telles que du texte, des photos, des vidéos ou des documents sonores, et changer ou supprimer ce qui a été créé ou modifié par d’autres. Tout cela, bien sûr, dans le but de rendre ces endroits plus esthétiques et plus authentiques et en tenant compte des différentes idées et des éventuelles oppositions de point de vue concernant les aménagements. Pour faciliter l'entente entre les personnes qui s'investissent dans ces changements, chaque pièce des bâtiments Wikimédia possède un espace annexe dédié à la discussion. Dans la plupart des bâtiments Wikimédia, une personne malintentionnée peut même faire disparaitre tout le contenu d'une pièce. Néanmoins, dans la seconde qui suit, un robot remettra tout en place, avant de transmettre un message concernant le traitement du vandalisme. Lorsqu'une action plus discrète n'est pas détectée par un robot, c'est alors quelqu'un qui surveille la pièce qui prendra le relais pour annuler les changements malveillants et contacter la personne responsable. En cas de multirécidive, celle-ci peut se voir privée de sa capacité de modifier les pièces, soit dans le bâtiment vandalisé, soit dans tout le quartier quand cela se justifie. Après discussion, cette sanction sera mise en application par un administrateur ou une administratrice bénévole, choisi ou choisie par l'ensemble des autres bénévoles qui prennent soin des bâtiments. [[Fichier:The Digital City, Riyadh 191957.jpg|vignette|<small>Figure 3. Photo de la ''Digital City'' de [[w:Riyad|Riyadh]] et son aspect visuel en lien avec la métaphore du quartier Wikimédia.</small>|300x300px]] On comprend donc que tout le monde peut enrichir, mais également surveiller et protéger les richesses partagées dans le quartier Wikimédia. Il suffit pour cela de rejoindre le mouvement en se créant un compte. Cela permet de profiter de nombreux outils, dont notamment un système qui envoie des notifications, dès qu'une pièce que l'on veut surveiller est modifiée. Pour créer ce compte, pas besoin de fournir une adresse ou un numéro de téléphone. Les seules informations personnelles indispensables au bon fonctionnement du quartier Wikimédia sont les [[w:Adresses_IP|adresses IP]] des visiteurs. Lors des visites et contrairement à ce qui se passe dans les quartiers commerciaux de la grande ville numérique, tels que les [[w:GAFAM|GAFAM]], [[w:NATU_(Netflix,_Airbnb,_Tesla_et_Uber)|NATU]], [[w:BATX|BATX]], le quartier Wikimédia ne récolte et ne vend aucune donnée à des fins d'exploitation. Même les adresses IP enregistrées par le système ne sont pas visibles par les autres visiteurs. Elles sont remplacées par les noms et les pseudonymes fournis lors de la création des comptes, ou masquées par des comptes temporaires pour les modifications faites par des personnes non connectées. Seules quelques personnes accréditées par la communauté pour effectuer des contrôles d’usurpation d’identité ont accès à ces informations<ref>{{Lien web|auteur=MédiaWiki|titre=Produit de confiance et de sécurité/Comptes temporaires|url=https://web.archive.org/web/20250813140004/https://www.mediawiki.org/wiki/Trust_and_Safety_Product/Temporary_Accounts/fr}}.</ref>. C’est là une précaution nécessaire au bon déroulement des votes qui succèdent parfois aux recherches de [[w:Consensus|consensus]] concernant l'aménagement du quartier Wikimédia. Dans cette ville numérique que constituerait l'espace web, Wikimédia apparait ainsi comme le plus grand quartier dédié au partage de la connaissance. Tout d'abord, il y a les plus de 350 bâtiments [[w:Wikipédia:Accueil_principal|Wikipédia]], chacun dédié à une version linguistique de l'encyclopédie. Puis, toujours séparés en versions linguistiques, on trouve ensuite les bibliothèques [[:en:fr:accueil|Wikilivres]] et [[s:fr:Wikisource:Accueil|Wikisource]], les bâtiments lexicaux multilingues [[wikt:fr:Wiktionnaire:Page_d’accueil|Wiktionnaire]], le centre journalistique [[n:fr:accueil|Wikinews]], le centre pédagogique et de recherche [[v:fr:accueil|Wikiversité]], le centre d'informations touristique [[voy:fr:accueil|Wikivoyage]], le répertoire des êtres vivants [[species:main page|Wikispecies]] et enfin l'institut des citations d’auteurs [[q:fr:accueil|Wikiquote]]. Cela sans oublier le musée médiatique [[commons:main page|Wikimedia Commons]] et la banque [[wikidata:wikidata:main_page|Wikidata]], reconnue comme étant la plus grande banque d’informations structurées au monde. Deux bâtiments dont l'une des fonctions principales communes est d’enrichir les pièces situées dans les autres buildings du quartier Wikimédia. Dans tous ces immeubles, il arrive souvent que plus de la moitié des étages soient uniquement attribués à l'organisation des activités qui s'y déroulent. Chaque bâtiment peut aussi compter sur le soutien d'autres édifices tels que [[mw:main page|MediaWiki]], [[wikitech:Main_Page|Wikitech]], [[w:fr:phabricator|Phabricator]], qui sont trois lieux entièrement dédiés aux maintenances techniques sur l'ensemble du quartier. Concernant les aspects administratifs, c'est dans le bâtiment [[metawiki:main page|Méta-Wiki]] que s'opère la gouvernance générale du quartier, alors que les courriers adressés à ce dernier sont traités en première ligne dans le bâtiment [[otrswiki:Main page|Wikimedia VRT]]. À la suite de quoi, il ne reste plus qu'à citer le bâtiment ''[[outreach:main page|Wikimedia outreach]],'' pour des initiatives de sensibilisation, et le bâtiment du journal [https://diff.wikimedia.org Diff Wikimedia], comme lieu de publication d'actualités sur le mouvement. En dehors de certains aspects techniques, tous ces bâtiments sont exclusivement régis par des communautés bénévoles, qui sont toujours prêtes à accueillir de nouveaux membres. Les seuls immeubles du quartier qui diffèrent de ce principe sont les bâtiments vitrines de la Fondation Wikimédia et des autres associations Wikimédia qui engagent du personnel. Quant au bâtiment du conseil d'administration de la Fondation, des raisons officielles justifient le fait que la modification de ses pièces est réservée à ces membres et aux employés qui les soutiennent. Face à tant d'utopies, il devient légitime de vouloir comprendre comment tout cela fut rendu possible. Or, pour répondre à cette question, il faut alors parcourir tout un pan de l'histoire de la révolution numérique, depuis la [[w:Contre-culture_des_années_1960|contre-culture des années 1960]] jusqu'à nos jours. On y découvre que les pionniers du réseau Internet étaient des chercheurs et étudiants en informatique, fortement influencés par de nouvelles idéologies, telles que celles qui furent à la source des évènements de [[w:mai_68|mai 68]] en France. C'est donc de là que naîtra la philosophie de partage, de liberté, de décentralisation et ce mode d’organisation tout à fait spécifique, que l'on observe aujourd'hui au sein du mouvement Wikimédia. Il y eut tout d'abord la création d'[[w:Internet|Internet]], comme réseau mondial de communication en libre accès, puis le développement du ''[[w:World_Wide_Web|World Wide Web]]'', qui a grandement facilité les interactions humaines à l’échelle planétaire. Par la suite, ce fut l'arrivée du [[w:Web_2.0|Web 2.0]], caractérisé par l'apparition de nouveaux sites web directement modifiables à l'aide d’un simple navigateur. Or, parmi ceux-ci se trouvent les [[w:fr:Moteur de Wiki|moteurs de Wiki]], dont le plus puissant d’entre eux, [[w:MediaWiki|MediaWiki]], est un [[Logiciels libres|logiciel libre]] développé par la Fondation Wikimédia. Le moment est donc venu d'en savoir plus sur ce type de programme informatique, ainsi que sur le [[w:Mouvement_du_logiciel_libre|mouvement du logiciel libre]], qui a fortement influencé la philosophie et les valeurs véhiculées au sein du mouvement Wikimédia.{{AutoCat}} tutrpsexr9f3fkyogmpyc9nm0e0x2mf 764794 764793 2026-04-24T10:10:27Z Lionel Scheepmans 20012 764794 wikitext text/x-wiki <noinclude>{{Le mouvement Wikimédia}}</noinclude> Au fil du temps, Wikipédia fut perçu comme une utopie en marche<ref>{{Article|langue=|prénom1=Christian|nom1=Vandendorpe|titre=Le phénomène Wikipédia: une utopie en marche|périodique=Le Débat|volume=148|numéro=1|éditeur=Gallimard|date=2008|issn=0246-2346|pages=17}}.</ref>, puis comme une utopie réalisée<ref>{{Ouvrage|prénom1=Théo|nom1=Henri|directeur1=|titre=Wikipédia : une utopie réalisée ?|lieu=Université de Poitier|date=juillet 2013|pages totales=98|lire en ligne=https://web.archive.org/web/20211103122912/https://www.seies.net/sites/theo/doc/HENRI_theo_-_master_1_-_memoire.pdf}}.</ref>, et finalement comme la dernière utopie collective du Web<ref>{{Lien web|langue=fr-FR|nom1=Dupont-Besnard|prénom1=Marcus|titre=Wikipédia, la dernière utopie collective du web ?|url=https://web.archive.org/web/20250516050814/https://www.numerama.com/tech/805447-wikipedia-la-derniere-utopie-collective-du-web.html|site=Numerama|date=2021-12-29|consulté le=2025-12-21}}</ref>. Mais qu'en est-il de l'ensemble du mouvement Wikimédia ? Pour nous aider à comprendre ce qui se passe dans la dimension numérique de ce mouvement, voici une métaphore qui décrit un quartier établi au sein d'une ville, imaginée au départ de l'espace web ». Dans cette ville imaginaire, Internet représenterait le réseau routier, pendant que des serveurs informatiques feraient office de bâtiments, et que les pages web qu'ils hébergent, constitueraient les différentes pièces de ces édifices. Le quartier Wikimédia rassemblerait ainsi plus d’un millier de bâtiments. Au sein de ceux-ci et à l’exception de quelques lieux administratifs, chaque pièce peut être visitée gratuitement et même modifiée au niveau de son contenu. On peut dès lors y ajouter de nouvelles choses, telles que du texte, des photos, des vidéos ou des documents sonores, et changer ou supprimer ce qui a été créé ou modifié par d’autres. Tout cela, bien sûr, dans le but de rendre ces endroits plus esthétiques et plus authentiques et en tenant compte des différentes idées et des éventuelles oppositions de point de vue concernant les aménagements. Pour faciliter l'entente entre les personnes qui s'investissent dans ces changements, chaque pièce des bâtiments Wikimédia possède un espace annexe dédié à la discussion. Dans la plupart des bâtiments Wikimédia, une personne malintentionnée peut même faire disparaitre tout le contenu d'une pièce. Néanmoins, dans la seconde qui suit, un robot remettra tout en place, avant de transmettre un message concernant le traitement du vandalisme. Lorsqu'une action plus discrète n'est pas détectée par un robot, c'est alors quelqu'un qui surveille la pièce qui prendra le relais pour annuler les changements malveillants et contacter la personne responsable. En cas de multirécidive, celle-ci peut se voir privée de sa capacité de modifier les pièces, soit dans le bâtiment vandalisé, soit dans tout le quartier quand cela se justifie. Après discussion, cette sanction sera mise en application par un administrateur ou une administratrice bénévole, choisi ou choisie par l'ensemble des autres bénévoles qui prennent soin des bâtiments. [[Fichier:The Digital City, Riyadh 191957.jpg|vignette|<small>Figure 3. Photo de la ''Digital City'' de [[w:Riyad|Riyadh]] et son aspect visuel en lien avec la métaphore du quartier Wikimédia.</small>|300x300px]] On comprend donc que tout le monde peut enrichir, mais également surveiller et protéger les richesses partagées dans le quartier Wikimédia. Il suffit pour cela de rejoindre le mouvement en se créant un compte. Cela permet de profiter de nombreux outils, dont notamment un système qui envoie des notifications, dès qu'une pièce que l'on veut surveiller est modifiée. Pour créer ce compte, pas besoin de fournir une adresse ou un numéro de téléphone. Les seules informations personnelles indispensables au bon fonctionnement du quartier Wikimédia sont les [[w:Adresses_IP|adresses IP]] des visiteurs. Lors des visites et contrairement à ce qui se passe dans les quartiers commerciaux de la grande ville numérique, tels que les [[w:GAFAM|GAFAM]], [[w:NATU_(Netflix,_Airbnb,_Tesla_et_Uber)|NATU]], [[w:BATX|BATX]], le quartier Wikimédia ne récolte et ne vend aucune donnée à des fins d'exploitation. Même les adresses IP enregistrées par le système ne sont pas visibles par les autres visiteurs. Elles sont remplacées par les noms et les pseudonymes fournis lors de la création des comptes, ou masquées par des comptes temporaires pour les modifications faites par des personnes non connectées. Seules quelques personnes accréditées par la communauté pour effectuer des contrôles d’usurpation d’identité ont accès à ces informations<ref>{{Lien web|auteur=MédiaWiki|titre=Produit de confiance et de sécurité/Comptes temporaires|url=https://web.archive.org/web/20250813140004/https://www.mediawiki.org/wiki/Trust_and_Safety_Product/Temporary_Accounts/fr}}.</ref>. C’est là une précaution nécessaire au bon déroulement des votes qui succèdent parfois aux recherches de [[w:Consensus|consensus]] concernant l'aménagement du quartier Wikimédia. Dans cette ville numérique que constituerait l'espace web, Wikimédia apparait ainsi comme le plus grand quartier dédié au partage de la connaissance. Tout d'abord, il y a les plus de 350 bâtiments [[w:Wikipédia:Accueil_principal|Wikipédia]], chacun dédié à une version linguistique de l'encyclopédie. Puis, toujours séparés en versions linguistiques, on trouve ensuite les bibliothèques [[:en:fr:accueil|Wikilivres]] et [[s:fr:Wikisource:Accueil|Wikisource]], les bâtiments lexicaux multilingues [[wikt:fr:Wiktionnaire:Page_d’accueil|Wiktionnaire]], le centre journalistique [[n:fr:accueil|Wikinews]], le centre pédagogique et de recherche [[v:fr:accueil|Wikiversité]], le centre d'informations touristique [[voy:fr:accueil|Wikivoyage]], le répertoire des êtres vivants [[species:main page|Wikispecies]] et enfin l'institut des citations d’auteurs [[q:fr:accueil|Wikiquote]]. Cela sans oublier le musée médiatique [[commons:main page|Wikimedia Commons]] et la banque [[wikidata:wikidata:main_page|Wikidata]], reconnue comme étant la plus grande banque d’informations structurées au monde. Deux bâtiments dont l'une des fonctions principales communes est d’enrichir les pièces situées dans les autres buildings du quartier Wikimédia. Dans tous ces immeubles, il arrive souvent que plus de la moitié des étages soient uniquement attribués à l'organisation des activités qui s'y déroulent. Chaque bâtiment peut aussi compter sur le soutien d'autres édifices tels que [[mw:main page|MediaWiki]], [[wikitech:Main_Page|Wikitech]], [[w:fr:phabricator|Phabricator]], qui sont trois lieux entièrement dédiés aux maintenances techniques sur l'ensemble du quartier. Concernant les aspects administratifs, c'est dans le bâtiment [[metawiki:main page|Méta-Wiki]] que s'opère la gouvernance générale du quartier, alors que les courriers adressés à ce dernier sont traités en première ligne dans le bâtiment [[otrswiki:Main page|Wikimedia VRT]]. À la suite de quoi, il ne reste plus qu'à citer le bâtiment ''[[outreach:main page|Wikimedia outreach]],'' pour des initiatives de sensibilisation, et le bâtiment du journal [https://diff.wikimedia.org Diff Wikimedia], comme lieu de publication d'actualités sur le mouvement. En dehors de certains aspects techniques, tous ces bâtiments sont exclusivement régis par des communautés bénévoles, qui sont toujours prêtes à accueillir de nouveaux membres. Les seuls immeubles du quartier qui diffèrent de ce principe sont les bâtiments vitrines de la Fondation Wikimédia et des autres associations Wikimédia qui engagent du personnel. Quant au bâtiment du conseil d'administration de la Fondation, des raisons officielles justifient le fait que la modification de ses pièces est réservée à ces membres et aux employés qui les soutiennent. Face à tant d'utopies, il devient légitime de vouloir comprendre comment tout cela fut rendu possible. Or, pour répondre à cette question, il faut alors parcourir tout un pan de l'histoire de la révolution numérique, depuis la [[w:Contre-culture_des_années_1960|contre-culture des années 1960]] jusqu'à nos jours. On y découvre que les pionniers du réseau Internet étaient des chercheurs et étudiants en informatique, fortement influencés par de nouvelles idéologies, telles que celles qui furent à la source des évènements de [[w:mai_68|mai 68]] en France. C'est donc de là que naîtra la philosophie de partage, de liberté, de décentralisation et ce mode d’organisation tout à fait spécifique, que l'on observe aujourd'hui au sein du mouvement Wikimédia. Il y eut tout d'abord la création d'[[w:Internet|Internet]], comme réseau mondial de communication en libre accès, puis le développement du ''[[w:World_Wide_Web|World Wide Web]]'', qui a grandement facilité les interactions humaines à l’échelle planétaire. Par la suite, ce fut l'arrivée du [[w:Web_2.0|Web 2.0]], caractérisé par l'apparition de nouveaux sites web directement modifiables à l'aide d’un simple navigateur. Or, parmi ceux-ci se trouvent les [[w:fr:Moteur de Wiki|moteurs de Wiki]], dont le plus puissant d’entre eux, [[w:MediaWiki|MediaWiki]], est un [[Logiciels libres|logiciel libre]] développé par la Fondation Wikimédia. Le moment est donc venu d'en savoir plus sur ce type de programme informatique, ainsi que sur le [[w:Mouvement_du_logiciel_libre|mouvement du logiciel libre]], qui a fortement influencé la philosophie et les valeurs véhiculées au sein du mouvement Wikimédia.{{AutoCat}} 2yn23wtcthyet0s0qvcdqqsoc7fg4de 764795 764794 2026-04-24T10:11:54Z Lionel Scheepmans 20012 764795 wikitext text/x-wiki <noinclude>{{Le mouvement Wikimédia}}</noinclude> Au fil du temps, Wikipédia fut perçu comme une utopie en marche<ref>{{Article|langue=|prénom1=Christian|nom1=Vandendorpe|titre=Le phénomène Wikipédia: une utopie en marche|périodique=Le Débat|volume=148|numéro=1|éditeur=Gallimard|date=2008|issn=0246-2346|pages=17}}.</ref>, puis comme une utopie réalisée<ref>{{Ouvrage|prénom1=Théo|nom1=Henri|directeur1=|titre=Wikipédia : une utopie réalisée ?|lieu=Université de Poitier|date=juillet 2013|pages totales=98|lire en ligne=https://web.archive.org/web/20211103122912/https://www.seies.net/sites/theo/doc/HENRI_theo_-_master_1_-_memoire.pdf}}.</ref>, et finalement comme la dernière utopie collective du Web<ref>{{Lien web|langue=fr-FR|nom1=Dupont-Besnard|prénom1=Marcus|titre=Wikipédia, la dernière utopie collective du web ?|url=https://web.archive.org/web/20250516050814/https://www.numerama.com/tech/805447-wikipedia-la-derniere-utopie-collective-du-web.html|site=Numerama|date=2021-12-29|consulté le=2025-12-21}}</ref>. Mais qu'en est-il de l'ensemble du mouvement Wikimédia ? Pour nous aider à comprendre ce qui se passe dans la dimension numérique de ce mouvement, voici une métaphore qui décrit un quartier établi au sein d'une ville, imaginée au départ de l'espace web ». Dans cette ville imaginaire, Internet représenterait le réseau routier, pendant que des serveurs informatiques feraient office de bâtiments, et que les pages web qu'ils hébergent, constitueraient les différentes pièces de ces édifices. Le quartier Wikimédia rassemblerait ainsi plus d’un millier de bâtiments. Au sein de ceux-ci et à l’exception de quelques lieux administratifs, chaque pièce peut être visitée gratuitement et même modifiée au niveau de son contenu. On peut dès lors y ajouter de nouvelles choses, telles que du texte, des photos, des vidéos ou des documents sonores, et même changer ou supprimer ce qui a été créé ou modifié par d’autres. Tout cela, bien sûr, dans le but de rendre ces endroits plus esthétiques et plus authentiques et en tenant compte des différentes idées et des éventuelles oppositions de point de vue concernant les aménagements. Pour faciliter l'entente entre les personnes qui s'investissent dans ces changements, chaque pièce des bâtiments Wikimédia possède un espace annexe dédié à la discussion. Dans la plupart des bâtiments Wikimédia, une personne malintentionnée peut même faire disparaitre tout le contenu d'une pièce. Néanmoins, dans la seconde qui suit, un robot remettra tout en place, avant de transmettre un message concernant le traitement du vandalisme. Lorsqu'une action plus discrète n'est pas détectée par un robot, c'est alors quelqu'un qui surveille la pièce qui prendra le relais pour annuler les changements malveillants et contacter la personne responsable. En cas de multirécidive, celle-ci peut se voir privée de sa capacité de modifier les pièces, soit dans le bâtiment vandalisé, soit dans tout le quartier quand cela se justifie. Après discussion, cette sanction sera mise en application par un administrateur ou une administratrice bénévole, choisi ou choisie par l'ensemble des autres bénévoles qui prennent soin des bâtiments. [[Fichier:The Digital City, Riyadh 191957.jpg|vignette|<small>Figure 3. Photo de la ''Digital City'' de [[w:Riyad|Riyadh]] et son aspect visuel en lien avec la métaphore du quartier Wikimédia.</small>|300x300px]] On comprend donc que tout le monde peut enrichir, mais également surveiller et protéger les richesses partagées dans le quartier Wikimédia. Il suffit pour cela de rejoindre le mouvement en se créant un compte. Cela permet de profiter de nombreux outils, dont notamment un système qui envoie des notifications, dès qu'une pièce que l'on veut surveiller est modifiée. Pour créer ce compte, pas besoin de fournir une adresse ou un numéro de téléphone. Les seules informations personnelles indispensables au bon fonctionnement du quartier Wikimédia sont les [[w:Adresses_IP|adresses IP]] des visiteurs. Lors des visites et contrairement à ce qui se passe dans les quartiers commerciaux de la grande ville numérique, tels que les [[w:GAFAM|GAFAM]], [[w:NATU_(Netflix,_Airbnb,_Tesla_et_Uber)|NATU]], [[w:BATX|BATX]], le quartier Wikimédia ne récolte et ne vend aucune donnée à des fins d'exploitation. Même les adresses IP enregistrées par le système ne sont pas visibles par les autres visiteurs. Elles sont remplacées par les noms et les pseudonymes fournis lors de la création des comptes, ou masquées par des comptes temporaires pour les modifications faites par des personnes non connectées. Seules quelques personnes accréditées par la communauté pour effectuer des contrôles d’usurpation d’identité ont accès à ces informations<ref>{{Lien web|auteur=MédiaWiki|titre=Produit de confiance et de sécurité/Comptes temporaires|url=https://web.archive.org/web/20250813140004/https://www.mediawiki.org/wiki/Trust_and_Safety_Product/Temporary_Accounts/fr}}.</ref>. C’est là une précaution nécessaire au bon déroulement des votes qui succèdent parfois aux recherches de [[w:Consensus|consensus]] concernant l'aménagement du quartier Wikimédia. Dans cette ville numérique que constituerait l'espace web, Wikimédia apparait ainsi comme le plus grand quartier dédié au partage de la connaissance. Tout d'abord, il y a les plus de 350 bâtiments [[w:Wikipédia:Accueil_principal|Wikipédia]], chacun dédié à une version linguistique de l'encyclopédie. Puis, toujours séparés en versions linguistiques, on trouve ensuite les bibliothèques [[:en:fr:accueil|Wikilivres]] et [[s:fr:Wikisource:Accueil|Wikisource]], les bâtiments lexicaux multilingues [[wikt:fr:Wiktionnaire:Page_d’accueil|Wiktionnaire]], le centre journalistique [[n:fr:accueil|Wikinews]], le centre pédagogique et de recherche [[v:fr:accueil|Wikiversité]], le centre d'informations touristique [[voy:fr:accueil|Wikivoyage]], le répertoire des êtres vivants [[species:main page|Wikispecies]] et enfin l'institut des citations d’auteurs [[q:fr:accueil|Wikiquote]]. Cela sans oublier le musée médiatique [[commons:main page|Wikimedia Commons]] et la banque [[wikidata:wikidata:main_page|Wikidata]], reconnue comme étant la plus grande banque d’informations structurées au monde. Deux bâtiments dont l'une des fonctions principales communes est d’enrichir les pièces situées dans les autres buildings du quartier Wikimédia. Dans tous ces immeubles, il arrive souvent que plus de la moitié des étages soient uniquement attribués à l'organisation des activités qui s'y déroulent. Chaque bâtiment peut aussi compter sur le soutien d'autres édifices tels que [[mw:main page|MediaWiki]], [[wikitech:Main_Page|Wikitech]], [[w:fr:phabricator|Phabricator]], qui sont trois lieux entièrement dédiés aux maintenances techniques sur l'ensemble du quartier. Concernant les aspects administratifs, c'est dans le bâtiment [[metawiki:main page|Méta-Wiki]] que s'opère la gouvernance générale du quartier, alors que les courriers adressés à ce dernier sont traités en première ligne dans le bâtiment [[otrswiki:Main page|Wikimedia VRT]]. À la suite de quoi, il ne reste plus qu'à citer le bâtiment ''[[outreach:main page|Wikimedia outreach]],'' pour des initiatives de sensibilisation, et le bâtiment du journal [https://diff.wikimedia.org Diff Wikimedia], comme lieu de publication d'actualités sur le mouvement. En dehors de certains aspects techniques, tous ces bâtiments sont exclusivement régis par des communautés bénévoles, qui sont toujours prêtes à accueillir de nouveaux membres. Les seuls immeubles du quartier qui diffèrent de ce principe sont les bâtiments vitrines de la Fondation Wikimédia et des autres associations Wikimédia qui engagent du personnel. Quant au bâtiment du conseil d'administration de la Fondation, des raisons officielles justifient le fait que la modification de ses pièces est réservée à ces membres et aux employés qui les soutiennent. Face à tant d'utopies, il devient légitime de vouloir comprendre comment tout cela fut rendu possible. Or, pour répondre à cette question, il faut alors parcourir tout un pan de l'histoire de la révolution numérique, depuis la [[w:Contre-culture_des_années_1960|contre-culture des années 1960]] jusqu'à nos jours. On y découvre que les pionniers du réseau Internet étaient des chercheurs et étudiants en informatique, fortement influencés par de nouvelles idéologies, telles que celles qui furent à la source des évènements de [[w:mai_68|mai 68]] en France. C'est donc de là que naîtra la philosophie de partage, de liberté, de décentralisation et ce mode d’organisation tout à fait spécifique, que l'on observe aujourd'hui au sein du mouvement Wikimédia. Il y eut tout d'abord la création d'[[w:Internet|Internet]], comme réseau mondial de communication en libre accès, puis le développement du ''[[w:World_Wide_Web|World Wide Web]]'', qui a grandement facilité les interactions humaines à l’échelle planétaire. Par la suite, ce fut l'arrivée du [[w:Web_2.0|Web 2.0]], caractérisé par l'apparition de nouveaux sites web directement modifiables à l'aide d’un simple navigateur. Or, parmi ceux-ci se trouvent les [[w:fr:Moteur de Wiki|moteurs de Wiki]], dont le plus puissant d’entre eux, [[w:MediaWiki|MediaWiki]], est un [[Logiciels libres|logiciel libre]] développé par la Fondation Wikimédia. Le moment est donc venu d'en savoir plus sur ce type de programme informatique, ainsi que sur le [[w:Mouvement_du_logiciel_libre|mouvement du logiciel libre]], qui a fortement influencé la philosophie et les valeurs véhiculées au sein du mouvement Wikimédia.{{AutoCat}} mw1r9ri83h6c1c5ps9874vfiowm68pi 764796 764795 2026-04-24T10:15:27Z Lionel Scheepmans 20012 764796 wikitext text/x-wiki <noinclude>{{Le mouvement Wikimédia}}</noinclude> Au fil du temps, Wikipédia fut perçu comme une utopie en marche<ref>{{Article|langue=|prénom1=Christian|nom1=Vandendorpe|titre=Le phénomène Wikipédia: une utopie en marche|périodique=Le Débat|volume=148|numéro=1|éditeur=Gallimard|date=2008|issn=0246-2346|pages=17}}.</ref>, puis comme une utopie réalisée<ref>{{Ouvrage|prénom1=Théo|nom1=Henri|directeur1=|titre=Wikipédia : une utopie réalisée ?|lieu=Université de Poitier|date=juillet 2013|pages totales=98|lire en ligne=https://web.archive.org/web/20211103122912/https://www.seies.net/sites/theo/doc/HENRI_theo_-_master_1_-_memoire.pdf}}.</ref>, et finalement comme la dernière utopie collective du Web<ref>{{Lien web|langue=fr-FR|nom1=Dupont-Besnard|prénom1=Marcus|titre=Wikipédia, la dernière utopie collective du web ?|url=https://web.archive.org/web/20250516050814/https://www.numerama.com/tech/805447-wikipedia-la-derniere-utopie-collective-du-web.html|site=Numerama|date=2021-12-29|consulté le=2025-12-21}}</ref>. Mais qu'en est-il de l'ensemble du mouvement Wikimédia ? Pour nous aider à comprendre ce qui se passe dans la dimension numérique de ce mouvement, voici une métaphore qui décrit un quartier établi au sein d'une ville, imaginée au départ de l'espace web ». Dans cette ville imaginaire, Internet représenterait le réseau routier, pendant que des serveurs informatiques feraient office de bâtiments, et que les pages web qu'ils hébergent, constitueraient les différentes pièces de ces édifices. Le quartier Wikimédia rassemblerait ainsi plus d’un millier de bâtiments. Au sein de ceux-ci et à l’exception de quelques lieux administratifs, chaque pièce peut être visitée gratuitement, mais aussi modifiée au niveau de son contenu. On peut ainsi y ajouter de nouvelles choses, telles que du texte, des photos, des vidéos ou des documents sonores, et même changer ou supprimer ce qui a été créé ou modifié par d’autres. Tout cela, bien sûr, dans le but de rendre ces endroits plus esthétiques et plus authentiques et en tenant compte des différentes idées et des éventuelles oppositions de point de vue concernant les aménagements. Pour faciliter l'entente entre les personnes qui s'investissent dans ces changements, chaque pièce des bâtiments Wikimédia possède un espace annexe dédié à la discussion. Dans la plupart des bâtiments Wikimédia, une personne malintentionnée peut même faire disparaitre tout le contenu d'une pièce. Néanmoins, dans la seconde qui suit, un robot remettra tout en place, avant de transmettre un message concernant le traitement du vandalisme. Lorsqu'une action plus discrète n'est pas détectée par un robot, c'est alors quelqu'un qui surveille la pièce qui prendra le relais pour annuler les changements malveillants et contacter la personne responsable. En cas de multirécidive, celle-ci peut se voir privée de sa capacité de modifier les pièces, soit dans le bâtiment vandalisé, soit dans tout le quartier quand cela se justifie. Après discussion, cette sanction sera mise en application par un administrateur ou une administratrice bénévole, choisi ou choisie par l'ensemble des autres bénévoles qui prennent soin des bâtiments. [[Fichier:The Digital City, Riyadh 191957.jpg|vignette|<small>Figure 3. Photo de la ''Digital City'' de [[w:Riyad|Riyadh]] et son aspect visuel en lien avec la métaphore du quartier Wikimédia.</small>|300x300px]] On comprend donc que tout le monde peut enrichir, mais également surveiller et protéger les richesses partagées dans le quartier Wikimédia. Il suffit pour cela de rejoindre le mouvement en se créant un compte. Cela permet de profiter de nombreux outils, dont notamment un système qui envoie des notifications, dès qu'une pièce que l'on veut surveiller est modifiée. Pour créer ce compte, pas besoin de fournir une adresse ou un numéro de téléphone. Les seules informations personnelles indispensables au bon fonctionnement du quartier Wikimédia sont les [[w:Adresses_IP|adresses IP]] des visiteurs. Lors des visites et contrairement à ce qui se passe dans les quartiers commerciaux de la grande ville numérique, tels que les [[w:GAFAM|GAFAM]], [[w:NATU_(Netflix,_Airbnb,_Tesla_et_Uber)|NATU]], [[w:BATX|BATX]], le quartier Wikimédia ne récolte et ne vend aucune donnée à des fins d'exploitation. Même les adresses IP enregistrées par le système ne sont pas visibles par les autres visiteurs. Elles sont remplacées par les noms et les pseudonymes fournis lors de la création des comptes, ou masquées par des comptes temporaires pour les modifications faites par des personnes non connectées. Seules quelques personnes accréditées par la communauté pour effectuer des contrôles d’usurpation d’identité ont accès à ces informations<ref>{{Lien web|auteur=MédiaWiki|titre=Produit de confiance et de sécurité/Comptes temporaires|url=https://web.archive.org/web/20250813140004/https://www.mediawiki.org/wiki/Trust_and_Safety_Product/Temporary_Accounts/fr}}.</ref>. C’est là une précaution nécessaire au bon déroulement des votes qui succèdent parfois aux recherches de [[w:Consensus|consensus]] concernant l'aménagement du quartier Wikimédia. Dans cette ville numérique que constituerait l'espace web, Wikimédia apparait ainsi comme le plus grand quartier dédié au partage de la connaissance. Tout d'abord, il y a les plus de 350 bâtiments [[w:Wikipédia:Accueil_principal|Wikipédia]], chacun dédié à une version linguistique de l'encyclopédie. Puis, toujours séparés en versions linguistiques, on trouve ensuite les bibliothèques [[:en:fr:accueil|Wikilivres]] et [[s:fr:Wikisource:Accueil|Wikisource]], les bâtiments lexicaux multilingues [[wikt:fr:Wiktionnaire:Page_d’accueil|Wiktionnaire]], le centre journalistique [[n:fr:accueil|Wikinews]], le centre pédagogique et de recherche [[v:fr:accueil|Wikiversité]], le centre d'informations touristique [[voy:fr:accueil|Wikivoyage]], le répertoire des êtres vivants [[species:main page|Wikispecies]] et enfin l'institut des citations d’auteurs [[q:fr:accueil|Wikiquote]]. Cela sans oublier le musée médiatique [[commons:main page|Wikimedia Commons]] et la banque [[wikidata:wikidata:main_page|Wikidata]], reconnue comme étant la plus grande banque d’informations structurées au monde. Deux bâtiments dont l'une des fonctions principales communes est d’enrichir les pièces situées dans les autres buildings du quartier Wikimédia. Dans tous ces immeubles, il arrive souvent que plus de la moitié des étages soient uniquement attribués à l'organisation des activités qui s'y déroulent. Chaque bâtiment peut aussi compter sur le soutien d'autres édifices tels que [[mw:main page|MediaWiki]], [[wikitech:Main_Page|Wikitech]], [[w:fr:phabricator|Phabricator]], qui sont trois lieux entièrement dédiés aux maintenances techniques sur l'ensemble du quartier. Concernant les aspects administratifs, c'est dans le bâtiment [[metawiki:main page|Méta-Wiki]] que s'opère la gouvernance générale du quartier, alors que les courriers adressés à ce dernier sont traités en première ligne dans le bâtiment [[otrswiki:Main page|Wikimedia VRT]]. À la suite de quoi, il ne reste plus qu'à citer le bâtiment ''[[outreach:main page|Wikimedia outreach]],'' pour des initiatives de sensibilisation, et le bâtiment du journal [https://diff.wikimedia.org Diff Wikimedia], comme lieu de publication d'actualités sur le mouvement. En dehors de certains aspects techniques, tous ces bâtiments sont exclusivement régis par des communautés bénévoles, qui sont toujours prêtes à accueillir de nouveaux membres. Les seuls immeubles du quartier qui diffèrent de ce principe sont les bâtiments vitrines de la Fondation Wikimédia et des autres associations Wikimédia qui engagent du personnel. Quant au bâtiment du conseil d'administration de la Fondation, des raisons officielles justifient le fait que la modification de ses pièces est réservée à ces membres et aux employés qui les soutiennent. Face à tant d'utopies, il devient légitime de vouloir comprendre comment tout cela fut rendu possible. Or, pour répondre à cette question, il faut alors parcourir tout un pan de l'histoire de la révolution numérique, depuis la [[w:Contre-culture_des_années_1960|contre-culture des années 1960]] jusqu'à nos jours. On y découvre que les pionniers du réseau Internet étaient des chercheurs et étudiants en informatique, fortement influencés par de nouvelles idéologies, telles que celles qui furent à la source des évènements de [[w:mai_68|mai 68]] en France. C'est donc de là que naîtra la philosophie de partage, de liberté, de décentralisation et ce mode d’organisation tout à fait spécifique, que l'on observe aujourd'hui au sein du mouvement Wikimédia. Il y eut tout d'abord la création d'[[w:Internet|Internet]], comme réseau mondial de communication en libre accès, puis le développement du ''[[w:World_Wide_Web|World Wide Web]]'', qui a grandement facilité les interactions humaines à l’échelle planétaire. Par la suite, ce fut l'arrivée du [[w:Web_2.0|Web 2.0]], caractérisé par l'apparition de nouveaux sites web directement modifiables à l'aide d’un simple navigateur. Or, parmi ceux-ci se trouvent les [[w:fr:Moteur de Wiki|moteurs de Wiki]], dont le plus puissant d’entre eux, [[w:MediaWiki|MediaWiki]], est un [[Logiciels libres|logiciel libre]] développé par la Fondation Wikimédia. Le moment est donc venu d'en savoir plus sur ce type de programme informatique, ainsi que sur le [[w:Mouvement_du_logiciel_libre|mouvement du logiciel libre]], qui a fortement influencé la philosophie et les valeurs véhiculées au sein du mouvement Wikimédia.{{AutoCat}} g3jj1q6k0zxrdc8ouylznxcg35i9nyr 764797 764796 2026-04-24T10:16:55Z Lionel Scheepmans 20012 764797 wikitext text/x-wiki <noinclude>{{Le mouvement Wikimédia}}</noinclude> Au fil du temps, Wikipédia fut perçu comme une utopie en marche<ref>{{Article|langue=|prénom1=Christian|nom1=Vandendorpe|titre=Le phénomène Wikipédia: une utopie en marche|périodique=Le Débat|volume=148|numéro=1|éditeur=Gallimard|date=2008|issn=0246-2346|pages=17}}.</ref>, puis comme une utopie réalisée<ref>{{Ouvrage|prénom1=Théo|nom1=Henri|directeur1=|titre=Wikipédia : une utopie réalisée ?|lieu=Université de Poitier|date=juillet 2013|pages totales=98|lire en ligne=https://web.archive.org/web/20211103122912/https://www.seies.net/sites/theo/doc/HENRI_theo_-_master_1_-_memoire.pdf}}.</ref>, et finalement comme la dernière utopie collective du Web<ref>{{Lien web|langue=fr-FR|nom1=Dupont-Besnard|prénom1=Marcus|titre=Wikipédia, la dernière utopie collective du web ?|url=https://web.archive.org/web/20250516050814/https://www.numerama.com/tech/805447-wikipedia-la-derniere-utopie-collective-du-web.html|site=Numerama|date=2021-12-29|consulté le=2025-12-21}}</ref>. Mais qu'en est-il de l'ensemble du mouvement Wikimédia ? Pour nous aider à comprendre ce qui se passe dans la dimension numérique de ce mouvement, voici une métaphore qui décrit un quartier établi au sein d'une ville, imaginée au départ de l'espace web ». Dans cette ville imaginaire, Internet représenterait le réseau routier, pendant que des serveurs informatiques feraient office de bâtiments, et que les pages web qu'ils hébergent, constitueraient les différentes pièces de ces édifices. Le quartier Wikimédia rassemblerait ainsi plus d’un millier de bâtiments. Au sein de ceux-ci et à l’exception de quelques lieux administratifs, chaque pièce peut être visitée gratuitement, mais aussi modifiée au niveau de son contenu. On peut ainsi y ajouter de nouvelles choses, telles que du texte, des photos, des vidéos ou des documents sonores, et même changer ou supprimer ce qui a été créé ou modifié par d’autres. Tout cela, bien sûr, dans le but de rendre ces endroits plus esthétiques, ou plus authentiques et en tenant compte des différentes idées et des éventuelles oppositions de point de vue concernant les aménagements. Pour faciliter l'entente entre les personnes qui s'investissent dans ces changements, chaque pièce des bâtiments Wikimédia possède un espace annexe dédié à la discussion. Dans la plupart des bâtiments Wikimédia, une personne malintentionnée peut même faire disparaitre tout le contenu d'une pièce. Néanmoins, dans la seconde qui suit, un robot remettra tout en place, avant de transmettre un message concernant le traitement du vandalisme. Lorsqu'une action plus discrète n'est pas détectée par un robot, c'est alors quelqu'un qui surveille la pièce qui prendra le relais pour annuler les changements malveillants et contacter la personne responsable. En cas de multirécidive, celle-ci peut se voir privée de sa capacité de modifier les pièces, soit dans le bâtiment vandalisé, soit dans tout le quartier quand cela se justifie. Après discussion, cette sanction sera mise en application par un administrateur ou une administratrice bénévole, choisi ou choisie par l'ensemble des autres bénévoles qui prennent soin des bâtiments. [[Fichier:The Digital City, Riyadh 191957.jpg|vignette|<small>Figure 3. Photo de la ''Digital City'' de [[w:Riyad|Riyadh]] et son aspect visuel en lien avec la métaphore du quartier Wikimédia.</small>|300x300px]] On comprend donc que tout le monde peut enrichir, mais également surveiller et protéger les richesses partagées dans le quartier Wikimédia. Il suffit pour cela de rejoindre le mouvement en se créant un compte. Cela permet de profiter de nombreux outils, dont notamment un système qui envoie des notifications, dès qu'une pièce que l'on veut surveiller est modifiée. Pour créer ce compte, pas besoin de fournir une adresse ou un numéro de téléphone. Les seules informations personnelles indispensables au bon fonctionnement du quartier Wikimédia sont les [[w:Adresses_IP|adresses IP]] des visiteurs. Lors des visites et contrairement à ce qui se passe dans les quartiers commerciaux de la grande ville numérique, tels que les [[w:GAFAM|GAFAM]], [[w:NATU_(Netflix,_Airbnb,_Tesla_et_Uber)|NATU]], [[w:BATX|BATX]], le quartier Wikimédia ne récolte et ne vend aucune donnée à des fins d'exploitation. Même les adresses IP enregistrées par le système ne sont pas visibles par les autres visiteurs. Elles sont remplacées par les noms et les pseudonymes fournis lors de la création des comptes, ou masquées par des comptes temporaires pour les modifications faites par des personnes non connectées. Seules quelques personnes accréditées par la communauté pour effectuer des contrôles d’usurpation d’identité ont accès à ces informations<ref>{{Lien web|auteur=MédiaWiki|titre=Produit de confiance et de sécurité/Comptes temporaires|url=https://web.archive.org/web/20250813140004/https://www.mediawiki.org/wiki/Trust_and_Safety_Product/Temporary_Accounts/fr}}.</ref>. C’est là une précaution nécessaire au bon déroulement des votes qui succèdent parfois aux recherches de [[w:Consensus|consensus]] concernant l'aménagement du quartier Wikimédia. Dans cette ville numérique que constituerait l'espace web, Wikimédia apparait ainsi comme le plus grand quartier dédié au partage de la connaissance. Tout d'abord, il y a les plus de 350 bâtiments [[w:Wikipédia:Accueil_principal|Wikipédia]], chacun dédié à une version linguistique de l'encyclopédie. Puis, toujours séparés en versions linguistiques, on trouve ensuite les bibliothèques [[:en:fr:accueil|Wikilivres]] et [[s:fr:Wikisource:Accueil|Wikisource]], les bâtiments lexicaux multilingues [[wikt:fr:Wiktionnaire:Page_d’accueil|Wiktionnaire]], le centre journalistique [[n:fr:accueil|Wikinews]], le centre pédagogique et de recherche [[v:fr:accueil|Wikiversité]], le centre d'informations touristique [[voy:fr:accueil|Wikivoyage]], le répertoire des êtres vivants [[species:main page|Wikispecies]] et enfin l'institut des citations d’auteurs [[q:fr:accueil|Wikiquote]]. Cela sans oublier le musée médiatique [[commons:main page|Wikimedia Commons]] et la banque [[wikidata:wikidata:main_page|Wikidata]], reconnue comme étant la plus grande banque d’informations structurées au monde. Deux bâtiments dont l'une des fonctions principales communes est d’enrichir les pièces situées dans les autres buildings du quartier Wikimédia. Dans tous ces immeubles, il arrive souvent que plus de la moitié des étages soient uniquement attribués à l'organisation des activités qui s'y déroulent. Chaque bâtiment peut aussi compter sur le soutien d'autres édifices tels que [[mw:main page|MediaWiki]], [[wikitech:Main_Page|Wikitech]], [[w:fr:phabricator|Phabricator]], qui sont trois lieux entièrement dédiés aux maintenances techniques sur l'ensemble du quartier. Concernant les aspects administratifs, c'est dans le bâtiment [[metawiki:main page|Méta-Wiki]] que s'opère la gouvernance générale du quartier, alors que les courriers adressés à ce dernier sont traités en première ligne dans le bâtiment [[otrswiki:Main page|Wikimedia VRT]]. À la suite de quoi, il ne reste plus qu'à citer le bâtiment ''[[outreach:main page|Wikimedia outreach]],'' pour des initiatives de sensibilisation, et le bâtiment du journal [https://diff.wikimedia.org Diff Wikimedia], comme lieu de publication d'actualités sur le mouvement. En dehors de certains aspects techniques, tous ces bâtiments sont exclusivement régis par des communautés bénévoles, qui sont toujours prêtes à accueillir de nouveaux membres. Les seuls immeubles du quartier qui diffèrent de ce principe sont les bâtiments vitrines de la Fondation Wikimédia et des autres associations Wikimédia qui engagent du personnel. Quant au bâtiment du conseil d'administration de la Fondation, des raisons officielles justifient le fait que la modification de ses pièces est réservée à ces membres et aux employés qui les soutiennent. Face à tant d'utopies, il devient légitime de vouloir comprendre comment tout cela fut rendu possible. Or, pour répondre à cette question, il faut alors parcourir tout un pan de l'histoire de la révolution numérique, depuis la [[w:Contre-culture_des_années_1960|contre-culture des années 1960]] jusqu'à nos jours. On y découvre que les pionniers du réseau Internet étaient des chercheurs et étudiants en informatique, fortement influencés par de nouvelles idéologies, telles que celles qui furent à la source des évènements de [[w:mai_68|mai 68]] en France. C'est donc de là que naîtra la philosophie de partage, de liberté, de décentralisation et ce mode d’organisation tout à fait spécifique, que l'on observe aujourd'hui au sein du mouvement Wikimédia. Il y eut tout d'abord la création d'[[w:Internet|Internet]], comme réseau mondial de communication en libre accès, puis le développement du ''[[w:World_Wide_Web|World Wide Web]]'', qui a grandement facilité les interactions humaines à l’échelle planétaire. Par la suite, ce fut l'arrivée du [[w:Web_2.0|Web 2.0]], caractérisé par l'apparition de nouveaux sites web directement modifiables à l'aide d’un simple navigateur. Or, parmi ceux-ci se trouvent les [[w:fr:Moteur de Wiki|moteurs de Wiki]], dont le plus puissant d’entre eux, [[w:MediaWiki|MediaWiki]], est un [[Logiciels libres|logiciel libre]] développé par la Fondation Wikimédia. Le moment est donc venu d'en savoir plus sur ce type de programme informatique, ainsi que sur le [[w:Mouvement_du_logiciel_libre|mouvement du logiciel libre]], qui a fortement influencé la philosophie et les valeurs véhiculées au sein du mouvement Wikimédia.{{AutoCat}} stgqi00v2itv74lr94betlhxn8n766e 764798 764797 2026-04-24T10:20:27Z Lionel Scheepmans 20012 764798 wikitext text/x-wiki <noinclude>{{Le mouvement Wikimédia}}</noinclude> Au fil du temps, Wikipédia fut perçu comme une utopie en marche<ref>{{Article|langue=|prénom1=Christian|nom1=Vandendorpe|titre=Le phénomène Wikipédia: une utopie en marche|périodique=Le Débat|volume=148|numéro=1|éditeur=Gallimard|date=2008|issn=0246-2346|pages=17}}.</ref>, puis comme une utopie réalisée<ref>{{Ouvrage|prénom1=Théo|nom1=Henri|directeur1=|titre=Wikipédia : une utopie réalisée ?|lieu=Université de Poitier|date=juillet 2013|pages totales=98|lire en ligne=https://web.archive.org/web/20211103122912/https://www.seies.net/sites/theo/doc/HENRI_theo_-_master_1_-_memoire.pdf}}.</ref>, et finalement comme la dernière utopie collective du Web<ref>{{Lien web|langue=fr-FR|nom1=Dupont-Besnard|prénom1=Marcus|titre=Wikipédia, la dernière utopie collective du web ?|url=https://web.archive.org/web/20250516050814/https://www.numerama.com/tech/805447-wikipedia-la-derniere-utopie-collective-du-web.html|site=Numerama|date=2021-12-29|consulté le=2025-12-21}}</ref>. Mais qu'en est-il de l'ensemble du mouvement Wikimédia ? Pour nous aider à comprendre ce qui se passe dans la dimension numérique de ce mouvement, voici une métaphore qui décrit un quartier établi au sein d'une ville, imaginée au départ de l'espace web ». Dans cette ville imaginaire, Internet représenterait le réseau routier, pendant que des serveurs informatiques feraient office de bâtiments, et que les pages web qu'ils hébergent, constitueraient les différentes pièces de ces édifices. Le quartier Wikimédia rassemblerait ainsi plus d’un millier de bâtiments. Au sein de ceux-ci et à l’exception de quelques lieux administratifs, chaque pièce peut être visitée gratuitement, mais aussi modifiée au niveau de son contenu. On peut ainsi y ajouter de nouvelles choses, telles que du texte, des photos, des vidéos ou des documents sonores, et même changer ou supprimer ce qui a été créé ou modifié par d’autres. Tout cela, bien sûr, dans le but de rendre ces endroits plus esthétiques, ou plus authentiques et en tenant compte des différentes idées et des éventuelles oppositions de point de vue concernant les aménagements. Pour faciliter l'entente entre les personnes qui s'investissent dans les modifications, chaque pièce des bâtiments Wikimédia possède un espace annexe dédié à la discussion. Dans la plupart des bâtiments Wikimédia, une personne malintentionnée peut même faire disparaitre tout le contenu d'une pièce. Néanmoins, dans la seconde qui suit, un robot remettra tout en place, avant de transmettre un message concernant le traitement du vandalisme. Lorsqu'une action plus discrète n'est pas détectée par un robot, c'est alors quelqu'un qui surveille la pièce qui prendra le relais pour annuler les changements malveillants et contacter la personne responsable. En cas de multirécidive, celle-ci peut se voir privée de sa capacité de modifier les pièces, soit dans le bâtiment vandalisé, soit dans tout le quartier quand cela se justifie. Après discussion, cette sanction sera mise en application par un administrateur ou une administratrice bénévole, choisi ou choisie par l'ensemble des autres bénévoles qui prennent soin des bâtiments. [[Fichier:The Digital City, Riyadh 191957.jpg|vignette|<small>Figure 3. Photo de la ''Digital City'' de [[w:Riyad|Riyadh]] et son aspect visuel en lien avec la métaphore du quartier Wikimédia.</small>|300x300px]] On comprend donc que tout le monde peut enrichir, mais également surveiller et protéger les richesses partagées dans le quartier Wikimédia. Il suffit pour cela de rejoindre le mouvement en se créant un compte. Cela permet de profiter de nombreux outils, dont notamment un système qui envoie des notifications, dès qu'une pièce que l'on veut surveiller est modifiée. Pour créer ce compte, pas besoin de fournir une adresse ou un numéro de téléphone. Les seules informations personnelles indispensables au bon fonctionnement du quartier Wikimédia sont les [[w:Adresses_IP|adresses IP]] des visiteurs. Lors des visites et contrairement à ce qui se passe dans les quartiers commerciaux de la grande ville numérique, tels que les [[w:GAFAM|GAFAM]], [[w:NATU_(Netflix,_Airbnb,_Tesla_et_Uber)|NATU]], [[w:BATX|BATX]], le quartier Wikimédia ne récolte et ne vend aucune donnée à des fins d'exploitation. Même les adresses IP enregistrées par le système ne sont pas visibles par les autres visiteurs. Elles sont remplacées par les noms et les pseudonymes fournis lors de la création des comptes, ou masquées par des comptes temporaires pour les modifications faites par des personnes non connectées. Seules quelques personnes accréditées par la communauté pour effectuer des contrôles d’usurpation d’identité ont accès à ces informations<ref>{{Lien web|auteur=MédiaWiki|titre=Produit de confiance et de sécurité/Comptes temporaires|url=https://web.archive.org/web/20250813140004/https://www.mediawiki.org/wiki/Trust_and_Safety_Product/Temporary_Accounts/fr}}.</ref>. C’est là une précaution nécessaire au bon déroulement des votes qui succèdent parfois aux recherches de [[w:Consensus|consensus]] concernant l'aménagement du quartier Wikimédia. Dans cette ville numérique que constituerait l'espace web, Wikimédia apparait ainsi comme le plus grand quartier dédié au partage de la connaissance. Tout d'abord, il y a les plus de 350 bâtiments [[w:Wikipédia:Accueil_principal|Wikipédia]], chacun dédié à une version linguistique de l'encyclopédie. Puis, toujours séparés en versions linguistiques, on trouve ensuite les bibliothèques [[:en:fr:accueil|Wikilivres]] et [[s:fr:Wikisource:Accueil|Wikisource]], les bâtiments lexicaux multilingues [[wikt:fr:Wiktionnaire:Page_d’accueil|Wiktionnaire]], le centre journalistique [[n:fr:accueil|Wikinews]], le centre pédagogique et de recherche [[v:fr:accueil|Wikiversité]], le centre d'informations touristique [[voy:fr:accueil|Wikivoyage]], le répertoire des êtres vivants [[species:main page|Wikispecies]] et enfin l'institut des citations d’auteurs [[q:fr:accueil|Wikiquote]]. Cela sans oublier le musée médiatique [[commons:main page|Wikimedia Commons]] et la banque [[wikidata:wikidata:main_page|Wikidata]], reconnue comme étant la plus grande banque d’informations structurées au monde. Deux bâtiments dont l'une des fonctions principales communes est d’enrichir les pièces situées dans les autres buildings du quartier Wikimédia. Dans tous ces immeubles, il arrive souvent que plus de la moitié des étages soient uniquement attribués à l'organisation des activités qui s'y déroulent. Chaque bâtiment peut aussi compter sur le soutien d'autres édifices tels que [[mw:main page|MediaWiki]], [[wikitech:Main_Page|Wikitech]], [[w:fr:phabricator|Phabricator]], qui sont trois lieux entièrement dédiés aux maintenances techniques sur l'ensemble du quartier. Concernant les aspects administratifs, c'est dans le bâtiment [[metawiki:main page|Méta-Wiki]] que s'opère la gouvernance générale du quartier, alors que les courriers adressés à ce dernier sont traités en première ligne dans le bâtiment [[otrswiki:Main page|Wikimedia VRT]]. À la suite de quoi, il ne reste plus qu'à citer le bâtiment ''[[outreach:main page|Wikimedia outreach]],'' pour des initiatives de sensibilisation, et le bâtiment du journal [https://diff.wikimedia.org Diff Wikimedia], comme lieu de publication d'actualités sur le mouvement. En dehors de certains aspects techniques, tous ces bâtiments sont exclusivement régis par des communautés bénévoles, qui sont toujours prêtes à accueillir de nouveaux membres. Les seuls immeubles du quartier qui diffèrent de ce principe sont les bâtiments vitrines de la Fondation Wikimédia et des autres associations Wikimédia qui engagent du personnel. Quant au bâtiment du conseil d'administration de la Fondation, des raisons officielles justifient le fait que la modification de ses pièces est réservée à ces membres et aux employés qui les soutiennent. Face à tant d'utopies, il devient légitime de vouloir comprendre comment tout cela fut rendu possible. Or, pour répondre à cette question, il faut alors parcourir tout un pan de l'histoire de la révolution numérique, depuis la [[w:Contre-culture_des_années_1960|contre-culture des années 1960]] jusqu'à nos jours. On y découvre que les pionniers du réseau Internet étaient des chercheurs et étudiants en informatique, fortement influencés par de nouvelles idéologies, telles que celles qui furent à la source des évènements de [[w:mai_68|mai 68]] en France. C'est donc de là que naîtra la philosophie de partage, de liberté, de décentralisation et ce mode d’organisation tout à fait spécifique, que l'on observe aujourd'hui au sein du mouvement Wikimédia. Il y eut tout d'abord la création d'[[w:Internet|Internet]], comme réseau mondial de communication en libre accès, puis le développement du ''[[w:World_Wide_Web|World Wide Web]]'', qui a grandement facilité les interactions humaines à l’échelle planétaire. Par la suite, ce fut l'arrivée du [[w:Web_2.0|Web 2.0]], caractérisé par l'apparition de nouveaux sites web directement modifiables à l'aide d’un simple navigateur. Or, parmi ceux-ci se trouvent les [[w:fr:Moteur de Wiki|moteurs de Wiki]], dont le plus puissant d’entre eux, [[w:MediaWiki|MediaWiki]], est un [[Logiciels libres|logiciel libre]] développé par la Fondation Wikimédia. Le moment est donc venu d'en savoir plus sur ce type de programme informatique, ainsi que sur le [[w:Mouvement_du_logiciel_libre|mouvement du logiciel libre]], qui a fortement influencé la philosophie et les valeurs véhiculées au sein du mouvement Wikimédia.{{AutoCat}} 82ljl9ctg80tfix7rp6czs865rjt5tj Le mouvement Wikimédia/L'arrivée des projets frères 0 79273 764666 764633 2026-04-23T15:59:46Z Lionel Scheepmans 20012 764666 wikitext text/x-wiki <noinclude>{{Le mouvement Wikimédia}}</noinclude> Dans le but de développer des contenus pédagogiques qui ne trouvaient pas leur place dans Wikipédia, d’autres projets pédagogiques et collaboratifs ont vu le jour, pour former ce que l'on appelle couramment aujourd'hui : l’écosystème Wikimedia. La naissance de tous ces projets, ainsi que les évènements importants qui ont contribué au développement du mouvement, ont été repris dans [[c:File:WikipediaTimeline.png|ligne du temps]] réalisée par [[m:user:Guillom|Guillaume Paumier]] à l’occasion du dixième anniversaire de Wikipédia. Dans ce graphique, qui peut être complété par [[m:Wikimedia News|une page web]] régulièrement mise à jour, on peut ainsi voir en détail l'évolution du nombre de projets, de versions linguistiques, de contributeurs et d'articles, pour avoir une vie précise de la vitesse à laquelle s'est développé le mouvement Wikimédia.[[Fichier:Wikimedia logo family complete-2022.svg|alt=Logo du mouvement Wikimédia entouré de 15 autres logos de projets actifs en son sein|vignette|<small>Figure 18. Logo de la Fondation Wikimédia entouré de 15 autres logos de projets actifs au sein du mouvement.</small>|300x300px]]Parmi tous les projets frères de Wikipédia, le premier apparu fut Méta-Wiki, une plateforme de référence pour centraliser la gestion de l'ensemble des sites web hébergés par la fondation Wikimédia. Dans un premier temps, cet espace communautaire en ligne a répondu à la nécessité de traiter en un seul lieu les questions communes aux différentes versions linguistiques de Wikipédia. Aujourd'hui, le site web est le principal endroit de coordination et de gestion de l'ensemble du mouvement Wikimédia. On y retrouve énormément d'informations au sujet des projets en ligne, mais aussi, et surtout, concernant la Fondation et tous les organismes affiliés. Après Méta-Wiki, sept autres projets de partage de la connaissance ont fait leur apparition, avant d'être déclinés à leur tour en plusieurs versions linguistiques. Tous ces projets émergent en général sur l’initiative d’un petit groupe de personnes actives au sein d’un projet préexistant. Ce fut le cas du projet Wiktionnaire en anglais, le deuxième projet à voir le jour après Méta-Wiki, en décembre 2002, soit deux ans avant la version francophone apparue en mars 2004<ref>{{Lien web|auteur=Wiktionnaire|titre=Wiktionnaire:Historique du Wiktionnaire|url=https://web.archive.org/web/20200416091043/https://fr.wiktionary.org/wiki/Wiktionnaire:Historique_du_Wiktionnaire|consulté le=}}.</ref>. Il est intéressant d'observer que la version francophone du Wiktionnaire n’a pas été créée à partir du projet anglophone, mais bien depuis le projet Wikipédia en français. D'ailleurs, on peut retrouver dans les archives de ce dernier projet, tout un débat concernant la pertinence de cette création, dont voici un extrait. <blockquote> En fait, ce qui me peine vraiment avec le projet Wiktionary, c’est que alors qu’on essaie de rassembler les gens (pas facile) pour créer une sorte de tour de Babel de la connaissance (tâche bien longue et difficile), ce nouveau projet va disperser les énergies pour une raison qui ne me semble pas valable. C’est la création de Wiktionary qui va créer des redondances. À mon avis il existera rapidement des pages sur le même mot, mais ne contenant pas les mêmes informations. Pour quelle raison ces connaissances devraient-elles être séparées ? Les encyclopédies sur papier devaient faire des choix à cause du manque de place, mais nous, pourquoi le ferions-nous ??? "Wikipédia n’est pas un dictionnaire" n’est pas un argument à mon avis... si vraiment c’était pas un dictionnaire, il faudrait virer tout un tas d’article. Je ne comprends vraiment pas cette volonté de séparer la connaissance entre ce qui est "encyclopédique" et ce qui n’est "qu’une définition". [Réponse] Pour moi ce qu’est Wiktionary, c’est une partie de Wikipédia s’intéressant plus particulièrement aux aspects linguistiques des mots. La différence que je verrais entre la partie ''dictionnaire'' de Wikipédia et sa partie dite encyclopédique, c’est que la partie dictionnaire s’intéresserait au sens des mots eux-mêmes alors que la partie encyclopédie s’attache plus à faire ressortir un état des connaissances à un moment donné. Le pourquoi de la séparation d’avec la partie encyclopédie tient plus à des raisons techniques qu’à une volonté de monter un projet indépendant, en effet, à mon humble avis, un dictionnaire nécessite une plus grande rigueur (de présentation) qu’une encyclopédie. Ceci entraîne beaucoup de problème et entre autres le choix de la mise en forme des articles du dictionnaire.<ref>{{Lien web|auteur=Wiktionnaire|titre=Wiktionnaire:Historique du Wiktionnaire/Discussion Wikipédia:Wiktionary|url=https://web.archive.org/web/20140831102908/http://fr.wiktionary.org/wiki/Wiktionnaire:Historique_du_Wiktionnaire/Discussion_Wikip%C3%A9dia:Wiktionary|date=|consulté le=}}.</ref> </blockquote> Créer un nouveau projet, c’est effectivement créer un nouveau site web, qui devra faire l’objet d’une nouvelle gestion, tant pour les serveurs de la Fondation, que pour la nouvelle communauté de contributeurs. L’importation de pages d’un projet à l'autre ou la traduction de celles-ci sont bien sûr toujours possibles, mais cela duplique alors aussi la maintenance et les mises à jour. Le choix de scinder un projet, en faveur d’une plus grande liberté, comporte donc certains coûts humains et financiers. Ce prix à payer n'a pour autant pas empêché le projet anglophone Wikibooks de faire son apparition le 10 juin 2003, soit près d’un an avant Wikilivres, la version francophone du projet, apparue le 22 juillet 2004. Cette dernière création avait de nouveau été débattue au sein de la communauté Wikipédia en français, et non pas dans le Wikibooks en anglais. Quant à l'objectif commun aux deux projets linguistiques, il était de créer une « bibliothèque de livres pédagogiques libres que chacun peut améliorer »<ref>{{Lien web|auteur=Wikilivres|url=https://fr.wikibooks.org/w/index.php?title=Accueil&oldid=586825|titre=Acceuil|consulté le=}}.</ref>. Environ un an après la création du projet en anglais, un nouvel [[w:fr:Espace de noms|espace de noms]] intitulé Wikijunior fut mis en place au sein de la bibliothèque en ligne. Ce sous-projet avait été créé pour répondre à un financement de la fondation ''[[w:en:Graham_Beck|Beck]],'' qui cherchait à promouvoir la production de nouvelles littératures pour des enfants de huit à onze ans<ref>{{Lien web|auteur=Méta-Wiki|titre=Wikijunior/proposal to Beck Foundation|url=https://web.archive.org/web/20150925041619/https://meta.wikimedia.org/wiki/Wikijunior/proposal_to_Beck_Foundation|consulté le=}}.</ref>. Peu de temps après, cette tranche d’âge fut toutefois élargie de zéro à douze ans au niveau du projet francophone, quand le sous-projet y fut adopté<ref>{{Lien web|auteur=Wikilivres|titre=Wikijunior|url=https://web.archive.org/web/20210414045051/https://fr.wikibooks.org/wiki/Wikijunior|consulté le=}}.</ref>. Ces deux évènements témoignent ainsi qu'il est toujours possible qu'un sous-projet apparaisse dans un projet Wikimédia. Comme autre exemple, il y a aussi le WikiJournal, un sous-projet développé au sein du projet Wikiversité en anglais et qui reçu le prix de l’''Open Publishing Awards'' en 2019<ref>{{Lien web|langue=|auteur=Open Publishing Awards|titre=Results|url=https://web.archive.org/web/20201125093419/https://openpublishingawards.org/|site=|consulté le=}}.</ref>. Une demande fut faite pour qu'il puisse bénéficier d'un nouveau site web dans le but de pouvoir se développer en dehors de Wikiversité. Malheureusement pour les initiateurs, la demande est restée sans suite jusqu'à ce jour<ref>{{Lien web|langue=|auteur1=Wikimedia Foundation Wiki|titre=Minutes/2020-02|url=https://web.archive.org/web/20201015115053/https://foundation.wikimedia.org/wiki/Minutes/2020-02#WikiJournal|site=|éditeur=|date=|consulté le=}}.</ref>, après que le conseil d’administration de la Fondation, chargé de répondre à leur demande, considéra que le projet n’était pas suffisamment abouti. Il faut savoir qu'avant cela, le projet Wikiversité, dans lequel est né Wikijournal, avait lui-même été un sous-projet du projet Wikibook<ref>{{Lien web|auteur=Méta-Wiki|titre=Talk:Wikiversity/Old|url=https://web.archive.org/web/20130723232149/http://meta.wikimedia.org/wiki/Talk:Wikiversity/Old|date=|consulté le=}}.</ref>. Initialement, il visait à « créer une communauté de personnes qui se soutiennent mutuellement dans leurs efforts éducatifs<ref>Texte original avant sa traduction par www.deepl.com/translator : « ''create a community of people who support each other in their educational endeavors »''</ref> »<ref>{{Lien web|auteur=Wikibooks|titre=Wikiversity|url=https://web.archive.org/web/20210506184146/https://en.wikibooks.org/wiki/Wikiversity|date=|consulté le=}}.</ref>. Cependant, en août 2005, une longue discussion remit en question l’existence du sous-projet Wikiversité dans Wikibooks. Au terme de celle-ci, la décision fut prise de transférer Wikiversité et son contenu sur le site Méta-Wiki<ref name="Wikibooks">{{Lien web|titre=Wikibooks:Requests for deletion/Wikiversity|url=https://en.wikibooks.org/w/index.php?title=Wikibooks:Requests_for_deletion/Wikiversity&oldid=3490139|auteur=Wikibooks|consulté le=}}.</ref>, là où de nouvelles discussions ont abouti à l’idée de faire de Wikiversité un nouveau projet indépendant<ref>{{Lien web|titre=Wikiversity|url=https://meta.wikimedia.org/w/index.php?title=Wikiversity&oldid=232819|auteur=Méta-Wiki|date=|consulté le=}}.</ref>. Déjà à l'époque, le conseil d’administration de la Fondation Wikimédia se montrait réticent à l'ouverture de nouveaux projets, et sa réaction fut de demander l'ouverture d'un sondage au sein de la communauté. Celui-ci devait rassembler une majorité des deux tiers en faveur de l'ouverture du nouveau site web<ref>{{Lien web|titre=Wikiversity/Vote/fr|url=https://meta.wikimedia.org/w/index.php?title=Wikiversity/Vote/fr&oldid=316555|consulté le=|auteur=Méta-Wiki}}.</ref>. Un résultat qui fut finalement obtenu, mais pas sans de longs débats, dont voici un extrait<ref name="Wikibooks" />. <blockquote> La principale raison pour laquelle la Fondation Wikimédia ne veut pas "lâcher le morceau" est une simple question de bureaucratie et la crainte que le projet ne devienne une autre Wikispecies [un projet de répertoire du vivant]. Wikispecies est une idée cool, mais les "fondateurs" du projet se sont dégonflés à mi-chemin de la mise en place du contenu et ont décidé de faire une révision majeure qui a pris plus de temps que ce que tout le monde était prêt à mettre. Le même problème s’applique à Wikiversity en ce qui concerne la Fondation, parce que les objectifs et les buts de ce projet ne sont pas clairement définis, et il semble que les participants essaient de mordre plus qu’ils ne peuvent mâcher en proposant une université de recherche multi-collèges entière (avec un statut de recherche et une accréditation) à former de toutes pièces plutôt qu’un simple centre d’éducation pour adultes avec quelques classes.<ref>Texte original avant sa traduction par deepl.com/translator : « ''The main reason why the Wikimedia Foundation doesn't want to "turn it loose" is pure bureaucratic BS and a fear that it will turn into another Wikispecies. Wikispecies is a cool idea, but the "founders" of the project got cold feet part-way into putting in content and decided to do a major revision that took more time than anybody was willing to put into it. The same issue applies to Wikiversity so far as the Foundation is concerned, because the goals and purposes of this project are not clearly defined, and it seems like the participants are trying to bite off more than they can chew by proposing an entire multi-college research university (with Carnegie-Mellon research status and accreditation as well) to be formed out of whole cloth rather than a simple adult education center with a few classes. If more thought is done on how to "bootstrap" this whole project, perhaps some thoughts on how to convince the Foundation board to let a separate wiki be kicked loose to let this project try to develop on its own can be made''.--Rob Horning 11:21, 14 August 2005 (UTC) »</ref> </blockquote> En novembre 2005 et malgré les résultats du sondage, l'indépendance du projet Wikiversité ne fut toutefois pas acceptée par cinq membres du conseil. Ceux-ci réclamaient une « réécriture de la proposition pour en exclure la remise de titre de compétence, la conduite de cours en ligne, et de clarifier le concept de plate-forme ''e-learning''<ref>Texte original avant sa traduction par www.deepl.com/translator : « ''rewriting the proposal to'' ''exclude credentials, exclude online-courses and clarify the concept of elearning platform'' »</ref> »<ref>{{Lien web|auteur=Wikimedia Foundation Wiki|titre=Meetings/November 13, 2005|url=https://foundation.wikimedia.org/w/index.php?title=Meetings/November_13,_2005&oldid=118181|consulté le=}}.</ref>. Quand ces rectifications furent faites, le projet bénéficia d'une période d’essai de plusieurs mois, jusqu'à ce que les amendements apportés au projet de départ soient enfin acceptés, le 31 juillet 2006. Ce long temps d'attente était justifié par la création du ''[[m:Special_projects_committee|special projects committee]]''<ref>{{Lien web|titre=Wikiversity/Modified project proposal|url=https://meta.wikimedia.org/w/index.php?title=Wikiversity/Modified_project_proposal&oldid=395364#Scope_of_Wikiversity%20scope|auteur=Méta-Wiki|consulté le=}}.</ref>'','' qui, jusqu'en décembre 2021, fut chargé de soulager le conseil d’administration de la fondation, par rapport aux demandes de création de nouveaux projets Wikimédia<ref>{{Lien web|titre=Difference between revisions of "Special projects committee/Resolutions" - Meta|url=https://meta.wikimedia.org/w/index.php?title=Special_projects_committee/Resolutions&diff=prev&oldid=418944&diffmode=source|auteur=Méta-Wiki|consulté le=}}.</ref>. Un nouveau site, nommé Beta-Wikiversity, fut ainsi créé pour assister le lancement des différentes versions linguistiques de Wikiversité<ref>{{Lien web|titre=Wikiversité|url=https://fr.wikiversity.org/w/index.php?title=Wikiversité:Accueil&oldid=787344|auteur=Wikiversité|consulté le=}}.</ref>. Durant six mois, son premier objectif a d'abord été l'élaboration des lignes directrices concernant la potentialité de produire des recherches collaboratives au sein du projet<ref>Texte original avant sa traduction par www.deepl.com/translator : « ''six months, during which guidelines for further potential uses of the site, including collaborative research, will be developed'' »</ref>. Par la suite, chaque nouvelle version linguistique, développée dans le projet Beta, devait avoir plus de 10 modifications par mois, réalisées par au moins trois personnes distinctes, avant de pouvoir bénéficier de son propre site web. À l'image de Beta-Wikiversity, le projet Wikisource<ref>{{Lien web|auteur=Wikisource|titre=Wikisource|url=https://web.archive.org/web/20210303213629/https://wikisource.org/wiki/Main_Page|date=|consulté le=}}.</ref> possède lui aussi un site indépendant pour lancer ces nouvelles déclinaisons linguistiques. Tandis que pour tous les autres projets pédagogiques, ce lancement s'effectue sur la plateforme [[incubator:Main_Page|Wikimedia Incubator]]<ref>{{Lien web|auteur=Wikimédia Incubator|titre=Welcome to Wikimedia Incubator!|url=https://web.archive.org/web/20210227091859/https://incubator.wikimedia.org/wiki/Incubator:Main_Page|date=|consulté le=}}.</ref>'','' créée à la même époque que Beta-Wikiversity. Cela ne concerne pas les nouveaux projets à proprement parler, dont les origines et processus de création peuvent varier, même si dans tous les cas, leurs lancements au sein du mouvement doivent faire l'objet d'une acceptation par le conseil d'administration de la Fondation Wikimédia. Le projet Wikivoyage, par exemple, fut initialement créé en 2003 dans un Wiki extérieur au mouvement Wikimédia, là où il portait le nom de Wikitravel<ref>{{Lien web|auteur=Giles Turnbull|titre=The DIY travel guide|url=https://web.archive.org/web/20210116050802/http://news.bbc.co.uk/2/hi/uk_news/magazine/3614517.stm|site=BBC News|éditeur=|date=12 avril 2004|consulté le=}}.</ref>. Comme cela arrive parfois, ce projet sans but lucratif fut acheté par une entreprise commerciale en 2006. Mais en raison du changement de gouvernance et de l'apparition de publicités, une scission est apparue au sein de la communauté d’éditeurs. Les personnes désireuses de quitter Wikitravel lancèrent alors un nouveau site appelé Wikivoyage, qui reçut en 2007, le [[w:fr:Webby Award|''Webby Award'']] du meilleur guide de voyage Internet<ref>{{Lien web|auteur=Jake Coyle|titre=On the Net: Web Sites to Travel By|url=https://web.archive.org/web/20210121071600/https://www.foxnews.com/printer_friendly_wires/2007May30/0,4675,OntheNet,00.html|site=Fox News|date=30 mai 2007|consulté le=}}.</ref>. L'intégration de Wikivoyage dans l'écosystème Wikimédia n'a cependant été fait qu'en 2012, à la suite d'un appel à commentaires durant lequel 540 personnes se sont manifestées en faveur de l’intégration du projet, face à 153 oppositions et 6 abstentions <ref>{{Lien web|url=https://web.archive.org/web/20210311055050/https://meta.wikimedia.org/wiki/Requests_for_comment/Travel_Guide|titre=Requests for comment/Travel Guide|auteur=Méta-Wiki|consulté le=}}.</ref>. Comme cette nouvelle déclencha une migration importante depuis Wikitravel vers Wikivoyage, une plainte fut déposée par la société commerciale en charge du premier projet. Celle-ci fut toutefois rejetée et le projet Wikivoyage continua à prendre de l’ampleur au sein du mouvement Wikimédia, avec la création de nouvelles versions linguistiques<ref>{{Lien web|auteur=Steven Musil|titre=Wikimedia, Internet Brands settle Wikivoyage lawsuits|url=https://web.archive.org/web/20211116013544/https://www.cnet.com/tech/services-and-software/wikimedia-internet-brands-settle-wikivoyage-lawsuits/|site=CNET|éditeur=|date=17 février 2013|consulté le=}}.</ref>.[[Fichier:WikiMOOC - vidéo 23 - Les projets frères.webm|vignette|<small>Vidéo 1. Présentation des projets frères dans le cadre du WIKIMOOC 2016.</small>|300x300px|gauche]]Le cas de Wikivoyage apparait toutefois comme une exception, car en général les nouveaux projets émergent des centaines de candidatures déposées sur le projet [[m:Proposals_for_new_projects|Méta-Wiki]]<ref>{{Lien web|auteur=Méta-Wiki|titre=Proposals for new projects|url=https://web.archive.org/web/20211019173812/https://meta.wikimedia.org/wiki/Proposals_for_new_projects|date=|consulté le=}}.</ref>. Celles-ci se soldent bien souvent par un refus, comme ce fut le cas pour le projet WikiLang dont le but était de lancer un laboratoire linguistique<ref>{{Lien web|langue=|auteur=Méta-Wiki|titre=WikiLang|url=https://web.archive.org/web/20210109011449/https://meta.wikimedia.org/wiki/WikiLang|consulté le=}}</ref>. Mais il arrive parfois que de nouveaux projets soient acceptés, comme cela s'est vu en octobre 2012, concernant un projet de lancement d'une base de données structurée et sémantique, que l'on nomme aujourd'hui [[w:Wikidata|Wikidata]] avec ses extensions [[w:Wikibase|Wikibase]], ou plus récemment, en 2020, avec l'arrivée du projet [[w:Abstract_Wikipedia|Abstract Wikipedia]]<ref>{{Lien web|langue=|auteur=Wikimedia Foundation Wiki|titre=Resolution:Abstract Wikipedia|url=https://web.archive.org/web/20201026191716/https://foundation.wikimedia.org/wiki/Resolution:Abstract_Wikipedia|date=|consulté le=}}.</ref> et [[w:Wikifunctions|Wikifunctions]]<ref>{{Lien web|langue=|auteur=Wikimedia Fundation|titre=Resolution:Abstract Wikipedia|url=https://web.archive.org/web/20200703234853/https://foundation.wikimedia.org/wiki/Resolution:Abstract_Wikipedia}}</ref>. Sans vouloir entrer dans les détails, il est intéressant de savoir que l'interconnexion entre ces trois projets permet de traduire automatiquement des articles encyclopédiques dans tous les langages naturels pris en charge par le mouvement Wikimédia. Plus précisément, les phrases des articles publiées sur Abstract Wikipédia, sont formulées par des fonctions informatiques produites dans le projet Wikifunctions, dans le but de traiter les informations de la base de données sémantique Wikidata. Autrement dit, un article dans Abstract Wikipédia ne possède qu'une seule page pour toutes les langues, au même titre que Wikidata ne possède qu'une seule page pour chacune de ses entités, avec pour titre un identifiant unique formé d'une lettre en préfixe, suivie d'un numéro<ref>{{Lien web|langue=|auteur=Thomas Douillard|titre=Abstract Wikipédia - LinuxFr.org|url=https://web.archive.org/web/20200923155546/https://linuxfr.org/news/abstract-wikipedia#fn1|site=Linux Fr|lieu=|date=05/09/20|consulté le=}}.</ref>. À la suite de ces explications, on comprend donc que ce n'est pas la complexité d'un projet qui détermine son refus au sein du mouvement Wikimédia, mais une série d'autres critères comparables à ceux choisis pour supprimer des projets ou versions linguistiques déjà existants. À ce propos, il existe sur Méta-Wiki une liste mise à jour des différents sites hébergés par la Fondation Wikimédia dont l'existence est remise en cause<ref>{{Lien web|auteur=Méta-Wiki|titre=Proposals for closing projects|url=https://web.archive.org/web/20210126030311/https://meta.wikimedia.org/wiki/Proposals_for_closing_projects|date=|consulté le=}}.</ref>. Dans celle-ci, on retrouve essentiellement des versions linguistiques de projets qui n’ont pas réussi à poursuivre leurs développements, bien qu'un projet tout entier soit toujours susceptible d'être mis à l'arrêt. C'est d'ailleurs ce qui est arrivé aux 31 versions linguistiques du projet Wikinews, qui, en date du 4 mai 2026, soit 22 ans après le lancement du site anglophone, ne sont accessibles qu'[[n:fr:Wikinews_ne_sera_plus_mis_à_jour_à_partir_du_4_mai_2026|en mode lecture uniquement]]. Dans le cas de Wikinews, ce fut le manque d'activité qui apparut comme principale justification de la suspension du projet. Cependant, d'autres raisons pourraient être invoquées, comme le prouve cet épisode de 2005, où, peu de temps après son lancement, la version francophone du recueil de citations [[w:Wikiquote|Wikiquote]] a bien risqué de disparaitre. Le projet fut effectivement accusé d’avoir récupéré le contenu d’une base de données soumise à un droit d’auteur restrictif et incompatible avec la licence Creative Commons appliquée sur l’ensemble des projets éditoriaux Wikimédia. Lorsqu'une plainte fut adressée à l’association Wikimédia France, pour être ensuite relayée sur le site Méta-Wiki, il fut bien question de fermer le projet<ref>{{Lien web|auteur1=Méta-Wiki|titre=Wikiquote FR/Closure of French Wikiquote|url=https://web.archive.org/web/20210204052058/https://meta.wikimedia.org/wiki/Wikiquote_FR/Closure_of_French_Wikiquote|date=}}.</ref>. Finalement, celui-ci fut maintenu, mais avec pour conditions de repartir de zéro, et d’établir une charte pour garantir la traçabilité des citations reprises par le projet<ref>{{Lien web|auteur1=Jean-Baptiste Soufron|titre=Redémarrage du Wikiquote Francophone / French Wikiquote Relaunch|url=https://web.archive.org/web/20200506074856/https://lists.wikimedia.org/pipermail/foundation-l/2006-March/019857.html|site=Foundation-l|lieu=|date=30 mars 2006}}.</ref>. Voici donc de quoi se faire une idée sur la manière dont les projets pédagogiques et leurs déclinaisons linguistiques apparaissent et disparaissent au sein du mouvement. Les exemples repris ci-dessus suffisent effectivement pour comprendre les principes généraux qui sous-tendent leurs créations. En dehors de Méta-Wiki, Wikidata, Wikifunctions'','' Abstract Wikipédia et Wikimedia Commons, qui ne sont pas des projets de contenu pédagogique à proprement parler, tous les autres projets semblent effectivement provenir d’un désir de spécialisation d’un projet préexistant. L'idée est généralement débattue dans un projet de même langue, avant d'être relayée vers le site Méta-Wiki, dans le but de discuter de la pertinence d'une candidature, qui doit être aujourd'hui adressée au [[m:Wikimedia_Foundation_Community_Affairs_Committee/Sister_Projects_Task_Force/fr|groupe de travail des projets frères]] du [[m:Wikimedia_Foundation_Community_Affairs_Committee/fr|comité des affaires communautaires de la Fondation Wikimédia]]. Quant aux nouvelles versions linguistiques, elles doivent être soumises actuellement à l'approbation du [[m:Language_committee/fr|comité des langues]], avant de faire preuve de leur capacité de développement, sur les plateformes Incubator, Beta-Wikiversité ou Wikisource Multilingue, dans le but de bénéficier d’un site web indépendant. Avant de clôturer ce chapitre consacré aux projets frères de l'encyclopédie Wikipédia, il reste important de parler de tous les sites qui ont un lien avec le mot Wiki, que ce soit par leur nom ou parce qu'ils utilisent un logiciel Wiki. En 2024, plus de 22 600 étaient répertoriés, dont plus de 95 % n'avaient aucun lien avec le mouvement Wikimédia, en dehors du fait, peut-être, qu’ils utilisaient le logiciel MediaWiki développé par la Fondation Wikimédia<ref>{{Lien web|langue=|auteur=WikiIndex|titre=the index of all wiki|url=https://web.archive.org/web/20240906224607/https://wikiindex.org/Category:All|site=|date=|consulté le=}}.</ref>. [[w:fr:WikiLeaks|WikiLeaks]] par exemple, créé par [[w:fr:Julian Assange|Julian Assange]] dans le but de publier des documents classifiés provenant de sources anonymes, n’est ni un projet Wikimédia, ni un site collaboratif. Comme autre exemple, il y a ensuite le recueil universel et multilingue de guides illustrés [[w:fr:WikiHow|WikiHow]], qui fonctionne pour sa part de manière collaborative et avec le logiciel MediaWiki développé par la Fondation Wikimédia<ref>{{Lien web|auteur=WikiHow|titre=wikiHow:Powered and Inspired by MediaWiki|url=https://web.archive.org/web/20211030092737/https://www.wikihow.com/wikiHow:Powered-and-Inspired-by-MediaWiki|date=|consulté le=}}.</ref>, mais qui n'est pas pour autant en lien avec le mouvement Wikimédia. D'ailleurs, son ergonomie, radicalement différente de celle des projets Wikimédia, permet de le comprendre au premier coup d’œil. L'encyclopédie libre pour enfant [[w:fr:Wikimini|Wikimini]], en revanche, a une apparence très proche de celle des projets Wikimédia. Mais contrairement au souhait de son fondateur, le projet n’aura jamais été accepté par la Fondation Wikimédia. Quant aux projets [[w:fr:WikiTribune|WikiTribune]] et [[w:fr:Wikia|Fandom]], ceux-ci entretiennent encore une plus grande ambiguïté, puisqu'ils ont été créés par [[w:fr:Jimmy Wales|Jimmy Wales]], le fondateur de Wikipédia et de la [[w:fr:Wikimedia Foundation|Fondation Wikimédia]]<ref>{{Lien web|langue=|auteur=Terry Collins|titre=Wikipedia co-founder launches project to fight fake news|url=https://web.archive.org/web/20201014061304/https://www.cnet.com/news/wikipedia-jimmy-wales-wikitribune-fighting-fake-news/|site=CNET|date=24 avril 2017|consulté le=}}.</ref>, mais à des fins commerciales et lucratives. Ce qui les rend incompatibles avec le mouvement Wikimédia. Au terme de ces explications concernant l'apparition des projets frères, il reste à signaler que certains projets et versions linguistiques ont été créés bien avant que la perception du mouvement Wikimédia fasse son apparition. C'est en effet suite au brassage de projets, de langues et de cultures différentes, et grâce à une conscience collective, manifestée par une coordination centrale de toutes les activités via la plateforme Méta-Wiki, que l'idée d'un mouvement social a pris du sens au sein des activités Wikimédia. En ce sens, la naissance du mouvement ne fut pas un événement ponctuel, mais plutôt la réalisation d’un long processus de conscientisation. {| class="wikitable"style="margin: auto;" "text-align:center;" |+Codes QR |[[Fichier:QR code page meta news.png|centré|sans_cadre|100x100px|lien=https://meta.wikimedia.org/wiki/Wikimedia_News]] |[[Fichier:QR-Code ligne du temps projets Wikimédia.png|centré|sans_cadre|100x100px|lien=https://upload.wikimedia.org/wikipedia/commons/b/b0/WikipediaTimeline.png]] |[[Fichier:Code QR vidéo présentation projets frères WikiMooc 2016.svg|centré|sans_cadre|100x100px]] |- |<small>Meta-Wiki</small> |<small>Ligne du temps</small> |<small>Vidéo projets frères</small> |} {{AutoCat}} s021zij4muf6uojr76px4f1677f7rul 764667 764666 2026-04-23T16:01:46Z Lionel Scheepmans 20012 764667 wikitext text/x-wiki <noinclude>{{Le mouvement Wikimédia}}</noinclude> Dans le but de développer des contenus pédagogiques qui ne trouvaient pas leur place dans Wikipédia, d’autres projets pédagogiques et collaboratifs ont vu le jour, pour former ce que l'on appelle couramment aujourd'hui : l’écosystème Wikimedia. La naissance de tous ces projets, ainsi que les évènements importants qui ont contribué au développement du mouvement, ont été repris dans [[c:File:WikipediaTimeline.png|ligne du temps]] réalisée par [[m:user:Guillom|Guillaume Paumier]] à l’occasion du dixième anniversaire de Wikipédia. Dans ce graphique, qui peut être complété par [[m:Wikimedia News|une page web]] régulièrement mise à jour, on peut ainsi voir en détail l'évolution du nombre de projets, de versions linguistiques, de contributeurs et d'articles, pour avoir une vie précise de la vitesse à laquelle s'est développé le mouvement Wikimédia.[[Fichier:Wikimedia logo family complete-2022.svg|alt=Logo du mouvement Wikimédia entouré de 15 autres logos de projets actifs en son sein|vignette|<small>Figure 18. Logo de la Fondation Wikimédia entouré de 15 autres logos de projets actifs au sein du mouvement.</small>|300x300px]]Parmi tous les projets frères de Wikipédia, le premier apparu fut Méta-Wiki, une plateforme de référence pour centraliser la gestion de l'ensemble des sites web hébergés par la fondation Wikimédia. Dans un premier temps, cet espace communautaire en ligne a répondu à la nécessité de traiter en un seul lieu les questions communes aux différentes versions linguistiques de Wikipédia. Aujourd'hui, le site web est le principal endroit de coordination et de gestion de l'ensemble du mouvement Wikimédia. On y retrouve énormément d'informations au sujet des projets en ligne, mais aussi, et surtout, concernant la Fondation et tous les organismes affiliés. Après Méta-Wiki, sept autres projets de partage de la connaissance ont fait leur apparition, avant d'être déclinés à leur tour en plusieurs versions linguistiques. Tous ces projets émergent en général sur l’initiative d’un petit groupe de personnes actives au sein d’un projet préexistant. Ce fut le cas du projet Wiktionnaire en anglais, le deuxième projet à voir le jour après Méta-Wiki, en décembre 2002, soit deux ans avant la version francophone apparue en mars 2004<ref>{{Lien web|auteur=Wiktionnaire|titre=Wiktionnaire:Historique du Wiktionnaire|url=https://web.archive.org/web/20200416091043/https://fr.wiktionary.org/wiki/Wiktionnaire:Historique_du_Wiktionnaire|consulté le=}}.</ref>. Il est intéressant d'observer que la version francophone du Wiktionnaire n’a pas été créée à partir du projet anglophone, mais bien depuis le projet Wikipédia en français. D'ailleurs, on peut retrouver dans les archives de ce dernier projet, tout un débat concernant la pertinence de cette création, dont voici un extrait. <blockquote> En fait, ce qui me peine vraiment avec le projet Wiktionary, c’est que alors qu’on essaie de rassembler les gens (pas facile) pour créer une sorte de tour de Babel de la connaissance (tâche bien longue et difficile), ce nouveau projet va disperser les énergies pour une raison qui ne me semble pas valable. C’est la création de Wiktionary qui va créer des redondances. À mon avis il existera rapidement des pages sur le même mot, mais ne contenant pas les mêmes informations. Pour quelle raison ces connaissances devraient-elles être séparées ? Les encyclopédies sur papier devaient faire des choix à cause du manque de place, mais nous, pourquoi le ferions-nous ??? "Wikipédia n’est pas un dictionnaire" n’est pas un argument à mon avis... si vraiment c’était pas un dictionnaire, il faudrait virer tout un tas d’article. Je ne comprends vraiment pas cette volonté de séparer la connaissance entre ce qui est "encyclopédique" et ce qui n’est "qu’une définition". [Réponse] Pour moi ce qu’est Wiktionary, c’est une partie de Wikipédia s’intéressant plus particulièrement aux aspects linguistiques des mots. La différence que je verrais entre la partie ''dictionnaire'' de Wikipédia et sa partie dite encyclopédique, c’est que la partie dictionnaire s’intéresserait au sens des mots eux-mêmes alors que la partie encyclopédie s’attache plus à faire ressortir un état des connaissances à un moment donné. Le pourquoi de la séparation d’avec la partie encyclopédie tient plus à des raisons techniques qu’à une volonté de monter un projet indépendant, en effet, à mon humble avis, un dictionnaire nécessite une plus grande rigueur (de présentation) qu’une encyclopédie. Ceci entraîne beaucoup de problème et entre autres le choix de la mise en forme des articles du dictionnaire.<ref>{{Lien web|auteur=Wiktionnaire|titre=Wiktionnaire:Historique du Wiktionnaire/Discussion Wikipédia:Wiktionary|url=https://web.archive.org/web/20140831102908/http://fr.wiktionary.org/wiki/Wiktionnaire:Historique_du_Wiktionnaire/Discussion_Wikip%C3%A9dia:Wiktionary|date=|consulté le=}}.</ref> </blockquote> Créer un nouveau projet, c’est effectivement créer un nouveau site web, qui devra faire l’objet d’une nouvelle gestion, tant pour les serveurs de la Fondation, que pour la nouvelle communauté de contributeurs. L’importation de pages d’un projet à l'autre ou la traduction de celles-ci sont bien sûr toujours possibles, mais cela duplique alors aussi la maintenance et les mises à jour. Le choix de scinder un projet, en faveur d’une plus grande liberté, comporte donc certains coûts humains et financiers. Ce prix à payer n'a pour autant pas empêché le projet anglophone Wikibooks de faire son apparition le 10 juin 2003, soit près d’un an avant Wikilivres, la version francophone du projet, apparue le 22 juillet 2004. Cette dernière création avait de nouveau été débattue au sein de la communauté Wikipédia en français, et non pas dans le Wikibooks en anglais. Quant à l'objectif commun aux deux projets linguistiques, il était de créer une « bibliothèque de livres pédagogiques libres que chacun peut améliorer »<ref>{{Lien web|auteur=Wikilivres|url=https://fr.wikibooks.org/w/index.php?title=Accueil&oldid=586825|titre=Acceuil|consulté le=}}.</ref>. Environ un an après la création du projet en anglais, un nouvel [[w:fr:Espace de noms|espace de noms]] intitulé Wikijunior fut mis en place au sein de la bibliothèque en ligne. Ce sous-projet avait été créé pour répondre à un financement de la fondation ''[[w:en:Graham_Beck|Beck]],'' qui cherchait à promouvoir la production de nouvelles littératures pour des enfants de huit à onze ans<ref>{{Lien web|auteur=Méta-Wiki|titre=Wikijunior/proposal to Beck Foundation|url=https://web.archive.org/web/20150925041619/https://meta.wikimedia.org/wiki/Wikijunior/proposal_to_Beck_Foundation|consulté le=}}.</ref>. Peu de temps après, cette tranche d’âge fut toutefois élargie de zéro à douze ans au niveau du projet francophone, quand le sous-projet y fut adopté<ref>{{Lien web|auteur=Wikilivres|titre=Wikijunior|url=https://web.archive.org/web/20210414045051/https://fr.wikibooks.org/wiki/Wikijunior|consulté le=}}.</ref>. Ces deux évènements témoignent ainsi qu'il est toujours possible qu'un sous-projet apparaisse dans un projet Wikimédia. Comme autre exemple, il y a aussi le WikiJournal, un sous-projet développé au sein du projet Wikiversité en anglais et qui reçu le prix de l’''Open Publishing Awards'' en 2019<ref>{{Lien web|langue=|auteur=Open Publishing Awards|titre=Results|url=https://web.archive.org/web/20201125093419/https://openpublishingawards.org/|site=|consulté le=}}.</ref>. Une demande fut faite pour qu'il puisse bénéficier d'un nouveau site web dans le but de pouvoir se développer en dehors de Wikiversité. Malheureusement pour les initiateurs, la demande est restée sans suite jusqu'à ce jour<ref>{{Lien web|langue=|auteur1=Wikimedia Foundation Wiki|titre=Minutes/2020-02|url=https://web.archive.org/web/20201015115053/https://foundation.wikimedia.org/wiki/Minutes/2020-02#WikiJournal|site=|éditeur=|date=|consulté le=}}.</ref>, après que le conseil d’administration de la Fondation, chargé de répondre à leur demande, considéra que le projet n’était pas suffisamment abouti. Il faut savoir qu'avant cela, le projet Wikiversité, dans lequel est né Wikijournal, avait lui-même été un sous-projet du projet Wikibook<ref>{{Lien web|auteur=Méta-Wiki|titre=Talk:Wikiversity/Old|url=https://web.archive.org/web/20130723232149/http://meta.wikimedia.org/wiki/Talk:Wikiversity/Old|date=|consulté le=}}.</ref>. Initialement, il visait à « créer une communauté de personnes qui se soutiennent mutuellement dans leurs efforts éducatifs<ref>Texte original avant sa traduction par www.deepl.com/translator : « ''create a community of people who support each other in their educational endeavors »''</ref> »<ref>{{Lien web|auteur=Wikibooks|titre=Wikiversity|url=https://web.archive.org/web/20210506184146/https://en.wikibooks.org/wiki/Wikiversity|date=|consulté le=}}.</ref>. Cependant, en août 2005, une longue discussion remit en question l’existence du sous-projet Wikiversité dans Wikibooks. Au terme de celle-ci, la décision fut prise de transférer Wikiversité et son contenu sur le site Méta-Wiki<ref name="Wikibooks">{{Lien web|titre=Wikibooks:Requests for deletion/Wikiversity|url=https://en.wikibooks.org/w/index.php?title=Wikibooks:Requests_for_deletion/Wikiversity&oldid=3490139|auteur=Wikibooks|consulté le=}}.</ref>, là où de nouvelles discussions ont abouti à l’idée de faire de Wikiversité un nouveau projet indépendant<ref>{{Lien web|titre=Wikiversity|url=https://meta.wikimedia.org/w/index.php?title=Wikiversity&oldid=232819|auteur=Méta-Wiki|date=|consulté le=}}.</ref>. Déjà à l'époque, le conseil d’administration de la Fondation Wikimédia se montrait réticent à l'ouverture de nouveaux projets, et sa réaction fut de demander l'ouverture d'un sondage au sein de la communauté. Celui-ci devait rassembler une majorité des deux tiers en faveur de l'ouverture du nouveau site web<ref>{{Lien web|titre=Wikiversity/Vote/fr|url=https://meta.wikimedia.org/w/index.php?title=Wikiversity/Vote/fr&oldid=316555|consulté le=|auteur=Méta-Wiki}}.</ref>. Un résultat qui fut finalement obtenu, mais pas sans de longs débats, dont voici un extrait<ref name="Wikibooks" />. <blockquote> La principale raison pour laquelle la Fondation Wikimédia ne veut pas "lâcher le morceau" est une simple question de bureaucratie et la crainte que le projet ne devienne une autre Wikispecies [un projet de répertoire du vivant]. Wikispecies est une idée cool, mais les "fondateurs" du projet se sont dégonflés à mi-chemin de la mise en place du contenu et ont décidé de faire une révision majeure qui a pris plus de temps que ce que tout le monde était prêt à mettre. Le même problème s’applique à Wikiversity en ce qui concerne la Fondation, parce que les objectifs et les buts de ce projet ne sont pas clairement définis, et il semble que les participants essaient de mordre plus qu’ils ne peuvent mâcher en proposant une université de recherche multi-collèges entière (avec un statut de recherche et une accréditation) à former de toutes pièces plutôt qu’un simple centre d’éducation pour adultes avec quelques classes.<ref>Texte original avant sa traduction par deepl.com/translator : « ''The main reason why the Wikimedia Foundation doesn't want to "turn it loose" is pure bureaucratic BS and a fear that it will turn into another Wikispecies. Wikispecies is a cool idea, but the "founders" of the project got cold feet part-way into putting in content and decided to do a major revision that took more time than anybody was willing to put into it. The same issue applies to Wikiversity so far as the Foundation is concerned, because the goals and purposes of this project are not clearly defined, and it seems like the participants are trying to bite off more than they can chew by proposing an entire multi-college research university (with Carnegie-Mellon research status and accreditation as well) to be formed out of whole cloth rather than a simple adult education center with a few classes. If more thought is done on how to "bootstrap" this whole project, perhaps some thoughts on how to convince the Foundation board to let a separate wiki be kicked loose to let this project try to develop on its own can be made''.--Rob Horning 11:21, 14 August 2005 (UTC) »</ref> </blockquote> En novembre 2005 et malgré les résultats du sondage, l'indépendance du projet Wikiversité ne fut toutefois pas acceptée par cinq membres du conseil. Ceux-ci réclamaient une « réécriture de la proposition pour en exclure la remise de titre de compétence, la conduite de cours en ligne, et de clarifier le concept de plate-forme ''e-learning''<ref>Texte original avant sa traduction par www.deepl.com/translator : « ''rewriting the proposal to'' ''exclude credentials, exclude online-courses and clarify the concept of elearning platform'' »</ref> »<ref>{{Lien web|auteur=Wikimedia Foundation Wiki|titre=Meetings/November 13, 2005|url=https://foundation.wikimedia.org/w/index.php?title=Meetings/November_13,_2005&oldid=118181|consulté le=}}.</ref>. Quand ces rectifications furent faites, le projet bénéficia d'une période d’essai de plusieurs mois, jusqu'à ce que les amendements apportés au projet de départ soient enfin acceptés, le 31 juillet 2006. Ce long temps d'attente était justifié par la création du ''[[m:Special_projects_committee|special projects committee]]''<ref>{{Lien web|titre=Wikiversity/Modified project proposal|url=https://meta.wikimedia.org/w/index.php?title=Wikiversity/Modified_project_proposal&oldid=395364#Scope_of_Wikiversity%20scope|auteur=Méta-Wiki|consulté le=}}.</ref>'','' qui, jusqu'en décembre 2021, fut chargé de soulager le conseil d’administration de la fondation, par rapport aux demandes de création de nouveaux projets Wikimédia<ref>{{Lien web|titre=Difference between revisions of "Special projects committee/Resolutions" - Meta|url=https://meta.wikimedia.org/w/index.php?title=Special_projects_committee/Resolutions&diff=prev&oldid=418944&diffmode=source|auteur=Méta-Wiki|consulté le=}}.</ref>. Un nouveau site, nommé Beta-Wikiversity, fut ainsi créé pour assister le lancement des différentes versions linguistiques de Wikiversité<ref>{{Lien web|titre=Wikiversité|url=https://fr.wikiversity.org/w/index.php?title=Wikiversité:Accueil&oldid=787344|auteur=Wikiversité|consulté le=}}.</ref>. Durant six mois, son premier objectif a d'abord été l'élaboration des lignes directrices concernant la potentialité de produire des recherches collaboratives au sein du projet<ref>Texte original avant sa traduction par www.deepl.com/translator : « ''six months, during which guidelines for further potential uses of the site, including collaborative research, will be developed'' »</ref>. Par la suite, chaque nouvelle version linguistique, développée dans le projet Beta, devait avoir plus de 10 modifications par mois, réalisées par au moins trois personnes distinctes, avant de pouvoir bénéficier de son propre site web. À l'image de Beta-Wikiversity, le projet Wikisource<ref>{{Lien web|auteur=Wikisource|titre=Wikisource|url=https://web.archive.org/web/20210303213629/https://wikisource.org/wiki/Main_Page|date=|consulté le=}}.</ref> possède lui aussi un site indépendant pour lancer ces nouvelles déclinaisons linguistiques. Tandis que pour tous les autres projets pédagogiques, ce lancement s'effectue sur la plateforme [[incubator:Main_Page|Wikimedia Incubator]]<ref>{{Lien web|auteur=Wikimédia Incubator|titre=Welcome to Wikimedia Incubator!|url=https://web.archive.org/web/20210227091859/https://incubator.wikimedia.org/wiki/Incubator:Main_Page|date=|consulté le=}}.</ref>'','' créée à la même époque que Beta-Wikiversity. Cela ne concerne pas les nouveaux projets à proprement parler, dont les origines et processus de création peuvent varier, même si dans tous les cas, leurs lancements au sein du mouvement doivent faire l'objet d'une acceptation par le conseil d'administration de la Fondation Wikimédia. Le projet Wikivoyage, par exemple, fut initialement créé en 2003 dans un Wiki extérieur au mouvement Wikimédia, là où il portait le nom de Wikitravel<ref>{{Lien web|auteur=Giles Turnbull|titre=The DIY travel guide|url=https://web.archive.org/web/20210116050802/http://news.bbc.co.uk/2/hi/uk_news/magazine/3614517.stm|site=BBC News|éditeur=|date=12 avril 2004|consulté le=}}.</ref>. Comme cela arrive parfois, ce projet sans but lucratif fut acheté par une entreprise commerciale en 2006. Mais en raison du changement de gouvernance et de l'apparition de publicités, une scission est apparue au sein de la communauté d’éditeurs. Les personnes désireuses de quitter Wikitravel lancèrent alors un nouveau site appelé Wikivoyage, qui reçut en 2007, le [[w:fr:Webby Award|''Webby Award'']] du meilleur guide de voyage Internet<ref>{{Lien web|auteur=Jake Coyle|titre=On the Net: Web Sites to Travel By|url=https://web.archive.org/web/20210121071600/https://www.foxnews.com/printer_friendly_wires/2007May30/0,4675,OntheNet,00.html|site=Fox News|date=30 mai 2007|consulté le=}}.</ref>. L'intégration de Wikivoyage dans l'écosystème Wikimédia n'a cependant été fait qu'en 2012, à la suite d'un appel à commentaires durant lequel 540 personnes se sont manifestées en faveur de l’intégration du projet, face à 153 oppositions et 6 abstentions <ref>{{Lien web|url=https://web.archive.org/web/20210311055050/https://meta.wikimedia.org/wiki/Requests_for_comment/Travel_Guide|titre=Requests for comment/Travel Guide|auteur=Méta-Wiki|consulté le=}}.</ref>. Comme cette nouvelle déclencha une migration importante depuis Wikitravel vers Wikivoyage, une plainte fut déposée par la société commerciale en charge du premier projet. Celle-ci fut toutefois rejetée et le projet Wikivoyage continua à prendre de l’ampleur au sein du mouvement Wikimédia, avec la création de nouvelles versions linguistiques<ref>{{Lien web|auteur=Steven Musil|titre=Wikimedia, Internet Brands settle Wikivoyage lawsuits|url=https://web.archive.org/web/20211116013544/https://www.cnet.com/tech/services-and-software/wikimedia-internet-brands-settle-wikivoyage-lawsuits/|site=CNET|éditeur=|date=17 février 2013|consulté le=}}.</ref>.[[Fichier:WikiMOOC - vidéo 23 - Les projets frères.webm|vignette|<small>Vidéo 1. Présentation des projets frères dans le cadre du WIKIMOOC 2016.</small>|300x300px|gauche]]Le cas de Wikivoyage apparait toutefois comme une exception, car en général les nouveaux projets émergent des centaines de candidatures déposées sur le projet [[m:Proposals_for_new_projects|Méta-Wiki]]<ref>{{Lien web|auteur=Méta-Wiki|titre=Proposals for new projects|url=https://web.archive.org/web/20211019173812/https://meta.wikimedia.org/wiki/Proposals_for_new_projects|date=|consulté le=}}.</ref>. Celles-ci se soldent bien souvent par un refus, comme ce fut le cas pour le projet WikiLang dont le but était de lancer un laboratoire linguistique<ref>{{Lien web|langue=|auteur=Méta-Wiki|titre=WikiLang|url=https://web.archive.org/web/20210109011449/https://meta.wikimedia.org/wiki/WikiLang|consulté le=}}</ref>. Mais il arrive parfois que de nouveaux projets soient acceptés, comme cela s'est vu en octobre 2012, concernant un projet de lancement d'une base de données structurée et sémantique, que l'on nomme aujourd'hui [[w:Wikidata|Wikidata]] avec ses extensions [[w:Wikibase|Wikibase]], ou plus récemment, en 2020, avec l'arrivée du projet [[w:Abstract_Wikipedia|Abstract Wikipedia]]<ref>{{Lien web|langue=|auteur=Wikimedia Foundation Wiki|titre=Resolution:Abstract Wikipedia|url=https://web.archive.org/web/20201026191716/https://foundation.wikimedia.org/wiki/Resolution:Abstract_Wikipedia|date=|consulté le=}}.</ref> et [[w:Wikifunctions|Wikifunctions]]<ref>{{Lien web|langue=|auteur=Wikimedia Fundation|titre=Resolution:Abstract Wikipedia|url=https://web.archive.org/web/20200703234853/https://foundation.wikimedia.org/wiki/Resolution:Abstract_Wikipedia}}</ref>. Sans vouloir entrer dans les détails, il est intéressant de savoir que l'interconnexion entre ces trois projets permet de traduire automatiquement des articles encyclopédiques dans tous les langages naturels pris en charge par le mouvement Wikimédia. Plus précisément, les phrases des articles publiées sur Abstract Wikipédia, sont formulées par des fonctions informatiques produites dans le projet Wikifunctions, dans le but de traiter les informations de la base de données sémantique Wikidata. Autrement dit, un article dans Abstract Wikipédia ne possède qu'une seule page pour toutes les langues, au même titre que Wikidata ne possède qu'une seule page pour chacune de ses entités, avec pour titre un identifiant unique formé d'une lettre en préfixe, suivie d'un numéro<ref>{{Lien web|langue=|auteur=Thomas Douillard|titre=Abstract Wikipédia - LinuxFr.org|url=https://web.archive.org/web/20200923155546/https://linuxfr.org/news/abstract-wikipedia#fn1|site=Linux Fr|lieu=|date=05/09/20|consulté le=}}.</ref>. À la suite de ces explications, on comprend donc que ce n'est pas la complexité d'un projet qui détermine son refus au sein du mouvement Wikimédia, mais une série d'autres critères comparables à ceux choisis pour supprimer des projets ou versions linguistiques déjà existants. À ce propos, il existe sur Méta-Wiki une liste mise à jour des différents sites hébergés par la Fondation Wikimédia dont l'existence est remise en cause<ref>{{Lien web|auteur=Méta-Wiki|titre=Proposals for closing projects|url=https://web.archive.org/web/20210126030311/https://meta.wikimedia.org/wiki/Proposals_for_closing_projects|date=|consulté le=}}.</ref>. Dans celle-ci, on retrouve essentiellement des versions linguistiques de projets qui n’ont pas réussi à poursuivre leurs développements, bien qu'un projet tout entier soit toujours susceptible d'être mis à l'arrêt. C'est d'ailleurs ce qui est arrivé aux 31 versions linguistiques du projet Wikinews, qui, en date du 4 mai 2026, soit 22 ans après le lancement du site anglophone, ne sont accessibles qu'[[n:fr:Wikinews_ne_sera_plus_mis_à_jour_à_partir_du_4_mai_2026|en mode lecture uniquement]]<ref>{{Lien web|auteur=Wikinews|titre=Wikinews ne sera plus mis à jour à partir du 4 mai 2026|url=https://web.archive.org/web/20260413082226/https://fr.wikinews.org/wiki/Wikinews_ne_sera_plus_mis_%C3%A0_jour_%C3%A0_partir_du_4_mai_2026}}.</ref>. Dans le cas de Wikinews, ce fut le manque d'activité qui apparut comme principale justification de la suspension du projet. Cependant, d'autres raisons pourraient être invoquées, comme le prouve cet épisode de 2005, où, peu de temps après son lancement, la version francophone du recueil de citations [[w:Wikiquote|Wikiquote]] a bien risqué de disparaitre. Le projet fut effectivement accusé d’avoir récupéré le contenu d’une base de données soumise à un droit d’auteur restrictif et incompatible avec la licence Creative Commons appliquée sur l’ensemble des projets éditoriaux Wikimédia. Lorsqu'une plainte fut adressée à l’association Wikimédia France, pour être ensuite relayée sur le site Méta-Wiki, il fut bien question de fermer le projet<ref>{{Lien web|auteur1=Méta-Wiki|titre=Wikiquote FR/Closure of French Wikiquote|url=https://web.archive.org/web/20210204052058/https://meta.wikimedia.org/wiki/Wikiquote_FR/Closure_of_French_Wikiquote|date=}}.</ref>. Finalement, celui-ci fut maintenu, mais avec pour conditions de repartir de zéro, et d’établir une charte pour garantir la traçabilité des citations reprises par le projet<ref>{{Lien web|auteur1=Jean-Baptiste Soufron|titre=Redémarrage du Wikiquote Francophone / French Wikiquote Relaunch|url=https://web.archive.org/web/20200506074856/https://lists.wikimedia.org/pipermail/foundation-l/2006-March/019857.html|site=Foundation-l|lieu=|date=30 mars 2006}}.</ref>. Voici donc de quoi se faire une idée sur la manière dont les projets pédagogiques et leurs déclinaisons linguistiques apparaissent et disparaissent au sein du mouvement. Les exemples repris ci-dessus suffisent effectivement pour comprendre les principes généraux qui sous-tendent leurs créations. En dehors de Méta-Wiki, Wikidata, Wikifunctions'','' Abstract Wikipédia et Wikimedia Commons, qui ne sont pas des projets de contenu pédagogique à proprement parler, tous les autres projets semblent effectivement provenir d’un désir de spécialisation d’un projet préexistant. L'idée est généralement débattue dans un projet de même langue, avant d'être relayée vers le site Méta-Wiki, dans le but de discuter de la pertinence d'une candidature, qui doit être aujourd'hui adressée au [[m:Wikimedia_Foundation_Community_Affairs_Committee/Sister_Projects_Task_Force/fr|groupe de travail des projets frères]] du [[m:Wikimedia_Foundation_Community_Affairs_Committee/fr|comité des affaires communautaires de la Fondation Wikimédia]]. Quant aux nouvelles versions linguistiques, elles doivent être soumises actuellement à l'approbation du [[m:Language_committee/fr|comité des langues]], avant de faire preuve de leur capacité de développement, sur les plateformes Incubator, Beta-Wikiversité ou Wikisource Multilingue, dans le but de bénéficier d’un site web indépendant. Avant de clôturer ce chapitre consacré aux projets frères de l'encyclopédie Wikipédia, il reste important de parler de tous les sites qui ont un lien avec le mot Wiki, que ce soit par leur nom ou parce qu'ils utilisent un logiciel Wiki. En 2024, plus de 22 600 étaient répertoriés, dont plus de 95 % n'avaient aucun lien avec le mouvement Wikimédia, en dehors du fait, peut-être, qu’ils utilisaient le logiciel MediaWiki développé par la Fondation Wikimédia<ref>{{Lien web|langue=|auteur=WikiIndex|titre=the index of all wiki|url=https://web.archive.org/web/20240906224607/https://wikiindex.org/Category:All|site=|date=|consulté le=}}.</ref>. [[w:fr:WikiLeaks|WikiLeaks]] par exemple, créé par [[w:fr:Julian Assange|Julian Assange]] dans le but de publier des documents classifiés provenant de sources anonymes, n’est ni un projet Wikimédia, ni un site collaboratif. Comme autre exemple, il y a ensuite le recueil universel et multilingue de guides illustrés [[w:fr:WikiHow|WikiHow]], qui fonctionne pour sa part de manière collaborative et avec le logiciel MediaWiki développé par la Fondation Wikimédia<ref>{{Lien web|auteur=WikiHow|titre=wikiHow:Powered and Inspired by MediaWiki|url=https://web.archive.org/web/20211030092737/https://www.wikihow.com/wikiHow:Powered-and-Inspired-by-MediaWiki|date=|consulté le=}}.</ref>, mais qui n'est pas pour autant en lien avec le mouvement Wikimédia. D'ailleurs, son ergonomie, radicalement différente de celle des projets Wikimédia, permet de le comprendre au premier coup d’œil. L'encyclopédie libre pour enfant [[w:fr:Wikimini|Wikimini]], en revanche, a une apparence très proche de celle des projets Wikimédia. Mais contrairement au souhait de son fondateur, le projet n’aura jamais été accepté par la Fondation Wikimédia. Quant aux projets [[w:fr:WikiTribune|WikiTribune]] et [[w:fr:Wikia|Fandom]], ceux-ci entretiennent encore une plus grande ambiguïté, puisqu'ils ont été créés par [[w:fr:Jimmy Wales|Jimmy Wales]], le fondateur de Wikipédia et de la [[w:fr:Wikimedia Foundation|Fondation Wikimédia]]<ref>{{Lien web|langue=|auteur=Terry Collins|titre=Wikipedia co-founder launches project to fight fake news|url=https://web.archive.org/web/20201014061304/https://www.cnet.com/news/wikipedia-jimmy-wales-wikitribune-fighting-fake-news/|site=CNET|date=24 avril 2017|consulté le=}}.</ref>, mais à des fins commerciales et lucratives. Ce qui les rend incompatibles avec le mouvement Wikimédia. Au terme de ces explications concernant l'apparition des projets frères, il reste à signaler que certains projets et versions linguistiques ont été créés bien avant que la perception du mouvement Wikimédia fasse son apparition. C'est en effet suite au brassage de projets, de langues et de cultures différentes, et grâce à une conscience collective, manifestée par une coordination centrale de toutes les activités via la plateforme Méta-Wiki, que l'idée d'un mouvement social a pris du sens au sein des activités Wikimédia. En ce sens, la naissance du mouvement ne fut pas un événement ponctuel, mais plutôt la réalisation d’un long processus de conscientisation. {| class="wikitable"style="margin: auto;" "text-align:center;" |+Codes QR |[[Fichier:QR code page meta news.png|centré|sans_cadre|100x100px|lien=https://meta.wikimedia.org/wiki/Wikimedia_News]] |[[Fichier:QR-Code ligne du temps projets Wikimédia.png|centré|sans_cadre|100x100px|lien=https://upload.wikimedia.org/wikipedia/commons/b/b0/WikipediaTimeline.png]] |[[Fichier:Code QR vidéo présentation projets frères WikiMooc 2016.svg|centré|sans_cadre|100x100px]] |- |<small>Meta-Wiki</small> |<small>Ligne du temps</small> |<small>Vidéo projets frères</small> |} {{AutoCat}} aoooi0zb0mpwq8wcshygfl45jm8tikb 764668 764667 2026-04-23T17:07:28Z Lionel Scheepmans 20012 764668 wikitext text/x-wiki <noinclude>{{Le mouvement Wikimédia}}</noinclude> Dans le but de développer des contenus pédagogiques qui ne trouvaient pas leur place dans Wikipédia, d’autres projets pédagogiques et collaboratifs ont vu le jour, pour former ce que l'on appelle couramment aujourd'hui : l’écosystème Wikimedia. La naissance de tous ces projets, ainsi que les évènements importants qui ont contribué au développement du mouvement, ont été repris dans une [[c:File:WikipediaTimeline.png|ligne du temps]] réalisée par [[m:user:Guillom|Guillaume Paumier]] à l’occasion du dixième anniversaire de Wikipédia. Grâce à ce graphique, on peut voir en détail l'évolution du nombre de projets, de versions linguistiques, de contributeurs et d'articles, et se faire ainsi une idée sur la vitesse à laquelle s'est développé le mouvement Wikimédia.[[Fichier:Wikimedia logo family complete-2022.svg|alt=Logo du mouvement Wikimédia entouré de 15 autres logos de projets actifs en son sein|vignette|<small>Figure 18. Logo de la Fondation Wikimédia entouré de 15 autres logos de projets actifs au sein du mouvement.</small>|300x300px]]Parmi tous les projets frères de Wikipédia, le premier apparu fut Méta-Wiki, une plateforme de référence pour centraliser la gestion de l'ensemble des sites web hébergés par la fondation Wikimédia. Dans un premier temps, cet espace communautaire en ligne a répondu à la nécessité de traiter en un seul lieu les questions communes aux différentes versions linguistiques de Wikipédia. Aujourd'hui, le site web est le principal endroit de coordination et de gestion de l'ensemble du mouvement Wikimédia. On y retrouve énormément d'informations au sujet des projets en ligne, et peut-être plus encore, concernant la Fondation et les organismes affiliés. Après Méta-Wiki, sept autres projets de partage de la connaissance ont fait leur apparition, avant d'être déclinés à leur tour en plusieurs versions linguistiques. Tous ces projets émergent en général sur l’initiative d’un petit groupe de personnes actives au sein d’un projet préexistant. Ce fut le cas du projet Wiktionnaire en anglais, le deuxième projet à voir le jour après Méta-Wiki, en décembre 2002, soit deux ans avant la version francophone apparue en mars 2004<ref>{{Lien web|auteur=Wiktionnaire|titre=Wiktionnaire:Historique du Wiktionnaire|url=https://web.archive.org/web/20200416091043/https://fr.wiktionary.org/wiki/Wiktionnaire:Historique_du_Wiktionnaire|consulté le=}}.</ref>. Il est intéressant d'observer que la version francophone du Wiktionnaire n’a pas été créée à partir du projet anglophone, mais bien depuis le projet Wikipédia en français. D'ailleurs, on peut retrouver dans les archives de ce dernier projet, tout un débat concernant la pertinence de cette création, dont voici un extrait. <blockquote> En fait, ce qui me peine vraiment avec le projet Wiktionary, c’est que alors qu’on essaie de rassembler les gens (pas facile) pour créer une sorte de tour de Babel de la connaissance (tâche bien longue et difficile), ce nouveau projet va disperser les énergies pour une raison qui ne me semble pas valable. C’est la création de Wiktionary qui va créer des redondances. À mon avis il existera rapidement des pages sur le même mot, mais ne contenant pas les mêmes informations. Pour quelle raison ces connaissances devraient-elles être séparées ? Les encyclopédies sur papier devaient faire des choix à cause du manque de place, mais nous, pourquoi le ferions-nous ??? "Wikipédia n’est pas un dictionnaire" n’est pas un argument à mon avis... si vraiment c’était pas un dictionnaire, il faudrait virer tout un tas d’article. Je ne comprends vraiment pas cette volonté de séparer la connaissance entre ce qui est "encyclopédique" et ce qui n’est "qu’une définition". [Réponse] Pour moi ce qu’est Wiktionary, c’est une partie de Wikipédia s’intéressant plus particulièrement aux aspects linguistiques des mots. La différence que je verrais entre la partie ''dictionnaire'' de Wikipédia et sa partie dite encyclopédique, c’est que la partie dictionnaire s’intéresserait au sens des mots eux-mêmes alors que la partie encyclopédie s’attache plus à faire ressortir un état des connaissances à un moment donné. Le pourquoi de la séparation d’avec la partie encyclopédie tient plus à des raisons techniques qu’à une volonté de monter un projet indépendant, en effet, à mon humble avis, un dictionnaire nécessite une plus grande rigueur (de présentation) qu’une encyclopédie. Ceci entraîne beaucoup de problème et entre autres le choix de la mise en forme des articles du dictionnaire.<ref>{{Lien web|auteur=Wiktionnaire|titre=Wiktionnaire:Historique du Wiktionnaire/Discussion Wikipédia:Wiktionary|url=https://web.archive.org/web/20140831102908/http://fr.wiktionary.org/wiki/Wiktionnaire:Historique_du_Wiktionnaire/Discussion_Wikip%C3%A9dia:Wiktionary|date=|consulté le=}}.</ref> </blockquote> Créer un nouveau projet, c’est effectivement créer un nouveau site web, qui devra faire l’objet d’une nouvelle gestion, tant pour les serveurs de la Fondation, que pour la nouvelle communauté de contributeurs. L’importation de pages d’un projet à l'autre ou la traduction de celles-ci sont bien sûr toujours possibles, mais cela duplique alors aussi la maintenance et les mises à jour. Le choix de scinder un projet, en faveur d’une plus grande liberté, comporte donc certains coûts humains et financiers. Ce prix à payer n'a pour autant pas empêché le projet anglophone Wikibooks de faire son apparition le 10 juin 2003, soit près d’un an avant Wikilivres, la version francophone du projet, apparue le 22 juillet 2004. Cette dernière création avait de nouveau été débattue au sein de la communauté Wikipédia en français, et non pas dans le Wikibooks en anglais. Quant à l'objectif commun aux deux projets linguistiques, il était de créer une « bibliothèque de livres pédagogiques libres que chacun peut améliorer »<ref>{{Lien web|auteur=Wikilivres|url=https://fr.wikibooks.org/w/index.php?title=Accueil&oldid=586825|titre=Acceuil|consulté le=}}.</ref>. Environ un an après la création du projet en anglais, un nouvel [[w:fr:Espace de noms|espace de noms]] intitulé Wikijunior fut mis en place au sein de la bibliothèque en ligne. Ce sous-projet avait été créé pour répondre à un financement de la fondation ''[[w:en:Graham_Beck|Beck]],'' qui cherchait à promouvoir la production de nouvelles littératures pour des enfants de huit à onze ans<ref>{{Lien web|auteur=Méta-Wiki|titre=Wikijunior/proposal to Beck Foundation|url=https://web.archive.org/web/20150925041619/https://meta.wikimedia.org/wiki/Wikijunior/proposal_to_Beck_Foundation|consulté le=}}.</ref>. Peu de temps après, cette tranche d’âge fut toutefois élargie de zéro à douze ans au niveau du projet francophone, quand le sous-projet y fut adopté<ref>{{Lien web|auteur=Wikilivres|titre=Wikijunior|url=https://web.archive.org/web/20210414045051/https://fr.wikibooks.org/wiki/Wikijunior|consulté le=}}.</ref>. Ces deux évènements témoignent ainsi qu'il est toujours possible qu'un sous-projet apparaisse dans un projet Wikimédia. Comme autre exemple, il y a aussi le WikiJournal, un sous-projet développé au sein du projet Wikiversité en anglais et qui reçu le prix de l’''Open Publishing Awards'' en 2019<ref>{{Lien web|langue=|auteur=Open Publishing Awards|titre=Results|url=https://web.archive.org/web/20201125093419/https://openpublishingawards.org/|site=|consulté le=}}.</ref>. Une demande fut faite pour qu'il puisse bénéficier d'un nouveau site web dans le but de pouvoir se développer en dehors de Wikiversité. Malheureusement pour les initiateurs, la demande est restée sans suite jusqu'à ce jour<ref>{{Lien web|langue=|auteur1=Wikimedia Foundation Wiki|titre=Minutes/2020-02|url=https://web.archive.org/web/20201015115053/https://foundation.wikimedia.org/wiki/Minutes/2020-02#WikiJournal|site=|éditeur=|date=|consulté le=}}.</ref>, après que le conseil d’administration de la Fondation, chargé de répondre à leur demande, considéra que le projet n’était pas suffisamment abouti. Il faut savoir qu'avant cela, le projet Wikiversité, dans lequel est né Wikijournal, avait lui-même été un sous-projet du projet Wikibook<ref>{{Lien web|auteur=Méta-Wiki|titre=Talk:Wikiversity/Old|url=https://web.archive.org/web/20130723232149/http://meta.wikimedia.org/wiki/Talk:Wikiversity/Old|date=|consulté le=}}.</ref>. Initialement, il visait à « créer une communauté de personnes qui se soutiennent mutuellement dans leurs efforts éducatifs<ref>Texte original avant sa traduction par www.deepl.com/translator : « ''create a community of people who support each other in their educational endeavors »''</ref> »<ref>{{Lien web|auteur=Wikibooks|titre=Wikiversity|url=https://web.archive.org/web/20210506184146/https://en.wikibooks.org/wiki/Wikiversity|date=|consulté le=}}.</ref>. Cependant, en août 2005, une longue discussion remit en question l’existence du sous-projet Wikiversité dans Wikibooks. Au terme de celle-ci, la décision fut prise de transférer Wikiversité et son contenu sur le site Méta-Wiki<ref name="Wikibooks">{{Lien web|titre=Wikibooks:Requests for deletion/Wikiversity|url=https://en.wikibooks.org/w/index.php?title=Wikibooks:Requests_for_deletion/Wikiversity&oldid=3490139|auteur=Wikibooks|consulté le=}}.</ref>, là où de nouvelles discussions ont abouti à l’idée de faire de Wikiversité un nouveau projet indépendant<ref>{{Lien web|titre=Wikiversity|url=https://meta.wikimedia.org/w/index.php?title=Wikiversity&oldid=232819|auteur=Méta-Wiki|date=|consulté le=}}.</ref>. Déjà à l'époque, le conseil d’administration de la Fondation Wikimédia se montrait réticent à l'ouverture de nouveaux projets, et sa réaction fut de demander l'ouverture d'un sondage au sein de la communauté. Celui-ci devait rassembler une majorité des deux tiers en faveur de l'ouverture du nouveau site web<ref>{{Lien web|titre=Wikiversity/Vote/fr|url=https://meta.wikimedia.org/w/index.php?title=Wikiversity/Vote/fr&oldid=316555|consulté le=|auteur=Méta-Wiki}}.</ref>. Un résultat qui fut finalement obtenu, mais pas sans de longs débats, dont voici un extrait<ref name="Wikibooks" />. <blockquote> La principale raison pour laquelle la Fondation Wikimédia ne veut pas "lâcher le morceau" est une simple question de bureaucratie et la crainte que le projet ne devienne une autre Wikispecies [un projet de répertoire du vivant]. Wikispecies est une idée cool, mais les "fondateurs" du projet se sont dégonflés à mi-chemin de la mise en place du contenu et ont décidé de faire une révision majeure qui a pris plus de temps que ce que tout le monde était prêt à mettre. Le même problème s’applique à Wikiversity en ce qui concerne la Fondation, parce que les objectifs et les buts de ce projet ne sont pas clairement définis, et il semble que les participants essaient de mordre plus qu’ils ne peuvent mâcher en proposant une université de recherche multi-collèges entière (avec un statut de recherche et une accréditation) à former de toutes pièces plutôt qu’un simple centre d’éducation pour adultes avec quelques classes.<ref>Texte original avant sa traduction par deepl.com/translator : « ''The main reason why the Wikimedia Foundation doesn't want to "turn it loose" is pure bureaucratic BS and a fear that it will turn into another Wikispecies. Wikispecies is a cool idea, but the "founders" of the project got cold feet part-way into putting in content and decided to do a major revision that took more time than anybody was willing to put into it. The same issue applies to Wikiversity so far as the Foundation is concerned, because the goals and purposes of this project are not clearly defined, and it seems like the participants are trying to bite off more than they can chew by proposing an entire multi-college research university (with Carnegie-Mellon research status and accreditation as well) to be formed out of whole cloth rather than a simple adult education center with a few classes. If more thought is done on how to "bootstrap" this whole project, perhaps some thoughts on how to convince the Foundation board to let a separate wiki be kicked loose to let this project try to develop on its own can be made''.--Rob Horning 11:21, 14 August 2005 (UTC) »</ref> </blockquote> En novembre 2005 et malgré les résultats du sondage, l'indépendance du projet Wikiversité ne fut toutefois pas acceptée par cinq membres du conseil. Ceux-ci réclamaient une « réécriture de la proposition pour en exclure la remise de titre de compétence, la conduite de cours en ligne, et de clarifier le concept de plate-forme ''e-learning''<ref>Texte original avant sa traduction par www.deepl.com/translator : « ''rewriting the proposal to'' ''exclude credentials, exclude online-courses and clarify the concept of elearning platform'' »</ref> »<ref>{{Lien web|auteur=Wikimedia Foundation Wiki|titre=Meetings/November 13, 2005|url=https://foundation.wikimedia.org/w/index.php?title=Meetings/November_13,_2005&oldid=118181|consulté le=}}.</ref>. Quand ces rectifications furent faites, le projet bénéficia d'une période d’essai de plusieurs mois, jusqu'à ce que les amendements apportés au projet de départ soient enfin acceptés, le 31 juillet 2006. Ce long temps d'attente était justifié par la création du ''[[m:Special_projects_committee|special projects committee]]''<ref>{{Lien web|titre=Wikiversity/Modified project proposal|url=https://meta.wikimedia.org/w/index.php?title=Wikiversity/Modified_project_proposal&oldid=395364#Scope_of_Wikiversity%20scope|auteur=Méta-Wiki|consulté le=}}.</ref>'','' qui, jusqu'en décembre 2021, fut chargé de soulager le conseil d’administration de la fondation, par rapport aux demandes de création de nouveaux projets Wikimédia<ref>{{Lien web|titre=Difference between revisions of "Special projects committee/Resolutions" - Meta|url=https://meta.wikimedia.org/w/index.php?title=Special_projects_committee/Resolutions&diff=prev&oldid=418944&diffmode=source|auteur=Méta-Wiki|consulté le=}}.</ref>. Un nouveau site, nommé Beta-Wikiversity, fut ainsi créé pour assister le lancement des différentes versions linguistiques de Wikiversité<ref>{{Lien web|titre=Wikiversité|url=https://fr.wikiversity.org/w/index.php?title=Wikiversité:Accueil&oldid=787344|auteur=Wikiversité|consulté le=}}.</ref>. Durant six mois, son premier objectif a d'abord été l'élaboration des lignes directrices concernant la potentialité de produire des recherches collaboratives au sein du projet<ref>Texte original avant sa traduction par www.deepl.com/translator : « ''six months, during which guidelines for further potential uses of the site, including collaborative research, will be developed'' »</ref>. Par la suite, chaque nouvelle version linguistique, développée dans le projet Beta, devait avoir plus de 10 modifications par mois, réalisées par au moins trois personnes distinctes, avant de pouvoir bénéficier de son propre site web. À l'image de Beta-Wikiversity, le projet Wikisource<ref>{{Lien web|auteur=Wikisource|titre=Wikisource|url=https://web.archive.org/web/20210303213629/https://wikisource.org/wiki/Main_Page|date=|consulté le=}}.</ref> possède lui aussi un site indépendant pour lancer ces nouvelles déclinaisons linguistiques. Tandis que pour tous les autres projets pédagogiques, ce lancement s'effectue sur la plateforme [[incubator:Main_Page|Wikimedia Incubator]]<ref>{{Lien web|auteur=Wikimédia Incubator|titre=Welcome to Wikimedia Incubator!|url=https://web.archive.org/web/20210227091859/https://incubator.wikimedia.org/wiki/Incubator:Main_Page|date=|consulté le=}}.</ref>'','' créée à la même époque que Beta-Wikiversity. Cela ne concerne pas les nouveaux projets, dont les origines et processus de création peuvent varier, même si dans tous les cas, leurs lancements doivent faire l'objet d'une acceptation par le conseil d'administration de la Fondation Wikimédia. Le projet Wikivoyage, par exemple, fut initialement créé en 2003 dans un Wiki extérieur au mouvement Wikimédia, là où il portait le nom de Wikitravel<ref>{{Lien web|auteur=Giles Turnbull|titre=The DIY travel guide|url=https://web.archive.org/web/20210116050802/http://news.bbc.co.uk/2/hi/uk_news/magazine/3614517.stm|site=BBC News|éditeur=|date=12 avril 2004|consulté le=}}.</ref>. Comme cela arrive parfois, ce projet sans but lucratif fut acheté par une entreprise commerciale en 2006. Mais en raison du changement de gouvernance et de l'apparition de publicités, une scission est apparue au sein de la communauté d’éditeurs. Les personnes désireuses de quitter Wikitravel lancèrent alors un nouveau site appelé Wikivoyage, qui reçut en 2007, le [[w:fr:Webby Award|''Webby Award'']] du meilleur guide de voyage Internet<ref>{{Lien web|auteur=Jake Coyle|titre=On the Net: Web Sites to Travel By|url=https://web.archive.org/web/20210121071600/https://www.foxnews.com/printer_friendly_wires/2007May30/0,4675,OntheNet,00.html|site=Fox News|date=30 mai 2007|consulté le=}}.</ref>. L'intégration de Wikivoyage dans l'écosystème Wikimédia n'a cependant été faite qu'en 2012, à la suite d'un appel à commentaires durant lequel 540 personnes furent en faveur de l’intégration du projet, 153 contre, pendant que 6 restèrent sans avis <ref>{{Lien web|url=https://web.archive.org/web/20210311055050/https://meta.wikimedia.org/wiki/Requests_for_comment/Travel_Guide|titre=Requests for comment/Travel Guide|auteur=Méta-Wiki|consulté le=}}.</ref>. Comme cette nouvelle déclencha une migration importante depuis Wikitravel vers Wikivoyage, une plainte fut déposée par la société commerciale en charge du premier projet. Celle-ci fut toutefois rejetée et le projet Wikivoyage continua à prendre de l’ampleur au sein du mouvement Wikimédia, avec la création de nouvelles versions linguistiques<ref>{{Lien web|auteur=Steven Musil|titre=Wikimedia, Internet Brands settle Wikivoyage lawsuits|url=https://web.archive.org/web/20211116013544/https://www.cnet.com/tech/services-and-software/wikimedia-internet-brands-settle-wikivoyage-lawsuits/|site=CNET|éditeur=|date=17 février 2013|consulté le=}}.</ref>.[[Fichier:WikiMOOC - vidéo 23 - Les projets frères.webm|vignette|<small>Vidéo 1. Présentation des projets frères dans le cadre du WIKIMOOC 2016.</small>|300x300px|gauche]]Le cas de Wikivoyage apparait toutefois comme une exception, car en général les nouveaux projets émergent des centaines de candidatures déposées sur le projet [[m:Proposals_for_new_projects|Méta-Wiki]]<ref>{{Lien web|auteur=Méta-Wiki|titre=Proposals for new projects|url=https://web.archive.org/web/20211019173812/https://meta.wikimedia.org/wiki/Proposals_for_new_projects|date=|consulté le=}}.</ref>. Celles-ci se soldent bien souvent par un refus, comme ce fut le cas pour le projet WikiLang dont le but était de lancer un laboratoire linguistique<ref>{{Lien web|langue=|auteur=Méta-Wiki|titre=WikiLang|url=https://web.archive.org/web/20210109011449/https://meta.wikimedia.org/wiki/WikiLang|consulté le=}}</ref>. Quelques rares projets ont pourtant la chance d'être élus. Ce fut notamment le cas en octobre 2012, avec le lancement de la base de données structurée et sémantique [[w:Wikidata|Wikidata]] et de ses extensions [[w:Wikibase|Wikibase]], ou plus récemment, en 2020, avec l'arrivée du projet [[w:Abstract_Wikipedia|Abstract Wikipedia]]<ref>{{Lien web|langue=|auteur=Wikimedia Foundation Wiki|titre=Resolution:Abstract Wikipedia|url=https://web.archive.org/web/20201026191716/https://foundation.wikimedia.org/wiki/Resolution:Abstract_Wikipedia|date=|consulté le=}}.</ref> et [[w:Wikifunctions|Wikifunctions]]<ref>{{Lien web|langue=|auteur=Wikimedia Fundation|titre=Resolution:Abstract Wikipedia|url=https://web.archive.org/web/20200703234853/https://foundation.wikimedia.org/wiki/Resolution:Abstract_Wikipedia}}</ref>. Sans vouloir entrer dans les détails, il est intéressant de savoir que l'interconnexion entre ces trois projets permet de traduire automatiquement des articles encyclopédiques dans tous les langages naturels pris en charge par le mouvement Wikimédia. Plus précisément, les phrases des articles publiées sur Abstract Wikipédia, sont formulées par des fonctions informatiques produites dans le projet Wikifunctions, dans le but de traiter les informations de la base de données sémantique Wikidata. Autrement dit, un article dans Abstract Wikipédia ne possède qu'une seule page pour toutes les langues, pareillement aux pages d'entités de Wikidata, qui ont pour titre une lettre suivie d'un chiffre<ref>{{Lien web|langue=|auteur=Thomas Douillard|titre=Abstract Wikipédia - LinuxFr.org|url=https://web.archive.org/web/20200923155546/https://linuxfr.org/news/abstract-wikipedia#fn1|site=Linux Fr|lieu=|date=05/09/20|consulté le=}}.</ref>. À la suite de ces explications, on observe donc que ce n'est pas la complexité qui détermine le refus projet, mais plutôt une série de critères comparables à ceux retenus pour supprimer des projets ou versions linguistiques déjà existants. À ce propos, il existe sur Méta-Wiki une liste mise à jour des différents sites hébergés par la Fondation Wikimédia dont l'existence est remise en cause<ref>{{Lien web|auteur=Méta-Wiki|titre=Proposals for closing projects|url=https://web.archive.org/web/20210126030311/https://meta.wikimedia.org/wiki/Proposals_for_closing_projects|date=|consulté le=}}.</ref>. Dans celle-ci, on retrouve essentiellement des versions linguistiques de projets qui n’ont pas réussi à poursuivre leurs développements, bien qu'un projet entier soit toujours susceptible d'être mis à l'arrêt. C'est d'ailleurs ce qui est arrivé aux 31 versions linguistiques du projet Wikinews, qui en date du 4 mai 2026, soit 22 ans après le lancement du site anglophone, sont accessibles [[n:fr:Wikinews_ne_sera_plus_mis_à_jour_à_partir_du_4_mai_2026|en mode lecture uniquement]]<ref>{{Lien web|auteur=Wikinews|titre=Wikinews ne sera plus mis à jour à partir du 4 mai 2026|url=https://web.archive.org/web/20260413082226/https://fr.wikinews.org/wiki/Wikinews_ne_sera_plus_mis_%C3%A0_jour_%C3%A0_partir_du_4_mai_2026}}.</ref>. Dans le cas de Wikinews, ce fut le manque d'activité qui apparut comme principale justification de la suspension du projet. Cependant, d'autres raisons pourraient être invoquées, comme le prouve cet épisode de 2005, où, peu de temps après son lancement, la version francophone du recueil de citations [[w:Wikiquote|Wikiquote]] a bien risqué de disparaitre. Le projet fut effectivement accusé d’avoir récupéré le contenu d’une base de données soumise à un droit d’auteur restrictif et incompatible avec la licence Creative Commons appliquée sur l’ensemble des projets pédagogiques Wikimédia. Lorsqu'une plainte fut adressée à l’association Wikimédia France, pour être ensuite relayée sur le site Méta-Wiki, il fut bien question de fermer le projet<ref>{{Lien web|auteur1=Méta-Wiki|titre=Wikiquote FR/Closure of French Wikiquote|url=https://web.archive.org/web/20210204052058/https://meta.wikimedia.org/wiki/Wikiquote_FR/Closure_of_French_Wikiquote|date=}}.</ref>. Après de longues discussions, celui-ci fut maintenu, mais avec pour conditions de repartir de zéro, et d’établir une charte pour garantir la traçabilité des citations reprises par le projet<ref>{{Lien web|auteur1=Jean-Baptiste Soufron|titre=Redémarrage du Wikiquote Francophone / French Wikiquote Relaunch|url=https://web.archive.org/web/20200506074856/https://lists.wikimedia.org/pipermail/foundation-l/2006-March/019857.html|site=Foundation-l|lieu=|date=30 mars 2006}}.</ref>. Voici donc de quoi se faire une idée sur la manière dont les projets pédagogiques et leurs déclinaisons linguistiques apparaissent et disparaissent au sein du mouvement. Les exemples repris ci-dessus suffisent effectivement pour comprendre les principes généraux qui sous-tendent leurs créations. En dehors de Méta-Wiki, Wikidata, Wikifunctions'','' Abstract Wikipédia et Wikimedia Commons, qui ne sont pas des projets de contenu pédagogique à proprement parler, tous les autres projets semblent effectivement provenir d’un désir de spécialisation d’un projet préexistant. L'idée est généralement débattue dans un projet de même langue, avant de relayer la discussion vers le site Méta-Wiki. Si le projet y est jugé pertinent, il fait alors l'objet d'une candidature qui doit actuellement être soumise au [[m:Wikimedia_Foundation_Community_Affairs_Committee/Sister_Projects_Task_Force/fr|groupe de travail des projets frères]] du [[m:Wikimedia_Foundation_Community_Affairs_Committee/fr|comité des affaires communautaires de la Fondation Wikimédia]]. Quant aux nouvelles versions linguistiques, elles doivent être aujourd'hui soumises à l'approbation du [[m:Language_committee/fr|comité des langues]], avant d'être testées sur les plateformes Incubator, Beta-Wikiversité ou Wikisource Multilingue, dans le but de bénéficier d’un site web indépendant. Voici donc pour ce qui est des sites Wikimédia, mais avant d'en finir, nous devons encore parler des sites qui ont un lien avec le mot Wiki, soit par leur nom, soit par le logiciel utilisé. En 2024 en effet, plus de 22 600 d'entre eux étaient répertoriés, dont plus de 95 % sans aucun lien avec le mouvement Wikimédia, en dehors du fait, peut-être, qu’ils utilisaient le logiciel MediaWiki développé par la Fondation Wikimédia<ref>{{Lien web|langue=|auteur=WikiIndex|titre=the index of all wiki|url=https://web.archive.org/web/20240906224607/https://wikiindex.org/Category:All|site=|date=|consulté le=}}.</ref>. [[w:fr:WikiLeaks|WikiLeaks]] par exemple, créé par [[w:fr:Julian Assange|Julian Assange]] dans le but de publier des documents classifiés provenant de sources anonymes, n’est ni un projet Wikimédia, ni un site collaboratif. Quant au recueil universel et multilingue de guides illustrés [[w:fr:WikiHow|WikiHow]], si celui-ci fonctionne pour sa part de manière collaborative et avec le logiciel MediaWiki<ref>{{Lien web|auteur=WikiHow|titre=wikiHow:Powered and Inspired by MediaWiki|url=https://web.archive.org/web/20211030092737/https://www.wikihow.com/wikiHow:Powered-and-Inspired-by-MediaWiki|date=|consulté le=}}.</ref>, il n'a pourtant aucun lien avec le mouvement Wikimédia. D'ailleurs, son ergonomie, radicalement différente de celle des projets Wikimédia, permet de le comprendre au premier coup d’œil. En revanche, Wikimini, l'encyclopédie libre pour les enfants, a une apparence tout à fait comparable à celle des projets Wikimédia, alors que le projet n’a jamais été accepté par la Fondation. Quant aux projets [[w:fr:WikiTribune|WikiTribune]] et [[w:fr:Wikia|Fandom]], l'ambiguïté qu'ils entretiennent avec le mouvement est d'autant plus grande qu'ils ont été créés par [[w:fr:Jimmy Wales|Jimmy Wales]], le fondateur de Wikipédia et de la [[w:fr:Wikimedia Foundation|Fondation Wikimédia]]<ref>{{Lien web|langue=|auteur=Terry Collins|titre=Wikipedia co-founder launches project to fight fake news|url=https://web.archive.org/web/20201014061304/https://www.cnet.com/news/wikipedia-jimmy-wales-wikitribune-fighting-fake-news/|site=CNET|date=24 avril 2017|consulté le=}}.</ref>. Cependant, comme ce sont des projets commerciaux, ils ne peuvent en aucun cas être soutenus par une fondation sans but lucratif. Au terme de cette présentation, il ne reste plus qu'à signaler que le mouvement Wikimédia ne fut conscientisé que tardivement par rapport à l'apparition des projets Wikimédia et de leurs différentes versions linguistiques. Pour qu'un sentiment de collectivité se manifeste entre tous ceux-ci, il fallut certainement attendre qu'une coordination se développe sur la plateforme Méta-Wiki, mais également que de nombreuses rencontres et associations apparaissent en dehors de l'espace numérique. En ce sens, la naissance du mouvement ne fut pas un événement ponctuel, mais plutôt la réalisation d’un long processus de conscientisation. {| class="wikitable"style="margin: auto;" "text-align:center;" |+Codes QR |[[Fichier:QR code page meta news.png|centré|sans_cadre|100x100px|lien=https://meta.wikimedia.org/wiki/Wikimedia_News]] |[[Fichier:QR-Code ligne du temps projets Wikimédia.png|centré|sans_cadre|100x100px|lien=https://upload.wikimedia.org/wikipedia/commons/b/b0/WikipediaTimeline.png]] |[[Fichier:Code QR vidéo présentation projets frères WikiMooc 2016.svg|centré|sans_cadre|100x100px]] |- |<small>Meta-Wiki</small> |<small>Ligne du temps</small> |<small>Vidéo projets frères</small> |} {{AutoCat}} 60t901weqrh6lls4b0nxmd7vje9eiox Le mouvement Wikimédia/La conscientisation du mouvement 0 79275 764669 758192 2026-04-23T17:46:15Z Lionel Scheepmans 20012 764669 wikitext text/x-wiki <noinclude>{{Le mouvement Wikimédia}}</noinclude> Avant d'aborder la question de la conscientisation du mouvement, il peut être intéressant de découvrir l'origine étymologique du mot « Wikim''é''dia ». Celui-ci est un [[w:fr:mot-valise|mot-valise]] composé du suffixe « média » et du préfixe « wiki » que l’on doit à cette expression hawaïenne « [[wikt:wikiwiki|wiki wiki]] », qui se traduit en français par l'expression : « vite, vite »<ref>{{Lien web|langue=|auteur=Wiktionnaire|titre=wiki|url=https://web.archive.org/web/20200905104709/https://fr.wiktionary.org/wiki/wiki|site=|date=|consulté le=}}.</ref>. Celle-ci fut récupérée une première fois par Ward Cunningham, le créateur du premier moteur Wiki, avant d'être réutilisée dans les noms inventés pour d'autres logiciels de cette même famille. UseModWiki en est un bel exemple, puisqu'il fut le premier programme utilisé par la firme Bomis pour héberger son projet d’encyclopédie collaborative. Raison pour laquelle, sans doute, le terme « wiki » fut utilisé pour créer le mot Wikipédia, en l'associant au suffixe « pedia » qui fait référence au mot anglais ''encyclopedia'', selon un principe qui fut ensuite repris pour tous les autres projets du mouvement. [[Fichier:Florence_Devouard_Wiki_Indaba_2017.jpg|vignette|<small>Figure 19. Florence Devouard en 2017.</small>|300x300px]] Le mot Wikimédia, pour sa part, n’est apparu que le seize mars 2003, lors d’une discussion concernant la déclinaison possible de l’encyclopédie en d’autres types de projets éditoriaux participatifs. Durant celle-ci, l’écrivain américain [[w:fr:Sheldon Rampton|Sheldon Rampton]] eu l’idée d’associer au terme wiki à celui de « média », afin de mettre en évidence la variété des médias produits et partagés par Wikipédia et ses projets frères<ref>{{Lien web|langue=|auteur=Sheldon Rampton|titre=WikiEN-l Re:Current events|url=https://web.archive.org/web/20201029044620/https://lists.wikimedia.org/pipermail/wikien-l/2003-March/001887.html|site=Wikimedia-l|date=16 mars 2003|consulté le=}}.</ref>. Toutefois, c'est seulement en juin 2008 que [[w:fr:Florence Devouard|Florence Devouard]], présidente de la Fondation à cette époque, associe le mot Wikimédia à un mouvement social qu’elle voyait apparaître au sein des projets Wikimédia et de leurs communautés d'usagers. Affirmer pour autant que ce moment précis coïncide avec la naissance du mouvement Wikimédia serait quelque peu arbitraire. Car si l’on peut déterminer plus ou moins facilement l’apparition d’une expression dans des archives, tout le monde sait qu’un mouvement social ne se forme pas en un seul instant. Dans le contexte du mouvement Wikimédia, sa naissance est bien sûr liée à celle du projet Wikipédia, mais également à tout ce qui permit la création de cette encyclopédie libre. Dans une autre perspective encore, on peut dater l'apparition du mouvement Wikimédia au 20 juin 2003<ref name="Création">{{Lien web|langue=|auteur=Division of Corporations - State of Florida|titre=Wikimedia Foundation, INC.|url=https://web.archive.org/web/20201021023841/http://search.sunbiz.org/Inquiry/CorporationSearch/SearchResultDetail?inquirytype=EntityName&directionType=Initial&searchNameOrder=WIKIMEDIAFOUNDATION%20N030000053230&aggregateId=domnp-n03000005323-6dc7ff3a-b7ba-4c97-9b9e-4545cef1ca0a&searchTerm=Wikimedia%20Foundation&listNameOrder=WIKIMEDIAFOUNDATION%20N030000053230|site=|consulté le=}}.</ref>, date de la création de la fondation qui porte le même nom. Ou pourquoi pas, associer la création du mouvement à la mise en ligne de la plate-forme Méta-Wiki, qui en représente le principal lieu de coordination. Quoi qu’il en soit, l’expression « ''Wikimedia movement'' » est bien apparue en juin 2008, sous la plume de Florence Devouard. Cela s'est passé sur la liste de diffusion de la Fondation et peu de temps avant qu'elle quitte son poste de présidente<ref>{{Lien web|langue=|auteur=Florence Devouard|titre=Candidacy to the board of WMF|url=https://web.archive.org/web/20201106192308/https://lists.wikimedia.org/pipermail/foundation-l/2008-May/043117.html|site=Foundation-l|date=19 mai 2008|consulté le=}}.</ref>. Dans son message, elle partageait l'idée de placer sous le nom de domaine Wikimedia.org un site vitrine de présentation du mouvement Wikimédia qu'elle concevait de la sorte<ref>{{Lien web|langue=|auteur=Méta-Wiki|titre=Talk:Www.wikimedia.org template/2008|url=https://web.archive.org/web/20201103025847/https://meta.wikimedia.org/w/index.php?title=Talk%3AWww.wikimedia.org_template%2F2008|site=|date=|consulté le=}}.</ref>. <blockquote> Le mouvement Wikimédia, comme je l’entends est – une collection de valeurs partagées par les individus (liberté d’expression, connaissance pour tous, partage communautaire, etc.) – un ensemble d’activités (conférences, ateliers, wikiacadémies, etc.) – un ensemble d’organisations (''Wikimedia Foundation'', Wikimedia Allemagne, Wikimedia Taïwan, etc.), ainsi que quelques électrons libres (individus sans chapitres) et des organisations aux vues similaires<ref>Texte original avant sa traduction par deepl.com/translator : « ''The Wikimedia Movement, as I understand it, is a collection of values shared by individuals (freedom of speech, knowledge for everyone, community sharing, etc.) a collection of activities (conferences, workshops, wikiacademies, etc.) a collection of organizations (Wikimedia Foundation, Wikimedia Germany, Wikimedia Taiwan, etc.), as well as some free electrons (individuals without chapters) and similar-minded organizations.'' »</ref> </blockquote> Avec autant de détails et d'explications, un tel message ne pouvait qu'accélérer la prise de conscience au sein du mouvement. Dans tous les cas, il mettait en évidence que les personnes actives dans les projets éditoriaux en ligne ou dans les organismes affiliés, faisaient partie de ce que [[w:fr:Ralf Dahrendorf|Ralf Dahrendorf]] appelle un « quasi-groupe<ref>{{Ouvrage|langue=|prénom1=Ralf|nom1=Dahrendorf|titre=Classes et conflits de classes dans la société industrielle.|éditeur=Mouton|date=1972|oclc=299690912}}.</ref> ». Ou autrement dit, un ensemble d’individus qui ont un mode de vie semblable, une culture commune, mais dont les points communs ne gravitent pas autour d’une prise de conscience de leur position commune dans la relation d’autorité<ref>{{Ouvrage|langue=|auteur=|prénom1=Pierre|nom1=Desmarez|titre=Sociologie générale (syllabus)|passage=34|lieu=Bruxelles|éditeur=Presses universitaires de Bruxelles|date=2006|numéro d'édition=10|pages totales=194|isbn=|lire en ligne=}}.</ref>. Suite à la naissance du projet Wikipédia et de nombreux projets frères, une dizaine d’années a donc été nécessaire pour que le mouvement Wikimédia prenne conscience de son existence. Aujourd’hui encore, et comme cela a déjà été vu, de nombreuses personnes actives dans les projets pédagogiques ne réalisent toujours pas qu’elles participent aux activités d’un mouvement social. Cela, contrairement aux personnes investies dans les activités en présentiel organisées au sein du mouvement, qui sont généralement plus conscientes de leur engagement. C’est là une raison de croire que le développement de la Fondation Wikimédia et de ses organismes affiliés a joué un rôle important dans l'apparition d'un sentiment d’appartenance. {{AutoCat}} blv03bp9wte0e0c2dsx7ibytq8bzdre Le mouvement Wikimédia/La création des organismes affiliés 0 79276 764690 716245 2026-04-23T19:19:05Z ~2026-24933-73 123594 764690 wikitext text/x-wiki <noinclude>{{Le mouvement Wikimédia}}</noinclude> Si c’est grâce à l’arrivée des groupes et des organismes affiliés à la Fondation Wikimédia que l’idée d’un mouvement apparut autour des projets Wikimédia, il est alors intéressant d’en décrire aussi les processus de création. Mais puisque cela représente plusieurs centaines d’instances, présenter l’histoire de chacune d’entre elles serait une entreprise beaucoup trop fastidieuse. De plus, s’il existe énormément d’archives numériques concernant la naissance des sites Wikimédia, ce n’est pas le cas pour ces organismes affiliés. Un bon nombre de ceux-ci se sont effectivement formés durant des rencontres ou des réunions hors ligne qui n'ont fait l’objet d’aucun enregistrement. Du reste, une bonne part des échanges effectués au sein de ces associations s'organise par des canaux de communication privés auxquels seuls les membres actifs ont accès. Puisque je suis l'un des membres fondateurs, je me limiterai donc ici à parler de l’association [[w:fr:Wikimédia Belgique|Wikimédia Belgique]]. Celle-ci fut fondée le huit octobre 2014 en tant qu’association sans but lucratif, avant d'être reconnue le six août 2015 par le conseil d’administration de la Fondation<ref>{{Lien web|url=https://web.archive.org/web/20211106193619/https://foundation.wikimedia.org/wiki/Resolution:Recognition_of_Wikimedia_Belgium|titre=Resolution:Recognition of Wikimedia Belgium|auteur=Wikimedia foundation Wiki|consulté le=}}.</ref>. Après plus de trois ans d’activités et de rencontres<ref>{{Lien web|titre=History|url=https://web.archive.org/web/20230324232839/https://be.wikimedia.org/wiki/History|auteur=Wikimedia Belgium}}.</ref> et sous l’impulsion de Maarten Deneckere qui assuma le premier mandat de présidence, nous étions 8 personnes à signer la première version des statuts de l’association<ref>{{Lien web|url=https://web.archive.org/web/20210711060039/http://www.ejustice.just.fgov.be/tsv_pdf/2014/10/17/14190820.pdf|titre=Wikimedia Belgium vzw|auteur=Moniteur Belge|consulté le=}}.</ref>. Jusqu’à ce jour, l’objet social de Wikimedia Belgique est d' « impliquer tout un chacun dans la connaissance libre<ref>{{Lien web|auteur=Wikimédia Belgique|titre=Wikimédia Belgique|url=https://web.archive.org/web/20211104105257/https://wikimedia.be/fr/|consulté le=}}.</ref> ». Contrairement à l'association Wikimédia Deutchland, la première à voir le jour en 2004 et qui rassemblait déjà en 2021 plus de 85 000 membres et près de 150 employés<ref name="Medieninsider">{{Lien web|url=https://web.archive.org/web/20210603013514/https://medieninsider.com/christian-humburg-wird-vorstandschef-von-wikimedia-deutschland/4846/|titre=Christian Humborg wird Vorstandschef von Wikimedia Deutschland|date=10 mai 2021|auteur1=Medieninsider|site=Medieninsider}}.</ref>, l’association belge n'a qu'une seule employée et 150 membres en 2025<ref>{{Lien web|url=https://meta.wikimedia.org/wiki/Wikimedia_chapters/Reports/Wikimedia_Belgium/Financial/2025|titre=Activity report of Wikimedia Belgium over 2025|auteur=Wikimédia Belgique|consulté le=}}.</ref>. [[Fichier:Wikimedia Conference 2018 – 164.jpg|vignette|Figure 20. Les membres du comité d’affiliation en 2018.|gauche|350x350px]] Avant d’être reconnues par le comité d’affiliations chargé de seconder le conseil d’administration de la Fondation, toutes les associations nationales, dites « ''chapters'' » en anglais, doivent réaliser un bon nombre de démarches. Celles-ci consistent à répondre à un ensemble de prérequis qui ont évolué suite à la création d'un comité décisionnel en avril 2006<ref>{{Lien web|url=https://web.archive.org/web/20060913000000/http://wikimediafoundation.org:80/wiki/Resolutions|titre=Resolution chapters committee|auteur1=Wikimedia Foundation Wiki}}.</ref>. Ces obligations diffèrent entre les groupes d’utilisateurs et d'utilisatrices et les associations locales ou thématiques. Parmi ceux-ci, on retrouve toutefois : un nombre minimum de membres et de référents, une mission et un règlement d’ordre intérieur conformes aux attentes du mouvement, la remise de plans et de rapports d’activités annuels, etc.<ref>{{Lien web|url=https://web.archive.org/web/20210307041150/https://meta.wikimedia.org/wiki/Template:Wikimedia_movement_affiliates/Requirements_comparison|titre=Template:Wikimedia movement affiliates/Requirements comparison|auteur=Méta-Wiki|consulté le=}}.</ref>. On comprend donc qu’il n’est pas évident de créer une nouvelle instance au sein du mouvement. Pour bénéficier du soutien logistique et financier de la Fondation réservé aux organismes affiliés, c’est ainsi toute une série de rapports qu’il faut alors transmettre à divers comités et commissions chargés de leurs évaluations. Cela représente une quantité de tâches administratives qu’il n’est pas toujours facile d’assumer, surtout lorsque les membres de l’organisme affilié sont tous des bénévoles. D’où sans doute cette régulière disparition d’affiliations, pendant que d’autres se créent ou réapparaissent en fonction des énergies et du dynamisme disponibles dans les équipes. Les activités liées à la récolte et à la redistribution des dons offerts au mouvement, ainsi que les autorisations d’usage de marques déposées, contrastent donc avec les valeurs de libre partage et d’autonomie décrites dans les projets pédagogiques. Cela semble confirmer que la partie hors ligne du mouvement est plus influencée par les habitudes d’un système économique dominant, contrairement à la partie en ligne qui semble plus fidèle à l’héritage de la contre-culture. {{AutoCat}} dsgfqk2kz01gdhggqhdrd0ai2qhvaic 764698 764690 2026-04-23T19:32:47Z ~2026-24933-73 123594 764698 wikitext text/x-wiki <noinclude>{{Le mouvement Wikimédia}}</noinclude> Si c’est grâce à l’arrivée des groupes et des organismes affiliés à la Fondation Wikimédia que l’idée d’un mouvement apparut autour des projets Wikimédia, il est alors intéressant d’en décrire aussi les processus de création. Mais puisque cela représente plusieurs centaines d’instances, présenter l’histoire de chacune d’entre elles serait une entreprise beaucoup trop fastidieuse. De plus, s’il existe énormément d’archives numériques concernant la naissance des sites Wikimédia, ce n’est pas le cas pour ces organismes affiliés. Un bon nombre de ceux-ci se sont effectivement formés durant des rencontres ou des réunions hors ligne qui n'ont fait l’objet d’aucun enregistrement. Du reste, une bonne part des échanges effectués au sein de ces associations s'organise par des canaux de communication privés auxquels seuls les membres actifs ont accès. Puisque je suis l'un des membres fondateurs, je me limiterai donc ici à parler de l’association [[w:fr:Wikimédia Belgique|Wikimédia Belgique]]. Celle-ci fut fondée le huit octobre 2014 en tant qu’association sans but lucratif, avant d'être reconnue le six août 2015 par le conseil d’administration de la Fondation<ref>{{Lien web|url=https://web.archive.org/web/20211106193619/https://foundation.wikimedia.org/wiki/Resolution:Recognition_of_Wikimedia_Belgium|titre=Resolution:Recognition of Wikimedia Belgium|auteur=Wikimedia foundation Wiki|consulté le=}}.</ref>. Après plus de trois ans d’activités et de rencontres<ref>{{Lien web|titre=History|url=https://web.archive.org/web/20230324232839/https://be.wikimedia.org/wiki/History|auteur=Wikimedia Belgium}}.</ref> et sous l’impulsion de Maarten Deneckere qui assuma le premier mandat de présidence, nous étions 8 personnes à signer la première version des statuts de l’association<ref>{{Lien web|url=https://web.archive.org/web/20210711060039/http://www.ejustice.just.fgov.be/tsv_pdf/2014/10/17/14190820.pdf|titre=Wikimedia Belgium vzw|auteur=Moniteur Belge|consulté le=}}.</ref>. Jusqu’à ce jour, l’objet social de Wikimedia Belgique est d'« impliquer tout un chacun dans la connaissance libre<ref>{{Lien web|auteur=Wikimédia Belgique|titre=Wikimédia Belgique|url=https://web.archive.org/web/20211104105257/https://wikimedia.be/fr/|consulté le=}}.</ref> ». Contrairement à l'association Wikimédia Deutchland, la première à voir le jour en 2004 et qui rassemblait déjà en 2021 plus de 85 000 membres et près de 150 employés<ref name="Medieninsider">{{Lien web|url=https://web.archive.org/web/20210603013514/https://medieninsider.com/christian-humburg-wird-vorstandschef-von-wikimedia-deutschland/4846/|titre=Christian Humborg wird Vorstandschef von Wikimedia Deutschland|date=10 mai 2021|auteur1=Medieninsider|site=Medieninsider}}.</ref>, l’association belge n'a qu'une seule employée et 150 membres en 2025<ref>{{Lien web|url=https://meta.wikimedia.org/wiki/Wikimedia_chapters/Reports/Wikimedia_Belgium/Financial/2025|titre=Activity report of Wikimedia Belgium over 2025|auteur=Wikimédia Belgique|consulté le=}}.</ref>. [[Fichier:Wikimedia Conference 2018 – 164.jpg|vignette|Figure 20. Les membres du comité d’affiliation en 2018.|gauche|350x350px]] Avant d’être reconnues par le comité d’affiliations chargé de seconder le conseil d’administration de la Fondation, toutes les associations nationales, dites « ''chapters'' » en anglais, doivent réaliser un bon nombre de démarches. Celles-ci consistent à répondre à un ensemble de prérequis qui ont évolué suite à la création d'un comité décisionnel en avril 2006<ref>{{Lien web|url=https://web.archive.org/web/20060913000000/http://wikimediafoundation.org:80/wiki/Resolutions|titre=Resolution chapters committee|auteur1=Wikimedia Foundation Wiki}}.</ref>. Ces obligations diffèrent entre les groupes d’utilisateurs et d'utilisatrices et les associations locales ou thématiques. Parmi ceux-ci, on retrouve toutefois : un nombre minimum de membres et de référents, une mission et un règlement d’ordre intérieur conformes aux attentes du mouvement, la remise de plans et de rapports d’activités annuels, etc.<ref>{{Lien web|url=https://web.archive.org/web/20210307041150/https://meta.wikimedia.org/wiki/Template:Wikimedia_movement_affiliates/Requirements_comparison|titre=Template:Wikimedia movement affiliates/Requirements comparison|auteur=Méta-Wiki|consulté le=}}.</ref>. On comprend donc qu’il n’est pas évident de créer une nouvelle instance au sein du mouvement. Pour bénéficier du soutien logistique et financier de la Fondation réservé aux organismes affiliés, c’est ainsi toute une série de rapports qu’il faut alors transmettre à divers comités et commissions chargés de leurs évaluations. Cela représente une quantité de tâches administratives qu’il n’est pas toujours facile d’assumer, surtout lorsque les membres de l’organisme affilié sont tous des bénévoles. D’où sans doute cette régulière disparition d’affiliations, pendant que d’autres se créent ou réapparaissent en fonction des énergies et du dynamisme disponibles dans les équipes. Les activités liées à la récolte et à la redistribution des dons offerts au mouvement, ainsi que les autorisations d’usage de marques déposées, contrastent donc avec les valeurs de libre partage et d’autonomie décrites dans les projets pédagogiques. Cela semble confirmer que la partie hors ligne du mouvement est plus influencée par les habitudes d’un système économique dominant, contrairement à la partie en ligne qui semble plus fidèle à l’héritage de la contre-culture. {{AutoCat}} qmm4eks7lgzyujuc5vvdnanlcqx6q8b Le mouvement Wikimédia/L'héritage d'une contre-culture 0 79277 764750 764345 2026-04-23T23:13:36Z Lionel Scheepmans 20012 764750 wikitext text/x-wiki <noinclude>{{Le mouvement Wikimédia}}</noinclude> [[Fichier:Wikimedia-logo black.svg|vignette|150x150px|<small>Figure 21. Logos du mouvement Wikimédia et de sa Fondation.</small>]] [[Fichier:Peace sign.svg|vignette|150x150px|<small>Figure 22. Logo du mouvement hippie et de la contre-culture.</small>]] Au terme de cette première partie d’ouvrage, il devient évident que la révolution numérique, que l’on considère généralement comme une révolution technique, fut aussi, et peut-être avant tout, une révolution sociale et culturelle. Quant à l'histoire de Wikimédia, reprise ici depuis les origines de son encyclopédie jusqu'à l'apparition de ses organismes affiliés, elle nous fit découvrir comment les idées de [[w:Contre-culture_des_années_1960|la contre-culture des années 1960]] furent transmises au mouvement. Si un doute persistait encore, rappelons-nous que Richard Stallman, celui qui a créé le concept d'encyclopédie libre, fut désigné par certains comme le gourou de la contre-culture hacker <ref>{{Ouvrage|auteur=|prénom1=Divers|nom1=auteurs|titre=L'Éthique Hacker|passage=11|éditeur=U.C.H Pour la Liberté|date=Version 9.3|pages totales=56|lire en ligne=https://web.archive.org/web/20211031170831/https://repo.zenk-security.com/Others/L%20Ethique%20Hacker.pdf}}.</ref> et le père du système d’exploitation hippie <ref>{{Lien web|langue=|auteur=Gavin Clarke|titre=Stallman's GNU at 30: The hippie OS that foresaw the rise of Apple — and is now trying to take it on|url=https://web.archive.org/web/20230602214539/https://www.theregister.com/2013/10/07/stallman_thiry_years_gnu/|site=Theregister|date=7 Oct 2013|consulté le=}}.</ref>. Quant à la culture [[w:fr:Hippie|hippie]], n'est-il pas troublant de constater que le renversement de son logo ressemble étrangement à celui du mouvement Wikimédia ? Incontestablement et au travers du mouvement des logiciels libres, le mouvement Wikimédia a donc bien hérité des valeurs produites par les mouvements sociaux des années 60. Des valeurs qui aujourd'hui contrastent fortement avec la marchandisation et la capitalisation du monde, dont l'espace web ne fait jamais que refléter ce qui se passe dans le reste de la société humaine. Un phénomène qui ne date pas d'hier, puisqu'en 2008, [[w:fr: André Gorz|André Gorz]], ce philosophe parmi les pères de la [[w:fr: Décroissance|décroissance]] <ref>{{Ouvrage|langue=|prénom1=David|nom1=Murray|prénom2=Cédric|nom2=Biagini|prénom3=Pierre|nom3=Thiesset|prénom4=Cyberlibris|nom4=ScholarVox International|titre=Aux origines de la décroissance: cinquante penseurs|date=2017|isbn=978-2-89719-329-4|isbn2=978-2-89719-330-0|isbn3=978-2-89719-331-7|oclc=1248948596}}.</ref> et théoricien de l’[[w:fr: Écologie politique|écologie politique]] <ref>{{Ouvrage|langue=|prénom1=André|nom1=Gorz|titre=Ecologie et politique: nouv. ed. et remaniee.|éditeur=Éditions du Seuil|date=1978|isbn=978-2-02-004771-5|oclc=796186896}}.</ref>, constatait déjà que : <blockquote> La lutte engagée entre les "logiciels propriétaires" et les "logiciels libres" [...] a été le coup d’envoi du conflit central de l’époque. Il s’étend et se prolonge dans la lutte contre la marchandisation de richesses premières – la terre, les semences, le génome, les biens culturels, les savoirs et compétences communs, constitutifs de la culture du quotidien et qui sont les préalables de l’existence d’une société. De la tournure que prendra cette lutte dépend la forme civilisée ou barbare que prendra la sortie du capitalisme. <ref>{{Lien web|langue=|auteur=André Gorz|titre=Le travail dans la sortie du capitalisme|url=https://web.archive.org/web/20200921155055/http://ecorev.org/spip.php?article641|site=Revue Critique d'Écologie Politique|lieu=|date=7 janvier 2008}}.</ref> </blockquote> [[Fichier:Wikimania_stallman_keynote2.jpg|alt=Photo de Richard Stallman lors du premier rassemblement Wikimania de 2005|vignette|<small>Figure 23. Photo de Richard Stallman lors du premier rassemblement internationnal du mouvement Wikimédia en 2005.</small>|gauche|300x300px]] Dans cette lutte et avec le seul [[w:fr: Nom de domaine|nom de domaine]] non commercial parmi le top 100 des sites web les plus fréquentés <ref>{{Lien web|auteur=Alexa|titre=Top sites|url=https://www.alexa.com/topsites|consulté le=}}.</ref>, le mouvement Wikimédia représente donc bien un des derniers lieux de liberté, de partage et d'équité. Sans compter qu'au-delà du code informatique, s’il y a bien une chose que l'on cherche à marchandiser et à transformer, c’est sans aucun doute le savoir. Un savoir qui, de plus, se décline en information, lorsqu'il s'agit de récolter des données relatives à l’identité et aux comportements des utilisateurs et des utilisatrices d'Internet. Un « nouvel or noir », diront certains, alors que d’autres préfèrent parler de « capitalisme 3.0 » <ref>{{Ouvrage|langue=|prénom1=Philippe|nom1=Escande|prénom2=Sandrine|nom2=Cassini|titre=Bienvenue dans le capitalisme 3.0|éditeur=Albin Michel|date=2015|isbn=978-2-226-31914-2|oclc=954080043}}.</ref> ou encore de « capitalisme de surveillance » <ref>{{Ouvrage|langue=|prénom1=Christophe|nom1=Masutti|prénom2=Francesca|nom2=Musiani|titre=Affaires privées : aux sources du capitalisme de surveillance|éditeur=Caen : C&F éditions|collection=Société numérique|date=2020|isbn=978-2-37662-004-4|oclc=1159990604|consulté le=}}.</ref><ref>{{Ouvrage|langue=|prénom1=Shoshana|nom1=Zuboff|titre=L'âge du capitalisme de surveillance|éditeur=Zulma|date=2020|isbn=978-2-84304-926-2|oclc=1199962619}}.</ref>. Évidemment, les enjeux de cette lutte sont difficiles à comprendre. La complexité de l’infrastructure informatique, mais également le fait que tout cela s'inscrit dans une révolution que [[w:fr: Rémy Rieffel|Rémy Rieffel]] décrit comme « instable et ambivalente, simultanément porteuse de promesse, et lourde de menaces », ne facilitent pas les choses. Cela d'autant plus que tout cela se place « dans un contexte où s’affrontent des valeurs d’émancipation, et d’ouverture d’un côté et des stratégies de contrôle et de domination de l’autre » <ref>{{Ouvrage|langue=|auteur=|prénom1=Rémy|nom1=Rieffel|titre=Révolution numérique, révolution culturelle ?|passage=20|lieu=|éditeur=Folio|date=2014|pages totales=|isbn=978-2-07-045172-2|oclc=953333541|lire en ligne=|consulté le=}}.</ref>. En fait d’ambivalence, il est surprenant, par exemple, d'apprendre que Jimmy Wales est adepte de l’[[w:fr:Objectivisme (Ayn Rand)|objectivisme]], alors qu'il fut fondateur du projet Wikipédia et qu'il transféra les avoirs de sa société à la fondation Wikimédia. Aurait-il oublié par moment que la philosophie objectiviste présente le capitalisme, comme une forme idéale d’organisation de la société <ref>{{Ouvrage|langue=|prénom1=Ayn|nom1=Rand|prénom2=Nathaniel|nom2=Branden|prénom3=Alan|nom3=Greenspan|prénom4=Robert|nom4=Hessen|titre=Capitalism: the unknown ideal|date=2013|isbn=978-0-451-14795-0|oclc=1052843511|consulté le=}}.</ref>, et la poursuite de l’égoïsme rationnel, comme une bonne intention morale ? <ref>{{Ouvrage|langue=|prénom1=Ayn|nom1=Rand|titre=La vertu d'égoïsme|éditeur=Les Belles lettres|date=2011|isbn=978-2-251-39046-8|oclc=937494401|consulté le=}}.</ref>. Quant à l'instabilité du numérique, remémorons-nous les appels répétés de [[w:fr:Tim Berners-Lee|Tim Berners-Lee]] au sujet de la « [[w:fr:Redécentralisation d'Internet|redécentralisation]] » <ref>{{Lien web|langue=|auteur=Liat Clark|titre=Tim Berners-Lee : we need to re-decentralise the web|url=https://web.archive.org/web/20201111164058/https://www.wired.co.uk/article/tim-berners-lee-reclaim-the-web|site=Wired UK|éditeur=|date=6 February 2014|consulté le=}}.</ref> et la « régulation » <ref>{{Lien web|auteur=Elsa Trujillo|titre=Tim Berners-Lee, inventeur du Web, appelle à la régulation de Facebook, Google et Twitter|url=https://web.archive.org/web/20201129111413/https://www.lefigaro.fr/secteur/high-tech/2018/03/12/32001-20180312ARTFIG00179-tim-berners-lee-inventeur-du-web-appelle-a-la-regulation-de-facebook-google-et-twitter.php|site=Le figaro|éditeur=|date=12/03/2018|consulté le=}}.</ref> d'un espace web qu'il avait conçu dans un esprit tout à fait opposé. Et les pionniers d'Internet, à quoi pensent-ils en observant que leur création est utilisée par des milliards d’[[w:Internet des objets|objets connectés]] qui commercialisent des données privées pour un bénéfice annuel de plus de 2.6 milliards d’euros ? <ref>{{Lien web|langue=|auteur=Tristan Gaudiaut|titre=Infographie: L'essor de l'Internet des objets|url=https://web.archive.org/web/20211004110619/https://fr.statista.com/infographie/24353/chiffre-affaires-marche-iot-objets-connectes-france/|site=Statista Infographies|date=30 sept. 2021|consulté le=}}.</ref> Quant à la question du contrôle et au-delà de ce qui est opéré par les [[w:GAFAM|GAFAM]], c'est bien sûr au niveau des États qu'il faut porter son attention. Face à un mouvement qui veut s’émanciper des contrôles étatiques, plusieurs gouvernements ont déjà censuré Wikipédia et parfois les projets frères. Ce fut le cas dans plus de 18 pays comme la Turquie, la Russie, l'Iran, mais également le Royaume-Uni, la France et l'Allemagne, et même de manière permanente en Chine, depuis 2004 <ref>{{Lien web|langue=|auteur=Christine Siméone|titre=Censurée en Turquie et en Chine, remise en cause en Russie, ces pays qui en veulent à Wikipédia|url=https://web.archive.org/web/20200225091639/https://www.franceinter.fr/societe/censuree-en-turquie-et-en-chine-remise-en-cause-en-russe-ces-pays-qui-remettent-wikipedia-en-cause|site=France Inter|lieu=|date=2019-12-26|consulté le=}}.</ref>. Dans certains contextes, des procédures juridiques ont été utilisées pour intimider les membres du mouvement. C'est arrivé en France lorsque le directeur de l'association Wikimédia fut menacé de poursuites pénales par la [[w:Direction_générale_de_la_Sécurité_intérieure|Direction Centrale du Renseignement Intérieur]], après un refus de supprimer un article qui traitait d’une station militaire dans Wikipédia <ref>{{Lien web|langue=|auteur=Stéphane Moccozet|titre=Une station hertzienne militaire du Puy-de-Dôme au cœur d'un désaccord entre Wikipédia et la DCRI|url=https://web.archive.org/web/20201124101244/https://france3-regions.francetvinfo.fr/auvergne-rhone-alpes/2013/04/06/un-station-hertzienne-militaire-du-puy-de-dome-au-coeur-d-un-desaccord-entre-wipikedia-et-la-dcri-229791.html|site=France 3 Auvergne-Rhône-Alpes|lieu=|date=06/04/2013|consulté le=}}.</ref>. Un épisode peu dramatique à la décision de l'État biélorusse de condamner [[w:Mark_Bernstein|Mark Bernstein]] à quinze jours de prison assortis de trois ans d’assignation à résidence, pour des propos tenus au sujet de la guerre en Ukraine <ref>{{Lien web|titre=Entrepreneur, Activist Mark Bernstein Detained In Minsk - Charter'97 :: News from Belarus - Belarusian News - Republic of Belarus - Minsk|url=https://web.archive.org/web/20220312011414/https://charter97.org/en/news/2022/3/11/458592/|site=Charter97|date=2022-03-11|consulté le=|auteur=Charter97}}.</ref>. Sans oublier qu'aux États-Unis, ce sont les conservateurs au pouvoir qui veulent la peau de Wikipédia en cherchant à obtenir l'identité réelle de certains contributeurs <ref>{{Lien web|langue=fr|titre=Les conservateurs veulent la peau de Wikipédia|url=https://www.radiofrance.fr/franceinter/podcasts/veille-sanitaire/veille-sanitaire-du-mardi-09-septembre-2025-3262622|site=France Inter|date=2025-09-09|consulté le=2026-04-23}}</ref>. Heureusement, rien est figé dans le temps. Si l’espace Web est aujourd'hui le terrain de jeu d'entreprises commerciales, à l’image des [[w:fr:GAFAM|GAFAM]], [[w:fr:BATX|BATX]], [[w:fr:NATU (Netflix, Airbnb, Tesla et Uber)|NATU]] et autres [[w:fr:Géants du web|géants du web]] accusés d'[[w:fr:Abus de position dominante|abus de position dominante]], rien ne dit que les choses ne changeront jamais. À l'image du projet commercial Nupedia qui aboutit à la création de Wikipédia et de la Fondation Wikimédia, certains projets à prétentions commerciales peuvent étonnamment donner naissance à des projets de partage sans but lucratif. C'est d'ailleurs précisément ce qui s'est produit pour le logiciel [[Firefox]] développé par la [[w:Mozilla_Foundation|fondation Mozilla]], après le placement sous licence libre du navigateur web de la société [[w:Netscape_Communications|Netscape Communications]] après sa faillite. Dans un autre contexte, observons aussi que le succès commercial de la messagerie instantanée [[w:fr:MSN Messenger|MSN Messenger]] a servi d'inspiration à de nombreux autres succès commerciaux parmi les réseaux sociaux apparus sur le web. Tandis que parrallèlement à cela, un succès non commercial tel que le projet Wikipédia, a inspiré d’autres projets collaboratifs sans but lucratif parmi lesquels figure le projet [[w:fr:OpenStreetMap|OpenStreetMap]] dédié à la cartographie du monde sous licence libre. [[Fichier:Davide_Dormino_-_Anything_to_say.jpg|alt=Davide Dormino prenant place sur sa sculpture debout sur une chaise à côté de trois lanceurs d'alertes|vignette|<small>Figure 24. Sculpture en bronze de Davide Dormino intitulée ''[[w:Anything_to_say?|Anything to say?]]'' à l’honneur des trois lanceurs d’alertes que sont de gauche à droite : Edward Snowden, Julian Assange et Chelsea Manning.</small>|350x350px]] Au niveau du contrôle étatique, pensons à présent à la figure emblématique du [[w:fr:Lanceur d'alerte|lanceur ou de la lanceuse d’alerte]], qui finalement est idéologiquement proche des figures contestataires apparues au sein de la contre-culture des années 1960. Ne peut-on pas associer à ces lanceurs des éditeurs des projets Wikimédia tels que [[w:Aaron Swartz|Aaron Swartz]], [[w:Bassel Khartabil|Bassel Khartabil]], [[w:Pavel_Pernikov|Pavel Pernikov]], [[w:Ihor_Kostenko|Ihor Kostenko]] ou [[w:Mark_Bernstein|Mark Bernstein]], qui ont sacrifié leur vie ou leur liberté pour défendre les valeurs de la transparence et du libre partage propre au mouvement ? D'une manière comparable à [[w:Julian Assange|Julian Assange]], [[w:Edward Snowden|Edward Snowden]] et [[w:Chelsea Manning|Chelsea Manning]], ne peut-on pas dire d’eux qu’ils « ont perdu leur liberté pour défendre la nôtre » <ref>{{Lien web|titre=Berlin: Des statues à l'effigie des lanceurs d'alerte Snowden, Manning et Assange|url=https://web.archive.org/web/20230326124921/https://www.20minutes.fr/insolite/1601039-20150504-berlin-statues-effigie-lanceurs-alerte-snowden-manning-assange|site=20minutes.fr|date=04/05/2015|consulté le=|auteur=B.D.}}.</ref>. Comme cela fut présenté dans l'introduction de cet ouvrage, une alerte peut aussi prendre la forme d'un appel à commentaires en réaction à une décision prise par la Fondation Wikimédia. D'autres exemples de ce type existent d'ailleurs dans le mouvement et souvent en raison d'une proximité ou d'un mimétisme trop grand entre les organisations hors ligne du mouvement et le système économique marchand<ref name=":0">{{Ouvrage|langue=fr|prénom1=Lionel|nom1=Scheepmans|lien auteur1=user:Lionel Scheepmans|titre=Imagine un monde : quand le mouvement Wikimédia nous aide à penser de manière prospective la société globale et numérique de demain|éditeur=UCL - Université Catholique de Louvain|année=2022|date=17/06/2022|lire en ligne=https://dial.uclouvain.be/pr/boreal/object/boreal:264603|consulté le=2024-03-10|nature article=Thèse de doctorat}}</ref>. Les règles et les valeurs entretenues dans la sphère hors ligne du mouvement ne plaisent donc pas toujours aux membres des communautés en ligne, qui possèdent aussi pour leur part leurs propres [[w:Wikipédia:Règles_et_recommandations|règles et des recommandations]] au niveau éditorial. Ce qui n'empèche pas non plus, à d'autres occasions, que l'ensemble du mouvement se mette d'accord, comme ce fut le cas lors du long processus qui abouti à l'adoption d'un code de conduite universel. Un code qui détermine le « référentiel minimum des comportements acceptables et inacceptables » dans toutes les sphères du mouvement, qu'elles soient en ligne ou hors ligne <ref>{{Lien web|titre=Policy:Universal Code of Conduct/fr|url=https://web.archive.org/web/20251007061014/https://foundation.wikimedia.org/wiki/Policy:Universal_Code_of_Conduct/fr|site=|date=|consulté le=|auteur=Wikimedia Foundation Governance Wiki}}.</ref>. D'un « bazar libertaire » <ref>{{Lien web|langue=|auteur=Frédéric Joignot|titre=Wikipédia, bazar libertaire|url=https://web.archive.org/web/20170630065818/http://www.lemonde.fr/technologies/article/2012/01/14/wikipedia-bazar-libertaire_1629135_651865.html|site=Le Monde|lieu=|date=2012|consulté le=}}.</ref> en apparence, c'est finalement vers l'héritage de toute une idéologie décrite par Steven Levy dans son ouvrage ''L’Éthique des hackers''<ref>{{Ouvrage|langue=|prénom1=Steven|nom1=Levy|prénom2=Gilles|nom2=Tordjman|titre=L'éthique des hackers|éditeur=Globe|date=2013|isbn=978-2-211-20410-1|oclc=844898302}}.</ref> qu'il faut revenir. Car dès la création de Wikipédia en 2001, c'est en réalité tout une structure complexe et terriblement organisée qui s'est mise en place au sein du mouvement Wikimédia. Ce qui n'empèche finalement en rien le développement du partage, de l'ouverture, la transparence et la liberté et même l'autonomie, comme nous alons le découvrir dans la deuxième partie de ce livre. Ce que démontre donc cette première partie d'ouvrage, c'est qu'il perdure au sein de Wikimédia, comme dans bien d'autres endroits, une alternative viable et même très efficace à la marchandisation du monde et à « l’interférence du gouvernement et des grandes sociétés<ref>{{Lien web|auteur=[[w:Timothy C. May|]]|titre=Manifeste Crypto-Anarchiste|url=https://web.archive.org/web/20221208203642/https://www.larevuedesressources.org/manifeste-crypto-anarchiste,2316.html|site=La Revue des Ressources|date=4 mai 2012|consulté le=}}.</ref> ». Cette alternative se base sur le partage, la liberté et l'équité pour imaginer autrement un monde toujours plus global et numérique. {{AutoCat}} 6b3br3iuk9pfnlyqykfxcvya4i83a0o 764751 764750 2026-04-23T23:18:14Z Lionel Scheepmans 20012 764751 wikitext text/x-wiki <noinclude>{{Le mouvement Wikimédia}}</noinclude> [[Fichier:Wikimedia-logo black.svg|vignette|150x150px|<small>Figure 21. Logos du mouvement Wikimédia et de sa Fondation.</small>]] [[Fichier:Peace sign.svg|vignette|150x150px|<small>Figure 22. Logo du mouvement hippie et de la contre-culture.</small>]] Au terme de cette première partie d’ouvrage, il devient évident que la révolution numérique, que l’on considère généralement comme une révolution technique, fut aussi, et peut-être avant tout, une révolution sociale et culturelle. Quant à l'histoire de Wikimédia, reprise ici depuis les origines de son encyclopédie jusqu'à l'apparition de ses organismes affiliés, elle nous fit découvrir comment les idées de [[w:Contre-culture_des_années_1960|la contre-culture des années 1960]] furent transmises au mouvement. Si un doute persistait encore, rappelons-nous que Richard Stallman, celui qui a créé le concept d'encyclopédie libre, fut désigné par certains comme le gourou de la contre-culture hacker <ref>{{Ouvrage|auteur=|prénom1=Divers|nom1=auteurs|titre=L'Éthique Hacker|passage=11|éditeur=U.C.H Pour la Liberté|date=Version 9.3|pages totales=56|lire en ligne=https://web.archive.org/web/20211031170831/https://repo.zenk-security.com/Others/L%20Ethique%20Hacker.pdf}}.</ref> et le père du système d’exploitation hippie <ref>{{Lien web|langue=|auteur=Gavin Clarke|titre=Stallman's GNU at 30: The hippie OS that foresaw the rise of Apple — and is now trying to take it on|url=https://web.archive.org/web/20230602214539/https://www.theregister.com/2013/10/07/stallman_thiry_years_gnu/|site=Theregister|date=7 Oct 2013|consulté le=}}.</ref>. Quant à la culture [[w:fr:Hippie|hippie]], n'est-il pas troublant de constater que le renversement de son logo ressemble étrangement à celui du mouvement Wikimédia ? Incontestablement et au travers du mouvement des logiciels libres, le mouvement Wikimédia a donc bien hérité des valeurs produites par les mouvements sociaux des années 60. Des valeurs qui aujourd'hui contrastent fortement avec la marchandisation et la capitalisation du monde, dont l'espace web ne fait jamais que refléter ce qui se passe dans le reste de la société humaine. Un phénomène qui ne date pas d'hier, puisqu'en 2008, [[w:fr: André Gorz|André Gorz]], ce philosophe parmi les pères de la [[w:fr: Décroissance|décroissance]] <ref>{{Ouvrage|langue=|prénom1=David|nom1=Murray|prénom2=Cédric|nom2=Biagini|prénom3=Pierre|nom3=Thiesset|prénom4=Cyberlibris|nom4=ScholarVox International|titre=Aux origines de la décroissance: cinquante penseurs|date=2017|isbn=978-2-89719-329-4|isbn2=978-2-89719-330-0|isbn3=978-2-89719-331-7|oclc=1248948596}}.</ref> et théoricien de l’[[w:fr: Écologie politique|écologie politique]] <ref>{{Ouvrage|langue=|prénom1=André|nom1=Gorz|titre=Ecologie et politique: nouv. ed. et remaniee.|éditeur=Éditions du Seuil|date=1978|isbn=978-2-02-004771-5|oclc=796186896}}.</ref>, constatait déjà que : <blockquote> La lutte engagée entre les "logiciels propriétaires" et les "logiciels libres" [...] a été le coup d’envoi du conflit central de l’époque. Il s’étend et se prolonge dans la lutte contre la marchandisation de richesses premières – la terre, les semences, le génome, les biens culturels, les savoirs et compétences communs, constitutifs de la culture du quotidien et qui sont les préalables de l’existence d’une société. De la tournure que prendra cette lutte dépend la forme civilisée ou barbare que prendra la sortie du capitalisme. <ref>{{Lien web|langue=|auteur=André Gorz|titre=Le travail dans la sortie du capitalisme|url=https://web.archive.org/web/20200921155055/http://ecorev.org/spip.php?article641|site=Revue Critique d'Écologie Politique|lieu=|date=7 janvier 2008}}.</ref> </blockquote> [[Fichier:Wikimania_stallman_keynote2.jpg|alt=Photo de Richard Stallman lors du premier rassemblement Wikimania de 2005|vignette|<small>Figure 23. Photo de Richard Stallman lors du premier rassemblement internationnal du mouvement Wikimédia en 2005.</small>|gauche|300x300px]] Dans cette lutte et avec le seul [[w:fr: Nom de domaine|nom de domaine]] non commercial parmi le top 100 des sites web les plus fréquentés <ref>{{Lien web|auteur=Alexa|titre=Top sites|url=https://www.alexa.com/topsites|consulté le=}}.</ref>, le mouvement Wikimédia représente donc bien un des derniers lieux de liberté, de partage et d'équité. Sans compter qu'au-delà du code informatique, s’il y a bien une chose que l'on cherche à marchandiser et à transformer, c’est sans aucun doute le savoir. Un savoir qui, de plus, se décline en information, lorsqu'il s'agit de récolter des données relatives à l’identité et aux comportements des utilisateurs et des utilisatrices d'Internet. Un « nouvel or noir », diront certains, alors que d’autres préfèrent parler de « capitalisme 3.0 » <ref>{{Ouvrage|langue=|prénom1=Philippe|nom1=Escande|prénom2=Sandrine|nom2=Cassini|titre=Bienvenue dans le capitalisme 3.0|éditeur=Albin Michel|date=2015|isbn=978-2-226-31914-2|oclc=954080043}}.</ref> ou encore de « capitalisme de surveillance » <ref>{{Ouvrage|langue=|prénom1=Christophe|nom1=Masutti|prénom2=Francesca|nom2=Musiani|titre=Affaires privées : aux sources du capitalisme de surveillance|éditeur=Caen : C&F éditions|collection=Société numérique|date=2020|isbn=978-2-37662-004-4|oclc=1159990604|consulté le=}}.</ref><ref>{{Ouvrage|langue=|prénom1=Shoshana|nom1=Zuboff|titre=L'âge du capitalisme de surveillance|éditeur=Zulma|date=2020|isbn=978-2-84304-926-2|oclc=1199962619}}.</ref>. Évidemment, les enjeux de cette lutte sont difficiles à comprendre. La complexité de l’infrastructure informatique, mais également le fait que tout cela s'inscrit dans une révolution que [[w:fr: Rémy Rieffel|Rémy Rieffel]] décrit comme « instable et ambivalente, simultanément porteuse de promesse, et lourde de menaces », ne facilitent pas les choses. Cela d'autant plus que tout cela se place « dans un contexte où s’affrontent des valeurs d’émancipation, et d’ouverture d’un côté et des stratégies de contrôle et de domination de l’autre » <ref>{{Ouvrage|langue=|auteur=|prénom1=Rémy|nom1=Rieffel|titre=Révolution numérique, révolution culturelle ?|passage=20|lieu=|éditeur=Folio|date=2014|pages totales=|isbn=978-2-07-045172-2|oclc=953333541|lire en ligne=|consulté le=}}.</ref>. En fait d’ambivalence, il est surprenant, par exemple, d'apprendre que Jimmy Wales est adepte de l’[[w:fr:Objectivisme (Ayn Rand)|objectivisme]], alors qu'il fut fondateur du projet Wikipédia et qu'il transféra les avoirs de sa société à la fondation Wikimédia. Aurait-il oublié par moment que la philosophie objectiviste présente le capitalisme, comme une forme idéale d’organisation de la société <ref>{{Ouvrage|langue=|prénom1=Ayn|nom1=Rand|prénom2=Nathaniel|nom2=Branden|prénom3=Alan|nom3=Greenspan|prénom4=Robert|nom4=Hessen|titre=Capitalism: the unknown ideal|date=2013|isbn=978-0-451-14795-0|oclc=1052843511|consulté le=}}.</ref>, et la poursuite de l’égoïsme rationnel, comme une bonne intention morale ? <ref>{{Ouvrage|langue=|prénom1=Ayn|nom1=Rand|titre=La vertu d'égoïsme|éditeur=Les Belles lettres|date=2011|isbn=978-2-251-39046-8|oclc=937494401|consulté le=}}.</ref>. Quant à l'instabilité du numérique, remémorons-nous les appels répétés de [[w:fr:Tim Berners-Lee|Tim Berners-Lee]] au sujet de la « [[w:fr:Redécentralisation d'Internet|redécentralisation]] » <ref>{{Lien web|langue=|auteur=Liat Clark|titre=Tim Berners-Lee : we need to re-decentralise the web|url=https://web.archive.org/web/20201111164058/https://www.wired.co.uk/article/tim-berners-lee-reclaim-the-web|site=Wired UK|éditeur=|date=6 February 2014|consulté le=}}.</ref> et la « régulation » <ref>{{Lien web|auteur=Elsa Trujillo|titre=Tim Berners-Lee, inventeur du Web, appelle à la régulation de Facebook, Google et Twitter|url=https://web.archive.org/web/20201129111413/https://www.lefigaro.fr/secteur/high-tech/2018/03/12/32001-20180312ARTFIG00179-tim-berners-lee-inventeur-du-web-appelle-a-la-regulation-de-facebook-google-et-twitter.php|site=Le figaro|éditeur=|date=12/03/2018|consulté le=}}.</ref> d'un espace web qu'il avait conçu dans un esprit tout à fait opposé. Et les pionniers d'Internet, à quoi pensent-ils en observant que leur création est utilisée par des milliards d’[[w:Internet des objets|objets connectés]] qui commercialisent des données privées pour un bénéfice annuel de plus de 2.6 milliards d’euros ? <ref>{{Lien web|langue=|auteur=Tristan Gaudiaut|titre=Infographie: L'essor de l'Internet des objets|url=https://web.archive.org/web/20211004110619/https://fr.statista.com/infographie/24353/chiffre-affaires-marche-iot-objets-connectes-france/|site=Statista Infographies|date=30 sept. 2021|consulté le=}}.</ref> Quant à la question du contrôle et au-delà de ce qui est opéré par les [[w:GAFAM|GAFAM]], c'est bien sûr au niveau des États qu'il faut porter son attention. Face à un mouvement qui veut s’émanciper des contrôles étatiques, plusieurs gouvernements ont déjà censuré Wikipédia et parfois les projets frères. Ce fut le cas dans plus de 18 pays comme la Turquie, la Russie, l'Iran, mais également le Royaume-Uni, la France et l'Allemagne, et même de manière permanente en Chine, depuis 2004 <ref>{{Lien web|langue=|auteur=Christine Siméone|titre=Censurée en Turquie et en Chine, remise en cause en Russie, ces pays qui en veulent à Wikipédia|url=https://web.archive.org/web/20200225091639/https://www.franceinter.fr/societe/censuree-en-turquie-et-en-chine-remise-en-cause-en-russe-ces-pays-qui-remettent-wikipedia-en-cause|site=France Inter|lieu=|date=2019-12-26|consulté le=}}.</ref>. Dans certains contextes, des procédures juridiques ont été utilisées pour intimider les membres du mouvement. C'est arrivé en France lorsque le directeur de l'association Wikimédia fut menacé de poursuites pénales par la [[w:Direction_générale_de_la_Sécurité_intérieure|Direction Centrale du Renseignement Intérieur]], après un refus de supprimer un article qui traitait d’une station militaire dans Wikipédia <ref>{{Lien web|langue=|auteur=Stéphane Moccozet|titre=Une station hertzienne militaire du Puy-de-Dôme au cœur d'un désaccord entre Wikipédia et la DCRI|url=https://web.archive.org/web/20201124101244/https://france3-regions.francetvinfo.fr/auvergne-rhone-alpes/2013/04/06/un-station-hertzienne-militaire-du-puy-de-dome-au-coeur-d-un-desaccord-entre-wipikedia-et-la-dcri-229791.html|site=France 3 Auvergne-Rhône-Alpes|lieu=|date=06/04/2013|consulté le=}}.</ref>. Un épisode peu dramatique à la décision de l'État biélorusse de condamner [[w:Mark_Bernstein|Mark Bernstein]] à quinze jours de prison assortis de trois ans d’assignation à résidence, pour des propos tenus au sujet de la guerre en Ukraine <ref>{{Lien web|titre=Entrepreneur, Activist Mark Bernstein Detained In Minsk - Charter'97 :: News from Belarus - Belarusian News - Republic of Belarus - Minsk|url=https://web.archive.org/web/20220312011414/https://charter97.org/en/news/2022/3/11/458592/|site=Charter97|date=2022-03-11|consulté le=|auteur=Charter97}}.</ref>. Sans oublier qu'aux États-Unis, ce sont les conservateurs au pouvoir qui veulent la peau de Wikipédia en cherchant à obtenir l'identité réelle de certains contributeurs <ref>{{Lien web|langue=fr|titre=Les conservateurs veulent la peau de Wikipédia|url=https://www.radiofrance.fr/franceinter/podcasts/veille-sanitaire/veille-sanitaire-du-mardi-09-septembre-2025-3262622|site=France Inter|date=2025-09-09|consulté le=2026-04-23}}</ref>. Heureusement, rien est figé dans le temps. Si l’espace Web est aujourd'hui le terrain de jeu d'entreprises commerciales, à l’image des [[w:fr:GAFAM|GAFAM]], [[w:fr:BATX|BATX]], [[w:fr:NATU (Netflix, Airbnb, Tesla et Uber)|NATU]] et autres [[w:fr:Géants du web|géants du web]] accusés d'[[w:fr:Abus de position dominante|abus de position dominante]], rien ne dit que les choses ne changeront jamais. À l'image du projet commercial Nupedia qui aboutit à la création de Wikipédia et de la Fondation Wikimédia, certains projets à prétentions commerciales peuvent étonnamment donner naissance à des projets de partage sans but lucratif. C'est d'ailleurs précisément ce qui s'est produit pour le logiciel [[Firefox]] développé par la [[w:Mozilla_Foundation|fondation Mozilla]], après le placement sous licence libre du navigateur web de la société [[w:Netscape_Communications|Netscape Communications]] après sa faillite. Dans un autre contexte, observons aussi que le succès commercial de la messagerie instantanée [[w:fr:MSN Messenger|MSN Messenger]] a servi d'inspiration à de nombreux autres succès commerciaux parmi les réseaux sociaux apparus sur le web. Tandis que parrallèlement à cela, un succès non commercial tel que le projet Wikipédia, a inspiré d’autres projets collaboratifs sans but lucratif parmi lesquels figure le projet [[w:fr:OpenStreetMap|OpenStreetMap]] dédié à la cartographie du monde sous licence libre. [[Fichier:Davide_Dormino_-_Anything_to_say.jpg|alt=Davide Dormino prenant place sur sa sculpture debout sur une chaise à côté de trois lanceurs d'alertes|vignette|<small>Figure 24. Sculpture en bronze de Davide Dormino intitulée ''[[w:Anything_to_say?|Anything to say?]]'' à l’honneur des trois lanceurs d’alertes que sont de gauche à droite : Edward Snowden, Julian Assange et Chelsea Manning.</small>|350x350px]] Au niveau du contrôle étatique, pensons à présent à la figure emblématique du [[w:fr:Lanceur d'alerte|lanceur ou de la lanceuse d’alerte]], qui finalement est idéologiquement proche des figures contestataires apparues au sein de la contre-culture des années 1960. Ne peut-on pas associer à ces lanceurs des éditeurs des projets Wikimédia tels que [[w:Aaron Swartz|Aaron Swartz]], [[w:Bassel Khartabil|Bassel Khartabil]], [[w:Pavel_Pernikov|Pavel Pernikov]], [[w:Ihor_Kostenko|Ihor Kostenko]] ou [[w:Mark_Bernstein|Mark Bernstein]], qui ont sacrifié leur vie ou leur liberté pour défendre les valeurs de la transparence et du libre partage propre au mouvement ? D'une manière comparable à [[w:Julian Assange|Julian Assange]], [[w:Edward Snowden|Edward Snowden]] et [[w:Chelsea Manning|Chelsea Manning]], ne peut-on pas dire d’eux qu’ils « ont perdu leur liberté pour défendre la nôtre » <ref>{{Lien web|titre=Berlin: Des statues à l'effigie des lanceurs d'alerte Snowden, Manning et Assange|url=https://web.archive.org/web/20230326124921/https://www.20minutes.fr/insolite/1601039-20150504-berlin-statues-effigie-lanceurs-alerte-snowden-manning-assange|site=20minutes.fr|date=04/05/2015|consulté le=|auteur=B.D.}}.</ref>. Comme cela fut présenté dans l'introduction de cet ouvrage, une alerte peut aussi prendre la forme d'un appel à commentaires en réaction à une décision prise par la Fondation Wikimédia. D'autres exemples de ce type existent d'ailleurs dans le mouvement et souvent en raison d'une proximité ou d'un mimétisme trop grand entre les organisations hors ligne du mouvement et le système économique marchand<ref name=":0">{{Ouvrage|langue=fr|prénom1=Lionel|nom1=Scheepmans|lien auteur1=user:Lionel Scheepmans|titre=Imagine un monde : quand le mouvement Wikimédia nous aide à penser de manière prospective la société globale et numérique de demain|éditeur=UCL - Université Catholique de Louvain|année=2022|date=17/06/2022|lire en ligne=https://dial.uclouvain.be/pr/boreal/object/boreal:264603|consulté le=2024-03-10|nature article=Thèse de doctorat}}</ref>. Les règles et les valeurs entretenues dans la sphère hors ligne du mouvement ne plaisent donc pas toujours aux membres des communautés en ligne, qui possèdent aussi pour leur part leurs propres [[w:Wikipédia:Règles_et_recommandations|règles et des recommandations]] au niveau éditorial. Ce qui n'empêche pas non plus, à d'autres occasions, que l'ensemble du mouvement se mette d'accord, comme ce fut le cas lors du long processus qui aboutit à l'adoption d'un code de conduite universel. Un code qui détermine le « référentiel minimum des comportements acceptables et inacceptables » dans toutes les sphères du mouvement, qu'elles soient en ligne ou hors ligne <ref>{{Lien web|titre=Policy:Universal Code of Conduct/fr|url=https://web.archive.org/web/20251007061014/https://foundation.wikimedia.org/wiki/Policy:Universal_Code_of_Conduct/fr|site=|date=|consulté le=|auteur=Wikimedia Foundation Governance Wiki}}.</ref>. D'un « bazar libertaire » <ref>{{Lien web|langue=|auteur=Frédéric Joignot|titre=Wikipédia, bazar libertaire|url=https://web.archive.org/web/20170630065818/http://www.lemonde.fr/technologies/article/2012/01/14/wikipedia-bazar-libertaire_1629135_651865.html|site=Le Monde|lieu=|date=2012|consulté le=}}.</ref> en apparence, c'est finalement vers l'héritage de toute une idéologie décrite par Steven Levy dans son ouvrage ''L’Éthique des hackers''<ref>{{Ouvrage|langue=|prénom1=Steven|nom1=Levy|prénom2=Gilles|nom2=Tordjman|titre=L'éthique des hackers|éditeur=Globe|date=2013|isbn=978-2-211-20410-1|oclc=844898302}}.</ref> qu'il faut revenir. Car dès la création de Wikipédia en 2001, c'est en réalité tout une structure complexe et terriblement organisée qui s'est mise en place au sein du mouvement Wikimédia. Ce qui n'empêche finalement en rien le développement du partage, de l'ouverture, de la transparence, de la liberté et même de l'autonomie, comme nous allons le découvrir dans la deuxième partie de ce livre. Avant cela, ce qu'a démontré cette première partie d'ouvrage, c'est qu'il perdure au sein de Wikimédia, comme dans bien d'autres endroits, une alternative viable et même très efficace à la marchandisation et à « l’interférence du gouvernement et des grandes sociétés » <ref>{{Lien web|auteur=[[w:Timothy C. May|]]|titre=Manifeste Crypto-Anarchiste|url=https://web.archive.org/web/20221208203642/https://www.larevuedesressources.org/manifeste-crypto-anarchiste,2316.html|site=La Revue des Ressources|date=4 mai 2012|consulté le=}}.</ref>. Cette alternative se fonde sur le partage, la liberté et l'équité, pour imaginer autrement un monde toujours plus global et numérique. {{AutoCat}} tlnlcylrmhy0pbfwt7rgl18h1yom0en Le mouvement Wikimédia/Le mouvement du logiciel libre 0 79318 764805 764243 2026-04-24T10:29:01Z Lionel Scheepmans 20012 764805 wikitext text/x-wiki <noinclude>{{Le mouvement Wikimédia}}</noinclude> L’un des premiers épisodes de la préhistoire de Wikipédia et du mouvement Wikimédia débuta en septembre 1983, lorsqu’un programmeur du ''[[w:fr:Massachusetts Institute of Technology|Massachusetts Institute of Technology]]'', appelé [[w:fr:Richard Stallman|Richard Stallman]], déposa un message sur la liste de diffusion net.unix-wizards. C’était un appel d’aide pour la création de [[w:Projet GNU|GNU]], un nouveau [[w:fr:Système d'exploitation|système d’exploitation]] qui devait réunir une suite de programmes que tout le monde pourrait utiliser librement sur son ordinateur personnel<ref>{{Ouvrage|langue=|prénom1=Richard M|nom1=Stallman|prénom2=Sam|nom2=Williams|titre=Richard Stallman et la révolution du logiciel libre - Une biographie autorisée|éditeur=Eyrolles|date=2013|oclc=708380925|lire en ligne=https://framabook.org/docs/stallman/framabook6_stallman_v1_gnu-fdl.pdf|consulté le=}}.</ref>. Dans son message transmis via [[w:Arpanet|ARPANET]], le premier réseau informatique à grande échelle qui précéda Internet, Stallman s’exprimait de la sorte<ref>{{Lien web|langue=|auteur=Richard Stallman|titre=Système d'exploitation GNU – Annonce initiale|url=https://web.archive.org/web/20010106133800/http://www.gnu.org:80/gnu/initial-announcement.fr.html|site=GNU|date=3 décembre 2000|consulté le=}}.</ref> : <blockquote> Je considère comme une [[w:Règle d'or|règle d’or]] que si j’apprécie un programme je dois le partager avec d’autres personnes qui l’apprécient. Je ne peux pas en bonne conscience signer un accord de non-divulgation ni un accord de licence de logiciel. Afin de pouvoir continuer à utiliser les ordinateurs sans violer mes principes, j’ai décidé de rassembler une quantité suffisante de logiciels libres, de manière à pouvoir m’en tirer sans aucun logiciel qui ne soit pas libre. </blockquote> Le projet de Stallman, qui reçut le soutien nécessaire à son accomplissement, marqua ainsi le début de l’[[w:Histoire_du_logiciel_libre|histoire du logiciel libre]]. Quant à la quantité d’aide fournie, elle permet de croire que Richard Stallman n’était pas seul à voir l’arrivée des [[w:Logiciel propriétaire|logiciels propriétaires]] d’un mauvais œil. Car pour les membres du projet GNU et du mouvement du logiciel libre en général, un bon programme informatique doit respecter ces quatre libertés fondamentales<ref>{{Lien web|langue=|auteur=Karl Pradène|titre=Qu'est-ce que le logiciel libre ?|url=https://web.archive.org/web/20000511101640/http://www.gnu.org/philosophy/free-sw.fr.html|site=GNU|date=6 mai 2000|consulté le=}}.</ref> : <blockquote> 1. La liberté d’exécuter le programme, pour tous les usages. 2. La liberté d’étudier le fonctionnement du programme, et de l’adapter à vos besoins. 3. La liberté de redistribuer des copies, donc d’aider votre voisin. 4. La liberté d’améliorer le programme, et de publier vos améliorations, pour en faire profiter toute la communauté. </blockquote> [[w:Histoire_du_logiciel_libre|Lors de l'apparition du logiciel libre]], le marché de l’informatique était de fait en pleine mutation. L'habituel partage des codes informatiques entre les rares étudiants ou chercheurs qui bénéficiaient d’un accès à un ordinateur faisait l'objet d'une remise en question. Ce changement faisait notamment suite au [[w:Copyright_Act_(1976)|Copyright Act]] de 1976, une nouvelle loi qui autorisait l'application d'un [[w:Droit_d'auteur|droit d'auteur]] sur le code informatique, et donc qui permettait d'en interdire le partage ou la réutilisation sans autorisation. Des [[w:Clause_de_confidentialité|clauses de confidentialité]] ont ainsi fait leur apparition, pendant que les employés des firmes informatiques étaient nouvellement soumis à des contrats de confidentialité. C'était la fin de l’entraide et de la solidarité pratiquées chez les pionniers de l’informatique. À sa place s'installaient la concurrence et la compétitivité, bien connues dans le système capitaliste marchand. [[Fichier:Commodore64withdisk.jpg|alt=Commodore 64 avec disquette et lecteur|gauche|vignette|<small>Figure 4. Commodore 64 avec disquette et lecteur.</small>|300x300px]] Cette mutation coïncidait avec l’arrivée des premiers ordinateurs « transportables ». Grâce à l’apparition des premiers [[w:Circuits_intégrés|circuits intégrés⁣⁣]], les premiers exemplaires avaient été créés par l’industrie aérospatiale au début des années 1960. Cependant, il fallut attendre le début des années 1980 pour que le prix d’un ordinateur soit suffisamment bas pour en faire un [[w:Bien_de_grande_consommation|bien de grande consommation]]. C’est ainsi qu’en 1982, le [[w:Commodore 64|Commodore 64]] entrait dans le [[w:Livre_Guinness_des_records|livre Guinness des records]], avec plus de 17 millions d’exemplaires vendus dans le monde<ref>{{Lien web|langue=|auteur=Brandon Griggs|titre=The Commodore 64, that '80 s computer icon, lives again|url=https://web.archive.org/web/20200706161515/http://edition.cnn.com/2011/TECH/gaming.gadgets/05/09/commodore.64.reborn|site=CNN|date=May 9, 2011|consulté le=}}.</ref>. Juste avant cela, en 1981, l’''[[w:fr:IBM PC|IBM Personal Computer]]'' avait déjà fait son apparition, en proposant une [[w:Architecture_(informatique)|architecture]] ouverte qui allait servir de modèle pour toute une gamme d’ordinateurs que l’on désigne toujours aujourd’hui par l’acronyme « PC ». Pour faire fonctionner ses nouveaux modèles d'ordinateurs, la société IBM avait confié à l’entreprise [[w:Microsoft|Microsoft]], créée en 1975, la mission de les équiper d’un système d’exploitation. Le contrat signé entre les deux firmes fut une véritable aubaine pour le fournisseur des programmes informatiques, qui faisaient tourner le nouveau matériel produit par la compagnie IBM. Car sans s'en apercevoir et sans jamais anticiper que son matériel serait cloné à grande échelle, celle-ci avait en effet permis à Microsoft d'établir un monopole dans la vente de logiciels. Cela fut condamné pour [[w:Abus_de_position_dominante|abus de position dominante]]<ref name="Combier_2018_01_24">{{Lien web|langue=fr|auteur=Étienne Combier|titre=Abus de position dominante : les plus grosses amendes de la Commission européenne|url=https://web.archive.org/web/20230511110018/https://www.lesechos.fr/2018/01/abus-de-position-dominante-les-plus-grosses-amendes-de-la-commission-europeenne-982719|périodique=[[w:Les Échos|Les Échos]]|date=2018-01-24|consulté le=}}.</ref> et [[w:Vente_liée_de_logiciels_avec_du_matériel_informatique|vente liée du logiciel avec le matériel informatique]]<ref>{{Lien web|langue=fr|auteur=Marc Rees|titre=Pourquoi la justice européenne a sanctuarisé la vente liée PC et OS|url=https://web.archive.org/web/20230209112015/https://www.nextinpact.com/article/23625/101268-la-justice-europeenne-sanctuarise-vente-liee-pc-et-os|site=nextinpact.com|éditeur=[[w:Next INpact|Next INpact]]|date=2016-07-09|consulté le=}}.</ref>, mais sans pour autant empêcher [[w:Bill_Gates|Bill Gates]], le principal actionnaire de Microsoft, d'être l'homme le plus riche du monde en 1994. [[Fichier:GNU_and_Tux.svg|alt=Mascotte du projet GNU à gauche et du projet Linux à droite.|vignette|<small>Figure 5. À gauche la mascotte du projet GNU ; à droite celle du projet Linux, appelée Tux.</small>]] Cependant, pendant que Microsoft renforçait sa position dominante, un nouvel événement majeur allait marquer l’histoire du logiciel libre. Celui-ci fut de nouveau déclenché par un appel à contribution, qui fut cette fois posté le vingt-cinq août 1991 par un jeune étudiant en informatique de 21 ans, appelé [[w:fr:Linus Torvalds|Linus Torvalds]]. Via le système de messagerie [[w:fr:Usenet|Usenet]], son message avait été posté dans une liste de diffusion consacrée au système d’exploitation [[w:fr:Minix|Minix]], une sorte d’[[w:UNIX|UNIX]] simplifié et développé dans un but didactique, par le programmeur [[w:fr:Andrew Tanenbaum|Andrew Tanenbaum]]. Loin d’imaginer que cela ferait de lui une nouvelle célébrité dans le monde du Libre<ref>{{Ouvrage|langue=|prénom1=Linus|nom1=Torvalds|prénom2=David|nom2=Diamond|prénom3=Olivier|nom3=Engler|titre=Il était une fois Linux|éditeur=Osman Eyrolles Multimédia|date=2001|isbn=978-2-7464-0321-5|oclc=48059105}}.</ref>, Torvalds entama son message par le paragraphe suivant<ref>{{Ouvrage|langue=|prénom1=Linus|nom1=Torvalds|prénom2=David|nom2=Diamond|titre=Just for fun : the story of an accidental revolutionary|éditeur=HarperBusiness|date=2002|isbn=978-0-06-662073-2|oclc=1049937833}}.</ref> : <blockquote> Je fais un système d’exploitation (gratuit) (juste un hobby, ne sera pas grand et professionnel comme gnu) pour les clones 386 (486) AT. Ce projet est en cours depuis avril et commence à se préparer. J’aimerais avoir un retour sur ce que les gens aiment ou n’aiment pas dans minix, car mon système d’exploitation lui ressemble un peu (même disposition physique du système de fichiers (pour des raisons pratiques) entre autres choses)<ref>Texte original avant sa traduction par www.deepl.com/translator : « ''I'm doing a (free) operating system (just a hobby, won't be big and professional like gnu) for 386(486) AT clones. This has been brewing since april, and is starting to get ready. I'd like any feedback on things people like/dislike in minix, as my OS resembles it somewhat (same physical layout of the file-system (due to practical reasons)among other things) ».''</ref>. </blockquote> Bien qu’il fût présenté comme un passe-temps, le projet qui répondait au nom de « [[w:fr:Noyau Linux|Linux]] », fut rapidement soutenu par des milliers de programmeurs du monde entier, pour devenir bientôt la pièce manquante du projet GNU. En effet, les contributeurs au projet de Stallman n’avaient pas encore terminé l’écriture du code informatique du [[w:noyau_de_système_d'exploitation|noyau]] [[w:GNU Hurd|Hurd]], alors qu'il était censé établir la communication entre la [[w:Suite_logicielle|suite logicielle]] produite par GNU et le [[w:Matériel informatique|matériel informatique]]. C'est donc la fusion des codes produits par les projets GNU et Linux qui permit la création du premier système complet, stable et entièrement libre baptisé [[w:GNU/Linux|GNU/Linux]]. [[Fichier:Debian-OpenLogo.svg|gauche|vignette|<small>Figure 6. Logo du système d’exploitation Debian.</small>|264x264px]] Au départ de ce nouveau système d’exploitation, de nombreuses variantes, que l’on nomme communément « [[w:Distribution_Linux|distributions]] », furent créées par des programmeurs de tous horizons. L’une de celles-ci s’intitule [[w:fr:Debian|Debian]] et tire sa réputation d'être la seule qui est simultanément libre, gratuite et produite par une communauté sans lien direct avec une société commerciale<ref>{{Ouvrage|langue=|auteur=|prénom1=Christophe|nom1=Lazaro|titre=La liberte logicielle|passage=|lieu=|éditeur=Academia Bruylant|collection=Anthropologie Prospective|date=2012|pages totales=56|isbn=978-2-87209-861-3|oclc=1104281978}}.</ref>. Ce qui n'a pas empêché pour autant que le code de ce système informatique soit récupéré par plus de 150 distributions dérivées. Quant à la fiabilité du système Debian, elle se confirme par son usage au sein de nombreuses entreprises et organisations, à l’image de la [[w:Wikimedia_Foundation|Fondation Wikimédia]] qui l’utilise sur [[m:Wikimedia_servers/fr|ses serveurs]] pour héberger les projets qu'elle supporte<ref>{{Lien web|langue=|auteur=Méta-Wiki|titre=Serveurs Wikimedia|url=https://web.archive.org/web/20251113214321/https://meta.wikimedia.org/wiki/Wikimedia_servers/fr|site=|date=|consulté le=}}.</ref>. En provenance des logiciels libres, l’un des premiers héritages du mouvement Wikimédia fut donc la possibilité d'activer ses serveurs informatiques avec un système d’exploitation fiable, libre et gratuit. Ensuite, et cela grâce à l'ouverture du [[w:Code_source|code source⁣⁣]], de permettre à la Fondation Wikimédia de modifier celui-ci pour répondre aux besoins spécifiques du mouvement. À la suite de quoi, et selon les règles formulées par la [[w:Communauté_du_logiciel_libre|communauté du logiciel libre]], les modifications faites par la Fondation deviennent à leur tour, gratuitement et librement, utilisables par d’autres personnes ou organismes. À ce premier héritage reçu par le mouvement Wikimédia s’ajoute ensuite une innovation méthodologique, toujours en provenance des logiciels libres. Dans son article ''[[w:La_Cathédrale_et_le_Bazar|La Cathédrale et le Bazar]]''<ref>{{Ouvrage|langue=|auteur=|prénom1=Eric Steven|nom1=Raymond|titre=Cathedral and the bazaar|titre original=Cathedral and the bazaar|traduction titre=La cathédrale et le bazar|passage=|lieu=|éditeur=SnowBall Publishing|date=2010|pages totales=|isbn=978-1-60796-228-1|oclc=833142152|lire en ligne=}}.</ref>, [[w:Eric_Raymond|Eric Raymond]] mobilise en effet le terme « [[w:Cathédrale|cathédrale]] » pour désigner le mode de production des logiciels propriétaires, en opposition au mot « [[w:fr:Bazar|bazar]] », qu'il utilise pour qualifier le mode de développement des logiciels libres. D’un côté, il décrit une organisation pyramidale, rigide et statutairement hiérarchisée, comme on peut la voir souvent au sein des entreprises. Tandis que de l’autre, il parle d’une organisation horizontale, flexible et peu hiérarchisée, qu’il a lui-même expérimentée en adoptant le « style de développement de Linus Torvalds – distribuez vite et souvent, déléguez tout ce que vous pouvez déléguer, soyez ouvert jusqu’à la promiscuité »<ref>{{Lien web|langue=|auteur=Eric S. Raymond|traducteur=Sébastien Blondeel|titre=La cathédrale et le bazar|url=https://web.archive.org/web/20200203054716/http://www.linux-france.org/article/these/cathedrale-bazar/cathedrale-bazar-1.html|site=Linux France|lieu=|date=1998|consulté le=}}.</ref>. À l’instar de la métaphore du quartier numérique présentée dans le précédent chapitre, cette manière de décrire les projets open source nous aide donc ici à mieux comprendre ce qui se passe dans le mouvement Wikimédia. D'un côté, on retrouve effectivement cette « ouverture jusqu’à la promiscuité », au niveau du libre accès accordé aux projets Wikimédia, alors que de l'autre, tout le monde peut participer aux projets Wikimédia, qu'ils soient en ligne ou hors ligne. Ces deux observations corroborent donc l’existence d’un deuxième héritage, d’ordre méthodologique cette fois, en provenance du mouvement du logiciel libre. Néanmoins, il nous reste encore à découvrir un phénomène négligé par Eric Raymond durant ses observations, et qui pourtant est d'une importance considérable concernant l'histoire de la révolution numérique. Il s’agit là de l’apparition de la licence libre, de la philosophie qu'elle sous-tend, et de la [[w:Culture_libre|culture libre]] dont elle fut à l’origine. {{AutoCat}} 3b5ttsifge4q18wskkzapaug87zuzlp 764807 764805 2026-04-24T10:31:18Z Lionel Scheepmans 20012 764807 wikitext text/x-wiki <noinclude>{{Le mouvement Wikimédia}}</noinclude> L’un des premiers épisodes de la préhistoire de Wikipédia et du mouvement Wikimédia débuta en septembre 1983, lorsqu’un programmeur du ''[[w:fr:Massachusetts Institute of Technology|Massachusetts Institute of Technology]]'', appelé [[w:fr:Richard Stallman|Richard Stallman]], déposa un message sur la liste de diffusion net.unix-wizards. C’était un appel d’aide pour la création de [[w:Projet GNU|GNU]], un nouveau [[w:fr:Système d'exploitation|système d’exploitation]] qui devait réunir une suite de programmes que tout le monde pourrait utiliser librement sur son ordinateur personnel<ref>{{Ouvrage|langue=|prénom1=Richard M|nom1=Stallman|prénom2=Sam|nom2=Williams|titre=Richard Stallman et la révolution du logiciel libre - Une biographie autorisée|éditeur=Eyrolles|date=2013|oclc=708380925|lire en ligne=https://framabook.org/docs/stallman/framabook6_stallman_v1_gnu-fdl.pdf|consulté le=}}.</ref>. Dans son message transmis via [[w:Arpanet|ARPANET]], le premier réseau informatique à grande échelle qui précéda Internet, Stallman s’exprimait de la sorte<ref>{{Lien web|langue=|auteur=Richard Stallman|titre=Système d'exploitation GNU – Annonce initiale|url=https://web.archive.org/web/20010106133800/http://www.gnu.org:80/gnu/initial-announcement.fr.html|site=GNU|date=3 décembre 2000|consulté le=}}.</ref> : <blockquote> Je considère comme une [[w:Règle d'or|règle d’or]] que si j’apprécie un programme je dois le partager avec d’autres personnes qui l’apprécient. Je ne peux pas en bonne conscience signer un accord de non-divulgation ni un accord de licence de logiciel. Afin de pouvoir continuer à utiliser les ordinateurs sans violer mes principes, j’ai décidé de rassembler une quantité suffisante de logiciels libres, de manière à pouvoir m’en tirer sans aucun logiciel qui ne soit pas libre. </blockquote> Le projet de Stallman, qui reçut le soutien nécessaire à son accomplissement, marqua ainsi le début de l’[[w:Histoire_du_logiciel_libre|histoire du logiciel libre]]. Quant à la quantité d’aide fournie, elle permet de croire que Richard Stallman n’était pas seul à voir l’arrivée des [[w:Logiciel propriétaire|logiciels propriétaires]] d’un mauvais œil. Car pour les membres du projet GNU et du mouvement du logiciel libre en général, un bon programme informatique doit respecter ces quatre libertés fondamentales<ref>{{Lien web|langue=|auteur=Karl Pradène|titre=Qu'est-ce que le logiciel libre ?|url=https://web.archive.org/web/20000511101640/http://www.gnu.org/philosophy/free-sw.fr.html|site=GNU|date=6 mai 2000|consulté le=}}.</ref> : <blockquote> 1. La liberté d’exécuter le programme, pour tous les usages. 2. La liberté d’étudier le fonctionnement du programme, et de l’adapter à vos besoins. 3. La liberté de redistribuer des copies, donc d’aider votre voisin. 4. La liberté d’améliorer le programme, et de publier vos améliorations, pour en faire profiter toute la communauté. </blockquote> [[w:Histoire_du_logiciel_libre|Lors de l'apparition du logiciel libre]], le marché de l’informatique était de fait en pleine mutation. L'habituel partage des codes informatiques entre les rares étudiants ou chercheurs qui bénéficiaient d’un accès à un ordinateur faisait l'objet d'une remise en question. Ce changement faisait notamment suite au [[w:Copyright_Act_(1976)|Copyright Act]] de 1976, une nouvelle loi qui autorisait l'application d'un [[w:Droit_d'auteur|droit d'auteur]] sur le code informatique, et donc qui permettait d'en interdire le partage ou la réutilisation sans autorisation. Des [[w:Clause_de_confidentialité|clauses de confidentialité]] ont ainsi fait leur apparition, pendant que les employés des firmes informatiques étaient nouvellement soumis à des contrats de confidentialité. C'était la fin de l’entraide et de la solidarité pratiquées chez les pionniers de l’informatique. À sa place s'installaient la concurrence et la compétitivité, bien connues dans le système capitaliste marchand. [[Fichier:Commodore64withdisk.jpg|alt=Commodore 64 avec disquette et lecteur|gauche|vignette|<small>Figure 4. Commodore 64 avec disquette et lecteur.</small>|300x300px]] Cette mutation coïncidait avec l’arrivée des premiers ordinateurs de taille réduite. Grâce à l’apparition des premiers [[w:Circuits_intégrés|circuits intégrés⁣⁣]], les premiers exemplaires avaient été créés par l’industrie aérospatiale au début des années 1960. Cependant, il fallut attendre le début des années 1980 pour que le prix d’un ordinateur soit suffisamment bas pour en faire un [[w:Bien_de_grande_consommation|bien de grande consommation]]. C’est ainsi qu’en 1982, le [[w:Commodore 64|Commodore 64]] entrait dans le [[w:Livre_Guinness_des_records|livre Guinness des records]], avec plus de 17 millions d’exemplaires vendus dans le monde<ref>{{Lien web|langue=|auteur=Brandon Griggs|titre=The Commodore 64, that '80 s computer icon, lives again|url=https://web.archive.org/web/20200706161515/http://edition.cnn.com/2011/TECH/gaming.gadgets/05/09/commodore.64.reborn|site=CNN|date=May 9, 2011|consulté le=}}.</ref>. Juste avant cela, en 1981, l’''[[w:fr:IBM PC|IBM Personal Computer]]'' avait déjà fait son apparition, en proposant une [[w:Architecture_(informatique)|architecture]] ouverte qui allait servir de modèle pour toute une gamme d’ordinateurs que l’on désigne toujours aujourd’hui par l’acronyme « PC ». Pour faire fonctionner ses nouveaux modèles d'ordinateurs, la société IBM avait confié à l’entreprise [[w:Microsoft|Microsoft]], créée en 1975, la mission de les équiper d’un système d’exploitation. Le contrat signé entre les deux firmes fut une véritable aubaine pour le fournisseur des programmes informatiques, qui faisaient tourner le nouveau matériel produit par la compagnie IBM. Car sans s'en apercevoir et sans jamais anticiper que son matériel serait cloné à grande échelle, celle-ci avait en effet permis à Microsoft d'établir un monopole dans la vente de logiciels. Cela fut condamné pour [[w:Abus_de_position_dominante|abus de position dominante]]<ref name="Combier_2018_01_24">{{Lien web|langue=fr|auteur=Étienne Combier|titre=Abus de position dominante : les plus grosses amendes de la Commission européenne|url=https://web.archive.org/web/20230511110018/https://www.lesechos.fr/2018/01/abus-de-position-dominante-les-plus-grosses-amendes-de-la-commission-europeenne-982719|périodique=[[w:Les Échos|Les Échos]]|date=2018-01-24|consulté le=}}.</ref> et [[w:Vente_liée_de_logiciels_avec_du_matériel_informatique|vente liée du logiciel avec le matériel informatique]]<ref>{{Lien web|langue=fr|auteur=Marc Rees|titre=Pourquoi la justice européenne a sanctuarisé la vente liée PC et OS|url=https://web.archive.org/web/20230209112015/https://www.nextinpact.com/article/23625/101268-la-justice-europeenne-sanctuarise-vente-liee-pc-et-os|site=nextinpact.com|éditeur=[[w:Next INpact|Next INpact]]|date=2016-07-09|consulté le=}}.</ref>, mais sans pour autant empêcher [[w:Bill_Gates|Bill Gates]], le principal actionnaire de Microsoft, d'être l'homme le plus riche du monde en 1994. [[Fichier:GNU_and_Tux.svg|alt=Mascotte du projet GNU à gauche et du projet Linux à droite.|vignette|<small>Figure 5. À gauche la mascotte du projet GNU ; à droite celle du projet Linux, appelée Tux.</small>]] Cependant, pendant que Microsoft renforçait sa position dominante, un nouvel événement majeur allait marquer l’histoire du logiciel libre. Celui-ci fut de nouveau déclenché par un appel à contribution, qui fut cette fois posté le vingt-cinq août 1991 par un jeune étudiant en informatique de 21 ans, appelé [[w:fr:Linus Torvalds|Linus Torvalds]]. Via le système de messagerie [[w:fr:Usenet|Usenet]], son message avait été posté dans une liste de diffusion consacrée au système d’exploitation [[w:fr:Minix|Minix]], une sorte d’[[w:UNIX|UNIX]] simplifié et développé dans un but didactique, par le programmeur [[w:fr:Andrew Tanenbaum|Andrew Tanenbaum]]. Loin d’imaginer que cela ferait de lui une nouvelle célébrité dans le monde du Libre<ref>{{Ouvrage|langue=|prénom1=Linus|nom1=Torvalds|prénom2=David|nom2=Diamond|prénom3=Olivier|nom3=Engler|titre=Il était une fois Linux|éditeur=Osman Eyrolles Multimédia|date=2001|isbn=978-2-7464-0321-5|oclc=48059105}}.</ref>, Torvalds entama son message par le paragraphe suivant<ref>{{Ouvrage|langue=|prénom1=Linus|nom1=Torvalds|prénom2=David|nom2=Diamond|titre=Just for fun : the story of an accidental revolutionary|éditeur=HarperBusiness|date=2002|isbn=978-0-06-662073-2|oclc=1049937833}}.</ref> : <blockquote> Je fais un système d’exploitation (gratuit) (juste un hobby, ne sera pas grand et professionnel comme gnu) pour les clones 386 (486) AT. Ce projet est en cours depuis avril et commence à se préparer. J’aimerais avoir un retour sur ce que les gens aiment ou n’aiment pas dans minix, car mon système d’exploitation lui ressemble un peu (même disposition physique du système de fichiers (pour des raisons pratiques) entre autres choses)<ref>Texte original avant sa traduction par www.deepl.com/translator : « ''I'm doing a (free) operating system (just a hobby, won't be big and professional like gnu) for 386(486) AT clones. This has been brewing since april, and is starting to get ready. I'd like any feedback on things people like/dislike in minix, as my OS resembles it somewhat (same physical layout of the file-system (due to practical reasons)among other things) ».''</ref>. </blockquote> Bien qu’il fût présenté comme un passe-temps, le projet qui répondait au nom de « [[w:fr:Noyau Linux|Linux]] », fut rapidement soutenu par des milliers de programmeurs du monde entier, pour devenir bientôt la pièce manquante du projet GNU. En effet, les contributeurs au projet de Stallman n’avaient pas encore terminé l’écriture du code informatique du [[w:noyau_de_système_d'exploitation|noyau]] [[w:GNU Hurd|Hurd]], alors qu'il était censé établir la communication entre la [[w:Suite_logicielle|suite logicielle]] produite par GNU et le [[w:Matériel informatique|matériel informatique]]. C'est donc la fusion des codes produits par les projets GNU et Linux qui permit la création du premier système complet, stable et entièrement libre baptisé [[w:GNU/Linux|GNU/Linux]]. [[Fichier:Debian-OpenLogo.svg|gauche|vignette|<small>Figure 6. Logo du système d’exploitation Debian.</small>|264x264px]] Au départ de ce nouveau système d’exploitation, de nombreuses variantes, que l’on nomme communément « [[w:Distribution_Linux|distributions]] », furent créées par des programmeurs de tous horizons. L’une de celles-ci s’intitule [[w:fr:Debian|Debian]] et tire sa réputation d'être la seule qui est simultanément libre, gratuite et produite par une communauté sans lien direct avec une société commerciale<ref>{{Ouvrage|langue=|auteur=|prénom1=Christophe|nom1=Lazaro|titre=La liberte logicielle|passage=|lieu=|éditeur=Academia Bruylant|collection=Anthropologie Prospective|date=2012|pages totales=56|isbn=978-2-87209-861-3|oclc=1104281978}}.</ref>. Ce qui n'a pas empêché pour autant que le code de ce système informatique soit récupéré par plus de 150 distributions dérivées. Quant à la fiabilité du système Debian, elle se confirme par son usage au sein de nombreuses entreprises et organisations, à l’image de la [[w:Wikimedia_Foundation|Fondation Wikimédia]] qui l’utilise sur [[m:Wikimedia_servers/fr|ses serveurs]] pour héberger les projets qu'elle supporte<ref>{{Lien web|langue=|auteur=Méta-Wiki|titre=Serveurs Wikimedia|url=https://web.archive.org/web/20251113214321/https://meta.wikimedia.org/wiki/Wikimedia_servers/fr|site=|date=|consulté le=}}.</ref>. En provenance des logiciels libres, l’un des premiers héritages du mouvement Wikimédia fut donc la possibilité d'activer ses serveurs informatiques avec un système d’exploitation fiable, libre et gratuit. Ensuite, et cela grâce à l'ouverture du [[w:Code_source|code source⁣⁣]], de permettre à la Fondation Wikimédia de modifier celui-ci pour répondre aux besoins spécifiques du mouvement. À la suite de quoi, et selon les règles formulées par la [[w:Communauté_du_logiciel_libre|communauté du logiciel libre]], les modifications faites par la Fondation deviennent à leur tour, gratuitement et librement, utilisables par d’autres personnes ou organismes. À ce premier héritage reçu par le mouvement Wikimédia s’ajoute ensuite une innovation méthodologique, toujours en provenance des logiciels libres. Dans son article ''[[w:La_Cathédrale_et_le_Bazar|La Cathédrale et le Bazar]]''<ref>{{Ouvrage|langue=|auteur=|prénom1=Eric Steven|nom1=Raymond|titre=Cathedral and the bazaar|titre original=Cathedral and the bazaar|traduction titre=La cathédrale et le bazar|passage=|lieu=|éditeur=SnowBall Publishing|date=2010|pages totales=|isbn=978-1-60796-228-1|oclc=833142152|lire en ligne=}}.</ref>, [[w:Eric_Raymond|Eric Raymond]] mobilise en effet le terme « [[w:Cathédrale|cathédrale]] » pour désigner le mode de production des logiciels propriétaires, en opposition au mot « [[w:fr:Bazar|bazar]] », qu'il utilise pour qualifier le mode de développement des logiciels libres. D’un côté, il décrit une organisation pyramidale, rigide et statutairement hiérarchisée, comme on peut la voir souvent au sein des entreprises. Tandis que de l’autre, il parle d’une organisation horizontale, flexible et peu hiérarchisée, qu’il a lui-même expérimentée en adoptant le « style de développement de Linus Torvalds – distribuez vite et souvent, déléguez tout ce que vous pouvez déléguer, soyez ouvert jusqu’à la promiscuité »<ref>{{Lien web|langue=|auteur=Eric S. Raymond|traducteur=Sébastien Blondeel|titre=La cathédrale et le bazar|url=https://web.archive.org/web/20200203054716/http://www.linux-france.org/article/these/cathedrale-bazar/cathedrale-bazar-1.html|site=Linux France|lieu=|date=1998|consulté le=}}.</ref>. À l’instar de la métaphore du quartier numérique présentée dans le précédent chapitre, cette manière de décrire les projets open source nous aide donc ici à mieux comprendre ce qui se passe dans le mouvement Wikimédia. D'un côté, on retrouve effectivement cette « ouverture jusqu’à la promiscuité », au niveau du libre accès accordé aux projets Wikimédia, alors que de l'autre, tout le monde peut participer aux projets Wikimédia, qu'ils soient en ligne ou hors ligne. Ces deux observations corroborent donc l’existence d’un deuxième héritage, d’ordre méthodologique cette fois, en provenance du mouvement du logiciel libre. Néanmoins, il nous reste encore à découvrir un phénomène négligé par Eric Raymond durant ses observations, et qui pourtant est d'une importance considérable concernant l'histoire de la révolution numérique. Il s’agit là de l’apparition de la licence libre, de la philosophie qu'elle sous-tend, et de la [[w:Culture_libre|culture libre]] dont elle fut à l’origine. {{AutoCat}} 52gu0dod58086wtdo3bmx6sfcy6k0vd 764809 764807 2026-04-24T10:33:30Z Lionel Scheepmans 20012 764809 wikitext text/x-wiki <noinclude>{{Le mouvement Wikimédia}}</noinclude> L’un des premiers épisodes de la préhistoire de Wikipédia et du mouvement Wikimédia débuta en septembre 1983, lorsqu’un programmeur du ''[[w:fr:Massachusetts Institute of Technology|Massachusetts Institute of Technology]]'', appelé [[w:fr:Richard Stallman|Richard Stallman]], déposa un message sur la liste de diffusion net.unix-wizards. C’était un appel d’aide pour la création de [[w:Projet GNU|GNU]], un nouveau [[w:fr:Système d'exploitation|système d’exploitation]] qui devait réunir une suite de programmes que tout le monde pourrait utiliser librement sur son ordinateur personnel<ref>{{Ouvrage|langue=|prénom1=Richard M|nom1=Stallman|prénom2=Sam|nom2=Williams|titre=Richard Stallman et la révolution du logiciel libre - Une biographie autorisée|éditeur=Eyrolles|date=2013|oclc=708380925|lire en ligne=https://framabook.org/docs/stallman/framabook6_stallman_v1_gnu-fdl.pdf|consulté le=}}.</ref>. Dans son message transmis via [[w:Arpanet|ARPANET]], le premier réseau informatique à grande échelle qui précéda Internet, Stallman s’exprimait de la sorte<ref>{{Lien web|langue=|auteur=Richard Stallman|titre=Système d'exploitation GNU – Annonce initiale|url=https://web.archive.org/web/20010106133800/http://www.gnu.org:80/gnu/initial-announcement.fr.html|site=GNU|date=3 décembre 2000|consulté le=}}.</ref> : <blockquote> Je considère comme une [[w:Règle d'or|règle d’or]] que si j’apprécie un programme je dois le partager avec d’autres personnes qui l’apprécient. Je ne peux pas en bonne conscience signer un accord de non-divulgation ni un accord de licence de logiciel. Afin de pouvoir continuer à utiliser les ordinateurs sans violer mes principes, j’ai décidé de rassembler une quantité suffisante de logiciels libres, de manière à pouvoir m’en tirer sans aucun logiciel qui ne soit pas libre. </blockquote> Le projet de Stallman, qui reçut le soutien nécessaire à son accomplissement, marqua ainsi le début de l’[[w:Histoire_du_logiciel_libre|histoire du logiciel libre]]. Quant à la quantité d’aide fournie, elle permet de croire que Richard Stallman n’était pas seul à voir l’arrivée des [[w:Logiciel propriétaire|logiciels propriétaires]] d’un mauvais œil. Car pour les membres du projet GNU et du mouvement du logiciel libre en général, un bon programme informatique doit respecter ces quatre libertés fondamentales<ref>{{Lien web|langue=|auteur=Karl Pradène|titre=Qu'est-ce que le logiciel libre ?|url=https://web.archive.org/web/20000511101640/http://www.gnu.org/philosophy/free-sw.fr.html|site=GNU|date=6 mai 2000|consulté le=}}.</ref> : <blockquote> 1. La liberté d’exécuter le programme, pour tous les usages. 2. La liberté d’étudier le fonctionnement du programme, et de l’adapter à vos besoins. 3. La liberté de redistribuer des copies, donc d’aider votre voisin. 4. La liberté d’améliorer le programme, et de publier vos améliorations, pour en faire profiter toute la communauté. </blockquote> [[w:Histoire_du_logiciel_libre|Lors de l'apparition du logiciel libre]], le marché de l’informatique était de fait en pleine mutation. L'habituel partage des codes informatiques entre les rares étudiants ou chercheurs qui bénéficiaient d’un accès à un ordinateur faisait l'objet d'une remise en question. Ce changement faisait notamment suite au [[w:Copyright_Act_(1976)|Copyright Act]] de 1976, une nouvelle loi qui autorisait l'application d'un [[w:Droit_d'auteur|droit d'auteur]] sur le code informatique, et donc qui permettait d'en interdire le partage ou la réutilisation sans autorisation. Des [[w:Clause_de_confidentialité|clauses de confidentialité]] ont ainsi fait leur apparition, pendant que les employés des firmes informatiques étaient nouvellement soumis à des contrats de confidentialité. C'était la fin de l’entraide et de la solidarité pratiquées chez les pionniers de l’informatique. À sa place s'installaient la concurrence et la compétitivité, bien connues dans le système capitaliste marchand. [[Fichier:Commodore64withdisk.jpg|alt=Commodore 64 avec disquette et lecteur|gauche|vignette|<small>Figure 4. Commodore 64 avec disquette et lecteur.</small>|300x300px]] Cette mutation coïncidait avec l’arrivée des premiers ordinateurs de taille réduite. Grâce à l’apparition des premiers [[w:Circuits_intégrés|circuits intégrés⁣⁣]], les premiers exemplaires avaient en effet été créés par l’industrie aérospatiale au début des années 1960. Cependant, il fallut attendre le début des années 1980 pour que le prix d’un ordinateur soit suffisamment bas pour en faire un [[w:Bien_de_grande_consommation|bien de grande consommation]]. C’est ainsi qu’en 1982, le [[w:Commodore 64|Commodore 64]] entrait dans le [[w:Livre_Guinness_des_records|livre Guinness des records]], avec plus de 17 millions d’exemplaires vendus dans le monde<ref>{{Lien web|langue=|auteur=Brandon Griggs|titre=The Commodore 64, that '80 s computer icon, lives again|url=https://web.archive.org/web/20200706161515/http://edition.cnn.com/2011/TECH/gaming.gadgets/05/09/commodore.64.reborn|site=CNN|date=May 9, 2011|consulté le=}}.</ref>. Juste avant cela, en 1981, l’''[[w:fr:IBM PC|IBM Personal Computer]]'' avait déjà fait son apparition, en proposant une [[w:Architecture_(informatique)|architecture]] ouverte qui allait servir de modèle pour toute une gamme d’ordinateurs que l’on désigne toujours aujourd’hui par l’acronyme « PC ». Pour faire fonctionner ses nouveaux modèles d'ordinateurs, la société IBM avait confié à l’entreprise [[w:Microsoft|Microsoft]], créée en 1975, la mission de les équiper d’un système d’exploitation. Le contrat signé entre les deux firmes fut une véritable aubaine pour le fournisseur des programmes informatiques, qui faisaient tourner le nouveau matériel produit par la compagnie IBM. Car sans s'en apercevoir et sans jamais anticiper que son matériel serait cloné à grande échelle, celle-ci avait en effet permis à Microsoft d'établir un monopole dans la vente de logiciels. Cela fut condamné pour [[w:Abus_de_position_dominante|abus de position dominante]]<ref name="Combier_2018_01_24">{{Lien web|langue=fr|auteur=Étienne Combier|titre=Abus de position dominante : les plus grosses amendes de la Commission européenne|url=https://web.archive.org/web/20230511110018/https://www.lesechos.fr/2018/01/abus-de-position-dominante-les-plus-grosses-amendes-de-la-commission-europeenne-982719|périodique=[[w:Les Échos|Les Échos]]|date=2018-01-24|consulté le=}}.</ref> et [[w:Vente_liée_de_logiciels_avec_du_matériel_informatique|vente liée du logiciel avec le matériel informatique]]<ref>{{Lien web|langue=fr|auteur=Marc Rees|titre=Pourquoi la justice européenne a sanctuarisé la vente liée PC et OS|url=https://web.archive.org/web/20230209112015/https://www.nextinpact.com/article/23625/101268-la-justice-europeenne-sanctuarise-vente-liee-pc-et-os|site=nextinpact.com|éditeur=[[w:Next INpact|Next INpact]]|date=2016-07-09|consulté le=}}.</ref>, mais sans pour autant empêcher [[w:Bill_Gates|Bill Gates]], le principal actionnaire de Microsoft, d'être l'homme le plus riche du monde en 1994. [[Fichier:GNU_and_Tux.svg|alt=Mascotte du projet GNU à gauche et du projet Linux à droite.|vignette|<small>Figure 5. À gauche la mascotte du projet GNU ; à droite celle du projet Linux, appelée Tux.</small>]] Cependant, pendant que Microsoft renforçait sa position dominante, un nouvel événement majeur allait marquer l’histoire du logiciel libre. Celui-ci fut de nouveau déclenché par un appel à contribution, qui fut cette fois posté le vingt-cinq août 1991 par un jeune étudiant en informatique de 21 ans, appelé [[w:fr:Linus Torvalds|Linus Torvalds]]. Via le système de messagerie [[w:fr:Usenet|Usenet]], son message avait été posté dans une liste de diffusion consacrée au système d’exploitation [[w:fr:Minix|Minix]], une sorte d’[[w:UNIX|UNIX]] simplifié et développé dans un but didactique, par le programmeur [[w:fr:Andrew Tanenbaum|Andrew Tanenbaum]]. Loin d’imaginer que cela ferait de lui une nouvelle célébrité dans le monde du Libre<ref>{{Ouvrage|langue=|prénom1=Linus|nom1=Torvalds|prénom2=David|nom2=Diamond|prénom3=Olivier|nom3=Engler|titre=Il était une fois Linux|éditeur=Osman Eyrolles Multimédia|date=2001|isbn=978-2-7464-0321-5|oclc=48059105}}.</ref>, Torvalds entama son message par le paragraphe suivant<ref>{{Ouvrage|langue=|prénom1=Linus|nom1=Torvalds|prénom2=David|nom2=Diamond|titre=Just for fun : the story of an accidental revolutionary|éditeur=HarperBusiness|date=2002|isbn=978-0-06-662073-2|oclc=1049937833}}.</ref> : <blockquote> Je fais un système d’exploitation (gratuit) (juste un hobby, ne sera pas grand et professionnel comme gnu) pour les clones 386 (486) AT. Ce projet est en cours depuis avril et commence à se préparer. J’aimerais avoir un retour sur ce que les gens aiment ou n’aiment pas dans minix, car mon système d’exploitation lui ressemble un peu (même disposition physique du système de fichiers (pour des raisons pratiques) entre autres choses)<ref>Texte original avant sa traduction par www.deepl.com/translator : « ''I'm doing a (free) operating system (just a hobby, won't be big and professional like gnu) for 386(486) AT clones. This has been brewing since april, and is starting to get ready. I'd like any feedback on things people like/dislike in minix, as my OS resembles it somewhat (same physical layout of the file-system (due to practical reasons)among other things) ».''</ref>. </blockquote> Bien qu’il fût présenté comme un passe-temps, le projet qui répondait au nom de « [[w:fr:Noyau Linux|Linux]] », fut rapidement soutenu par des milliers de programmeurs du monde entier, pour devenir bientôt la pièce manquante du projet GNU. En effet, les contributeurs au projet de Stallman n’avaient pas encore terminé l’écriture du code informatique du [[w:noyau_de_système_d'exploitation|noyau]] [[w:GNU Hurd|Hurd]], alors qu'il était censé établir la communication entre la [[w:Suite_logicielle|suite logicielle]] produite par GNU et le [[w:Matériel informatique|matériel informatique]]. C'est donc la fusion des codes produits par les projets GNU et Linux qui permit la création du premier système complet, stable et entièrement libre baptisé [[w:GNU/Linux|GNU/Linux]]. [[Fichier:Debian-OpenLogo.svg|gauche|vignette|<small>Figure 6. Logo du système d’exploitation Debian.</small>|264x264px]] Au départ de ce nouveau système d’exploitation, de nombreuses variantes, que l’on nomme communément « [[w:Distribution_Linux|distributions]] », furent créées par des programmeurs de tous horizons. L’une de celles-ci s’intitule [[w:fr:Debian|Debian]] et tire sa réputation d'être la seule qui est simultanément libre, gratuite et produite par une communauté sans lien direct avec une société commerciale<ref>{{Ouvrage|langue=|auteur=|prénom1=Christophe|nom1=Lazaro|titre=La liberte logicielle|passage=|lieu=|éditeur=Academia Bruylant|collection=Anthropologie Prospective|date=2012|pages totales=56|isbn=978-2-87209-861-3|oclc=1104281978}}.</ref>. Ce qui n'a pas empêché pour autant que le code de ce système informatique soit récupéré par plus de 150 distributions dérivées. Quant à la fiabilité du système Debian, elle se confirme par son usage au sein de nombreuses entreprises et organisations, à l’image de la [[w:Wikimedia_Foundation|Fondation Wikimédia]] qui l’utilise sur [[m:Wikimedia_servers/fr|ses serveurs]] pour héberger les projets qu'elle supporte<ref>{{Lien web|langue=|auteur=Méta-Wiki|titre=Serveurs Wikimedia|url=https://web.archive.org/web/20251113214321/https://meta.wikimedia.org/wiki/Wikimedia_servers/fr|site=|date=|consulté le=}}.</ref>. En provenance des logiciels libres, l’un des premiers héritages du mouvement Wikimédia fut donc la possibilité d'activer ses serveurs informatiques avec un système d’exploitation fiable, libre et gratuit. Ensuite, et cela grâce à l'ouverture du [[w:Code_source|code source⁣⁣]], de permettre à la Fondation Wikimédia de modifier celui-ci pour répondre aux besoins spécifiques du mouvement. À la suite de quoi, et selon les règles formulées par la [[w:Communauté_du_logiciel_libre|communauté du logiciel libre]], les modifications faites par la Fondation deviennent à leur tour, gratuitement et librement, utilisables par d’autres personnes ou organismes. À ce premier héritage reçu par le mouvement Wikimédia s’ajoute ensuite une innovation méthodologique, toujours en provenance des logiciels libres. Dans son article ''[[w:La_Cathédrale_et_le_Bazar|La Cathédrale et le Bazar]]''<ref>{{Ouvrage|langue=|auteur=|prénom1=Eric Steven|nom1=Raymond|titre=Cathedral and the bazaar|titre original=Cathedral and the bazaar|traduction titre=La cathédrale et le bazar|passage=|lieu=|éditeur=SnowBall Publishing|date=2010|pages totales=|isbn=978-1-60796-228-1|oclc=833142152|lire en ligne=}}.</ref>, [[w:Eric_Raymond|Eric Raymond]] mobilise en effet le terme « [[w:Cathédrale|cathédrale]] » pour désigner le mode de production des logiciels propriétaires, en opposition au mot « [[w:fr:Bazar|bazar]] », qu'il utilise pour qualifier le mode de développement des logiciels libres. D’un côté, il décrit une organisation pyramidale, rigide et statutairement hiérarchisée, comme on peut la voir souvent au sein des entreprises. Tandis que de l’autre, il parle d’une organisation horizontale, flexible et peu hiérarchisée, qu’il a lui-même expérimentée en adoptant le « style de développement de Linus Torvalds – distribuez vite et souvent, déléguez tout ce que vous pouvez déléguer, soyez ouvert jusqu’à la promiscuité »<ref>{{Lien web|langue=|auteur=Eric S. Raymond|traducteur=Sébastien Blondeel|titre=La cathédrale et le bazar|url=https://web.archive.org/web/20200203054716/http://www.linux-france.org/article/these/cathedrale-bazar/cathedrale-bazar-1.html|site=Linux France|lieu=|date=1998|consulté le=}}.</ref>. À l’instar de la métaphore du quartier numérique présentée dans le précédent chapitre, cette manière de décrire les projets open source nous aide donc ici à mieux comprendre ce qui se passe dans le mouvement Wikimédia. D'un côté, on retrouve effectivement cette « ouverture jusqu’à la promiscuité », au niveau du libre accès accordé aux projets Wikimédia, alors que de l'autre, tout le monde peut participer aux projets Wikimédia, qu'ils soient en ligne ou hors ligne. Ces deux observations corroborent donc l’existence d’un deuxième héritage, d’ordre méthodologique cette fois, en provenance du mouvement du logiciel libre. Néanmoins, il nous reste encore à découvrir un phénomène négligé par Eric Raymond durant ses observations, et qui pourtant est d'une importance considérable concernant l'histoire de la révolution numérique. Il s’agit là de l’apparition de la licence libre, de la philosophie qu'elle sous-tend, et de la [[w:Culture_libre|culture libre]] dont elle fut à l’origine. {{AutoCat}} syicxzh3xfqiynm9cv5tdmtmw9es07x 764811 764809 2026-04-24T10:37:19Z Lionel Scheepmans 20012 764811 wikitext text/x-wiki <noinclude>{{Le mouvement Wikimédia}}</noinclude> L’un des premiers épisodes de la préhistoire de Wikipédia et du mouvement Wikimédia débuta en septembre 1983, lorsqu’un programmeur du ''[[w:fr:Massachusetts Institute of Technology|Massachusetts Institute of Technology]]'', appelé [[w:fr:Richard Stallman|Richard Stallman]], déposa un message sur la liste de diffusion net.unix-wizards. C’était un appel d’aide pour la création de [[w:Projet GNU|GNU]], un nouveau [[w:fr:Système d'exploitation|système d’exploitation]] qui devait réunir une suite de programmes que tout le monde pourrait utiliser librement sur son ordinateur personnel<ref>{{Ouvrage|langue=|prénom1=Richard M|nom1=Stallman|prénom2=Sam|nom2=Williams|titre=Richard Stallman et la révolution du logiciel libre - Une biographie autorisée|éditeur=Eyrolles|date=2013|oclc=708380925|lire en ligne=https://framabook.org/docs/stallman/framabook6_stallman_v1_gnu-fdl.pdf|consulté le=}}.</ref>. Dans son message transmis via [[w:Arpanet|ARPANET]], le premier réseau informatique à grande échelle qui précéda Internet, Stallman s’exprimait de la sorte<ref>{{Lien web|langue=|auteur=Richard Stallman|titre=Système d'exploitation GNU – Annonce initiale|url=https://web.archive.org/web/20010106133800/http://www.gnu.org:80/gnu/initial-announcement.fr.html|site=GNU|date=3 décembre 2000|consulté le=}}.</ref> : <blockquote> Je considère comme une [[w:Règle d'or|règle d’or]] que si j’apprécie un programme je dois le partager avec d’autres personnes qui l’apprécient. Je ne peux pas en bonne conscience signer un accord de non-divulgation ni un accord de licence de logiciel. Afin de pouvoir continuer à utiliser les ordinateurs sans violer mes principes, j’ai décidé de rassembler une quantité suffisante de logiciels libres, de manière à pouvoir m’en tirer sans aucun logiciel qui ne soit pas libre. </blockquote> Le projet de Stallman, qui reçut le soutien nécessaire à son accomplissement, marqua ainsi le début de l’[[w:Histoire_du_logiciel_libre|histoire du logiciel libre]]. Quant à la quantité d’aide fournie, elle permet de croire que Richard Stallman n’était pas seul à voir l’arrivée des [[w:Logiciel propriétaire|logiciels propriétaires]] d’un mauvais œil. Car pour les membres du projet GNU et du mouvement du logiciel libre en général, un bon programme informatique doit respecter ces quatre libertés fondamentales<ref>{{Lien web|langue=|auteur=Karl Pradène|titre=Qu'est-ce que le logiciel libre ?|url=https://web.archive.org/web/20000511101640/http://www.gnu.org/philosophy/free-sw.fr.html|site=GNU|date=6 mai 2000|consulté le=}}.</ref> : <blockquote> 1. La liberté d’exécuter le programme, pour tous les usages. 2. La liberté d’étudier le fonctionnement du programme, et de l’adapter à vos besoins. 3. La liberté de redistribuer des copies, donc d’aider votre voisin. 4. La liberté d’améliorer le programme, et de publier vos améliorations, pour en faire profiter toute la communauté. </blockquote> [[w:Histoire_du_logiciel_libre|Lors de l'apparition du logiciel libre]], le marché de l’informatique était de fait en pleine mutation. L'habituel partage des codes informatiques entre les rares étudiants ou chercheurs qui bénéficiaient d’un accès à un ordinateur faisait l'objet d'une remise en question. Ce changement faisait notamment suite au [[w:Copyright_Act_(1976)|Copyright Act]] de 1976, une nouvelle loi qui autorisait l'application d'un [[w:Droit_d'auteur|droit d'auteur]] sur le code informatique, et donc qui permettait d'en interdire le partage ou la réutilisation sans autorisation. Des [[w:Clause_de_confidentialité|clauses de confidentialité]] ont ainsi fait leur apparition, pendant que les employés des firmes informatiques étaient nouvellement soumis à des contrats de confidentialité. C'était la fin de l’entraide et de la solidarité pratiquées chez les pionniers de l’informatique. À sa place s'installaient la concurrence et la compétitivité, bien connues dans le système capitaliste marchand. [[Fichier:Commodore64withdisk.jpg|alt=Commodore 64 avec disquette et lecteur|gauche|vignette|<small>Figure 4. Commodore 64 avec disquette et lecteur.</small>|300x300px]] Cette mutation coïncidait avec l’arrivée des premiers ordinateurs de taille réduite. Grâce à l’apparition des premiers [[w:Circuits_intégrés|circuits intégrés⁣⁣]], les premiers exemplaires avaient en effet été créés par l’industrie aérospatiale au début des années 1960. Cependant, il fallut attendre le début des années 1980 pour que le prix d’un ordinateur soit suffisamment bas pour en faire un [[w:Bien_de_grande_consommation|bien de grande consommation]]. C’est ainsi qu’en 1982, le [[w:Commodore 64|Commodore 64]] entrait dans le [[w:Livre_Guinness_des_records|livre Guinness des records]], avec plus de 17 millions d’exemplaires vendus dans le monde<ref>{{Lien web|langue=|auteur=Brandon Griggs|titre=The Commodore 64, that '80 s computer icon, lives again|url=https://web.archive.org/web/20200706161515/http://edition.cnn.com/2011/TECH/gaming.gadgets/05/09/commodore.64.reborn|site=CNN|date=May 9, 2011|consulté le=}}.</ref>. Juste avant cela, en 1981, l’''[[w:fr:IBM PC|IBM Personal Computer]]'' avait déjà fait son apparition, en proposant une [[w:Architecture_(informatique)|architecture]] ouverte qui allait servir de modèle pour toute une gamme d’ordinateurs que l’on désigne toujours aujourd’hui par l’acronyme « PC ». Pour faire fonctionner ses nouveaux modèles d'ordinateurs, la société IBM avait confié à l’entreprise [[w:Microsoft|Microsoft]], créée en 1975, la mission de les équiper d’un système d’exploitation. Le contrat signé entre les deux firmes fut une véritable aubaine pour le fournisseur des programmes informatiques. Car sans s'en apercevoir, et sans jamais anticiper que son matériel serait cloné à grande échelle, celle-ci avait en effet permis à Microsoft d'établir un monopole dans la vente de logiciels. Cela fut condamné pour [[w:Abus_de_position_dominante|abus de position dominante]]<ref name="Combier_2018_01_24">{{Lien web|langue=fr|auteur=Étienne Combier|titre=Abus de position dominante : les plus grosses amendes de la Commission européenne|url=https://web.archive.org/web/20230511110018/https://www.lesechos.fr/2018/01/abus-de-position-dominante-les-plus-grosses-amendes-de-la-commission-europeenne-982719|périodique=[[w:Les Échos|Les Échos]]|date=2018-01-24|consulté le=}}.</ref> et [[w:Vente_liée_de_logiciels_avec_du_matériel_informatique|vente liée du logiciel avec le matériel informatique]]<ref>{{Lien web|langue=fr|auteur=Marc Rees|titre=Pourquoi la justice européenne a sanctuarisé la vente liée PC et OS|url=https://web.archive.org/web/20230209112015/https://www.nextinpact.com/article/23625/101268-la-justice-europeenne-sanctuarise-vente-liee-pc-et-os|site=nextinpact.com|éditeur=[[w:Next INpact|Next INpact]]|date=2016-07-09|consulté le=}}.</ref>, mais sans pour autant empêcher [[w:Bill_Gates|Bill Gates]], le principal actionnaire de Microsoft, d'être l'homme le plus riche du monde en 1994. [[Fichier:GNU_and_Tux.svg|alt=Mascotte du projet GNU à gauche et du projet Linux à droite.|vignette|<small>Figure 5. À gauche la mascotte du projet GNU ; à droite celle du projet Linux, appelée Tux.</small>]] Cependant, pendant que Microsoft renforçait sa position dominante, un nouvel événement majeur allait marquer l’histoire du logiciel libre. Celui-ci fut de nouveau déclenché par un appel à contribution, qui fut cette fois posté le vingt-cinq août 1991 par un jeune étudiant en informatique de 21 ans, appelé [[w:fr:Linus Torvalds|Linus Torvalds]]. Via le système de messagerie [[w:fr:Usenet|Usenet]], son message avait été posté dans une liste de diffusion consacrée au système d’exploitation [[w:fr:Minix|Minix]], une sorte d’[[w:UNIX|UNIX]] simplifié et développé dans un but didactique, par le programmeur [[w:fr:Andrew Tanenbaum|Andrew Tanenbaum]]. Loin d’imaginer que cela ferait de lui une nouvelle célébrité dans le monde du Libre<ref>{{Ouvrage|langue=|prénom1=Linus|nom1=Torvalds|prénom2=David|nom2=Diamond|prénom3=Olivier|nom3=Engler|titre=Il était une fois Linux|éditeur=Osman Eyrolles Multimédia|date=2001|isbn=978-2-7464-0321-5|oclc=48059105}}.</ref>, Torvalds entama son message par le paragraphe suivant<ref>{{Ouvrage|langue=|prénom1=Linus|nom1=Torvalds|prénom2=David|nom2=Diamond|titre=Just for fun : the story of an accidental revolutionary|éditeur=HarperBusiness|date=2002|isbn=978-0-06-662073-2|oclc=1049937833}}.</ref> : <blockquote> Je fais un système d’exploitation (gratuit) (juste un hobby, ne sera pas grand et professionnel comme gnu) pour les clones 386 (486) AT. Ce projet est en cours depuis avril et commence à se préparer. J’aimerais avoir un retour sur ce que les gens aiment ou n’aiment pas dans minix, car mon système d’exploitation lui ressemble un peu (même disposition physique du système de fichiers (pour des raisons pratiques) entre autres choses)<ref>Texte original avant sa traduction par www.deepl.com/translator : « ''I'm doing a (free) operating system (just a hobby, won't be big and professional like gnu) for 386(486) AT clones. This has been brewing since april, and is starting to get ready. I'd like any feedback on things people like/dislike in minix, as my OS resembles it somewhat (same physical layout of the file-system (due to practical reasons)among other things) ».''</ref>. </blockquote> Bien qu’il fût présenté comme un passe-temps, le projet qui répondait au nom de « [[w:fr:Noyau Linux|Linux]] », fut rapidement soutenu par des milliers de programmeurs du monde entier, pour devenir bientôt la pièce manquante du projet GNU. En effet, les contributeurs au projet de Stallman n’avaient pas encore terminé l’écriture du code informatique du [[w:noyau_de_système_d'exploitation|noyau]] [[w:GNU Hurd|Hurd]], alors qu'il était censé établir la communication entre la [[w:Suite_logicielle|suite logicielle]] produite par GNU et le [[w:Matériel informatique|matériel informatique]]. C'est donc la fusion des codes produits par les projets GNU et Linux qui permit la création du premier système complet, stable et entièrement libre baptisé [[w:GNU/Linux|GNU/Linux]]. [[Fichier:Debian-OpenLogo.svg|gauche|vignette|<small>Figure 6. Logo du système d’exploitation Debian.</small>|264x264px]] Au départ de ce nouveau système d’exploitation, de nombreuses variantes, que l’on nomme communément « [[w:Distribution_Linux|distributions]] », furent créées par des programmeurs de tous horizons. L’une de celles-ci s’intitule [[w:fr:Debian|Debian]] et tire sa réputation d'être la seule qui est simultanément libre, gratuite et produite par une communauté sans lien direct avec une société commerciale<ref>{{Ouvrage|langue=|auteur=|prénom1=Christophe|nom1=Lazaro|titre=La liberte logicielle|passage=|lieu=|éditeur=Academia Bruylant|collection=Anthropologie Prospective|date=2012|pages totales=56|isbn=978-2-87209-861-3|oclc=1104281978}}.</ref>. Ce qui n'a pas empêché pour autant que le code de ce système informatique soit récupéré par plus de 150 distributions dérivées. Quant à la fiabilité du système Debian, elle se confirme par son usage au sein de nombreuses entreprises et organisations, à l’image de la [[w:Wikimedia_Foundation|Fondation Wikimédia]] qui l’utilise sur [[m:Wikimedia_servers/fr|ses serveurs]] pour héberger les projets qu'elle supporte<ref>{{Lien web|langue=|auteur=Méta-Wiki|titre=Serveurs Wikimedia|url=https://web.archive.org/web/20251113214321/https://meta.wikimedia.org/wiki/Wikimedia_servers/fr|site=|date=|consulté le=}}.</ref>. En provenance des logiciels libres, l’un des premiers héritages du mouvement Wikimédia fut donc la possibilité d'activer ses serveurs informatiques avec un système d’exploitation fiable, libre et gratuit. Ensuite, et cela grâce à l'ouverture du [[w:Code_source|code source⁣⁣]], de permettre à la Fondation Wikimédia de modifier celui-ci pour répondre aux besoins spécifiques du mouvement. À la suite de quoi, et selon les règles formulées par la [[w:Communauté_du_logiciel_libre|communauté du logiciel libre]], les modifications faites par la Fondation deviennent à leur tour, gratuitement et librement, utilisables par d’autres personnes ou organismes. À ce premier héritage reçu par le mouvement Wikimédia s’ajoute ensuite une innovation méthodologique, toujours en provenance des logiciels libres. Dans son article ''[[w:La_Cathédrale_et_le_Bazar|La Cathédrale et le Bazar]]''<ref>{{Ouvrage|langue=|auteur=|prénom1=Eric Steven|nom1=Raymond|titre=Cathedral and the bazaar|titre original=Cathedral and the bazaar|traduction titre=La cathédrale et le bazar|passage=|lieu=|éditeur=SnowBall Publishing|date=2010|pages totales=|isbn=978-1-60796-228-1|oclc=833142152|lire en ligne=}}.</ref>, [[w:Eric_Raymond|Eric Raymond]] mobilise en effet le terme « [[w:Cathédrale|cathédrale]] » pour désigner le mode de production des logiciels propriétaires, en opposition au mot « [[w:fr:Bazar|bazar]] », qu'il utilise pour qualifier le mode de développement des logiciels libres. D’un côté, il décrit une organisation pyramidale, rigide et statutairement hiérarchisée, comme on peut la voir souvent au sein des entreprises. Tandis que de l’autre, il parle d’une organisation horizontale, flexible et peu hiérarchisée, qu’il a lui-même expérimentée en adoptant le « style de développement de Linus Torvalds – distribuez vite et souvent, déléguez tout ce que vous pouvez déléguer, soyez ouvert jusqu’à la promiscuité »<ref>{{Lien web|langue=|auteur=Eric S. Raymond|traducteur=Sébastien Blondeel|titre=La cathédrale et le bazar|url=https://web.archive.org/web/20200203054716/http://www.linux-france.org/article/these/cathedrale-bazar/cathedrale-bazar-1.html|site=Linux France|lieu=|date=1998|consulté le=}}.</ref>. À l’instar de la métaphore du quartier numérique présentée dans le précédent chapitre, cette manière de décrire les projets open source nous aide donc ici à mieux comprendre ce qui se passe dans le mouvement Wikimédia. D'un côté, on retrouve effectivement cette « ouverture jusqu’à la promiscuité », au niveau du libre accès accordé aux projets Wikimédia, alors que de l'autre, tout le monde peut participer aux projets Wikimédia, qu'ils soient en ligne ou hors ligne. Ces deux observations corroborent donc l’existence d’un deuxième héritage, d’ordre méthodologique cette fois, en provenance du mouvement du logiciel libre. Néanmoins, il nous reste encore à découvrir un phénomène négligé par Eric Raymond durant ses observations, et qui pourtant est d'une importance considérable concernant l'histoire de la révolution numérique. Il s’agit là de l’apparition de la licence libre, de la philosophie qu'elle sous-tend, et de la [[w:Culture_libre|culture libre]] dont elle fut à l’origine. {{AutoCat}} f8iqr8bzi52eewju4psh0z6ejl0iddz 764812 764811 2026-04-24T10:42:01Z Lionel Scheepmans 20012 764812 wikitext text/x-wiki <noinclude>{{Le mouvement Wikimédia}}</noinclude> L’un des premiers épisodes de la préhistoire de Wikipédia et du mouvement Wikimédia débuta en septembre 1983, lorsqu’un programmeur du ''[[w:fr:Massachusetts Institute of Technology|Massachusetts Institute of Technology]]'', appelé [[w:fr:Richard Stallman|Richard Stallman]], déposa un message sur la liste de diffusion net.unix-wizards. C’était un appel d’aide pour la création de [[w:Projet GNU|GNU]], un nouveau [[w:fr:Système d'exploitation|système d’exploitation]] qui devait réunir une suite de programmes que tout le monde pourrait utiliser librement sur son ordinateur personnel<ref>{{Ouvrage|langue=|prénom1=Richard M|nom1=Stallman|prénom2=Sam|nom2=Williams|titre=Richard Stallman et la révolution du logiciel libre - Une biographie autorisée|éditeur=Eyrolles|date=2013|oclc=708380925|lire en ligne=https://framabook.org/docs/stallman/framabook6_stallman_v1_gnu-fdl.pdf|consulté le=}}.</ref>. Dans son message transmis via [[w:Arpanet|ARPANET]], le premier réseau informatique à grande échelle qui précéda Internet, Stallman s’exprimait de la sorte<ref>{{Lien web|langue=|auteur=Richard Stallman|titre=Système d'exploitation GNU – Annonce initiale|url=https://web.archive.org/web/20010106133800/http://www.gnu.org:80/gnu/initial-announcement.fr.html|site=GNU|date=3 décembre 2000|consulté le=}}.</ref> : <blockquote> Je considère comme une [[w:Règle d'or|règle d’or]] que si j’apprécie un programme je dois le partager avec d’autres personnes qui l’apprécient. Je ne peux pas en bonne conscience signer un accord de non-divulgation ni un accord de licence de logiciel. Afin de pouvoir continuer à utiliser les ordinateurs sans violer mes principes, j’ai décidé de rassembler une quantité suffisante de logiciels libres, de manière à pouvoir m’en tirer sans aucun logiciel qui ne soit pas libre. </blockquote> Le projet de Stallman, qui reçut le soutien nécessaire à son accomplissement, marqua ainsi le début de l’[[w:Histoire_du_logiciel_libre|histoire du logiciel libre]]. Quant à la quantité d’aide fournie, elle permet de croire que Richard Stallman n’était pas seul à voir l’arrivée des [[w:Logiciel propriétaire|logiciels propriétaires]] d’un mauvais œil. Car pour les membres du projet GNU et du mouvement du logiciel libre en général, un bon programme informatique doit respecter ces quatre libertés fondamentales<ref>{{Lien web|langue=|auteur=Karl Pradène|titre=Qu'est-ce que le logiciel libre ?|url=https://web.archive.org/web/20000511101640/http://www.gnu.org/philosophy/free-sw.fr.html|site=GNU|date=6 mai 2000|consulté le=}}.</ref> : <blockquote> 1. La liberté d’exécuter le programme, pour tous les usages. 2. La liberté d’étudier le fonctionnement du programme, et de l’adapter à vos besoins. 3. La liberté de redistribuer des copies, donc d’aider votre voisin. 4. La liberté d’améliorer le programme, et de publier vos améliorations, pour en faire profiter toute la communauté. </blockquote> [[w:Histoire_du_logiciel_libre|Lors de l'apparition du logiciel libre]], le marché de l’informatique était de fait en pleine mutation. L'habituel partage des codes informatiques entre les rares étudiants ou chercheurs qui bénéficiaient d’un accès à un ordinateur faisait l'objet d'une remise en question. Ce changement faisait notamment suite au [[w:Copyright_Act_(1976)|Copyright Act]] de 1976, une nouvelle loi qui autorisait l'application d'un [[w:Droit_d'auteur|droit d'auteur]] sur le code informatique, et donc qui permettait d'en interdire le partage ou la réutilisation sans autorisation. Des [[w:Clause_de_confidentialité|clauses de confidentialité]] ont ainsi fait leur apparition, pendant que les employés des firmes informatiques étaient nouvellement soumis à des contrats de confidentialité. C'était la fin de l’entraide et de la solidarité pratiquées chez les pionniers de l’informatique. À sa place s'installaient la concurrence et la compétitivité, bien connues dans le système capitaliste marchand. [[Fichier:Commodore64withdisk.jpg|alt=Commodore 64 avec disquette et lecteur|gauche|vignette|<small>Figure 4. Commodore 64 avec disquette et lecteur.</small>|300x300px]] Cette mutation coïncidait avec l’arrivée des premiers ordinateurs de taille réduite. Grâce à l’apparition des premiers [[w:Circuits_intégrés|circuits intégrés⁣⁣]], les premiers exemplaires avaient en effet été créés par l’industrie aérospatiale au début des années 1960. Cependant, il fallut attendre le début des années 1980 pour que le prix d’un ordinateur soit suffisamment bas pour en faire un [[w:Bien_de_grande_consommation|bien de grande consommation]]. C’est ainsi qu’en 1982, le [[w:Commodore 64|Commodore 64]] entrait dans le [[w:Livre_Guinness_des_records|livre Guinness des records]], avec plus de 17 millions d’exemplaires vendus dans le monde<ref>{{Lien web|langue=|auteur=Brandon Griggs|titre=The Commodore 64, that '80 s computer icon, lives again|url=https://web.archive.org/web/20200706161515/http://edition.cnn.com/2011/TECH/gaming.gadgets/05/09/commodore.64.reborn|site=CNN|date=May 9, 2011|consulté le=}}.</ref>. Juste avant cela, en 1981, l’''[[w:fr:IBM PC|IBM Personal Computer]]'' avait déjà fait son apparition, en proposant une [[w:Architecture_(informatique)|architecture]] ouverte qui allait servir de modèle pour toute une gamme d’ordinateurs que l’on désigne toujours aujourd’hui par l’acronyme « PC ». Pour faire fonctionner ses nouveaux modèles d'ordinateurs, la société IBM avait confié à l’entreprise [[w:Microsoft|Microsoft]], créée en 1975, la mission de les équiper d’un système d’exploitation. Le contrat signé entre les deux firmes fut une véritable aubaine pour le fournisseur des programmes informatiques. Car sans s'en apercevoir, et sans jamais anticiper que son matériel serait cloné à grande échelle, celle-ci avait en effet permis à Microsoft d'établir un monopole dans la vente de logiciels. Cela fut condamné pour [[w:Abus_de_position_dominante|abus de position dominante]]<ref name="Combier_2018_01_24">{{Lien web|langue=fr|auteur=Étienne Combier|titre=Abus de position dominante : les plus grosses amendes de la Commission européenne|url=https://web.archive.org/web/20230511110018/https://www.lesechos.fr/2018/01/abus-de-position-dominante-les-plus-grosses-amendes-de-la-commission-europeenne-982719|périodique=[[w:Les Échos|Les Échos]]|date=2018-01-24|consulté le=}}.</ref> et [[w:Vente_liée_de_logiciels_avec_du_matériel_informatique|vente liée du logiciel avec le matériel informatique]]<ref>{{Lien web|langue=fr|auteur=Marc Rees|titre=Pourquoi la justice européenne a sanctuarisé la vente liée PC et OS|url=https://web.archive.org/web/20230209112015/https://www.nextinpact.com/article/23625/101268-la-justice-europeenne-sanctuarise-vente-liee-pc-et-os|site=nextinpact.com|éditeur=[[w:Next INpact|Next INpact]]|date=2016-07-09|consulté le=}}.</ref>, mais sans pour autant empêcher [[w:Bill_Gates|Bill Gates]], le principal actionnaire de Microsoft, d'être l'homme le plus riche du monde en 1994. [[Fichier:GNU_and_Tux.svg|alt=Mascotte du projet GNU à gauche et du projet Linux à droite.|vignette|<small>Figure 5. À gauche la mascotte du projet GNU ; à droite celle du projet Linux, appelée Tux.</small>]] Cependant, pendant que Microsoft renforçait sa position dominante, un nouvel événement majeur allait marquer l’histoire du logiciel libre. Celui-ci fut de nouveau déclenché par un appel à contribution, qui fut cette fois posté le vingt-cinq août 1991 par un jeune étudiant en informatique de 21 ans, appelé [[w:fr:Linus Torvalds|Linus Torvalds]]. Via le système de messagerie [[w:fr:Usenet|Usenet]], son message avait été posté dans une liste de diffusion consacrée au système d’exploitation [[w:fr:Minix|Minix]], une sorte d’[[w:UNIX|UNIX]] simplifié et développé dans un but didactique, par le programmeur [[w:fr:Andrew Tanenbaum|Andrew Tanenbaum]]. Loin d’imaginer que cela ferait de lui une nouvelle célébrité dans le monde du Libre<ref>{{Ouvrage|langue=|prénom1=Linus|nom1=Torvalds|prénom2=David|nom2=Diamond|prénom3=Olivier|nom3=Engler|titre=Il était une fois Linux|éditeur=Osman Eyrolles Multimédia|date=2001|isbn=978-2-7464-0321-5|oclc=48059105}}.</ref>, Torvalds entama son message par le paragraphe suivant<ref>{{Ouvrage|langue=|prénom1=Linus|nom1=Torvalds|prénom2=David|nom2=Diamond|titre=Just for fun : the story of an accidental revolutionary|éditeur=HarperBusiness|date=2002|isbn=978-0-06-662073-2|oclc=1049937833}}.</ref> : <blockquote> Je fais un système d’exploitation (gratuit) (juste un hobby, ne sera pas grand et professionnel comme gnu) pour les clones 386 (486) AT. Ce projet est en cours depuis avril et commence à se préparer. J’aimerais avoir un retour sur ce que les gens aiment ou n’aiment pas dans minix, car mon système d’exploitation lui ressemble un peu (même disposition physique du système de fichiers (pour des raisons pratiques) entre autres choses)<ref>Texte original avant sa traduction par www.deepl.com/translator : « ''I'm doing a (free) operating system (just a hobby, won't be big and professional like gnu) for 386(486) AT clones. This has been brewing since april, and is starting to get ready. I'd like any feedback on things people like/dislike in minix, as my OS resembles it somewhat (same physical layout of the file-system (due to practical reasons)among other things) ».''</ref>. </blockquote> Bien qu’il fût présenté comme un passe-temps, le projet qui répondait au nom de « [[w:fr:Noyau Linux|Linux]] », fut rapidement soutenu par des milliers de programmeurs du monde entier, avant de devenir la pièce manquante du projet GNU. En effet, les contributeurs au projet de Stallman n’avaient pas encore terminé l’écriture du code informatique du [[w:noyau_de_système_d'exploitation|noyau]] [[w:GNU Hurd|Hurd]], alors qu'il était censé établir la communication entre la [[w:Suite_logicielle|suite logicielle]] produite par GNU et le [[w:Matériel informatique|matériel informatique]]. C'est donc la fusion des codes produits par les projets GNU et Linux qui permit la création du premier système complet, stable et entièrement libre baptisé [[w:GNU/Linux|GNU/Linux]]. [[Fichier:Debian-OpenLogo.svg|gauche|vignette|<small>Figure 6. Logo du système d’exploitation Debian.</small>|264x264px]] Au départ de ce nouveau système d’exploitation, de nombreuses variantes, que l’on nomme communément « [[w:Distribution_Linux|distributions]] », furent créées par des programmeurs de tous horizons. L’une de celles-ci s’intitule [[w:fr:Debian|Debian]] et tire sa réputation d'être la seule qui est simultanément libre, gratuite et produite par une communauté sans lien direct avec une société commerciale<ref>{{Ouvrage|langue=|auteur=|prénom1=Christophe|nom1=Lazaro|titre=La liberte logicielle|passage=|lieu=|éditeur=Academia Bruylant|collection=Anthropologie Prospective|date=2012|pages totales=56|isbn=978-2-87209-861-3|oclc=1104281978}}.</ref>. Ce qui n'a pas empêché pour autant que le code de ce système informatique soit récupéré par plus de 150 distributions dérivées. Quant à la fiabilité du système Debian, elle se confirme par son usage au sein de nombreuses entreprises et organisations, à l’image de la [[w:Wikimedia_Foundation|Fondation Wikimédia]] qui l’utilise sur [[m:Wikimedia_servers/fr|ses serveurs]] pour héberger les projets qu'elle supporte<ref>{{Lien web|langue=|auteur=Méta-Wiki|titre=Serveurs Wikimedia|url=https://web.archive.org/web/20251113214321/https://meta.wikimedia.org/wiki/Wikimedia_servers/fr|site=|date=|consulté le=}}.</ref>. En provenance des logiciels libres, l’un des premiers héritages du mouvement Wikimédia fut donc la possibilité d'activer ses serveurs informatiques avec un système d’exploitation fiable, libre et gratuit. Ensuite, et cela grâce à l'ouverture du [[w:Code_source|code source⁣⁣]], de permettre à la Fondation Wikimédia de modifier celui-ci pour répondre aux besoins spécifiques du mouvement. À la suite de quoi, et selon les règles formulées par la [[w:Communauté_du_logiciel_libre|communauté du logiciel libre]], les modifications faites par la Fondation deviennent à leur tour, gratuitement et librement, utilisables par d’autres personnes ou organismes. À ce premier héritage reçu par le mouvement Wikimédia s’ajoute ensuite une innovation méthodologique, toujours en provenance des logiciels libres. Dans son article ''[[w:La_Cathédrale_et_le_Bazar|La Cathédrale et le Bazar]]''<ref>{{Ouvrage|langue=|auteur=|prénom1=Eric Steven|nom1=Raymond|titre=Cathedral and the bazaar|titre original=Cathedral and the bazaar|traduction titre=La cathédrale et le bazar|passage=|lieu=|éditeur=SnowBall Publishing|date=2010|pages totales=|isbn=978-1-60796-228-1|oclc=833142152|lire en ligne=}}.</ref>, [[w:Eric_Raymond|Eric Raymond]] mobilise en effet le terme « [[w:Cathédrale|cathédrale]] » pour désigner le mode de production des logiciels propriétaires, en opposition au mot « [[w:fr:Bazar|bazar]] », qu'il utilise pour qualifier le mode de développement des logiciels libres. D’un côté, il décrit une organisation pyramidale, rigide et statutairement hiérarchisée, comme on peut la voir souvent au sein des entreprises. Tandis que de l’autre, il parle d’une organisation horizontale, flexible et peu hiérarchisée, qu’il a lui-même expérimentée en adoptant le « style de développement de Linus Torvalds – distribuez vite et souvent, déléguez tout ce que vous pouvez déléguer, soyez ouvert jusqu’à la promiscuité »<ref>{{Lien web|langue=|auteur=Eric S. Raymond|traducteur=Sébastien Blondeel|titre=La cathédrale et le bazar|url=https://web.archive.org/web/20200203054716/http://www.linux-france.org/article/these/cathedrale-bazar/cathedrale-bazar-1.html|site=Linux France|lieu=|date=1998|consulté le=}}.</ref>. À l’instar de la métaphore du quartier numérique présentée dans le précédent chapitre, cette manière de décrire les projets open source nous aide donc ici à mieux comprendre ce qui se passe dans le mouvement Wikimédia. D'un côté, on retrouve effectivement cette « ouverture jusqu’à la promiscuité », au niveau du libre accès accordé aux projets Wikimédia, alors que de l'autre, tout le monde peut participer aux projets Wikimédia, qu'ils soient en ligne ou hors ligne. Ces deux observations corroborent donc l’existence d’un deuxième héritage, d’ordre méthodologique cette fois, en provenance du mouvement du logiciel libre. Néanmoins, il nous reste encore à découvrir un phénomène négligé par Eric Raymond durant ses observations, et qui pourtant est d'une importance considérable concernant l'histoire de la révolution numérique. Il s’agit là de l’apparition de la licence libre, de la philosophie qu'elle sous-tend, et de la [[w:Culture_libre|culture libre]] dont elle fut à l’origine. {{AutoCat}} k6ub6jae4xe2fc9obagvy03eygmjd7t 764814 764812 2026-04-24T10:45:15Z Lionel Scheepmans 20012 764814 wikitext text/x-wiki <noinclude>{{Le mouvement Wikimédia}}</noinclude> L’un des premiers épisodes de la préhistoire de Wikipédia et du mouvement Wikimédia débuta en septembre 1983, lorsqu’un programmeur du ''[[w:fr:Massachusetts Institute of Technology|Massachusetts Institute of Technology]]'', appelé [[w:fr:Richard Stallman|Richard Stallman]], déposa un message sur la liste de diffusion net.unix-wizards. C’était un appel d’aide pour la création de [[w:Projet GNU|GNU]], un nouveau [[w:fr:Système d'exploitation|système d’exploitation]] qui devait réunir une suite de programmes que tout le monde pourrait utiliser librement sur son ordinateur personnel<ref>{{Ouvrage|langue=|prénom1=Richard M|nom1=Stallman|prénom2=Sam|nom2=Williams|titre=Richard Stallman et la révolution du logiciel libre - Une biographie autorisée|éditeur=Eyrolles|date=2013|oclc=708380925|lire en ligne=https://framabook.org/docs/stallman/framabook6_stallman_v1_gnu-fdl.pdf|consulté le=}}.</ref>. Dans son message transmis via [[w:Arpanet|ARPANET]], le premier réseau informatique à grande échelle qui précéda Internet, Stallman s’exprimait de la sorte<ref>{{Lien web|langue=|auteur=Richard Stallman|titre=Système d'exploitation GNU – Annonce initiale|url=https://web.archive.org/web/20010106133800/http://www.gnu.org:80/gnu/initial-announcement.fr.html|site=GNU|date=3 décembre 2000|consulté le=}}.</ref> : <blockquote> Je considère comme une [[w:Règle d'or|règle d’or]] que si j’apprécie un programme je dois le partager avec d’autres personnes qui l’apprécient. Je ne peux pas en bonne conscience signer un accord de non-divulgation ni un accord de licence de logiciel. Afin de pouvoir continuer à utiliser les ordinateurs sans violer mes principes, j’ai décidé de rassembler une quantité suffisante de logiciels libres, de manière à pouvoir m’en tirer sans aucun logiciel qui ne soit pas libre. </blockquote> Le projet de Stallman, qui reçut le soutien nécessaire à son accomplissement, marqua ainsi le début de l’[[w:Histoire_du_logiciel_libre|histoire du logiciel libre]]. Quant à la quantité d’aide fournie, elle permet de croire que Richard Stallman n’était pas seul à voir l’arrivée des [[w:Logiciel propriétaire|logiciels propriétaires]] d’un mauvais œil. Car pour les membres du projet GNU et du mouvement du logiciel libre en général, un bon programme informatique doit respecter ces quatre libertés fondamentales<ref>{{Lien web|langue=|auteur=Karl Pradène|titre=Qu'est-ce que le logiciel libre ?|url=https://web.archive.org/web/20000511101640/http://www.gnu.org/philosophy/free-sw.fr.html|site=GNU|date=6 mai 2000|consulté le=}}.</ref> : <blockquote> 1. La liberté d’exécuter le programme, pour tous les usages. 2. La liberté d’étudier le fonctionnement du programme, et de l’adapter à vos besoins. 3. La liberté de redistribuer des copies, donc d’aider votre voisin. 4. La liberté d’améliorer le programme, et de publier vos améliorations, pour en faire profiter toute la communauté. </blockquote> [[w:Histoire_du_logiciel_libre|Lors de l'apparition du logiciel libre]], le marché de l’informatique était de fait en pleine mutation. L'habituel partage des codes informatiques entre les rares étudiants ou chercheurs qui bénéficiaient d’un accès à un ordinateur faisait l'objet d'une remise en question. Ce changement faisait notamment suite au [[w:Copyright_Act_(1976)|Copyright Act]] de 1976, une nouvelle loi qui autorisait l'application d'un [[w:Droit_d'auteur|droit d'auteur]] sur le code informatique, et donc qui permettait d'en interdire le partage ou la réutilisation sans autorisation. Des [[w:Clause_de_confidentialité|clauses de confidentialité]] ont ainsi fait leur apparition, pendant que les employés des firmes informatiques étaient nouvellement soumis à des contrats de confidentialité. C'était la fin de l’entraide et de la solidarité pratiquées chez les pionniers de l’informatique. À sa place s'installaient la concurrence et la compétitivité, bien connues dans le système capitaliste marchand. [[Fichier:Commodore64withdisk.jpg|alt=Commodore 64 avec disquette et lecteur|gauche|vignette|<small>Figure 4. Commodore 64 avec disquette et lecteur.</small>|300x300px]] Cette mutation coïncidait avec l’arrivée des premiers ordinateurs de taille réduite. Grâce à l’apparition des premiers [[w:Circuits_intégrés|circuits intégrés⁣⁣]], les premiers exemplaires avaient en effet été créés par l’industrie aérospatiale au début des années 1960. Cependant, il fallut attendre le début des années 1980 pour que le prix d’un ordinateur soit suffisamment bas pour en faire un [[w:Bien_de_grande_consommation|bien de grande consommation]]. C’est ainsi qu’en 1982, le [[w:Commodore 64|Commodore 64]] entrait dans le [[w:Livre_Guinness_des_records|livre Guinness des records]], avec plus de 17 millions d’exemplaires vendus dans le monde<ref>{{Lien web|langue=|auteur=Brandon Griggs|titre=The Commodore 64, that '80 s computer icon, lives again|url=https://web.archive.org/web/20200706161515/http://edition.cnn.com/2011/TECH/gaming.gadgets/05/09/commodore.64.reborn|site=CNN|date=May 9, 2011|consulté le=}}.</ref>. Juste avant cela, en 1981, l’''[[w:fr:IBM PC|IBM Personal Computer]]'' avait déjà fait son apparition, en proposant une [[w:Architecture_(informatique)|architecture]] ouverte qui allait servir de modèle pour toute une gamme d’ordinateurs que l’on désigne toujours aujourd’hui par l’acronyme « PC ». Pour faire fonctionner ses nouveaux modèles d'ordinateurs, la société IBM avait confié à l’entreprise [[w:Microsoft|Microsoft]], créée en 1975, la mission de les équiper d’un système d’exploitation. Le contrat signé entre les deux firmes fut une véritable aubaine pour le fournisseur des programmes informatiques. Car sans s'en apercevoir, et sans jamais anticiper que son matériel serait cloné à grande échelle, celle-ci avait en effet permis à Microsoft d'établir un monopole dans la vente de logiciels. Cela fut condamné pour [[w:Abus_de_position_dominante|abus de position dominante]]<ref name="Combier_2018_01_24">{{Lien web|langue=fr|auteur=Étienne Combier|titre=Abus de position dominante : les plus grosses amendes de la Commission européenne|url=https://web.archive.org/web/20230511110018/https://www.lesechos.fr/2018/01/abus-de-position-dominante-les-plus-grosses-amendes-de-la-commission-europeenne-982719|périodique=[[w:Les Échos|Les Échos]]|date=2018-01-24|consulté le=}}.</ref> et [[w:Vente_liée_de_logiciels_avec_du_matériel_informatique|vente liée du logiciel avec le matériel informatique]]<ref>{{Lien web|langue=fr|auteur=Marc Rees|titre=Pourquoi la justice européenne a sanctuarisé la vente liée PC et OS|url=https://web.archive.org/web/20230209112015/https://www.nextinpact.com/article/23625/101268-la-justice-europeenne-sanctuarise-vente-liee-pc-et-os|site=nextinpact.com|éditeur=[[w:Next INpact|Next INpact]]|date=2016-07-09|consulté le=}}.</ref>, mais sans pour autant empêcher [[w:Bill_Gates|Bill Gates]], le principal actionnaire de Microsoft, d'être l'homme le plus riche du monde en 1994. [[Fichier:GNU_and_Tux.svg|alt=Mascotte du projet GNU à gauche et du projet Linux à droite.|vignette|<small>Figure 5. À gauche la mascotte du projet GNU ; à droite celle du projet Linux, appelée Tux.</small>]] Cependant, pendant que Microsoft renforçait sa position dominante, un nouvel événement majeur allait marquer l’histoire du logiciel libre. Celui-ci fut de nouveau déclenché par un appel à contribution, qui fut cette fois posté le vingt-cinq août 1991 par un jeune étudiant en informatique de 21 ans, appelé [[w:fr:Linus Torvalds|Linus Torvalds]]. Via le système de messagerie [[w:fr:Usenet|Usenet]], son message avait été posté dans une liste de diffusion consacrée au système d’exploitation [[w:fr:Minix|Minix]], une sorte d’[[w:UNIX|UNIX]] simplifié et développé dans un but didactique, par le programmeur [[w:fr:Andrew Tanenbaum|Andrew Tanenbaum]]. Loin d’imaginer que cela ferait de lui une nouvelle célébrité dans le monde du Libre<ref>{{Ouvrage|langue=|prénom1=Linus|nom1=Torvalds|prénom2=David|nom2=Diamond|prénom3=Olivier|nom3=Engler|titre=Il était une fois Linux|éditeur=Osman Eyrolles Multimédia|date=2001|isbn=978-2-7464-0321-5|oclc=48059105}}.</ref>, Torvalds entama son message par le paragraphe suivant<ref>{{Ouvrage|langue=|prénom1=Linus|nom1=Torvalds|prénom2=David|nom2=Diamond|titre=Just for fun : the story of an accidental revolutionary|éditeur=HarperBusiness|date=2002|isbn=978-0-06-662073-2|oclc=1049937833}}.</ref> : <blockquote> Je fais un système d’exploitation (gratuit) (juste un hobby, ne sera pas grand et professionnel comme gnu) pour les clones 386 (486) AT. Ce projet est en cours depuis avril et commence à se préparer. J’aimerais avoir un retour sur ce que les gens aiment ou n’aiment pas dans minix, car mon système d’exploitation lui ressemble un peu (même disposition physique du système de fichiers (pour des raisons pratiques) entre autres choses)<ref>Texte original avant sa traduction par www.deepl.com/translator : « ''I'm doing a (free) operating system (just a hobby, won't be big and professional like gnu) for 386(486) AT clones. This has been brewing since april, and is starting to get ready. I'd like any feedback on things people like/dislike in minix, as my OS resembles it somewhat (same physical layout of the file-system (due to practical reasons)among other things) ».''</ref>. </blockquote> Bien qu’il fût présenté comme un passe-temps, le projet qui répondait au nom de « [[w:fr:Noyau Linux|Linux]] », fut rapidement soutenu par des milliers de programmeurs du monde entier, avant de devenir la pièce manquante du projet GNU. En effet, les contributeurs au projet de Stallman n’avaient pas encore terminé l’écriture du code informatique du [[w:noyau_de_système_d'exploitation|noyau]] [[w:GNU Hurd|Hurd]], alors qu'il était censé établir la communication entre la [[w:Suite_logicielle|suite logicielle]] produite par GNU et le [[w:Matériel informatique|matériel informatique]]. C'est donc la fusion des codes produits par les projets GNU et Linux qui permit la création du premier système complet, stable et entièrement libre baptisé [[w:GNU/Linux|GNU/Linux]]. [[Fichier:Debian-OpenLogo.svg|gauche|vignette|<small>Figure 6. Logo du système d’exploitation Debian.</small>|264x264px]] Au départ de ce nouveau système informatique, de nombreuses variantes, que l’on nomme communément « [[w:Distribution_Linux|distributions]] », furent créées par des programmeurs de tous horizons. L’une de celles-ci s’intitule [[w:fr:Debian|Debian]] et tire sa réputation d'être la seule qui est simultanément libre, gratuite et produite par une communauté sans lien direct avec une société commerciale<ref>{{Ouvrage|langue=|auteur=|prénom1=Christophe|nom1=Lazaro|titre=La liberte logicielle|passage=|lieu=|éditeur=Academia Bruylant|collection=Anthropologie Prospective|date=2012|pages totales=56|isbn=978-2-87209-861-3|oclc=1104281978}}.</ref>. Ce qui n'a pas empêché pour autant que le code de ce système informatique soit récupéré par plus de 150 distributions dérivées. Quant à la fiabilité du système Debian, elle se confirme par son usage au sein de nombreuses entreprises et organisations, à l’image de la [[w:Wikimedia_Foundation|Fondation Wikimédia]] qui l’utilise sur [[m:Wikimedia_servers/fr|ses serveurs]] pour héberger les projets qu'elle supporte<ref>{{Lien web|langue=|auteur=Méta-Wiki|titre=Serveurs Wikimedia|url=https://web.archive.org/web/20251113214321/https://meta.wikimedia.org/wiki/Wikimedia_servers/fr|site=|date=|consulté le=}}.</ref>. En provenance des logiciels libres, l’un des premiers héritages du mouvement Wikimédia fut donc la possibilité d'activer ses serveurs informatiques avec un système d’exploitation fiable, libre et gratuit. Ensuite, et cela grâce à l'ouverture du [[w:Code_source|code source⁣⁣]], de permettre à la Fondation Wikimédia de modifier celui-ci pour répondre aux besoins spécifiques du mouvement. À la suite de quoi, et selon les règles formulées par la [[w:Communauté_du_logiciel_libre|communauté du logiciel libre]], les modifications faites par la Fondation deviennent à leur tour, gratuitement et librement, utilisables par d’autres personnes ou organismes. À ce premier héritage reçu par le mouvement Wikimédia s’ajoute ensuite une innovation méthodologique, toujours en provenance des logiciels libres. Dans son article ''[[w:La_Cathédrale_et_le_Bazar|La Cathédrale et le Bazar]]''<ref>{{Ouvrage|langue=|auteur=|prénom1=Eric Steven|nom1=Raymond|titre=Cathedral and the bazaar|titre original=Cathedral and the bazaar|traduction titre=La cathédrale et le bazar|passage=|lieu=|éditeur=SnowBall Publishing|date=2010|pages totales=|isbn=978-1-60796-228-1|oclc=833142152|lire en ligne=}}.</ref>, [[w:Eric_Raymond|Eric Raymond]] mobilise en effet le terme « [[w:Cathédrale|cathédrale]] » pour désigner le mode de production des logiciels propriétaires, en opposition au mot « [[w:fr:Bazar|bazar]] », qu'il utilise pour qualifier le mode de développement des logiciels libres. D’un côté, il décrit une organisation pyramidale, rigide et statutairement hiérarchisée, comme on peut la voir souvent au sein des entreprises. Tandis que de l’autre, il parle d’une organisation horizontale, flexible et peu hiérarchisée, qu’il a lui-même expérimentée en adoptant le « style de développement de Linus Torvalds – distribuez vite et souvent, déléguez tout ce que vous pouvez déléguer, soyez ouvert jusqu’à la promiscuité »<ref>{{Lien web|langue=|auteur=Eric S. Raymond|traducteur=Sébastien Blondeel|titre=La cathédrale et le bazar|url=https://web.archive.org/web/20200203054716/http://www.linux-france.org/article/these/cathedrale-bazar/cathedrale-bazar-1.html|site=Linux France|lieu=|date=1998|consulté le=}}.</ref>. À l’instar de la métaphore du quartier numérique présentée dans le précédent chapitre, cette manière de décrire les projets open source nous aide donc ici à mieux comprendre ce qui se passe dans le mouvement Wikimédia. D'un côté, on retrouve effectivement cette « ouverture jusqu’à la promiscuité », au niveau du libre accès accordé aux projets Wikimédia, alors que de l'autre, tout le monde peut participer aux projets Wikimédia, qu'ils soient en ligne ou hors ligne. Ces deux observations corroborent donc l’existence d’un deuxième héritage, d’ordre méthodologique cette fois, en provenance du mouvement du logiciel libre. Néanmoins, il nous reste encore à découvrir un phénomène négligé par Eric Raymond durant ses observations, et qui pourtant est d'une importance considérable concernant l'histoire de la révolution numérique. Il s’agit là de l’apparition de la licence libre, de la philosophie qu'elle sous-tend, et de la [[w:Culture_libre|culture libre]] dont elle fut à l’origine. {{AutoCat}} 38ykmaqfcsinmidkcdib5b6kxhgp2p2 764822 764814 2026-04-24T10:54:54Z Lionel Scheepmans 20012 764822 wikitext text/x-wiki <noinclude>{{Le mouvement Wikimédia}}</noinclude> L’un des premiers épisodes de la préhistoire de Wikipédia et du mouvement Wikimédia débuta en septembre 1983, lorsqu’un programmeur du ''[[w:fr:Massachusetts Institute of Technology|Massachusetts Institute of Technology]]'', appelé [[w:fr:Richard Stallman|Richard Stallman]], déposa un message sur la liste de diffusion net.unix-wizards. C’était un appel d’aide pour la création de [[w:Projet GNU|GNU]], un nouveau [[w:fr:Système d'exploitation|système d’exploitation]] qui devait réunir une suite de programmes que tout le monde pourrait utiliser librement sur son ordinateur personnel<ref>{{Ouvrage|langue=|prénom1=Richard M|nom1=Stallman|prénom2=Sam|nom2=Williams|titre=Richard Stallman et la révolution du logiciel libre - Une biographie autorisée|éditeur=Eyrolles|date=2013|oclc=708380925|lire en ligne=https://framabook.org/docs/stallman/framabook6_stallman_v1_gnu-fdl.pdf|consulté le=}}.</ref>. Dans son message transmis via [[w:Arpanet|ARPANET]], le premier réseau informatique à grande échelle qui précéda Internet, Stallman s’exprimait de la sorte<ref>{{Lien web|langue=|auteur=Richard Stallman|titre=Système d'exploitation GNU – Annonce initiale|url=https://web.archive.org/web/20010106133800/http://www.gnu.org:80/gnu/initial-announcement.fr.html|site=GNU|date=3 décembre 2000|consulté le=}}.</ref> : <blockquote> Je considère comme une [[w:Règle d'or|règle d’or]] que si j’apprécie un programme je dois le partager avec d’autres personnes qui l’apprécient. Je ne peux pas en bonne conscience signer un accord de non-divulgation ni un accord de licence de logiciel. Afin de pouvoir continuer à utiliser les ordinateurs sans violer mes principes, j’ai décidé de rassembler une quantité suffisante de logiciels libres, de manière à pouvoir m’en tirer sans aucun logiciel qui ne soit pas libre. </blockquote> Le projet de Stallman, qui reçut le soutien nécessaire à son accomplissement, marqua ainsi le début de l’[[w:Histoire_du_logiciel_libre|histoire du logiciel libre]]. Quant à la quantité d’aide fournie, elle permet de croire que Richard Stallman n’était pas seul à voir l’arrivée des [[w:Logiciel propriétaire|logiciels propriétaires]] d’un mauvais œil. Car pour les membres du projet GNU et du mouvement du logiciel libre en général, un bon programme informatique doit respecter ces quatre libertés fondamentales<ref>{{Lien web|langue=|auteur=Karl Pradène|titre=Qu'est-ce que le logiciel libre ?|url=https://web.archive.org/web/20000511101640/http://www.gnu.org/philosophy/free-sw.fr.html|site=GNU|date=6 mai 2000|consulté le=}}.</ref> : <blockquote> 1. La liberté d’exécuter le programme, pour tous les usages. 2. La liberté d’étudier le fonctionnement du programme, et de l’adapter à vos besoins. 3. La liberté de redistribuer des copies, donc d’aider votre voisin. 4. La liberté d’améliorer le programme, et de publier vos améliorations, pour en faire profiter toute la communauté. </blockquote> [[w:Histoire_du_logiciel_libre|Lors de l'apparition du logiciel libre]], le marché de l’informatique était de fait en pleine mutation. L'habituel partage des codes informatiques entre les rares étudiants ou chercheurs qui bénéficiaient d’un accès à un ordinateur faisait l'objet d'une remise en question. Ce changement faisait notamment suite au [[w:Copyright_Act_(1976)|Copyright Act]] de 1976, une nouvelle loi qui autorisait l'application d'un [[w:Droit_d'auteur|droit d'auteur]] sur le code informatique, et donc qui permettait d'en interdire le partage ou la réutilisation sans autorisation. Des [[w:Clause_de_confidentialité|clauses de confidentialité]] ont ainsi fait leur apparition, pendant que les employés des firmes informatiques étaient nouvellement soumis à des contrats de confidentialité. C'était la fin de l’entraide et de la solidarité pratiquées chez les pionniers de l’informatique. À sa place s'installaient la concurrence et la compétitivité, bien connues dans le système capitaliste marchand. [[Fichier:Commodore64withdisk.jpg|alt=Commodore 64 avec disquette et lecteur|gauche|vignette|<small>Figure 4. Commodore 64 avec disquette et lecteur.</small>|300x300px]] Cette mutation coïncidait avec l’arrivée des premiers ordinateurs de taille réduite. Grâce à l’apparition des premiers [[w:Circuits_intégrés|circuits intégrés⁣⁣]], les premiers exemplaires avaient en effet été créés par l’industrie aérospatiale au début des années 1960. Cependant, il fallut attendre le début des années 1980 pour que le prix d’un ordinateur soit suffisamment bas pour en faire un [[w:Bien_de_grande_consommation|bien de grande consommation]]. C’est ainsi qu’en 1982, le [[w:Commodore 64|Commodore 64]] entrait dans le [[w:Livre_Guinness_des_records|livre Guinness des records]], avec plus de 17 millions d’exemplaires vendus dans le monde<ref>{{Lien web|langue=|auteur=Brandon Griggs|titre=The Commodore 64, that '80 s computer icon, lives again|url=https://web.archive.org/web/20200706161515/http://edition.cnn.com/2011/TECH/gaming.gadgets/05/09/commodore.64.reborn|site=CNN|date=May 9, 2011|consulté le=}}.</ref>. Juste avant cela, en 1981, l’''[[w:fr:IBM PC|IBM Personal Computer]]'' avait déjà fait son apparition, en proposant une [[w:Architecture_(informatique)|architecture]] ouverte qui allait servir de modèle pour toute une gamme d’ordinateurs que l’on désigne toujours aujourd’hui par l’acronyme « PC ». Pour faire fonctionner ses nouveaux modèles d'ordinateurs, la société IBM avait confié à l’entreprise [[w:Microsoft|Microsoft]], créée en 1975, la mission de les équiper d’un système d’exploitation. Le contrat signé entre les deux firmes fut une véritable aubaine pour le fournisseur des programmes informatiques. Car sans s'en apercevoir, et sans jamais anticiper que son matériel serait cloné à grande échelle, celle-ci avait en effet permis à Microsoft d'établir un monopole dans la vente de logiciels. Cela fut condamné pour [[w:Abus_de_position_dominante|abus de position dominante]]<ref name="Combier_2018_01_24">{{Lien web|langue=fr|auteur=Étienne Combier|titre=Abus de position dominante : les plus grosses amendes de la Commission européenne|url=https://web.archive.org/web/20230511110018/https://www.lesechos.fr/2018/01/abus-de-position-dominante-les-plus-grosses-amendes-de-la-commission-europeenne-982719|périodique=[[w:Les Échos|Les Échos]]|date=2018-01-24|consulté le=}}.</ref> et [[w:Vente_liée_de_logiciels_avec_du_matériel_informatique|vente liée du logiciel avec le matériel informatique]]<ref>{{Lien web|langue=fr|auteur=Marc Rees|titre=Pourquoi la justice européenne a sanctuarisé la vente liée PC et OS|url=https://web.archive.org/web/20230209112015/https://www.nextinpact.com/article/23625/101268-la-justice-europeenne-sanctuarise-vente-liee-pc-et-os|site=nextinpact.com|éditeur=[[w:Next INpact|Next INpact]]|date=2016-07-09|consulté le=}}.</ref>, mais sans pour autant empêcher [[w:Bill_Gates|Bill Gates]], le principal actionnaire de Microsoft, d'être l'homme le plus riche du monde en 1994. [[Fichier:GNU_and_Tux.svg|alt=Mascotte du projet GNU à gauche et du projet Linux à droite.|vignette|<small>Figure 5. À gauche la mascotte du projet GNU ; à droite celle du projet Linux, appelée Tux.</small>]] Cependant, pendant que Microsoft renforçait sa position dominante, un nouvel événement majeur allait marquer l’histoire du logiciel libre. Celui-ci fut de nouveau déclenché par un appel à contribution, qui fut cette fois posté le vingt-cinq août 1991 par un jeune étudiant en informatique de 21 ans, appelé [[w:fr:Linus Torvalds|Linus Torvalds]]. Via le système de messagerie [[w:fr:Usenet|Usenet]], son message avait été posté dans une liste de diffusion consacrée au système d’exploitation [[w:fr:Minix|Minix]], une sorte d’[[w:UNIX|UNIX]] simplifié et développé dans un but didactique, par le programmeur [[w:fr:Andrew Tanenbaum|Andrew Tanenbaum]]. Loin d’imaginer que cela ferait de lui une nouvelle célébrité dans le monde du Libre<ref>{{Ouvrage|langue=|prénom1=Linus|nom1=Torvalds|prénom2=David|nom2=Diamond|prénom3=Olivier|nom3=Engler|titre=Il était une fois Linux|éditeur=Osman Eyrolles Multimédia|date=2001|isbn=978-2-7464-0321-5|oclc=48059105}}.</ref>, Torvalds entama son message par le paragraphe suivant<ref>{{Ouvrage|langue=|prénom1=Linus|nom1=Torvalds|prénom2=David|nom2=Diamond|titre=Just for fun : the story of an accidental revolutionary|éditeur=HarperBusiness|date=2002|isbn=978-0-06-662073-2|oclc=1049937833}}.</ref> : <blockquote> Je fais un système d’exploitation (gratuit) (juste un hobby, ne sera pas grand et professionnel comme gnu) pour les clones 386 (486) AT. Ce projet est en cours depuis avril et commence à se préparer. J’aimerais avoir un retour sur ce que les gens aiment ou n’aiment pas dans minix, car mon système d’exploitation lui ressemble un peu (même disposition physique du système de fichiers (pour des raisons pratiques) entre autres choses)<ref>Texte original avant sa traduction par www.deepl.com/translator : « ''I'm doing a (free) operating system (just a hobby, won't be big and professional like gnu) for 386(486) AT clones. This has been brewing since april, and is starting to get ready. I'd like any feedback on things people like/dislike in minix, as my OS resembles it somewhat (same physical layout of the file-system (due to practical reasons)among other things) ».''</ref>. </blockquote> Bien qu’il fût présenté comme un passe-temps, le projet qui répondait au nom de « [[w:fr:Noyau Linux|Linux]] », fut rapidement soutenu par des milliers de programmeurs du monde entier, avant de devenir la pièce manquante du projet GNU. En effet, les contributeurs au projet de Stallman n’avaient pas encore terminé l’écriture du code informatique du [[w:noyau_de_système_d'exploitation|noyau]] [[w:GNU Hurd|Hurd]], alors qu'il était censé établir la communication entre la [[w:Suite_logicielle|suite logicielle]] produite par GNU et le [[w:Matériel informatique|matériel informatique]]. C'est donc la fusion des codes produits par les projets GNU et Linux qui permit la création du premier système complet, stable et entièrement libre baptisé [[w:GNU/Linux|GNU/Linux]]. [[Fichier:Debian-OpenLogo.svg|gauche|vignette|<small>Figure 6. Logo du système d’exploitation Debian.</small>|264x264px]] Au départ de ce nouveau système informatique, de nombreuses variantes, que l’on nomme communément « [[w:Distribution_Linux|distributions]] », furent créées par des programmeurs de tous horizons. L’une de celles-ci s’intitule [[w:fr:Debian|Debian]] et tire sa réputation d'être la seule qui est simultanément libre, gratuite et produite par une communauté sans lien direct avec une société commerciale<ref>{{Ouvrage|langue=|auteur=|prénom1=Christophe|nom1=Lazaro|titre=La liberte logicielle|passage=|lieu=|éditeur=Academia Bruylant|collection=Anthropologie Prospective|date=2012|pages totales=56|isbn=978-2-87209-861-3|oclc=1104281978}}.</ref>. Ce qui n'a pas empêché pour autant que le code de ce système informatique soit récupéré par plus de 150 distributions dérivées. Quant à la fiabilité du système Debian, elle se confirme par son usage au sein de nombreuses entreprises et organisations, à l’image de la [[w:Wikimedia_Foundation|Fondation Wikimédia]] qui l’utilise sur [[m:Wikimedia_servers/fr|ses serveurs]] pour héberger les projets qu'elle supporte<ref>{{Lien web|langue=|auteur=Méta-Wiki|titre=Serveurs Wikimedia|url=https://web.archive.org/web/20251113214321/https://meta.wikimedia.org/wiki/Wikimedia_servers/fr|site=|date=|consulté le=}}.</ref>. Grâce à la naissance des logiciels libres, le mouvement Wikimédia a donc la possibilité de faire tourner ses serveurs informatiques, avec un système d’exploitation fiable, libre et gratuit. Comme son [[w:Code_source|code source⁣⁣]], il permet aussi à la Fondation Wikimédia de le modifier pour répondre aux besoins spécifiques du mouvement. À la suite de quoi, et selon les règles formulées par la [[w:Communauté_du_logiciel_libre|communauté du logiciel libre]], les modifications faites par la Fondation deviennent à leur tour, gratuitement et librement, utilisables par d’autres personnes ou organismes. À ce premier héritage reçu par le mouvement Wikimédia s’ajoute ensuite une innovation méthodologique, toujours en provenance des logiciels libres. Dans son article ''[[w:La_Cathédrale_et_le_Bazar|La Cathédrale et le Bazar]]''<ref>{{Ouvrage|langue=|auteur=|prénom1=Eric Steven|nom1=Raymond|titre=Cathedral and the bazaar|titre original=Cathedral and the bazaar|traduction titre=La cathédrale et le bazar|passage=|lieu=|éditeur=SnowBall Publishing|date=2010|pages totales=|isbn=978-1-60796-228-1|oclc=833142152|lire en ligne=}}.</ref>, [[w:Eric_Raymond|Eric Raymond]] mobilise en effet le terme « [[w:Cathédrale|cathédrale]] » pour désigner le mode de production des logiciels propriétaires, en opposition au mot « [[w:fr:Bazar|bazar]] », qu'il utilise pour qualifier le mode de développement des logiciels libres. D’un côté, il décrit une organisation pyramidale, rigide et statutairement hiérarchisée, comme on peut la voir souvent au sein des entreprises. Tandis que de l’autre, il parle d’une organisation horizontale, flexible et peu hiérarchisée, qu’il a lui-même expérimentée en adoptant le « style de développement de Linus Torvalds – distribuez vite et souvent, déléguez tout ce que vous pouvez déléguer, soyez ouvert jusqu’à la promiscuité »<ref>{{Lien web|langue=|auteur=Eric S. Raymond|traducteur=Sébastien Blondeel|titre=La cathédrale et le bazar|url=https://web.archive.org/web/20200203054716/http://www.linux-france.org/article/these/cathedrale-bazar/cathedrale-bazar-1.html|site=Linux France|lieu=|date=1998|consulté le=}}.</ref>. À l’instar de la métaphore du quartier numérique présentée dans le précédent chapitre, cette manière de décrire les projets open source nous aide donc ici à mieux comprendre ce qui se passe dans le mouvement Wikimédia. D'un côté, on retrouve effectivement cette « ouverture jusqu’à la promiscuité », au niveau du libre accès accordé aux projets Wikimédia, alors que de l'autre, tout le monde peut participer aux projets Wikimédia, qu'ils soient en ligne ou hors ligne. Ces deux observations corroborent donc l’existence d’un deuxième héritage, d’ordre méthodologique cette fois, en provenance du mouvement du logiciel libre. Néanmoins, il nous reste encore à découvrir un phénomène négligé par Eric Raymond durant ses observations, et qui pourtant est d'une importance considérable concernant l'histoire de la révolution numérique. Il s’agit là de l’apparition de la licence libre, de la philosophie qu'elle sous-tend, et de la [[w:Culture_libre|culture libre]] dont elle fut à l’origine. {{AutoCat}} f6cc9t1stcn1og4jr2e1nb4512uzd6j 764823 764822 2026-04-24T10:56:42Z Lionel Scheepmans 20012 764823 wikitext text/x-wiki <noinclude>{{Le mouvement Wikimédia}}</noinclude> L’un des premiers épisodes de la préhistoire de Wikipédia et du mouvement Wikimédia débuta en septembre 1983, lorsqu’un programmeur du ''[[w:fr:Massachusetts Institute of Technology|Massachusetts Institute of Technology]]'', appelé [[w:fr:Richard Stallman|Richard Stallman]], déposa un message sur la liste de diffusion net.unix-wizards. C’était un appel d’aide pour la création de [[w:Projet GNU|GNU]], un nouveau [[w:fr:Système d'exploitation|système d’exploitation]] qui devait réunir une suite de programmes que tout le monde pourrait utiliser librement sur son ordinateur personnel<ref>{{Ouvrage|langue=|prénom1=Richard M|nom1=Stallman|prénom2=Sam|nom2=Williams|titre=Richard Stallman et la révolution du logiciel libre - Une biographie autorisée|éditeur=Eyrolles|date=2013|oclc=708380925|lire en ligne=https://framabook.org/docs/stallman/framabook6_stallman_v1_gnu-fdl.pdf|consulté le=}}.</ref>. Dans son message transmis via [[w:Arpanet|ARPANET]], le premier réseau informatique à grande échelle qui précéda Internet, Stallman s’exprimait de la sorte<ref>{{Lien web|langue=|auteur=Richard Stallman|titre=Système d'exploitation GNU – Annonce initiale|url=https://web.archive.org/web/20010106133800/http://www.gnu.org:80/gnu/initial-announcement.fr.html|site=GNU|date=3 décembre 2000|consulté le=}}.</ref> : <blockquote> Je considère comme une [[w:Règle d'or|règle d’or]] que si j’apprécie un programme je dois le partager avec d’autres personnes qui l’apprécient. Je ne peux pas en bonne conscience signer un accord de non-divulgation ni un accord de licence de logiciel. Afin de pouvoir continuer à utiliser les ordinateurs sans violer mes principes, j’ai décidé de rassembler une quantité suffisante de logiciels libres, de manière à pouvoir m’en tirer sans aucun logiciel qui ne soit pas libre. </blockquote> Le projet de Stallman, qui reçut le soutien nécessaire à son accomplissement, marqua ainsi le début de l’[[w:Histoire_du_logiciel_libre|histoire du logiciel libre]]. Quant à la quantité d’aide fournie, elle permet de croire que Richard Stallman n’était pas seul à voir l’arrivée des [[w:Logiciel propriétaire|logiciels propriétaires]] d’un mauvais œil. Car pour les membres du projet GNU et du mouvement du logiciel libre en général, un bon programme informatique doit respecter ces quatre libertés fondamentales<ref>{{Lien web|langue=|auteur=Karl Pradène|titre=Qu'est-ce que le logiciel libre ?|url=https://web.archive.org/web/20000511101640/http://www.gnu.org/philosophy/free-sw.fr.html|site=GNU|date=6 mai 2000|consulté le=}}.</ref> : <blockquote> 1. La liberté d’exécuter le programme, pour tous les usages. 2. La liberté d’étudier le fonctionnement du programme, et de l’adapter à vos besoins. 3. La liberté de redistribuer des copies, donc d’aider votre voisin. 4. La liberté d’améliorer le programme, et de publier vos améliorations, pour en faire profiter toute la communauté. </blockquote> [[w:Histoire_du_logiciel_libre|Lors de l'apparition du logiciel libre]], le marché de l’informatique était de fait en pleine mutation. L'habituel partage des codes informatiques entre les rares étudiants ou chercheurs qui bénéficiaient d’un accès à un ordinateur faisait l'objet d'une remise en question. Ce changement faisait notamment suite au [[w:Copyright_Act_(1976)|Copyright Act]] de 1976, une nouvelle loi qui autorisait l'application d'un [[w:Droit_d'auteur|droit d'auteur]] sur le code informatique, et donc qui permettait d'en interdire le partage ou la réutilisation sans autorisation. Des [[w:Clause_de_confidentialité|clauses de confidentialité]] ont ainsi fait leur apparition, pendant que les employés des firmes informatiques étaient nouvellement soumis à des contrats de confidentialité. C'était la fin de l’entraide et de la solidarité pratiquées chez les pionniers de l’informatique. À sa place s'installaient la concurrence et la compétitivité, bien connues dans le système capitaliste marchand. [[Fichier:Commodore64withdisk.jpg|alt=Commodore 64 avec disquette et lecteur|gauche|vignette|<small>Figure 4. Commodore 64 avec disquette et lecteur.</small>|300x300px]] Cette mutation coïncidait avec l’arrivée des premiers ordinateurs de taille réduite. Grâce à l’apparition des premiers [[w:Circuits_intégrés|circuits intégrés⁣⁣]], les premiers exemplaires avaient en effet été créés par l’industrie aérospatiale au début des années 1960. Cependant, il fallut attendre le début des années 1980 pour que le prix d’un ordinateur soit suffisamment bas pour en faire un [[w:Bien_de_grande_consommation|bien de grande consommation]]. C’est ainsi qu’en 1982, le [[w:Commodore 64|Commodore 64]] entrait dans le [[w:Livre_Guinness_des_records|livre Guinness des records]], avec plus de 17 millions d’exemplaires vendus dans le monde<ref>{{Lien web|langue=|auteur=Brandon Griggs|titre=The Commodore 64, that '80 s computer icon, lives again|url=https://web.archive.org/web/20200706161515/http://edition.cnn.com/2011/TECH/gaming.gadgets/05/09/commodore.64.reborn|site=CNN|date=May 9, 2011|consulté le=}}.</ref>. Juste avant cela, en 1981, l’''[[w:fr:IBM PC|IBM Personal Computer]]'' avait déjà fait son apparition, en proposant une [[w:Architecture_(informatique)|architecture]] ouverte qui allait servir de modèle pour toute une gamme d’ordinateurs que l’on désigne toujours aujourd’hui par l’acronyme « PC ». Pour faire fonctionner ses nouveaux modèles d'ordinateurs, la société IBM avait confié à l’entreprise [[w:Microsoft|Microsoft]], créée en 1975, la mission de les équiper d’un système d’exploitation. Le contrat signé entre les deux firmes fut une véritable aubaine pour le fournisseur des programmes informatiques. Car sans s'en apercevoir, et sans jamais anticiper que son matériel serait cloné à grande échelle, celle-ci avait en effet permis à Microsoft d'établir un monopole dans la vente de logiciels. Cela fut condamné pour [[w:Abus_de_position_dominante|abus de position dominante]]<ref name="Combier_2018_01_24">{{Lien web|langue=fr|auteur=Étienne Combier|titre=Abus de position dominante : les plus grosses amendes de la Commission européenne|url=https://web.archive.org/web/20230511110018/https://www.lesechos.fr/2018/01/abus-de-position-dominante-les-plus-grosses-amendes-de-la-commission-europeenne-982719|périodique=[[w:Les Échos|Les Échos]]|date=2018-01-24|consulté le=}}.</ref> et [[w:Vente_liée_de_logiciels_avec_du_matériel_informatique|vente liée du logiciel avec le matériel informatique]]<ref>{{Lien web|langue=fr|auteur=Marc Rees|titre=Pourquoi la justice européenne a sanctuarisé la vente liée PC et OS|url=https://web.archive.org/web/20230209112015/https://www.nextinpact.com/article/23625/101268-la-justice-europeenne-sanctuarise-vente-liee-pc-et-os|site=nextinpact.com|éditeur=[[w:Next INpact|Next INpact]]|date=2016-07-09|consulté le=}}.</ref>, mais sans pour autant empêcher [[w:Bill_Gates|Bill Gates]], le principal actionnaire de Microsoft, d'être l'homme le plus riche du monde en 1994. [[Fichier:GNU_and_Tux.svg|alt=Mascotte du projet GNU à gauche et du projet Linux à droite.|vignette|<small>Figure 5. À gauche la mascotte du projet GNU ; à droite celle du projet Linux, appelée Tux.</small>]] Cependant, pendant que Microsoft renforçait sa position dominante, un nouvel événement majeur allait marquer l’histoire du logiciel libre. Celui-ci fut de nouveau déclenché par un appel à contribution, qui fut cette fois posté le vingt-cinq août 1991 par un jeune étudiant en informatique de 21 ans, appelé [[w:fr:Linus Torvalds|Linus Torvalds]]. Via le système de messagerie [[w:fr:Usenet|Usenet]], son message avait été posté dans une liste de diffusion consacrée au système d’exploitation [[w:fr:Minix|Minix]], une sorte d’[[w:UNIX|UNIX]] simplifié et développé dans un but didactique, par le programmeur [[w:fr:Andrew Tanenbaum|Andrew Tanenbaum]]. Loin d’imaginer que cela ferait de lui une nouvelle célébrité dans le monde du Libre<ref>{{Ouvrage|langue=|prénom1=Linus|nom1=Torvalds|prénom2=David|nom2=Diamond|prénom3=Olivier|nom3=Engler|titre=Il était une fois Linux|éditeur=Osman Eyrolles Multimédia|date=2001|isbn=978-2-7464-0321-5|oclc=48059105}}.</ref>, Torvalds entama son message par le paragraphe suivant<ref>{{Ouvrage|langue=|prénom1=Linus|nom1=Torvalds|prénom2=David|nom2=Diamond|titre=Just for fun : the story of an accidental revolutionary|éditeur=HarperBusiness|date=2002|isbn=978-0-06-662073-2|oclc=1049937833}}.</ref> : <blockquote> Je fais un système d’exploitation (gratuit) (juste un hobby, ne sera pas grand et professionnel comme gnu) pour les clones 386 (486) AT. Ce projet est en cours depuis avril et commence à se préparer. J’aimerais avoir un retour sur ce que les gens aiment ou n’aiment pas dans minix, car mon système d’exploitation lui ressemble un peu (même disposition physique du système de fichiers (pour des raisons pratiques) entre autres choses)<ref>Texte original avant sa traduction par www.deepl.com/translator : « ''I'm doing a (free) operating system (just a hobby, won't be big and professional like gnu) for 386(486) AT clones. This has been brewing since april, and is starting to get ready. I'd like any feedback on things people like/dislike in minix, as my OS resembles it somewhat (same physical layout of the file-system (due to practical reasons)among other things) ».''</ref>. </blockquote> Bien qu’il fût présenté comme un passe-temps, le projet qui répondait au nom de « [[w:fr:Noyau Linux|Linux]] », fut rapidement soutenu par des milliers de programmeurs du monde entier, avant de devenir la pièce manquante du projet GNU. En effet, les contributeurs au projet de Stallman n’avaient pas encore terminé l’écriture du code informatique du [[w:noyau_de_système_d'exploitation|noyau]] [[w:GNU Hurd|Hurd]], alors qu'il était censé établir la communication entre la [[w:Suite_logicielle|suite logicielle]] produite par GNU et le [[w:Matériel informatique|matériel informatique]]. C'est donc la fusion des codes produits par les projets GNU et Linux qui permit la création du premier système complet, stable et entièrement libre baptisé [[w:GNU/Linux|GNU/Linux]]. [[Fichier:Debian-OpenLogo.svg|gauche|vignette|<small>Figure 6. Logo du système d’exploitation Debian.</small>|264x264px]] Au départ de ce nouveau système informatique, de nombreuses variantes, que l’on nomme communément « [[w:Distribution_Linux|distributions]] », furent créées par des programmeurs de tous horizons. L’une de celles-ci s’intitule [[w:fr:Debian|Debian]] et tire sa réputation d'être la seule qui est simultanément libre, gratuite et produite par une communauté sans lien direct avec une société commerciale<ref>{{Ouvrage|langue=|auteur=|prénom1=Christophe|nom1=Lazaro|titre=La liberte logicielle|passage=|lieu=|éditeur=Academia Bruylant|collection=Anthropologie Prospective|date=2012|pages totales=56|isbn=978-2-87209-861-3|oclc=1104281978}}.</ref>. Ce qui n'a pas empêché pour autant que le code de ce système informatique soit récupéré par plus de 150 distributions dérivées. Quant à la fiabilité du système Debian, elle se confirme par son usage au sein de nombreuses entreprises et organisations, à l’image de la [[w:Wikimedia_Foundation|Fondation Wikimédia]] qui l’utilise sur [[m:Wikimedia_servers/fr|ses serveurs]] pour héberger les projets qu'elle supporte<ref>{{Lien web|langue=|auteur=Méta-Wiki|titre=Serveurs Wikimedia|url=https://web.archive.org/web/20251113214321/https://meta.wikimedia.org/wiki/Wikimedia_servers/fr|site=|date=|consulté le=}}.</ref>. Grâce à la naissance des logiciels libres, le mouvement Wikimédia a donc la possibilité de faire tourner ses serveurs informatiques, avec un système d’exploitation fiable, libre et gratuit. Comme son [[w:Code_source|code source⁣⁣]] est ouvert, cela permet aussi à la Fondation Wikimédia de le modifier pour répondre aux besoins spécifiques du mouvement. À la suite de quoi, et selon les règles formulées par la [[w:Communauté_du_logiciel_libre|communauté du logiciel libre]], les modifications faites par la Fondation deviennent à leur tour, gratuitement et librement, utilisables par d’autres personnes ou organismes. À ce premier héritage reçu par le mouvement Wikimédia s’ajoute ensuite une innovation méthodologique, toujours en provenance des logiciels libres. Dans son article ''[[w:La_Cathédrale_et_le_Bazar|La Cathédrale et le Bazar]]''<ref>{{Ouvrage|langue=|auteur=|prénom1=Eric Steven|nom1=Raymond|titre=Cathedral and the bazaar|titre original=Cathedral and the bazaar|traduction titre=La cathédrale et le bazar|passage=|lieu=|éditeur=SnowBall Publishing|date=2010|pages totales=|isbn=978-1-60796-228-1|oclc=833142152|lire en ligne=}}.</ref>, [[w:Eric_Raymond|Eric Raymond]] mobilise en effet le terme « [[w:Cathédrale|cathédrale]] » pour désigner le mode de production des logiciels propriétaires, en opposition au mot « [[w:fr:Bazar|bazar]] », qu'il utilise pour qualifier le mode de développement des logiciels libres. D’un côté, il décrit une organisation pyramidale, rigide et statutairement hiérarchisée, comme on peut la voir souvent au sein des entreprises. Tandis que de l’autre, il parle d’une organisation horizontale, flexible et peu hiérarchisée, qu’il a lui-même expérimentée en adoptant le « style de développement de Linus Torvalds – distribuez vite et souvent, déléguez tout ce que vous pouvez déléguer, soyez ouvert jusqu’à la promiscuité »<ref>{{Lien web|langue=|auteur=Eric S. Raymond|traducteur=Sébastien Blondeel|titre=La cathédrale et le bazar|url=https://web.archive.org/web/20200203054716/http://www.linux-france.org/article/these/cathedrale-bazar/cathedrale-bazar-1.html|site=Linux France|lieu=|date=1998|consulté le=}}.</ref>. À l’instar de la métaphore du quartier numérique présentée dans le précédent chapitre, cette manière de décrire les projets open source nous aide donc ici à mieux comprendre ce qui se passe dans le mouvement Wikimédia. D'un côté, on retrouve effectivement cette « ouverture jusqu’à la promiscuité », au niveau du libre accès accordé aux projets Wikimédia, alors que de l'autre, tout le monde peut participer aux projets Wikimédia, qu'ils soient en ligne ou hors ligne. Ces deux observations corroborent donc l’existence d’un deuxième héritage, d’ordre méthodologique cette fois, en provenance du mouvement du logiciel libre. Néanmoins, il nous reste encore à découvrir un phénomène négligé par Eric Raymond durant ses observations, et qui pourtant est d'une importance considérable concernant l'histoire de la révolution numérique. Il s’agit là de l’apparition de la licence libre, de la philosophie qu'elle sous-tend, et de la [[w:Culture_libre|culture libre]] dont elle fut à l’origine. {{AutoCat}} ehx5hmhdrg5ei1rov7fgbdokpzuz63y 764824 764823 2026-04-24T11:01:24Z Lionel Scheepmans 20012 764824 wikitext text/x-wiki <noinclude>{{Le mouvement Wikimédia}}</noinclude> L’un des premiers épisodes de la préhistoire de Wikipédia et du mouvement Wikimédia débuta en septembre 1983, lorsqu’un programmeur du ''[[w:fr:Massachusetts Institute of Technology|Massachusetts Institute of Technology]]'', appelé [[w:fr:Richard Stallman|Richard Stallman]], déposa un message sur la liste de diffusion net.unix-wizards. C’était un appel d’aide pour la création de [[w:Projet GNU|GNU]], un nouveau [[w:fr:Système d'exploitation|système d’exploitation]] qui devait réunir une suite de programmes que tout le monde pourrait utiliser librement sur son ordinateur personnel<ref>{{Ouvrage|langue=|prénom1=Richard M|nom1=Stallman|prénom2=Sam|nom2=Williams|titre=Richard Stallman et la révolution du logiciel libre - Une biographie autorisée|éditeur=Eyrolles|date=2013|oclc=708380925|lire en ligne=https://framabook.org/docs/stallman/framabook6_stallman_v1_gnu-fdl.pdf|consulté le=}}.</ref>. Dans son message transmis via [[w:Arpanet|ARPANET]], le premier réseau informatique à grande échelle qui précéda Internet, Stallman s’exprimait de la sorte<ref>{{Lien web|langue=|auteur=Richard Stallman|titre=Système d'exploitation GNU – Annonce initiale|url=https://web.archive.org/web/20010106133800/http://www.gnu.org:80/gnu/initial-announcement.fr.html|site=GNU|date=3 décembre 2000|consulté le=}}.</ref> : <blockquote> Je considère comme une [[w:Règle d'or|règle d’or]] que si j’apprécie un programme je dois le partager avec d’autres personnes qui l’apprécient. Je ne peux pas en bonne conscience signer un accord de non-divulgation ni un accord de licence de logiciel. Afin de pouvoir continuer à utiliser les ordinateurs sans violer mes principes, j’ai décidé de rassembler une quantité suffisante de logiciels libres, de manière à pouvoir m’en tirer sans aucun logiciel qui ne soit pas libre. </blockquote> Le projet de Stallman, qui reçut le soutien nécessaire à son accomplissement, marqua ainsi le début de l’[[w:Histoire_du_logiciel_libre|histoire du logiciel libre]]. Quant à la quantité d’aide fournie, elle permet de croire que Richard Stallman n’était pas seul à voir l’arrivée des [[w:Logiciel propriétaire|logiciels propriétaires]] d’un mauvais œil. Car pour les membres du projet GNU et du mouvement du logiciel libre en général, un bon programme informatique doit respecter ces quatre libertés fondamentales<ref>{{Lien web|langue=|auteur=Karl Pradène|titre=Qu'est-ce que le logiciel libre ?|url=https://web.archive.org/web/20000511101640/http://www.gnu.org/philosophy/free-sw.fr.html|site=GNU|date=6 mai 2000|consulté le=}}.</ref> : <blockquote> 1. La liberté d’exécuter le programme, pour tous les usages. 2. La liberté d’étudier le fonctionnement du programme, et de l’adapter à vos besoins. 3. La liberté de redistribuer des copies, donc d’aider votre voisin. 4. La liberté d’améliorer le programme, et de publier vos améliorations, pour en faire profiter toute la communauté. </blockquote> [[w:Histoire_du_logiciel_libre|Lors de l'apparition du logiciel libre]], le marché de l’informatique était de fait en pleine mutation. L'habituel partage des codes informatiques entre les rares étudiants ou chercheurs qui bénéficiaient d’un accès à un ordinateur faisait l'objet d'une remise en question. Ce changement faisait notamment suite au [[w:Copyright_Act_(1976)|Copyright Act]] de 1976, une nouvelle loi qui autorisait l'application d'un [[w:Droit_d'auteur|droit d'auteur]] sur le code informatique, et donc qui permettait d'en interdire le partage ou la réutilisation sans autorisation. Des [[w:Clause_de_confidentialité|clauses de confidentialité]] ont ainsi fait leur apparition, pendant que les employés des firmes informatiques étaient nouvellement soumis à des contrats de confidentialité. C'était la fin de l’entraide et de la solidarité pratiquées chez les pionniers de l’informatique. À sa place s'installaient la concurrence et la compétitivité, bien connues dans le système capitaliste marchand. [[Fichier:Commodore64withdisk.jpg|alt=Commodore 64 avec disquette et lecteur|gauche|vignette|<small>Figure 4. Commodore 64 avec disquette et lecteur.</small>|300x300px]] Cette mutation coïncidait avec l’arrivée des premiers ordinateurs de taille réduite. Grâce à l’apparition des premiers [[w:Circuits_intégrés|circuits intégrés⁣⁣]], les premiers exemplaires avaient en effet été créés par l’industrie aérospatiale au début des années 1960. Cependant, il fallut attendre le début des années 1980 pour que le prix d’un ordinateur soit suffisamment bas pour en faire un [[w:Bien_de_grande_consommation|bien de grande consommation]]. C’est ainsi qu’en 1982, le [[w:Commodore 64|Commodore 64]] entrait dans le [[w:Livre_Guinness_des_records|livre Guinness des records]], avec plus de 17 millions d’exemplaires vendus dans le monde<ref>{{Lien web|langue=|auteur=Brandon Griggs|titre=The Commodore 64, that '80 s computer icon, lives again|url=https://web.archive.org/web/20200706161515/http://edition.cnn.com/2011/TECH/gaming.gadgets/05/09/commodore.64.reborn|site=CNN|date=May 9, 2011|consulté le=}}.</ref>. Juste avant cela, en 1981, l’''[[w:fr:IBM PC|IBM Personal Computer]]'' avait déjà fait son apparition, en proposant une [[w:Architecture_(informatique)|architecture]] ouverte qui allait servir de modèle pour toute une gamme d’ordinateurs que l’on désigne toujours aujourd’hui par l’acronyme « PC ». Pour faire fonctionner ses nouveaux modèles d'ordinateurs, la société IBM avait confié à l’entreprise [[w:Microsoft|Microsoft]], créée en 1975, la mission de les équiper d’un système d’exploitation. Le contrat signé entre les deux firmes fut une véritable aubaine pour le fournisseur des programmes informatiques. Car sans s'en apercevoir, et sans jamais anticiper que son matériel serait cloné à grande échelle, celle-ci avait en effet permis à Microsoft d'établir un monopole dans la vente de logiciels. Cela fut condamné pour [[w:Abus_de_position_dominante|abus de position dominante]]<ref name="Combier_2018_01_24">{{Lien web|langue=fr|auteur=Étienne Combier|titre=Abus de position dominante : les plus grosses amendes de la Commission européenne|url=https://web.archive.org/web/20230511110018/https://www.lesechos.fr/2018/01/abus-de-position-dominante-les-plus-grosses-amendes-de-la-commission-europeenne-982719|périodique=[[w:Les Échos|Les Échos]]|date=2018-01-24|consulté le=}}.</ref> et [[w:Vente_liée_de_logiciels_avec_du_matériel_informatique|vente liée du logiciel avec le matériel informatique]]<ref>{{Lien web|langue=fr|auteur=Marc Rees|titre=Pourquoi la justice européenne a sanctuarisé la vente liée PC et OS|url=https://web.archive.org/web/20230209112015/https://www.nextinpact.com/article/23625/101268-la-justice-europeenne-sanctuarise-vente-liee-pc-et-os|site=nextinpact.com|éditeur=[[w:Next INpact|Next INpact]]|date=2016-07-09|consulté le=}}.</ref>, mais sans pour autant empêcher [[w:Bill_Gates|Bill Gates]], le principal actionnaire de Microsoft, d'être l'homme le plus riche du monde en 1994. [[Fichier:GNU_and_Tux.svg|alt=Mascotte du projet GNU à gauche et du projet Linux à droite.|vignette|<small>Figure 5. À gauche la mascotte du projet GNU ; à droite celle du projet Linux, appelée Tux.</small>]] Cependant, pendant que Microsoft renforçait sa position dominante, un nouvel événement majeur allait marquer l’histoire du logiciel libre. Celui-ci fut de nouveau déclenché par un appel à contribution, qui fut cette fois posté le vingt-cinq août 1991 par un jeune étudiant en informatique de 21 ans, appelé [[w:fr:Linus Torvalds|Linus Torvalds]]. Via le système de messagerie [[w:fr:Usenet|Usenet]], son message avait été posté dans une liste de diffusion consacrée au système d’exploitation [[w:fr:Minix|Minix]], une sorte d’[[w:UNIX|UNIX]] simplifié et développé dans un but didactique, par le programmeur [[w:fr:Andrew Tanenbaum|Andrew Tanenbaum]]. Loin d’imaginer que cela ferait de lui une nouvelle célébrité dans le monde du Libre<ref>{{Ouvrage|langue=|prénom1=Linus|nom1=Torvalds|prénom2=David|nom2=Diamond|prénom3=Olivier|nom3=Engler|titre=Il était une fois Linux|éditeur=Osman Eyrolles Multimédia|date=2001|isbn=978-2-7464-0321-5|oclc=48059105}}.</ref>, Torvalds entama son message par le paragraphe suivant<ref>{{Ouvrage|langue=|prénom1=Linus|nom1=Torvalds|prénom2=David|nom2=Diamond|titre=Just for fun : the story of an accidental revolutionary|éditeur=HarperBusiness|date=2002|isbn=978-0-06-662073-2|oclc=1049937833}}.</ref> : <blockquote> Je fais un système d’exploitation (gratuit) (juste un hobby, ne sera pas grand et professionnel comme gnu) pour les clones 386 (486) AT. Ce projet est en cours depuis avril et commence à se préparer. J’aimerais avoir un retour sur ce que les gens aiment ou n’aiment pas dans minix, car mon système d’exploitation lui ressemble un peu (même disposition physique du système de fichiers (pour des raisons pratiques) entre autres choses)<ref>Texte original avant sa traduction par www.deepl.com/translator : « ''I'm doing a (free) operating system (just a hobby, won't be big and professional like gnu) for 386(486) AT clones. This has been brewing since april, and is starting to get ready. I'd like any feedback on things people like/dislike in minix, as my OS resembles it somewhat (same physical layout of the file-system (due to practical reasons)among other things) ».''</ref>. </blockquote> Bien qu’il fût présenté comme un passe-temps, le projet qui répondait au nom de « [[w:fr:Noyau Linux|Linux]] », fut rapidement soutenu par des milliers de programmeurs du monde entier, avant de devenir la pièce manquante du projet GNU. En effet, les contributeurs au projet de Stallman n’avaient pas encore terminé l’écriture du code informatique du [[w:noyau_de_système_d'exploitation|noyau]] [[w:GNU Hurd|Hurd]], alors qu'il était censé établir la communication entre la [[w:Suite_logicielle|suite logicielle]] produite par GNU et le [[w:Matériel informatique|matériel informatique]]. C'est donc la fusion des codes produits par les projets GNU et Linux qui permit la création du premier système complet, stable et entièrement libre baptisé [[w:GNU/Linux|GNU/Linux]]. [[Fichier:Debian-OpenLogo.svg|gauche|vignette|<small>Figure 6. Logo du système d’exploitation Debian.</small>|264x264px]] Au départ de ce nouveau système informatique, de nombreuses variantes, que l’on nomme communément « [[w:Distribution_Linux|distributions]] », furent créées par des programmeurs de tous horizons. L’une de celles-ci s’intitule [[w:fr:Debian|Debian]] et tire sa réputation d'être la seule qui est simultanément libre, gratuite et produite par une communauté sans lien direct avec une société commerciale<ref>{{Ouvrage|langue=|auteur=|prénom1=Christophe|nom1=Lazaro|titre=La liberte logicielle|passage=|lieu=|éditeur=Academia Bruylant|collection=Anthropologie Prospective|date=2012|pages totales=56|isbn=978-2-87209-861-3|oclc=1104281978}}.</ref>. Ce qui n'a pas empêché pour autant que le code de ce système informatique soit récupéré par plus de 150 distributions dérivées. Quant à la fiabilité du système Debian, elle se confirme par son usage au sein de nombreuses entreprises et organisations, à l’image de la [[w:Wikimedia_Foundation|Fondation Wikimédia]] qui l’utilise sur [[m:Wikimedia_servers/fr|ses serveurs]] pour héberger les projets qu'elle supporte<ref>{{Lien web|langue=|auteur=Méta-Wiki|titre=Serveurs Wikimedia|url=https://web.archive.org/web/20251113214321/https://meta.wikimedia.org/wiki/Wikimedia_servers/fr|site=|date=|consulté le=}}.</ref>. Grâce à la naissance des logiciels libres, le mouvement Wikimédia a donc la possibilité de faire tourner ses serveurs informatiques, avec un système d’exploitation fiable, libre et gratuit. Comme son [[w:Code_source|code source⁣⁣]] est ouvert, cela permet aussi à la Fondation Wikimédia de le modifier pour répondre aux besoins spécifiques du mouvement. À la suite de quoi, et selon les règles formulées par la [[w:Communauté_du_logiciel_libre|communauté du logiciel libre]], les modifications faites par la Fondation deviennent à leur tour, gratuitement et librement, utilisables par d’autres personnes ou organismes. À ce premier héritage reçu par le mouvement Wikimédia s’ajoute ensuite une innovation méthodologique, toujours en provenance des logiciels libres. Dans son article ''[[w:La_Cathédrale_et_le_Bazar|La Cathédrale et le Bazar]]''<ref>{{Ouvrage|langue=|auteur=|prénom1=Eric Steven|nom1=Raymond|titre=Cathedral and the bazaar|titre original=Cathedral and the bazaar|traduction titre=La cathédrale et le bazar|passage=|lieu=|éditeur=SnowBall Publishing|date=2010|pages totales=|isbn=978-1-60796-228-1|oclc=833142152|lire en ligne=}}.</ref>, [[w:Eric_Raymond|Eric Raymond]] mobilise en effet le terme « [[w:Cathédrale|cathédrale]] » pour désigner le mode de production des logiciels propriétaires, en opposition au mot « [[w:fr:Bazar|bazar]] », qu'il utilise pour qualifier le mode de développement des logiciels libres. D’un côté, il décrit une organisation pyramidale, rigide et statutairement hiérarchisée, comme on peut la voir souvent au sein des entreprises. Tandis que de l’autre, il parle d’une organisation horizontale, flexible et peu hiérarchisée, qu’il a lui-même expérimentée en adoptant le style de développement de Linus Torvalds, à savoir : « distribuez vite et souvent, déléguez tout ce que vous pouvez déléguer, soyez ouvert jusqu’à la promiscuité »<ref>{{Lien web|langue=|auteur=Eric S. Raymond|traducteur=Sébastien Blondeel|titre=La cathédrale et le bazar|url=https://web.archive.org/web/20200203054716/http://www.linux-france.org/article/these/cathedrale-bazar/cathedrale-bazar-1.html|site=Linux France|lieu=|date=1998|consulté le=}}.</ref>. À l’instar de la métaphore du quartier numérique présentée dans le précédent chapitre, cette manière de décrire les projets open source nous aide donc ici à mieux comprendre ce qui se passe dans le mouvement Wikimédia. D'un côté, on retrouve effectivement cette « ouverture jusqu’à la promiscuité », dans le libre accès accordé aux projets Wikimédia, alors que de l'autre, tout le monde peut participer aux projets Wikimédia, qu'ils soient en ligne ou hors ligne. Ces deux observations corroborent donc l’existence d’un deuxième héritage, d’ordre méthodologique cette fois, en provenance du mouvement du logiciel libre. Néanmoins, il nous reste encore à découvrir un phénomène négligé par Eric Raymond durant ses observations, et qui pourtant est d'une importance considérable concernant l'histoire de la révolution numérique. Il s’agit là de l’apparition de la licence libre, de la philosophie qu'elle sous-tend, et de la [[w:Culture_libre|culture libre]] dont elle fut à l’origine. {{AutoCat}} p1fboywe5bzkk26t4zix3yzopxpebc2 764826 764824 2026-04-24T11:08:28Z Lionel Scheepmans 20012 764826 wikitext text/x-wiki <noinclude>{{Le mouvement Wikimédia}}</noinclude> L’un des premiers épisodes de la préhistoire de Wikipédia et du mouvement Wikimédia débuta en septembre 1983, lorsqu’un programmeur du ''[[w:fr:Massachusetts Institute of Technology|Massachusetts Institute of Technology]]'', appelé [[w:fr:Richard Stallman|Richard Stallman]], déposa un message sur la liste de diffusion net.unix-wizards. C’était un appel d’aide pour la création de [[w:Projet GNU|GNU]], un nouveau [[w:fr:Système d'exploitation|système d’exploitation]] qui devait réunir une suite de programmes que tout le monde pourrait utiliser librement sur son ordinateur personnel<ref>{{Ouvrage|langue=|prénom1=Richard M|nom1=Stallman|prénom2=Sam|nom2=Williams|titre=Richard Stallman et la révolution du logiciel libre - Une biographie autorisée|éditeur=Eyrolles|date=2013|oclc=708380925|lire en ligne=https://framabook.org/docs/stallman/framabook6_stallman_v1_gnu-fdl.pdf|consulté le=}}.</ref>. Dans son message transmis via [[w:Arpanet|ARPANET]], le premier réseau informatique à grande échelle qui précéda Internet, Stallman s’exprimait de la sorte<ref>{{Lien web|langue=|auteur=Richard Stallman|titre=Système d'exploitation GNU – Annonce initiale|url=https://web.archive.org/web/20010106133800/http://www.gnu.org:80/gnu/initial-announcement.fr.html|site=GNU|date=3 décembre 2000|consulté le=}}.</ref> : <blockquote> Je considère comme une [[w:Règle d'or|règle d’or]] que si j’apprécie un programme je dois le partager avec d’autres personnes qui l’apprécient. Je ne peux pas en bonne conscience signer un accord de non-divulgation ni un accord de licence de logiciel. Afin de pouvoir continuer à utiliser les ordinateurs sans violer mes principes, j’ai décidé de rassembler une quantité suffisante de logiciels libres, de manière à pouvoir m’en tirer sans aucun logiciel qui ne soit pas libre. </blockquote> Le projet de Stallman, qui reçut le soutien nécessaire à son accomplissement, marqua ainsi le début de l’[[w:Histoire_du_logiciel_libre|histoire du logiciel libre]]. Quant à la quantité d’aide fournie, elle permet de croire que Richard Stallman n’était pas seul à voir l’arrivée des [[w:Logiciel propriétaire|logiciels propriétaires]] d’un mauvais œil. Car pour les membres du projet GNU et du mouvement du logiciel libre en général, un bon programme informatique doit respecter ces quatre libertés fondamentales<ref>{{Lien web|langue=|auteur=Karl Pradène|titre=Qu'est-ce que le logiciel libre ?|url=https://web.archive.org/web/20000511101640/http://www.gnu.org/philosophy/free-sw.fr.html|site=GNU|date=6 mai 2000|consulté le=}}.</ref> : <blockquote> 1. La liberté d’exécuter le programme, pour tous les usages. 2. La liberté d’étudier le fonctionnement du programme, et de l’adapter à vos besoins. 3. La liberté de redistribuer des copies, donc d’aider votre voisin. 4. La liberté d’améliorer le programme, et de publier vos améliorations, pour en faire profiter toute la communauté. </blockquote> [[w:Histoire_du_logiciel_libre|Lors de l'apparition du logiciel libre]], le marché de l’informatique était de fait en pleine mutation. L'habituel partage des codes informatiques entre les rares étudiants ou chercheurs qui bénéficiaient d’un accès à un ordinateur faisait l'objet d'une remise en question. Ce changement faisait notamment suite au [[w:Copyright_Act_(1976)|Copyright Act]] de 1976, une nouvelle loi qui autorisait l'application d'un [[w:Droit_d'auteur|droit d'auteur]] sur le code informatique, et donc qui permettait d'en interdire le partage ou la réutilisation sans autorisation. Des [[w:Clause_de_confidentialité|clauses de confidentialité]] ont ainsi fait leur apparition, pendant que les employés des firmes informatiques étaient nouvellement soumis à des contrats de confidentialité. C'était la fin de l’entraide et de la solidarité pratiquées chez les pionniers de l’informatique. À sa place s'installaient la concurrence et la compétitivité, bien connues dans le système capitaliste marchand. [[Fichier:Commodore64withdisk.jpg|alt=Commodore 64 avec disquette et lecteur|gauche|vignette|<small>Figure 4. Commodore 64 avec disquette et lecteur.</small>|300x300px]] Cette mutation coïncidait avec l’arrivée des premiers ordinateurs de taille réduite. Grâce à l’apparition des premiers [[w:Circuits_intégrés|circuits intégrés⁣⁣]], les premiers exemplaires avaient en effet été créés par l’industrie aérospatiale au début des années 1960. Cependant, il fallut attendre le début des années 1980 pour que le prix d’un ordinateur soit suffisamment bas pour en faire un [[w:Bien_de_grande_consommation|bien de grande consommation]]. C’est ainsi qu’en 1982, le [[w:Commodore 64|Commodore 64]] entrait dans le [[w:Livre_Guinness_des_records|livre Guinness des records]], avec plus de 17 millions d’exemplaires vendus dans le monde<ref>{{Lien web|langue=|auteur=Brandon Griggs|titre=The Commodore 64, that '80 s computer icon, lives again|url=https://web.archive.org/web/20200706161515/http://edition.cnn.com/2011/TECH/gaming.gadgets/05/09/commodore.64.reborn|site=CNN|date=May 9, 2011|consulté le=}}.</ref>. Juste avant cela, en 1981, l’''[[w:fr:IBM PC|IBM Personal Computer]]'' avait déjà fait son apparition, en proposant une [[w:Architecture_(informatique)|architecture]] ouverte qui allait servir de modèle pour toute une gamme d’ordinateurs que l’on désigne toujours aujourd’hui par l’acronyme « PC ». Pour faire fonctionner ses nouveaux modèles d'ordinateurs, la société IBM avait confié à l’entreprise [[w:Microsoft|Microsoft]], créée en 1975, la mission de les équiper d’un système d’exploitation. Le contrat signé entre les deux firmes fut une véritable aubaine pour le fournisseur des programmes informatiques. Car sans s'en apercevoir, et sans jamais anticiper que son matériel serait cloné à grande échelle, celle-ci avait en effet permis à Microsoft d'établir un monopole dans la vente de logiciels. Cela fut condamné pour [[w:Abus_de_position_dominante|abus de position dominante]]<ref name="Combier_2018_01_24">{{Lien web|langue=fr|auteur=Étienne Combier|titre=Abus de position dominante : les plus grosses amendes de la Commission européenne|url=https://web.archive.org/web/20230511110018/https://www.lesechos.fr/2018/01/abus-de-position-dominante-les-plus-grosses-amendes-de-la-commission-europeenne-982719|périodique=[[w:Les Échos|Les Échos]]|date=2018-01-24|consulté le=}}.</ref> et [[w:Vente_liée_de_logiciels_avec_du_matériel_informatique|vente liée du logiciel avec le matériel informatique]]<ref>{{Lien web|langue=fr|auteur=Marc Rees|titre=Pourquoi la justice européenne a sanctuarisé la vente liée PC et OS|url=https://web.archive.org/web/20230209112015/https://www.nextinpact.com/article/23625/101268-la-justice-europeenne-sanctuarise-vente-liee-pc-et-os|site=nextinpact.com|éditeur=[[w:Next INpact|Next INpact]]|date=2016-07-09|consulté le=}}.</ref>, mais sans pour autant empêcher [[w:Bill_Gates|Bill Gates]], le principal actionnaire de Microsoft, d'être l'homme le plus riche du monde en 1994. [[Fichier:GNU_and_Tux.svg|alt=Mascotte du projet GNU à gauche et du projet Linux à droite.|vignette|<small>Figure 5. À gauche la mascotte du projet GNU ; à droite celle du projet Linux, appelée Tux.</small>]] Cependant, pendant que Microsoft renforçait sa position dominante, un nouvel événement majeur allait marquer l’histoire du logiciel libre. Celui-ci fut de nouveau déclenché par un appel à contribution, qui fut cette fois posté le vingt-cinq août 1991 par un jeune étudiant en informatique de 21 ans, appelé [[w:fr:Linus Torvalds|Linus Torvalds]]. Via le système de messagerie [[w:fr:Usenet|Usenet]], son message avait été posté dans une liste de diffusion consacrée au système d’exploitation [[w:fr:Minix|Minix]], une sorte d’[[w:UNIX|UNIX]] simplifié et développé dans un but didactique, par le programmeur [[w:fr:Andrew Tanenbaum|Andrew Tanenbaum]]. Loin d’imaginer que cela ferait de lui une nouvelle célébrité dans le monde du Libre<ref>{{Ouvrage|langue=|prénom1=Linus|nom1=Torvalds|prénom2=David|nom2=Diamond|prénom3=Olivier|nom3=Engler|titre=Il était une fois Linux|éditeur=Osman Eyrolles Multimédia|date=2001|isbn=978-2-7464-0321-5|oclc=48059105}}.</ref>, Torvalds entama son message par le paragraphe suivant<ref>{{Ouvrage|langue=|prénom1=Linus|nom1=Torvalds|prénom2=David|nom2=Diamond|titre=Just for fun : the story of an accidental revolutionary|éditeur=HarperBusiness|date=2002|isbn=978-0-06-662073-2|oclc=1049937833}}.</ref> : <blockquote> Je fais un système d’exploitation (gratuit) (juste un hobby, ne sera pas grand et professionnel comme gnu) pour les clones 386 (486) AT. Ce projet est en cours depuis avril et commence à se préparer. J’aimerais avoir un retour sur ce que les gens aiment ou n’aiment pas dans minix, car mon système d’exploitation lui ressemble un peu (même disposition physique du système de fichiers (pour des raisons pratiques) entre autres choses)<ref>Texte original avant sa traduction par www.deepl.com/translator : « ''I'm doing a (free) operating system (just a hobby, won't be big and professional like gnu) for 386(486) AT clones. This has been brewing since april, and is starting to get ready. I'd like any feedback on things people like/dislike in minix, as my OS resembles it somewhat (same physical layout of the file-system (due to practical reasons)among other things) ».''</ref>. </blockquote> Bien qu’il fût présenté comme un passe-temps, le projet qui répondait au nom de « [[w:fr:Noyau Linux|Linux]] », fut rapidement soutenu par des milliers de programmeurs du monde entier, avant de devenir la pièce manquante du projet GNU. En effet, les contributeurs au projet de Stallman n’avaient pas encore terminé l’écriture du code informatique du [[w:noyau_de_système_d'exploitation|noyau]] [[w:GNU Hurd|Hurd]], alors qu'il était censé établir la communication entre la [[w:Suite_logicielle|suite logicielle]] produite par GNU et le [[w:Matériel informatique|matériel informatique]]. C'est donc la fusion des codes produits par les projets GNU et Linux qui permit la création du premier système complet, stable et entièrement libre baptisé [[w:GNU/Linux|GNU/Linux]]. [[Fichier:Debian-OpenLogo.svg|gauche|vignette|<small>Figure 6. Logo du système d’exploitation Debian.</small>|264x264px]] Au départ de ce nouveau système informatique, de nombreuses variantes, que l’on nomme communément « [[w:Distribution_Linux|distributions]] », furent créées par des programmeurs de tous horizons. L’une de celles-ci s’intitule [[w:fr:Debian|Debian]] et tire sa réputation d'être la seule qui est simultanément libre, gratuite et produite par une communauté sans lien direct avec une société commerciale<ref>{{Ouvrage|langue=|auteur=|prénom1=Christophe|nom1=Lazaro|titre=La liberte logicielle|passage=|lieu=|éditeur=Academia Bruylant|collection=Anthropologie Prospective|date=2012|pages totales=56|isbn=978-2-87209-861-3|oclc=1104281978}}.</ref>. Ce qui n'a pas empêché pour autant que le code de ce système informatique soit récupéré par plus de 150 distributions dérivées. Quant à la fiabilité du système Debian, elle se confirme par son usage au sein de nombreuses entreprises et organisations, à l’image de la [[w:Wikimedia_Foundation|Fondation Wikimédia]] qui l’utilise sur [[m:Wikimedia_servers/fr|ses serveurs]] pour héberger les projets qu'elle supporte<ref>{{Lien web|langue=|auteur=Méta-Wiki|titre=Serveurs Wikimedia|url=https://web.archive.org/web/20251113214321/https://meta.wikimedia.org/wiki/Wikimedia_servers/fr|site=|date=|consulté le=}}.</ref>. Grâce à la naissance des logiciels libres, le mouvement Wikimédia a donc la possibilité de faire tourner ses serveurs informatiques, avec un système d’exploitation fiable, libre et gratuit. Comme son [[w:Code_source|code source⁣⁣]] est ouvert, cela permet aussi à la Fondation Wikimédia de le modifier pour répondre aux besoins spécifiques du mouvement. À la suite de quoi, et selon les règles formulées par la [[w:Communauté_du_logiciel_libre|communauté du logiciel libre]], les modifications faites par la Fondation deviennent à leur tour, gratuitement et librement, utilisables par d’autres personnes ou organismes. À ce premier héritage reçu par le mouvement Wikimédia s’ajoute ensuite une innovation méthodologique, toujours en provenance des logiciels libres. Dans son article ''[[w:La_Cathédrale_et_le_Bazar|La Cathédrale et le Bazar]]''<ref>{{Ouvrage|langue=|auteur=|prénom1=Eric Steven|nom1=Raymond|titre=Cathedral and the bazaar|titre original=Cathedral and the bazaar|traduction titre=La cathédrale et le bazar|passage=|lieu=|éditeur=SnowBall Publishing|date=2010|pages totales=|isbn=978-1-60796-228-1|oclc=833142152|lire en ligne=}}.</ref>, [[w:Eric_Raymond|Eric Raymond]] mobilise en effet le terme « [[w:Cathédrale|cathédrale]] » pour désigner le mode de production des logiciels propriétaires, en opposition au mot « [[w:fr:Bazar|bazar]] », qu'il utilise pour qualifier le mode de développement des logiciels libres. D’un côté, il décrit une organisation pyramidale, rigide et statutairement hiérarchisée, comme on peut la voir souvent au sein des entreprises. Tandis que de l’autre, il parle d’une organisation horizontale, flexible et peu hiérarchisée, qu’il a lui-même expérimentée en adoptant le style de développement de Linus Torvalds, à savoir : « distribuez vite et souvent, déléguez tout ce que vous pouvez déléguer, soyez ouvert jusqu’à la promiscuité »<ref>{{Lien web|langue=|auteur=Eric S. Raymond|traducteur=Sébastien Blondeel|titre=La cathédrale et le bazar|url=https://web.archive.org/web/20200203054716/http://www.linux-france.org/article/these/cathedrale-bazar/cathedrale-bazar-1.html|site=Linux France|lieu=|date=1998|consulté le=}}.</ref>. À l’instar de la métaphore du quartier numérique présentée dans le précédent chapitre, cette manière de décrire les projets open source nous aide donc ici à mieux comprendre ce qui se passe dans le mouvement Wikimédia. D'un côté, on retrouve effectivement cette « ouverture jusqu’à la promiscuité », dans le libre accès accordé aux projets Wikimédia, alors que de l'autre, tout le monde peut participer aux projets Wikimédia, qu'ils soient en ligne ou hors ligne. Ces deux observations corroborent donc l’existence d’un deuxième héritage, en provenance du mouvement du logiciel libre. Néanmoins, il nous reste encore à découvrir un phénomène négligé par Eric Raymond durant ses observations, et qui a pourtant une importance considérable dans l'histoire de la révolution numérique. Il s’agit là de l’apparition de la licence libre, de la philosophie qu'elle sous-tend, et de la [[w:Culture_libre|culture libre]] dont elle fut à l’origine. {{AutoCat}} sdbpoyv0tu893od0mpse48cx0jlf590 Le mouvement Wikimédia 0 80088 764765 764196 2026-04-24T09:03:24Z Lionel Scheepmans 20012 Up 764765 wikitext text/x-wiki <!--<noinclude>{{NavDébut|book={{PAGENAME}}|page=Avant-propos|pageText=Démarrer}}</noinclude>--> <noinclude>{{NavDébut|book=Le mouvement Wikimédia|page=Avant-propos|pageText=Démarrer}}</noinclude> {{Page de garde |image=Photo de couverture du wikilivre Le mouvement Wikimédia.jpg |description=<big>'''{{Centrer|Dernier refuge du altruiste du savoir libre et fiable ?}}'''</big><br/> En lien avec la révolution numérique et la [[W:Contre-culture des années 1960|contre-culture des années 1960]], ce livre retrace les origines du [[W:Mouvement Wikimédia|mouvement Wikimédia]], avant d'en présenter l'organisation. Issu de la première partie d'une [[v:fr:recherche:Imagine_un_monde|thèse de doctorat rédigée sur Wikiversité]], il est accessible sur Wikilivres, chapitre par chapitre, ou [[Le mouvement Wikimédia/Version complète| en version complète sur une seule page]] que l'on peut imprimer avec son navigateur web. Cet ouvrage est également disponible au format [https://upload.wikimedia.org/wikipedia/commons/8/88/Le_mouvement_Wikim%C3%A9dia_%E2%80%94_Wikilivres.pdf PDF] et bientôt aux formats [https://upload.wikimedia.org/wikipedia/commons/1/15/Le_mouvement_Wikim%C3%A9dia.oga audio], EPUB et livre de poche. Dans le but d'améliorer son contenu, les questions et les commentaires sont les bienvenus sur [[discussion:Le mouvement Wikimédia|cette page de discussion]]. |avancement=Terminé |cdu= * {{CDU item|3/31/316|316.3/316.35}} {{Moteur}} {{Version complète}} {{Statistiques}} }} == Quatrième de couverture == {{/Quatrième de couverture}} == Sommaire == {{/Sommaire}} [[Catégorie:Livres]] [[Catégorie:Étude du cyber-mouvement du logiciel libre (livre)]] [[Catégorie:Anthropologie]] [[Catégorie:Sciences humaines]] [[Catégorie:Livres terminés]] [[Catégorie:Le mouvement Wikimédia (livre)]] [[Catégorie:Livres en vitrine]] [[Catégorie:Le mouvement Wikimédia]] gimmfoj12spjsh4ce42562eiqlv3akh 764766 764765 2026-04-24T09:03:47Z Lionel Scheepmans 20012 764766 wikitext text/x-wiki <!--<noinclude>{{NavDébut|book={{PAGENAME}}|page=Avant-propos|pageText=Démarrer}}</noinclude>--> <noinclude>{{NavDébut|book=Le mouvement Wikimédia|page=Avant-propos|pageText=Démarrer}}</noinclude> {{Page de garde |image=Photo de couverture du wikilivre Le mouvement Wikimédia.jpg |description=<big>'''{{Centrer|Dernier refuge altruiste du savoir libre et fiable ?}}'''</big><br/> En lien avec la révolution numérique et la [[W:Contre-culture des années 1960|contre-culture des années 1960]], ce livre retrace les origines du [[W:Mouvement Wikimédia|mouvement Wikimédia]], avant d'en présenter l'organisation. Issu de la première partie d'une [[v:fr:recherche:Imagine_un_monde|thèse de doctorat rédigée sur Wikiversité]], il est accessible sur Wikilivres, chapitre par chapitre, ou [[Le mouvement Wikimédia/Version complète| en version complète sur une seule page]] que l'on peut imprimer avec son navigateur web. Cet ouvrage est également disponible au format [https://upload.wikimedia.org/wikipedia/commons/8/88/Le_mouvement_Wikim%C3%A9dia_%E2%80%94_Wikilivres.pdf PDF] et bientôt aux formats [https://upload.wikimedia.org/wikipedia/commons/1/15/Le_mouvement_Wikim%C3%A9dia.oga audio], EPUB et livre de poche. Dans le but d'améliorer son contenu, les questions et les commentaires sont les bienvenus sur [[discussion:Le mouvement Wikimédia|cette page de discussion]]. |avancement=Terminé |cdu= * {{CDU item|3/31/316|316.3/316.35}} {{Moteur}} {{Version complète}} {{Statistiques}} }} == Quatrième de couverture == {{/Quatrième de couverture}} == Sommaire == {{/Sommaire}} [[Catégorie:Livres]] [[Catégorie:Étude du cyber-mouvement du logiciel libre (livre)]] [[Catégorie:Anthropologie]] [[Catégorie:Sciences humaines]] [[Catégorie:Livres terminés]] [[Catégorie:Le mouvement Wikimédia (livre)]] [[Catégorie:Livres en vitrine]] [[Catégorie:Le mouvement Wikimédia]] ieszl7yhj939mcmp0en6p2g0lc7sr2c 764768 764766 2026-04-24T09:10:11Z Lionel Scheepmans 20012 764768 wikitext text/x-wiki <!--<noinclude>{{NavDébut|book={{PAGENAME}}|page=Avant-propos|pageText=Démarrer}}</noinclude>--> <noinclude>{{NavDébut|book=Le mouvement Wikimédia|page=Avant-propos|pageText=Démarrer}}</noinclude> {{Page de garde |image=Photo de couverture du wikilivre Le mouvement Wikimédia.jpg |description=<big>'''{{Centrer|Dernier partage altruiste<br/>d'un savoir libre et fiable ?}}'''</big><br/> En lien avec la révolution numérique et la [[W:Contre-culture des années 1960|contre-culture des années 1960]], ce livre retrace les origines du [[W:Mouvement Wikimédia|mouvement Wikimédia]], avant d'en présenter l'organisation. Issu de la première partie d'une [[v:fr:recherche:Imagine_un_monde|thèse de doctorat rédigée sur Wikiversité]], il est accessible sur Wikilivres, chapitre par chapitre, ou [[Le mouvement Wikimédia/Version complète| en version complète sur une seule page]] que l'on peut imprimer avec son navigateur web. Cet ouvrage est également disponible au format [https://upload.wikimedia.org/wikipedia/commons/8/88/Le_mouvement_Wikim%C3%A9dia_%E2%80%94_Wikilivres.pdf PDF] et bientôt aux formats [https://upload.wikimedia.org/wikipedia/commons/1/15/Le_mouvement_Wikim%C3%A9dia.oga audio], EPUB et livre de poche. Dans le but d'améliorer son contenu, les questions et les commentaires sont les bienvenus sur [[discussion:Le mouvement Wikimédia|cette page de discussion]]. |avancement=Terminé |cdu= * {{CDU item|3/31/316|316.3/316.35}} {{Moteur}} {{Version complète}} {{Statistiques}} }} == Quatrième de couverture == {{/Quatrième de couverture}} == Sommaire == {{/Sommaire}} [[Catégorie:Livres]] [[Catégorie:Étude du cyber-mouvement du logiciel libre (livre)]] [[Catégorie:Anthropologie]] [[Catégorie:Sciences humaines]] [[Catégorie:Livres terminés]] [[Catégorie:Le mouvement Wikimédia (livre)]] [[Catégorie:Livres en vitrine]] [[Catégorie:Le mouvement Wikimédia]] nlzffuvb31jjkx64k9z5y8z38999vfz 764774 764768 2026-04-24T09:21:20Z Lionel Scheepmans 20012 764774 wikitext text/x-wiki <!--<noinclude>{{NavDébut|book={{PAGENAME}}|page=Avant-propos|pageText=Démarrer}}</noinclude>--> <noinclude>{{NavDébut|book=Le mouvement Wikimédia|page=Avant-propos|pageText=Démarrer}}</noinclude> {{Page de garde |image=Photo de couverture du wikilivre Le mouvement Wikimédia.jpg |description=<big>'''{{Centrer|Dernier partage altruiste<br/>de la connaissance libre ?}}'''</big><br/> En lien avec la révolution numérique et la [[W:Contre-culture des années 1960|contre-culture des années 1960]], ce livre retrace les origines du [[W:Mouvement Wikimédia|mouvement Wikimédia]], avant d'en présenter l'organisation. Issu de la première partie d'une [[v:fr:recherche:Imagine_un_monde|thèse de doctorat rédigée sur Wikiversité]], il est accessible sur Wikilivres, chapitre par chapitre, ou [[Le mouvement Wikimédia/Version complète| en version complète sur une seule page]] que l'on peut imprimer avec son navigateur web. Cet ouvrage est également disponible au format [https://upload.wikimedia.org/wikipedia/commons/8/88/Le_mouvement_Wikim%C3%A9dia_%E2%80%94_Wikilivres.pdf PDF] et bientôt aux formats [https://upload.wikimedia.org/wikipedia/commons/1/15/Le_mouvement_Wikim%C3%A9dia.oga audio], EPUB et livre de poche. Dans le but d'améliorer son contenu, les questions et les commentaires sont les bienvenus sur [[discussion:Le mouvement Wikimédia|cette page de discussion]]. |avancement=Terminé |cdu= * {{CDU item|3/31/316|316.3/316.35}} {{Moteur}} {{Version complète}} {{Statistiques}} }} == Quatrième de couverture == {{/Quatrième de couverture}} == Sommaire == {{/Sommaire}} [[Catégorie:Livres]] [[Catégorie:Étude du cyber-mouvement du logiciel libre (livre)]] [[Catégorie:Anthropologie]] [[Catégorie:Sciences humaines]] [[Catégorie:Livres terminés]] [[Catégorie:Le mouvement Wikimédia (livre)]] [[Catégorie:Livres en vitrine]] [[Catégorie:Le mouvement Wikimédia]] 090321f3gbu9cds5eiioibi2qpg2zr3 Dictionnaire de philosophie/Abstraction 0 83013 764755 763935 2026-04-24T05:37:26Z PandaMystique 119061 764755 wikitext text/x-wiki {{DicoPhilo|Abstraction}} == Définition et problématique générale == L''''abstraction''' désigne à la fois un processus mental et son résultat : l'opération par laquelle l'esprit isole certaines caractéristiques d'un objet ou d'une pluralité d'objets en négligeant les autres, et le concept général ainsi obtenu. Cette notion occupe une place centrale dans toute théorie de la connaissance depuis l'Antiquité, car elle pose la question fondamentale des rapports entre le particulier et l'[[Philosophie/Repères|universel]], entre l'expérience sensible et la pensée conceptuelle.<ref>Armstrong, David M., ''Universals. An Opinionated Introduction'', Boulder, Westview Press, 1989, p. 1-15.</ref> Le terme vient du latin ''abstractio'', dérivé du verbe ''abstrahere'' (« tirer de », « séparer »), qui traduit le grec ''aphairesis''. Cette étymologie révèle la dimension essentielle du processus : il s'agit d'extraire quelque chose d'un ensemble plus vaste, de faire un prélèvement sur la richesse du donné pour ne retenir que certains aspects.<ref>Klima, Gyula, "The Medieval Problem of Universals", ''Stanford Encyclopedia of Philosophy'', 2008 (version en ligne).</ref> L'abstraction apparaît ainsi comme un mouvement de simplification qui écarte volontairement certaines particularités pour saisir ce qui est commun à plusieurs objets ou situations. Cette capacité d'abstraction constitue l'un des traits distinctifs de la cognition humaine. Elle permet de former des concepts généraux à partir d'expériences singulières, de classer les objets en catégories, de formuler des lois scientifiques et de développer des raisonnements mathématiques.<ref>Martínez, Sergio F. et Huang, Xiang, "Epistemic Groundings of Abstraction and Their Cognitive Dimension", ''Philosophy of Science'', vol. 78, n° 3, 2011, p. 490-511.</ref> Sans elle, toute pensée serait enfermée dans l'immédiateté du particulier et ne pourrait s'élever vers la compréhension des structures et des régularités qui organisent le réel. Pourtant, l'abstraction soulève des difficultés philosophiques considérables. Comment ce qui est général peut-il exister, alors que seuls les objets [[Dictionnaire de philosophie/Individu|individuels]] semblent avoir une réalité concrète ? Les concepts abstraits correspondent-ils à quelque chose dans le monde, ou ne sont-ils que des constructions mentales ? Le processus d'abstraction nous éloigne-t-il de la [[Dictionnaire de philosophie/Vérité|vérité]] en nous faisant perdre la richesse du concret, ou nous en rapproche-t-il en nous permettant de saisir l'essentiel ? Avant d'entrer dans l'histoire de la notion, il importe de distinguer plusieurs sens de l'abstraction qui, bien que liés, relèvent de problèmes hétérogènes et ne peuvent être confondus sans dommage. Premièrement, l'abstraction désigne une opération psychologique de généralisation par laquelle l'esprit dégage, à partir d'expériences particulières, des représentations communes à plusieurs objets : c'est le sens qu'explorent principalement la tradition [[Dictionnaire de philosophie/Empirisme|empiriste]], de Locke à Hume, et la psychologie cognitive contemporaine. Deuxièmement, elle renvoie à une question ontologique portant sur le statut des universaux (genres, espèces, propriétés) et, plus largement, des « objets abstraits » (nombres, ensembles, propositions) dont la réalité fait l'objet de controverses entre réalistes, conceptualistes et nominalistes. Troisièmement, elle peut désigner une condition transcendantale ou eidétique de la connaissance : chez Kant, les formes pures ne sont pas abstraites du sensible mais le structurent [[Dictionnaire de philosophie/A priori|a priori]] ; chez Husserl, l'intuition eidétique suppose une variation imaginative libre qui ne se réduit pas à l'omission de traits particuliers au sens lockéen. Quatrièmement, comme le montrera Marx, l'abstraction peut être saisie comme forme sociale historiquement déterminée, produite par les rapports marchands avant même toute réflexion philosophique.<ref>Pour cette cartographie des sens de l'abstraction, voir Rosen, Gideon, "Abstract Objects", ''Stanford Encyclopedia of Philosophy'', 2020 (version en ligne) ; Horsten, Leon et Leitgeb, Hannes, "How Abstraction Works", ''Philosophia Mathematica'', vol. 17, n° 3, 2009, p. 334-358.</ref> Ces quatre registres mobilisent des méthodes différentes (psychologie, ontologie, philosophie transcendantale, critique sociale) ; les confondre revient à traiter comme un seul problème ce qui engage en réalité des champs conceptuels distincts. La suite de cet article s'efforce de les articuler sans les confondre. == Histoire philosophique de la notion == === Antiquité : Platon et Aristote === La réflexion sur l'abstraction prend naissance avec la philosophie grecque, qui affronte pour la première fois le problème des universaux de manière systématique. Platon (428-348 av. J.-C.) développe sa théorie des [[Pour lire Platon/Vocabulaire|Formes]] (ou Idées) pour expliquer comment il peut exister une connaissance stable et universelle alors que le monde sensible est en perpétuel changement.<ref>Platon, ''République'', Livre VI-VII, trad. fr. G. Leroux, Paris, Flammarion, 2002.</ref> Les Formes platoniciennes sont des réalités transcendantes, éternelles et immuables qui existent dans un monde intelligible séparé du monde sensible. La Forme du Beau, par exemple, existe indépendamment de tous les objets beaux particuliers et constitue leur cause formelle : c'est parce qu'ils participent à cette Forme que les objets sensibles peuvent être dits beaux. Dans cette perspective, l'abstraction n'est pas une construction de l'esprit mais une réminiscence : connaître, c'est se souvenir des Formes que l'[[Dictionnaire de philosophie/Âme|âme]] a contemplées avant sa chute dans le corps.<ref>Platon, ''Phédon'', 72e-76e, trad. fr. M. Dixsaut, Paris, Flammarion, 1991, p. 221-235.</ref> Le processus de connaissance consiste à s'élever du sensible vers l'intelligible, du particulier changeant vers l'universel éternel. Cette ascension dialectique, décrite notamment dans l'allégorie de la caverne, constitue le mouvement même de la philosophie. Aristote (384-322 av. J.-C.), élève de Platon, rejette la séparation platonicienne entre le monde sensible et le monde des Formes.<ref>Aristote, ''Métaphysique'', Livre I, 9, 990b-991b, trad. fr. M.-P. Duminil et A. Jaulin, Paris, Flammarion, 2008, p. 83-87.</ref> Pour lui, les universaux n'existent pas indépendamment des particuliers : ils existent ''dans'' les choses individuelles comme leur forme substantielle. La forme d'un cheval n'existe pas séparément dans un monde intelligible, mais seulement actualisée dans les chevaux individuels. Cette théorie hylémorphique (de ''hylè'', matière, et ''morphè'', forme) affirme que toute substance est composée d'une matière et d'une forme indissociables dans l'existence concrète. L'abstraction devient chez Aristote un processus mental authentique : c'est l'intellect agent (''nous poiètikos'') qui extrait la forme intelligible des données sensibles fournies par la phantasia.<ref>Aristote, ''De l'âme'', III, 4-5, 429a-430a, trad. fr. R. Bodéüs, Paris, Flammarion, 1993, p. 217-222.</ref> Ce processus ne crée pas artificiellement l'universel, mais le dégage de la gangue du particulier où il est immanent. L'abstraction aristotélicienne procède par négation progressive des conditions matérielles : on peut abstraire la forme géométrique d'un objet en faisant abstraction de sa couleur, de sa texture, de son poids, pour ne retenir que sa configuration spatiale. Cette divergence entre Platon et Aristote inaugure un débat qui traversera toute l'histoire de la philosophie : les universaux existent-ils avant les choses (''ante res''), dans les choses (''in rebus''), ou seulement après les choses, comme produits de l'esprit (''post res'') ? === Scolastique médiévale : le problème des universaux === La philosophie médiévale hérite de cette question à travers Boèce (c. 480-524), qui transmet au Moyen Âge latin le problème formulé par Porphyre dans l'Isagoge : les genres et les espèces subsistent-ils réellement ou seulement dans la pensée ? S'ils subsistent réellement, sont-ils corporels ou incorporels ? Sont-ils séparés des choses sensibles ou existent-ils en elles ?<ref>Porphyre, ''Isagoge'', trad. fr. A. de Libera et A.-P. Segonds, Paris, Vrin, 1998, p. 1-3.</ref> Thomas d'Aquin (1225-1274) développe une solution qui synthétise l'aristotélisme et la théologie chrétienne.<ref>Thomas d'Aquin, ''Somme théologique'', Ia, q. 84-85, trad. fr. A.-M. Roguet, Paris, Cerf, 1984, vol. I, p. 714-772.</ref> Pour lui, l'universel possède un triple statut. D'abord, la nature commune (''natura communis'') existe dans les choses individuelles : l'humanité existe réellement dans Socrate et dans Platon, mais sans y être universelle, elle n'y est présente qu'individualisée. Ensuite, cette même nature existe dans l'intellect humain comme concept universel, après avoir été abstraite par l'intellect agent à partir des images sensibles (''phantasmata''). Enfin, les natures existent aussi dans l'intellect divin comme modèles exemplaires de la création. Le processus d'abstraction thomiste comporte plusieurs degrés. L'abstraction ''totale'' permet de former les concepts de genres et d'espèces en considérant l'essence commune indépendamment des [[Dictionnaire de philosophie/Individu|individus]]. L'abstraction ''formelle'' permet de saisir la forme sans la matière sensible, donnant accès aux objets mathématiques.<ref>Thomas d'Aquin, ''De ente et essentia'', chap. 3, in ''L'Être et l'essence'', trad. fr. C. Capelle, Paris, Vrin, 1985, p. 38-45.</ref> Mais Thomas insiste sur le fait que, bien que l'intellect puisse considérer séparément ce qui n'existe pas séparément dans la réalité, cette séparation mentale n'est pas une falsification mais une condition de la connaissance intellectuelle. Guillaume d'Ockham (c. 1287-1347) rompt avec ce réalisme modéré en affirmant que seuls les individus existent réellement : il n'y a rien de commun dans la réalité entre Socrate et Platon si ce n'est qu'ils peuvent être désignés par le même terme « homme ».<ref>Guillaume d'Ockham, ''Summa logicae'', I, 14-17, dans ''Philosophical Writings'', éd. et trad. P. Boehner, Indianapolis, Hackett, 1990, p. 32-41.</ref> Les universaux ne sont que des signes mentaux (''termini'') ou vocaux qui, par convention, se réfèrent à plusieurs individus. Cette position nominaliste, qui refuse toute réalité extramentale aux universaux, aura des conséquences considérables pour l'[[Dictionnaire de philosophie/Épistémologie|épistémologie]] moderne. === Empirisme moderne : de Locke à Hume === John Locke (1632-1704) renouvelle la théorie de l'abstraction dans le cadre de son [[Dictionnaire de philosophie/Empirisme|empirisme]]. Dans l{{'}}''Essai sur l'entendement humain'' (1690), il affirme que l'esprit est initialement une ''tabula rasa'', une page blanche sur laquelle l'expérience vient inscrire toutes nos idées.<ref>Locke, John, ''Essai sur l'entendement humain'', II, 1, §2, trad. fr. J.-M. Vienne, Paris, Vrin, 2001, p. 105.</ref> Les idées simples proviennent directement de la sensation ou de la réflexion, tandis que les idées complexes sont construites par l'esprit à partir des idées simples. L'abstraction lockéenne consiste à séparer mentalement certaines idées qui accompagnent un objet pour ne retenir que celles qui sont communes à plusieurs objets. Ainsi, pour former l'idée générale de « blanc », l'esprit observe cette couleur dans la craie ou la neige, la considère isolément en faisant abstraction du lieu, du moment et des autres qualités, et lui donne le nom de « blancheur » qui peut alors s'appliquer à tous les objets partageant cette apparence.<ref>Locke, John, ''Essai sur l'entendement humain'', III, 3, §6-9, op. cit., p. 393-396.</ref> Les idées abstraites sont donc formées par omission des particularités et conservation des ressemblances. George Berkeley (1685-1753) soumet cette théorie à une critique serrée dans le ''Traité des principes de la connaissance humaine'' (1710).<ref>Berkeley, George, ''Traité des principes de la connaissance humaine'', Introduction, §10-15, trad. fr. D. Berlioz, Paris, Flammarion, 1991, p. 63-69.</ref> Il argue qu'il est impossible de former l'idée abstraite générale d'un triangle qui ne serait ni scalène, ni isocèle, ni équilatéral, ni grand, ni petit, mais tous ces triangles à la fois. Une telle idée serait contradictoire. Berkeley conclut que les soi-disant idées abstraites de Locke sont psychologiquement impossibles : nous ne pouvons concevoir que des idées particulières, même si nous pouvons utiliser ces idées particulières de manière générale en ne prêtant attention qu'à certains de leurs aspects. Cette critique révèle une tension interne à l'empirisme : si toute connaissance provient de l'expérience sensible, qui ne nous donne que du particulier, comment peut-on former des concepts généraux authentiquement universels ? Le nominalisme de Berkeley résout le problème en niant qu'il existe véritablement des idées générales : il n'y a que des mots généraux appliqués à des idées toujours particulières. David Hume (1711-1776) prolonge la critique berkeleyenne des idées abstraites tout en déplaçant son centre de gravité. Dans le ''Traité de la nature humaine'' (1739-1740), il souscrit explicitement à la thèse selon laquelle « toutes les idées générales ne sont rien que des idées particulières, attachées à un certain terme qui leur donne une signification plus étendue, et qui les fait rappeler à l'occasion d'autres idées individuelles qui leur ressemblent ».<ref>Hume, David, ''Traité de la nature humaine. Livre I : De l'entendement'', Partie I, Section VII, trad. fr. P. Baranger et P. Saltel, Paris, GF-Flammarion, 1995, p. 73.</ref> Ce que nous prenons pour une idée abstraite est en réalité une idée particulière accompagnée d'une disposition à évoquer d'autres idées qui lui ressemblent. Hume rejoint ainsi le nominalisme berkeleyen, mais il l'adosse à une analyse psychologique plus développée des mécanismes par lesquels l'esprit manipule les représentations générales. La nouveauté humienne tient dans le rôle attribué à l'association des idées. Pour Hume, l'esprit ne saisit pas une essence commune par inspection directe : il forme des regroupements sous l'effet de trois principes associatifs, la ressemblance, la contiguïté dans l'espace ou le temps, et la causalité, qui agissent comme des habitudes mentales acquises par la répétition des expériences.<ref>Hume, David, ''Traité de la nature humaine'', Livre I, Partie I, Section IV, op. cit., p. 60-63.</ref> Un terme général comme « homme » ne renvoie donc pas à une essence universelle préexistante, mais déclenche un faisceau variable d'idées particulières selon le contexte d'énonciation et les habitudes acquises par le locuteur. L'habitude, et non l'intellection d'un universel, constitue le ressort effectif de la généralité. Cette analyse naturalise l'abstraction : elle la rabat sur des mécanismes cognitifs descriptibles empiriquement, sans faire appel à une faculté spéciale d'intellection des universaux. Dans l{{'}}''Enquête sur l'entendement humain'' (1748), Hume accentue cette réduction en insistant sur l'origine expérientielle de toutes nos idées et sur le caractère conventionnel des dénominations générales.<ref>Hume, David, ''Enquête sur l'entendement humain'', Section II, trad. fr. A. Leroy revue par M. Beyssade, Paris, GF-Flammarion, 2008, p. 70-76.</ref> Le même mouvement explique, pour Hume, l'idée de cause : elle n'est pas saisie dans l'expérience mais produite par l'habitude née de la conjonction constante de deux événements. La théorie humienne de l'abstraction est ainsi solidaire d'une théorie générale de la connaissance où les régularités mentales remplacent les nécessités logiques ou essentielles. L'héritage humien se déploie dans plusieurs directions. La psychologie associationniste du XIXe siècle (James Mill, Alexander Bain) développera cette analyse des idées générales comme sédimentations d'associations. Kant, dans la préface de la seconde édition de la ''Critique de la raison pure'', reconnaît avoir été « réveillé de son sommeil dogmatique » par Hume ; mais il inverse la démarche humienne en soutenant que les catégories ne peuvent être ramenées à des habitudes associatives sans rendre inintelligible la nécessité des lois scientifiques.<ref>Kant, Emmanuel, ''Prolégomènes à toute métaphysique future qui pourra se présenter comme science'', Préface, trad. fr. L. Guillermit, Paris, Vrin, 2001, p. 13-15.</ref> Le différend n'est pas anecdotique : il oppose deux conceptions inconciliables de l'abstraction, l'une qui la réduit à la psychologie de l'habitude, l'autre qui cherche au contraire les conditions [[Dictionnaire de philosophie/A priori|a priori]] qui rendent toute habitude elle-même possible. Ce débat traversera, sous des formes renouvelées, tout le XIXe et le XXe siècle. === Rationalisme : Descartes et le refus de l'abstractionnisme === René Descartes (1596-1650) n'aborde pas frontalement la question de l'abstraction sous ce nom, mais sa théorie des idées innées constitue une contestation implicite du modèle abstractionniste. Si les idées les plus importantes, celle de Dieu, celles de l'étendue, de la pensée, des vérités mathématiques, ne sont pas formées par extraction à partir de l'expérience sensible mais sont trouvées dans l'entendement, alors l'abstraction cesse d'être la source privilégiée de la connaissance : elle en devient au mieux un cas dérivé et subordonné.<ref>Descartes, René, ''Méditations métaphysiques'', III, AT VII, p. 37-52, in ''Œuvres de Descartes'', éd. C. Adam et P. Tannery, Paris, Vrin, 1996, vol. VII.</ref> Dans les ''Méditations métaphysiques'' (1641), Descartes distingue trois types d'idées : les idées adventices (qui semblent venir du dehors), les idées factices (que l'esprit construit), et les idées innées (qui appartiennent originellement à la nature de l'esprit). L'idée de Dieu est innée, car aucune expérience finie ne peut être la cause adéquate d'une représentation de l'infini. Les idées mathématiques le sont également : elles ne dérivent pas de l'abstraction à partir de figures sensibles mais sont saisies par l'intellect pur.<ref>Descartes, René, ''Méditations métaphysiques'', V, AT VII, p. 63-71, op. cit.</ref> L'exemple cartésien du chiliogone (figure à mille côtés) dans la Sixième Méditation est éloquent sur ce point : je conçois clairement cette figure et puis en démontrer rigoureusement les propriétés, alors même que je ne puis en former aucune image particulière distincte de celle d'un myriagone ou d'un polygone quelconque à grand nombre de côtés.<ref>Descartes, René, ''Méditations métaphysiques'', VI, AT VII, p. 72-73, op. cit.</ref> La conception intellectuelle ne repose donc pas sur la capacité à former une représentation imageante à partir d'instances sensibles : l'ordre de l'intellection et celui de l'imagination se séparent. Cette position ne supprime pas toute place pour l'abstraction chez Descartes. La distinction entre une substance et ses [[Dictionnaire de philosophie/Attribut|attributs]], ou entre l'étendue et telle figure particulière qui la modifie, repose sur une opération que l'on peut qualifier d'abstractive : la ''distinction de raison'' des ''Principes de la philosophie'' (1644) sépare mentalement ce qui ne peut exister séparément.<ref>Descartes, René, ''Principes de la philosophie'', I, §§53-54, trad. fr. D. Moreau, Paris, Vrin, 2009, p. 96-98.</ref> Mais cette opération secondaire présuppose l'intuition préalable de natures simples, claires et distinctes. L'ordre cartésien inverse donc l'ordre empiriste : ce ne sont pas les abstractions qui fondent la connaissance des essences, ce sont les essences intellectuellement saisies qui permettent, en aval, des abstractions contrôlées. Cette inversion annonce le geste kantien consistant à chercher dans l'entendement lui-même les conditions formelles de toute abstraction empirique, quoiqu'avec des présupposés métaphysiques très différents, puisque Kant rejettera la théologie cartésienne de l'innéité au profit d'une analyse transcendantale des structures a priori. === Kant : abstraction et catégories a priori === Emmanuel Kant (1724-1804) opère une synthèse entre empirisme et rationalisme en distinguant la forme et la matière de la connaissance.<ref>Kant, Emmanuel, ''Critique de la raison pure'', « Esthétique transcendantale » et « Analytique transcendantale », trad. fr. A. Tremesaygues et B. Pacaud, Paris, PUF, 2012 [1781/1787], p. 53-223.</ref> Dans la ''Critique de la raison pure'' (1781, 1787), il montre que l'expérience n'est possible que si l'esprit impose ses formes [[Dictionnaire de philosophie/A priori|a priori]] (l'espace, le temps, les catégories) à la matière sensible fournie par l'intuition. Les catégories kantiennes, quantité, qualité, relation, modalité, ne sont pas abstraites de l'expérience mais constituent les conditions transcendantales de possibilité de toute expérience.<ref>Kant, Emmanuel, ''Critique de la raison pure'', « Analytique des concepts », §10-13, op. cit., p. 105-119.</ref> Elles appartiennent à la structure même de l'entendement et s'appliquent nécessairement à tous les phénomènes. La déduction transcendantale démontre que les objets doivent se conformer aux catégories pour pouvoir être objets d'expérience. Ce renversement copernicien transforme le statut de l'abstraction. Les concepts empiriques peuvent certes être formés par abstraction à partir de l'expérience : je peux former le concept de « chien » en comparant différents chiens, en réfléchissant sur leurs points communs et en faisant abstraction de leurs différences. Mais les concepts purs de l'entendement (les catégories) ne proviennent pas de cette abstraction empirique : ils sont les formes mêmes selon lesquelles l'entendement structure l'expérience. Kant distingue ainsi l'abstraction comme processus de formation des concepts empiriques de l'analyse transcendantale qui dévoile les structures a priori de la connaissance. Cette distinction pèsera durablement sur la philosophie ultérieure, même si ses interprétations ont divergé : les idéalistes allemands y verront l'amorce d'une histoire de la raison, les néokantiens (Cohen, Cassirer) une doctrine des formes symboliques, tandis que les lectures analytiques contemporaines (Sellars, Brandom) en réinvestiront le noyau normatif pour critiquer le « mythe du donné ».<ref>Sellars, Wilfrid, "Empiricism and the Philosophy of Mind", in ''Science, Perception and Reality'', Atascadero, Ridgeview, 1963, p. 127-196 ; Cassirer, Ernst, ''La Philosophie des formes symboliques'', trad. fr. O. Hansen-Løve et J. Lacoste, Paris, Minuit, 1972, 3 vol.</ref> Ce partage entre concepts empiriques et concepts purs continue de structurer les débats contemporains sur la possibilité d'une théorie générale de l'abstraction. === Hegel : dialectique du concret et de l'abstrait === Georg Wilhelm Friedrich Hegel (1770-1831) bouleverse la conception traditionnelle de l'abstraction en montrant que le [[Philosophie/Repères|concret]] authentique n'est pas le donné immédiat mais le résultat d'un processus de médiation.<ref>Hegel, Georg Wilhelm Friedrich, ''Science de la logique'', Livre I, « La doctrine de l'être », trad. fr. P.-J. Labarrière et G. Jarczyk, Paris, Aubier, 1972, p. 53-78.</ref> Dans la ''Science de la logique'' (1812-1816) et l{{'}}''Encyclopédie des sciences philosophiques'' (1817-1827), Hegel critique l'entendement abstrait (''Verstand'') qui isole et fige les déterminations, et lui oppose la raison dialectique (''Vernunft'') qui saisit le mouvement vivant des concepts. Pour Hegel, l'abstrait n'est pas le général opposé au particulier, mais l'unilatéral opposé au concret. Est abstrait ce qui est séparé de ses conditions, de ses relations, de son devenir historique.<ref>Hegel, Georg Wilhelm Friedrich, ''Encyclopédie des sciences philosophiques'', I, « La Science de la Logique », §82, Add., trad. fr. B. Bourgeois, Paris, Vrin, 1970, p. 343-345.</ref> Ainsi, l'être pur dont part la Logique est la catégorie la plus abstraite parce qu'elle est la plus indéterminée, la plus pauvre en contenu. Le concret, au contraire, est « l'unité du divers », la synthèse de multiples déterminations. Cette inversion a des conséquences épistémologiques majeures. La connaissance ne procède pas du concret vers l'abstrait en appauvrissant progressivement le réel, mais de l'abstrait vers le concret en enrichissant la pensée par l'intégration dialectique de déterminations toujours plus nombreuses. Le concept hégélien (''Begriff'') n'est pas une représentation générale obtenue par abstraction, mais la structure rationnelle immanente à la chose même, saisie dans son automouvement. Dans ses écrits critiques, Hegel dénonce l'« abstraction » au sens péjoratif : la pensée qui s'en tient aux oppositions rigides et aux identités figées au lieu de comprendre les contradictions vivantes et les transitions dialectiques. Dans son célèbre texte « Qui pense abstraitement ? » (1807), il montre ironiquement que c'est la pensée commune qui pense abstraitement en réduisant un meurtrier à son crime, tandis que la pensée philosophique pense concrètement en le comprenant dans la totalité de ses déterminations sociales et historiques.<ref>Hegel, Georg Wilhelm Friedrich, « Qui pense abstraitement ? », in ''Écrits sur la critique'', trad. fr. M. Jacob, Paris, Aubier-Montaigne, 1978, p. 113-121.</ref> === Marx : critique de l'abstraction capitaliste === [[Dictionnaire de philosophie/Karl Marx|Karl Marx]] (1818-1883) hérite de la dialectique hégélienne mais la transforme en la plaçant sur ses bases matérielles. Dans les ''Grundrisse'' (1857-1858) et le ''Capital'' (1867), il développe une méthode qui part des abstractions les plus simples pour reconstituer le concret comme « synthèse de multiples déterminations ».<ref>Marx, Karl, ''Grundrisse. Fondements de la critique de l'économie politique'', « Introduction de 1857 », trad. fr. J.-P. Lefebvre, Paris, Éditions sociales, 2011, p. 34-42.</ref> La section « La méthode de l'économie politique » des Grundrisse expose cette démarche. Les économistes du XVIIe siècle commençaient par la population, la nation, l'État, totalités concrètes mais confuses. L'analyse économique classique a produit des concepts abstraits : division du travail, monnaie, valeur. Ces abstractions ne sont pas de simples constructions mentales mais correspondent à des réalités sociales historiquement déterminées. Marx distingue soigneusement deux mouvements. Le premier va du concret chaotique (la représentation d'ensemble) aux déterminations abstraites simples par analyse. Le second reconstruit le concret par synthèse des déterminations abstraites : « Le concret est concret parce qu'il est la synthèse de multiples déterminations, donc unité du divers ».<ref>Marx, Karl, ''Grundrisse'', op. cit., p. 38.</ref> Cette méthode d'ascension de l'abstrait au concret est la seule scientifiquement correcte parce qu'elle reproduit dans la pensée le processus réel de constitution de l'objet. Mais Marx apporte une dimension critique essentielle. L'abstraction n'est pas seulement un procédé méthodologique : elle est inscrite dans la réalité sociale elle-même. Le capitalisme produit des abstractions réelles. La valeur d'échange abstrait de la diversité concrète des valeurs d'usage : toutes les marchandises deviennent commensurables en tant qu'incarnations de travail abstrait.<ref>Marx, Karl, ''Le Capital. Critique de l'économie politique, Livre I'', section I, chapitre 1, trad. fr. J.-P. Lefebvre, Paris, PUF, 1993, p. 39-93.</ref> La monnaie est l'abstraction par excellence, l'équivalent général qui efface toutes les différences qualitatives. Cette abstraction sociale réelle engendre ce que Marx nomme le « fétichisme de la marchandise » : les rapports sociaux entre personnes prennent la forme de rapports entre choses.<ref>Marx, Karl, ''Le Capital'', Livre I, section I, chapitre 1, §4, op. cit., p. 82-93.</ref> Les travailleurs ne se rapportent plus les uns aux autres directement mais à travers les marchandises qu'ils échangent. Cette inversion, où les abstractions (valeur, capital, argent) semblent dotées d'une vie propre tandis que les relations humaines concrètes disparaissent, constitue l'[[Philosophie/Aliénation|aliénation]] fondamentale de la société capitaliste. L'analyse marxienne révèle ainsi que l'abstraction n'est pas un simple outil cognitif neutre mais un processus historiquement situé qui structure les formes de vie sociale. La critique de l'économie politique devient critique des abstractions réelles produites par le mode de production capitaliste. === Phénoménologie : Husserl et l'intuition eidétique === Edmund Husserl (1859-1938) renouvelle la question en opérant une distinction explicite entre deux opérations que la tradition avait souvent confondues. Dans les ''Recherches logiques'' (1900-1901) et ''Idées directrices pour une phénoménologie'' (1913), il oppose l'abstraction au sens classique, l'isolement d'un « moment non-indépendant » d'un tout concret, comme lorsqu'on abstrait la couleur d'une surface étendue dont elle est inséparable, à l'idéation ou ''intuition eidétique'' (''Wesensschau''), saisie directe d'une essence (''Eidos'') qui ne se confond pas avec la généralisation empirique.<ref>Husserl, Edmund, ''Recherches logiques'', tome 2, Recherche II, §§1-10, trad. fr. H. Élie, A. L. Kelkel et R. Schérer, Paris, PUF, 1961, p. 117-144.</ref> La différence méthodologique est essentielle et doit être soulignée contre toute lecture qui assimilerait la phénoménologie à un raffinement de l'abstraction lockéenne. L'abstraction empiriste, telle que l'entendent Locke ou Hume, procède par comparaison inductive entre plusieurs cas particuliers réellement observés ; elle suppose une pluralité d'expériences effectives et aboutit à une représentation générale qui subsume les ressemblances remarquées. L'intuition eidétique husserlienne, au contraire, peut partir d'un unique exemple, réel ou seulement imaginé, et procède par ''variation imaginative libre'' (''freie Variation''). En faisant varier mentalement tous les traits possibles de l'objet considéré, le phénoménologue cherche à identifier ce qui ne peut être supprimé sans que l'objet cesse d'être ce qu'il est : on saisit ainsi son essence comme ''structure d'invariance modale'', comme ce qui doit nécessairement se maintenir pour qu'un tel type d'objet soit possible.<ref>Husserl, Edmund, ''Expérience et jugement. Recherches en vue d'une généalogie de la logique'', §§87-93, trad. fr. D. Souche-Dagues, Paris, PUF, 1970, p. 409-436.</ref> Ce procédé n'est donc pas une simple omission de traits particuliers au sens lockéen. Pour saisir l'essence du triangle, on n'additionne pas des caractères communs observés empiriquement ; on explore la variation libre des triangles possibles, équilatéraux, isocèles, scalènes, aussi déformés que l'on voudra, pour voir ce qui demeure nécessairement là pour que l'on ait encore affaire à un triangle. La faillibilité de l'abstraction empirique, toujours vulnérable au contre-exemple non encore rencontré, cède la place à une saisie des structures modales de l'objet, réputée porter sur des nécessités eidétiques.<ref>Husserl, Edmund, ''Idées directrices pour une phénoménologie et une philosophie phénoménologique pures. Livre premier'', §§4, 69-70, trad. fr. P. Ricœur, Paris, Gallimard, 1950, p. 22-26, 228-236.</ref> Les essences husserliennes ne sont dès lors ni des universaux platoniciens séparés dans un monde intelligible, ni de simples concepts psychologiques, ni de pures abréviations nominalistes. Elles sont des structures idéales appréhendées dans l'acte même de connaissance, corrélats noématiques d'un type particulier d'intentionnalité. La ''réduction eidétique'', distincte de la réduction transcendantale, met entre parenthèses (''epoché'') l'existence factuelle de l'objet pour se concentrer sur son essence pure. Elle permet de constituer des sciences eidétiques a priori qui fondent les sciences empiriques : la géométrie pure fonde la physique géométrique, l'ontologie formelle fonde toute théorie déductive. La prétention phénoménologique à livrer des nécessités essentielles par la variation imaginative a toutefois été contestée. Theodor Adorno, dans sa ''Métacritique de la théorie de la connaissance'' (1956), soutient que la libre variation suppose toujours un horizon de variantes historiquement et culturellement circonscrit, et que l'« essence » ainsi obtenue peut n'être que la cristallisation des préjugés de celui qui varie.<ref>Adorno, Theodor W., ''Sur la métacritique de la théorie de la connaissance. Études sur Husserl et les antinomies phénoménologiques'', trad. fr. C. David, Paris, Payot, 1976, p. 190-225.</ref> Des objections analogues ont été formulées, depuis des perspectives pourtant très différentes, par les tenants d'une philosophie naturaliste de l'esprit, pour qui aucune intuition, fût-elle eidétique, n'est à l'abri de déterminations empiriques et conceptuelles préalables. Ces critiques n'invalident pas la distinction husserlienne entre abstraction empiriste et intuition eidétique, mais elles interrogent la portée ontologique des essences ainsi obtenues. == Théories contemporaines == === Frege et les principes d'abstraction === Gottlob Frege (1848-1925) introduit une approche logique rigoureuse de l'abstraction dans les ''Fondements de l'arithmétique'' (1884) et les ''Lois fondamentales de l'arithmétique'' (1893-1903).<ref>Frege, Gottlob, ''Les Fondements de l'arithmétique'', §§62-69, trad. fr. C. Imbert, Paris, Seuil, 1969, p. 179-198.</ref> Frege formule des principes d'abstraction de la forme : « l'abstrait de a = l'abstrait de b si et seulement si a et b sont dans la relation R », où R est une relation d'équivalence. Le principe qu'il baptise d'après Hume, par exemple, énonce que « le nombre des F = le nombre des G si et seulement si les F et les G peuvent être mis en correspondance bi-univoque ».<ref>Frege, Gottlob, ''Les Fondements de l'arithmétique'', §63, op. cit., p. 180-182.</ref> Ce principe définit implicitement les nombres cardinaux en spécifiant leurs conditions d'identité à partir d'une relation (l'équinumérosité) qui ne les présuppose pas. L'intérêt philosophique de cette approche est qu'elle permet d'introduire des objets abstraits (les nombres) non comme des entités mystérieuses préexistantes mais comme corrélats d'une opération logique sur des concepts. L'abstraction frégéenne transforme ainsi une relation d'équivalence entre concepts en une relation d'identité entre objets, en quoi elle fournit une réponse nouvelle au problème empiriste : les objets abstraits n'ont pas à être extraits du sensible, ils sont introduits par leurs conditions d'identité logiques. Frege lui-même rencontre cependant une difficulté interne, désignée depuis dans la littérature comme le « problème de César » (''Caesar problem''). Le principe d'abstraction fixe les conditions d'identité entre deux nombres, mais ne détermine pas celles qui permettraient de trancher une identité mixte comme « Jules César est-il ou non identique au nombre 7 ? ».<ref>Frege, Gottlob, ''Les Fondements de l'arithmétique'', §§56, 66, op. cit., p. 164-166, 190-192.</ref> Frege juge cette lacune rédhibitoire : si la définition implicite laisse une question d'identité indéterminée, elle ne suffit pas à fixer la référence des termes en cause. C'est l'une des raisons pour lesquelles il rejette le principe de Hume comme définition autosuffisante et entreprend, dans les ''Lois fondamentales'', de remplacer les principes d'abstraction par des définitions explicites des nombres comme extensions de concepts. Le système ainsi obtenu s'avère pourtant inconsistant. La Loi fondamentale V, qui associe à chaque concept son extension et généralise de la sorte les principes d'abstraction, conduit à la contradiction mise en évidence par Bertrand Russell en 1902 : l'extension du concept « ensemble qui ne se contient pas lui-même » engendre un paradoxe logique.<ref>Russell, Bertrand, lettre à Frege du 16 juin 1902, in Jean van Heijenoort (dir.), ''From Frege to Gödel. A Source Book in Mathematical Logic, 1879-1931'', Cambridge, Harvard University Press, 1967, p. 124-125.</ref> L'échec de la Loi V met en lumière un problème général : tous les principes d'abstraction ne se valent pas, et certains, apparemment naturels, engendrent des contradictions logiques. Le néo-logicisme contemporain, développé notamment par Crispin Wright et Bob Hale depuis les années 1980, reprend le programme frégéen en cherchant des principes d'abstraction à la fois consistants et suffisamment puissants pour fonder les mathématiques.<ref>Wright, Crispin, ''Frege's Conception of Numbers as Objects'', Aberdeen, Aberdeen University Press, 1983, p. 106-180.</ref> L'observation-clé du programme est que le principe de Hume, contrairement à la Loi V, s'avère consistant relativement à l'arithmétique classique et permet de dériver l'arithmétique du second ordre (axiomes de Dedekind-Peano) à partir de la seule logique du second ordre augmentée de ce principe : c'est ce que la littérature appelle le « théorème de Frege », redécouvert par George Boolos.<ref>Boolos, George, "The Consistency of Frege's ''Foundations of Arithmetic''", in ''Logic, Logic, and Logic'', Cambridge, Harvard University Press, 1998, p. 183-201 ; Hale, Bob et Wright, Crispin, ''The Reason's Proper Study. Essays towards a Neo-Fregean Philosophy of Mathematics'', Oxford, Clarendon Press, 2001, p. 1-39.</ref> Le néo-logicisme affronte cependant deux difficultés structurelles qui alimentent des débats analytiques nourris et n'ont pas reçu de solution consensuelle. La première, dite ''bad company problem'' (« problème de la mauvaise compagnie »), peut se formuler ainsi : si la Loi V est inconsistante alors que le principe de Hume est consistant, comment justifier l'acceptation du second et le rejet de la première sur des bases non arbitraires ?<ref>Linnebo, Øystein, "Bad Company Tamed", ''Synthese'', vol. 170, n° 3, 2009, p. 371-391 ; Weir, Alan, "Neo-Fregeanism: An Embarrassment of Riches", ''Notre Dame Journal of Formal Logic'', vol. 44, n° 1, 2003, p. 13-48.</ref> Plusieurs principes d'abstraction peuvent être pris individuellement consistants mais se révéler collectivement incompatibles ; d'autres, comme le « principe de parité » (qui associe le même objet aux concepts dont les extensions diffèrent d'un cardinal pair), sont consistants mais paraissent intuitivement illégitimes car ils engendrent des objets sans motivation conceptuelle claire. Divers critères de légitimité ont été proposés, conservativité, stabilité, irénisme, invariance, sans qu'aucun ne fasse accord. La seconde difficulté est la réapparition du problème de César dans le cadre néo-frégéen lui-même : le principe de Hume ne détermine toujours pas si Jules César est ou non identique au nombre trois, ce qui semble indiquer que les conditions d'identité fournies par les principes d'abstraction sont insuffisantes pour fixer pleinement la référence des termes abstraits.<ref>Hale, Bob, "Grundlagen §64", in ''Philosophical Papers'', Oxford, Clarendon Press, 2001, p. 203-237 ; Rosen, Gideon, "The Refutation of Nominalism (?)", ''Philosophical Topics'', vol. 21, n° 2, 1993, p. 149-186.</ref> Ces discussions engagent la possibilité même de définir les objets abstraits par des équivalences conceptuelles. Elles prolongent la question frégéenne initiale, à quelles conditions un principe d'abstraction peut-il être légitimement posé et quelle est la nature des objets qu'il introduit ?, en montrant que la réponse logique, si séduisante soit-elle, ne dissipe pas la perplexité métaphysique qui entoure les entités abstraites. Le débat contemporain sur ces principes constitue aujourd'hui l'un des laboratoires les plus actifs de la philosophie des mathématiques, à la croisée de la logique formelle, de la métaphysique analytique et de l'épistémologie. === Débat contemporain : nominalisme contre platonisme === La philosophie contemporaine est traversée par un débat fondamental sur le statut des entités abstraites. Les nominalistes affirment que seuls les objets concrets, localisés dans l'espace et le temps, existent réellement.<ref>Field, Hartry, ''Science without Numbers'', Oxford, Blackwell, 1980, p. 1-43.</ref> Les universaux, les nombres, les ensembles, les propositions ne sont que des façons de parler, des instruments utiles mais dépourvus de réalité ontologique propre. Les arguments nominalistes font valoir le principe de parcimonie ontologique (le rasoir d'Ockham) : il ne faut pas multiplier les entités sans nécessité. Les entités abstraites posent aussi un problème épistémologique : comment pourrions-nous connaître des objets qui n'ont aucune relation causale avec nous, qui n'agissent pas dans l'espace et le temps ?<ref>Benacerraf, Paul, "Mathematical Truth", ''Journal of Philosophy'', vol. 70, n° 19, 1973, p. 661-679.</ref> Les platonistes ou réalistes répondent que les entités abstraites sont indispensables à la science et aux mathématiques. Les théories scientifiques quantifient sur des ensembles, des fonctions, des espaces abstraits. Si nous devons prendre nos meilleures théories scientifiques au sérieux, alors nous devons accepter l'existence de ce sur quoi elles quantifient (argument d'indispensabilité de Quine et Putnam).<ref>Quine, W. V. O., "On What There Is", in ''From a Logical Point of View'', Cambridge, Harvard University Press, 1953, p. 1-19.</ref> Le débat se poursuit sur plusieurs fronts. Certains nominalistes tentent de montrer qu'on peut reformuler les théories scientifiques sans référence aux entités mathématiques abstraites. D'autres, comme les fictionnalistes, admettent l'utilité des mathématiques tout en niant que les énoncés mathématiques soient littéralement vrais. Les structuralistes, de leur côté, proposent que les mathématiques ne portent pas sur des objets abstraits individuels mais sur des structures relationnelles.<ref>Shapiro, Stewart, ''Philosophy of Mathematics: Structure and Ontology'', Oxford, Oxford University Press, 1997, p. 72-104.</ref> Cette controverse touche aux fondements mêmes de notre compréhension du langage, de la [[Dictionnaire de philosophie/Vérité|vérité]] et de la réalité. Elle montre que la question de l'abstraction n'est pas un problème technique isolé mais engage toute une vision du monde. === Dimensions cognitives et épistémologiques === La psychologie cognitive et les neurosciences contemporaines ont renouvelé l'étude empirique des processus d'abstraction. Ces recherches montrent que l'abstraction n'est pas un processus unitaire mais implique différents mécanismes cognitifs : catégorisation perceptuelle, généralisation conceptuelle, représentation schématique, raisonnement analogique.<ref>Murphy, Gregory L., ''The Big Book of Concepts'', Cambridge, MIT Press, 2002, p. 11-43.</ref> Les travaux sur les concepts montrent que la formation de représentations abstraites commence très tôt dans le développement cognitif. Dès les premiers mois, les nourrissons manifestent des capacités de catégorisation qui leur permettent de regrouper des objets selon leurs propriétés communes. Ces compétences précoces suggèrent que certaines formes d'abstraction pourraient être innées ou du moins contraintes par l'architecture cognitive.<ref>Mandler, Jean M., "How to Build a Baby: II. Conceptual Primitives", ''Psychological Review'', vol. 99, n° 4, 1992, p. 587-604.</ref> D'un point de vue [[Dictionnaire de philosophie/Épistémologie|épistémologique]], les philosophes débattent du statut des concepts abstraits : sont-ils des entités mentales privées ou des structures publiques partagées ? Comment les concepts se rapportent-ils aux propriétés des objets ? Quelle est la relation entre les concepts scientifiques théoriques (comme « quark » ou « sélection naturelle ») et l'expérience ordinaire ? Certains philosophes, comme Wilfrid Sellars, défendent un nominalisme psychologique selon lequel toute conscience de similitudes, de types et de structures est médiatisée par le langage et les pratiques sociales.<ref>Sellars, Wilfrid, "Empiricism and the Philosophy of Mind", in ''Science, Perception and Reality'', Atascadero, Ridgeview, 1963, p. 127-196.</ref> Cette perspective souligne la dimension sociale et historique de la formation des concepts abstraits, rejoignant ainsi certaines intuitions de la tradition marxiste sur les abstractions réelles. == Enjeux philosophiques == L'abstraction soulève des questions qui touchent au cœur de la philosophie. Premièrement, elle interroge la nature de l'universel et son rapport au particulier. Les universaux ont-ils une existence indépendante des particuliers qui les instancient, ou ne sont-ils que des produits de l'activité classificatoire de l'esprit ? Cette question engage toute une métaphysique. Deuxièmement, l'abstraction pose le problème de la référence : comment nos concepts abstraits se rapportent-ils au monde ? Quelle garantie avons-nous que nos catégories mentales correspondent à de véritables articulations du réel et ne sont pas de simples projections anthropomorphiques ? Cette interrogation est centrale pour l'épistémologie et la philosophie des sciences. Troisièmement, comme l'a montré [[Dictionnaire de philosophie/Karl Marx|Marx]], l'abstraction n'est pas seulement une opération cognitive mais aussi un processus social et historique. Les formes d'abstraction dominantes dans une société reflètent et reproduisent les rapports sociaux de production. Ainsi, l'abstraction marchande qui réduit tous les produits du travail à des quantités de valeur échangeable correspond à la structure du mode de production capitaliste. Cette dimension sociale de l'abstraction révèle son caractère idéologique potentiel. Quatrièmement, l'abstraction soulève une question normative : quel est le bon niveau d'abstraction pour comprendre un phénomène ? Une abstraction excessive conduit à l'appauvrissement et à la perte du réel, comme le dénonce Hegel. Mais une absence d'abstraction empêche toute compréhension théorique. La science doit constamment négocier entre ces deux écueils, cherchant les abstractions fécondes qui révèlent l'essentiel tout en restant suffisamment riches pour rendre compte de la complexité du donné. Cinquièmement, l'abstraction a une dimension politique. Les catégories abstraites par lesquelles nous pensons le monde social, classe, race, genre, nation, [[Dictionnaire de philosophie/Individu|individu]], ne sont jamais neutres. Elles structurent notre perception de la réalité et orientent notre action. C'est pourquoi la critique des abstractions dominantes constitue un moment essentiel de toute pensée émancipatrice. == Notes et références == {{Références|colonnes = 2}} == Bibliographie == === Textes classiques === * Aristote, ''De l'âme'', trad. fr. R. Bodéüs, Paris, Flammarion, 1993 * Aristote, ''Métaphysique'', trad. fr. M.-P. Duminil et A. Jaulin, Paris, Flammarion, 2008 * Berkeley, George, ''Traité des principes de la connaissance humaine'', trad. fr. D. Berlioz, Paris, Flammarion, 1991 * Descartes, René, ''Méditations métaphysiques'', in ''Œuvres de Descartes'', éd. C. Adam et P. Tannery, Paris, Vrin, 1996, vol. VII * Descartes, René, ''Principes de la philosophie'', trad. fr. D. Moreau, Paris, Vrin, 2009 * Frege, Gottlob, ''Les Fondements de l'arithmétique'', trad. fr. C. Imbert, Paris, Seuil, 1969 * Guillaume d'Ockham, ''Summa logicae'', dans ''Philosophical Writings'', éd. et trad. P. Boehner, Indianapolis, Hackett, 1990 * Hegel, Georg Wilhelm Friedrich, ''Science de la logique'', trad. fr. P.-J. Labarrière et G. Jarczyk, Paris, Aubier, 1972-1981, 3 vol. * Hegel, Georg Wilhelm Friedrich, ''Encyclopédie des sciences philosophiques'', I, « La Science de la Logique », trad. fr. B. Bourgeois, Paris, Vrin, 1970 * Hegel, Georg Wilhelm Friedrich, « Qui pense abstraitement ? », in ''Écrits sur la critique'', trad. fr. M. Jacob, Paris, Aubier-Montaigne, 1978 * Hume, David, ''Traité de la nature humaine. Livre I : De l'entendement'', trad. fr. P. Baranger et P. Saltel, Paris, GF-Flammarion, 1995 * Hume, David, ''Enquête sur l'entendement humain'', trad. fr. A. Leroy revue par M. Beyssade, Paris, GF-Flammarion, 2008 * Husserl, Edmund, ''Recherches logiques'', tome 2, trad. fr. H. Élie, A. L. Kelkel et R. Schérer, Paris, PUF, 1961 * Husserl, Edmund, ''Idées directrices pour une phénoménologie et une philosophie phénoménologique pures. Livre premier'', trad. fr. P. Ricœur, Paris, Gallimard, 1950 * Husserl, Edmund, ''Expérience et jugement. Recherches en vue d'une généalogie de la logique'', trad. fr. D. Souche-Dagues, Paris, PUF, 1970 * Kant, Emmanuel, ''Critique de la raison pure'', trad. fr. A. Tremesaygues et B. Pacaud, Paris, PUF, 2012 [1781/1787] * Kant, Emmanuel, ''Prolégomènes à toute métaphysique future qui pourra se présenter comme science'', trad. fr. L. Guillermit, Paris, Vrin, 2001 * Locke, John, ''Essai sur l'entendement humain'', trad. fr. J.-M. Vienne, Paris, Vrin, 2001 * Marx, Karl, ''Grundrisse. Fondements de la critique de l'économie politique'', trad. fr. J.-P. Lefebvre, Paris, Éditions sociales, 2011 * Marx, Karl, ''Le Capital. Critique de l'économie politique, Livre I'', trad. fr. J.-P. Lefebvre, Paris, PUF, 1993 * Platon, ''République'', trad. fr. G. Leroux, Paris, Flammarion, 2002 * Platon, ''Phédon'', trad. fr. M. Dixsaut, Paris, Flammarion, 1991 * Porphyre, ''Isagoge'', trad. fr. A. de Libera et A.-P. Segonds, Paris, Vrin, 1998 * Thomas d'Aquin, ''Somme théologique'', trad. fr. A.-M. Roguet, Paris, Cerf, 1984-1986, 4 vol. * Thomas d'Aquin, ''De ente et essentia'', in ''L'Être et l'essence'', trad. fr. C. Capelle, Paris, Vrin, 1985 === Études contemporaines === * Adorno, Theodor W., ''Sur la métacritique de la théorie de la connaissance. Études sur Husserl et les antinomies phénoménologiques'', trad. fr. C. David, Paris, Payot, 1976 * Armstrong, David M., ''Universals. An Opinionated Introduction'', Boulder, Westview Press, 1989 * Bäck, Allan, "Aristotle's Abstract Ontology", ''Proceedings of the Society for Ancient Greek Philosophy'', 2008 * Benacerraf, Paul, "Mathematical Truth", ''Journal of Philosophy'', vol. 70, n° 19, 1973, p. 661-679 * Boolos, George, ''Logic, Logic, and Logic'', Cambridge, Harvard University Press, 1998 * Burgess, John P. et Rosen, Gideon, ''A Subject with No Object: Strategies for Nominalistic Interpretation of Mathematics'', Oxford, Clarendon Press, 1997 * Cassirer, Ernst, ''La Philosophie des formes symboliques'', trad. fr. O. Hansen-Løve et J. Lacoste, Paris, Minuit, 1972, 3 vol. * Cocchiarella, Nino B., ''Conceptual Realism as a Formal Ontology'', dans Roberto Poli et Peter Simons (dir.), ''Formal Ontology'', Dordrecht, Kluwer, 1996, p. 27-60 * Dummett, Michael, ''Frege: Philosophy of Mathematics'', Cambridge, Harvard University Press, 1991 * Field, Hartry, ''Science without Numbers'', Oxford, Blackwell, 1980 * Fine, Kit, "The Question of Ontology", dans David J. Chalmers, David Manley et Ryan Wasserman (dir.), ''Metametaphysics'', Oxford, Oxford University Press, 2009, p. 157-177 * Goodman, Nelson, ''The Structure of Appearance'', Cambridge, Harvard University Press, 1951 * Gordon, Liran, "Reconstructing Aquinas's Process of Abstraction", ''Revista Española de Filosofía Medieval'', vol. 25, 2018, p. 41-67 * Hale, Bob, ''Abstract Objects'', Oxford, Blackwell, 1987 * Hale, Bob, ''Philosophical Papers'', Oxford, Clarendon Press, 2001 * Hale, Bob et Wright, Crispin, ''The Reason's Proper Study. Essays towards a Neo-Fregean Philosophy of Mathematics'', Oxford, Clarendon Press, 2001 * Horsten, Leon et Leitgeb, Hannes, "How Abstraction Works", ''Philosophia Mathematica'', vol. 17, n° 3, 2009, p. 334-358 * Ilyenkov, Evald, "The Dialectics of the Abstract and the Concrete in Marx's Capital", Moscou, Progress Publishers, 1982 * Klima, Gyula, "The Medieval Problem of Universals", ''Stanford Encyclopedia of Philosophy'', Edward N. Zalta (dir.), 2008 (version en ligne) * Kriegel, Uriah, "Nominalism and Material Plenitude", ''Res Philosophica'', vol. 98, 2021, p. 89-112 * Linnebo, Øystein, "Bad Company Tamed", ''Synthese'', vol. 170, n° 3, 2009, p. 371-391 * Linnebo, Øystein, ''Philosophy of Mathematics'', Princeton, Princeton University Press, 2017 * Linsky, Bernard et Zalta, Edward N., "In Defense of the Simplest Quantified Modal Logic", ''Philosophical Perspectives'', vol. 8, 1994, p. 431-458 * Lowe, E. J., ''The Possibility of Metaphysics: Substance, Identity, and Time'', Oxford, Clarendon Press, 1998 * Mandler, Jean M., "How to Build a Baby: II. Conceptual Primitives", ''Psychological Review'', vol. 99, n° 4, 1992, p. 587-604 * Martínez, Sergio F. et Huang, Xiang, "Epistemic Groundings of Abstraction and Their Cognitive Dimension", ''Philosophy of Science'', vol. 78, n° 3, 2011, p. 490-511 * Murphy, Gregory L., ''The Big Book of Concepts'', Cambridge, MIT Press, 2002 * Quine, W. V. O., "On What There Is", in ''From a Logical Point of View'', Cambridge, Harvard University Press, 1953, p. 1-19 * Rodriguez-Pereyra, Gonzalo, ''Resemblance Nominalism: A Solution to the Problem of Universals'', Oxford, Clarendon Press, 2002 * Rosen, Gideon, "The Refutation of Nominalism (?)", ''Philosophical Topics'', vol. 21, n° 2, 1993, p. 149-186 * Rosen, Gideon, "Abstract Objects", ''Stanford Encyclopedia of Philosophy'', Edward N. Zalta (dir.), 2020 (version en ligne) * Russell, Bertrand, lettre à Frege du 16 juin 1902, in Jean van Heijenoort (dir.), ''From Frege to Gödel. A Source Book in Mathematical Logic, 1879-1931'', Cambridge, Harvard University Press, 1967, p. 124-125 * Sellars, Wilfrid, "Empiricism and the Philosophy of Mind", in ''Science, Perception and Reality'', Atascadero, Ridgeview, 1963, p. 127-196 * Shapiro, Stewart, ''Philosophy of Mathematics: Structure and Ontology'', Oxford, Oxford University Press, 1997 * Sohn-Rethel, Alfred, ''Intellectual and Manual Labour: A Critique of Epistemology'', Londres, Macmillan, 1978 * Strevens, Michael, "The Essentialist Aspect of Naive Theories", ''Cognition'', vol. 74, 2000, p. 149-175 * Taylor, C. C. W., "Berkeley's Theory of Abstract Ideas", ''Philosophical Quarterly'', vol. 28, n° 111, 1978, p. 97-115 * Weir, Alan, "Neo-Fregeanism: An Embarrassment of Riches", ''Notre Dame Journal of Formal Logic'', vol. 44, n° 1, 2003, p. 13-48 * Wright, Crispin, ''Frege's Conception of Numbers as Objects'', Aberdeen, Aberdeen University Press, 1983 == Voir aussi == * Universel, général, particulier, singulier * [[Dictionnaire de philosophie/Concept|Concept]] * [[Dictionnaire de philosophie/Attribut|Attribut (propriétés et catégories)]] * [[Dictionnaire de philosophie/Individu|Individu]] * [[Dictionnaire de philosophie/Empirisme|Empirisme]] * [[Dictionnaire de philosophie/A priori|A priori]] * [[Dictionnaire de philosophie/Épistémologie|Épistémologie]] * [[Dictionnaire de philosophie/Karl Marx|Karl Marx]] * [[Philosophie/Aliénation|Aliénation]] * [[Dictionnaire de philosophie/Vérité|Vérité]] * Abstrait et concret {{Autocat}} [[Catégorie:Logique]] [[Catégorie:Métaphysique]] fft0ze6ccjqudun7he2fnvnjupuxko6 Dictionnaire de philosophie/Aristote 0 83057 764801 753033 2026-04-24T10:28:10Z ~2026-25114-99 123599 764801 wikitext text/x-wiki {{DicoPhilo|- Aristote -}} == Introduction générale : Aristote, le philosophe universel == === Vie et contexte historique === ==== Origines familiales et contexte macédonien ==== Aristote naquit en 384 av. J.-C. à Stagire<ref>Stagire, l'actuelle Stavro, était une colonie grecque de la Chalcidique de Thrace, située sur la côte septentrionale de la mer Égée, à proximité de la Macédoine. Voir Werner Jaeger, ''Aristote. Fondements pour une histoire de son évolution'', trad. fr. O. Sedeyn, Paris, L'Éclat, 1997 (1923), p. 103-104.</ref>, petite cité de la Chalcidique en Macédoine, d'où son surnom de Stagirite<ref>Ce patronyme sera employé tout au long de l'Antiquité pour le désigner. Cf. Ingemar Düring, ''Aristotle in the Ancient Biographical Tradition'', Göteborg, Almqvist & Wiksell, 1957, p. 253-257.</ref>. Son père, Nicomaque, était médecin et appartenait à la prestigieuse corporation des Asclépiadés, dont les membres prétendaient descendre du dieu de la médecine, Asclépios<ref>Les Asclépiadés formaient une famille sacerdotale de médecins héritiers d'une longue tradition hippocratique. Voir Diogène Laërce, ''Vies et doctrines des philosophes illustres'', V, 1, trad. sous la dir. de M.-O. Goulet-Cazé, Paris, Le Livre de Poche, 1999, p. 573.</ref>. Nicomaque exerçait la fonction de médecin personnel et ami (''philos'') du roi Amyntas III de Macédoine<ref>Cette position à la cour macédonienne conférait à la famille d'Aristote un statut privilégié et des relations influentes avec la maison royale. Voir Jean Brun, ''Aristote et le Lycée'', Paris, PUF, « Que sais-je ? », 1961, p. 7-8.</ref><ref>Diogène Laërce, ''Vies'', V, 1, op. cit., p. 573.</ref>. Sa mère, Phæstias (ou Phaestis), originaire de Chalcis en Eubée, était sage-femme<ref>Certaines sources antiques indiquent qu'elle descendait d'une famille chalcidienne de notables. Voir Ingemar Düring, ''Aristotle in the Ancient Biographical Tradition'', op. cit., p. 259.</ref>. La famille d'Aristote s'inscrivait ainsi dans un milieu cultivé, marqué à la fois par la pratique médicale rationnelle et par des ascendances ioniennes<ref>Ces origines ioniennes expliqueraient en partie son goût pour l'investigation scientifique de la nature (''physis''), caractéristique des premiers Physiciens d'Ionie. Voir Werner Jaeger, ''Aristote'', op. cit., p. 105-106.</ref><ref>Ernest Barker, ''The Politics of Aristotle'', Oxford, Clarendon Press, 1946, Introduction, p. XI.</ref>. Aristote devint orphelin à un âge précoce, perdant son père alors qu'il était encore enfant<ref>Sa mère mourut également jeune, laissant Aristote orphelin de ses deux parents avant ses dix-sept ans. Voir Werner Jaeger, ''Aristote'', op. cit., p. 104.</ref>. Il fut dès lors élevé par Proxène d'Atarnée, un ami de sa famille, qui était originaire d'Atarnée en Mysie<ref>Atarnée était une cité située en Asie Mineure, en Troade. Diogène Laërce, ''Vies'', V, 3, op. cit., p. 574.</ref>. En reconnaissance, Aristote adoptera plus tard Nicanor, le fils de Proxène, et lui destinera même sa propre fille Pythias par son testament<ref>Le testament d'Aristote, conservé par Diogène Laërce (V, 11-16), témoigne de ces liens familiaux durables. Voir l'édition dans Pierre Pellegrin (dir.), ''Aristote. Œuvres complètes'', Paris, Flammarion, 2014, p. 2597-2600.</ref><ref>Ces nouvelles attaches familiales constitueront les premiers rapports qu'Aristote entretiendra avec la région d'Atarnée, où il rencontrera plus tard Hermias, le futur tyran du lieu. Voir Ingemar Düring, ''Aristotle in the Ancient Biographical Tradition'', op. cit., p. 272-277.</ref>. ==== Formation à l'Académie de Platon (367-347 av. J.-C.) ==== À l'âge de dix-sept ans, en 367-366 av. J.-C., Aristote se rend à Athènes, qui demeure alors, malgré son déclin politique et économique après les Guerres du Péloponnèse, le principal foyer intellectuel et culturel du monde grec<ref>Sous l'archontat de Polyzélos (selon Denys d'Halicarnasse) ou de Nausigénès (selon d'autres sources). Apollodore d'Athènes, cité par Diogène Laërce, V, 9, op. cit., p. 577.</ref>. Il entre à l'Académie de Platon, l'école philosophique fondée par Platon en 387 av. J.-C. après son retour de Sicile<ref>L'Académie tirait son nom du gymnase d'Académos, situé dans un faubourg d'Athènes. Voir Harold Cherniss, ''The Riddle of the Early Academy'', Berkeley, University of California Press, 1945, p. 1-72.</ref>. Cette institution, véritable prototype des universités futures, dispensait un enseignement encyclopédique en mathématiques, astronomie, musique, dialectique, politique et philosophie<ref>Léon Robin, ''Aristote'', Paris, PUF, 1944, p. 3.</ref>. Aristote y demeura vingt ans, jusqu'à la mort de Platon en 348-347 av. J.-C.<ref>Apollodore d'Athènes, cité par Diogène Laërce (V, 9-10), fournit cette chronologie précise. Voir Diogène Laërce, ''Vies'', op. cit., p. 577-578.</ref>. Très rapidement, le jeune Aristote se distingua par son intelligence exceptionnelle et son ardeur à l'étude. Platon lui-même l'aurait surnommé le Liseur (''anagnôstês'') ou encore l'Intelligence de l'école (''ho nous tês scholês'')<ref>Ces surnoms témoignent de la reconnaissance par le Maître des dons hors du commun de son élève. Voir Ingemar Düring, ''Aristotle in the Ancient Biographical Tradition'', op. cit., p. 325-327.</ref><ref>''Vita Marciana'', dans Valentin Rose, ''Aristoteles Pseudepigraphus'', Leipzig, Teubner, 1863, p. 428 ; Jean Philopon, dans Proclus, ''De aeternitate mundi'', VI, 27, éd. Hugo Rabe, Leipzig, Teubner, 1899, p. 145.</ref>. Contrairement à une légende tenace, probablement issue des polémiques ultérieures entre épicuriens et péripatéticiens, Aristote ne rompit jamais avec Platon de son vivant<ref>Les récits de Diogène Laërce évoquant une rupture violente sont aujourd'hui considérés comme des médisances sans fondement historique. Voir Werner Jaeger, ''Aristote'', op. cit., p. 113-128.</ref>. Plusieurs éléments confirment au contraire la profonde amitié et le respect mutuel qui les unirent jusqu'à la fin. Aristote composa une élégie en l'honneur de son ami Eudème de Chypre, où il célèbre magnifiquement Platon comme le maître qui ne saurait jamais être loué comme il le mériterait par des hommes méchants<ref>Fragment 623 (Rose), conservé par Olympiodore. Voir Valentin Rose, ''Aristotelis qui ferebantur librorum fragmenta'', Leipzig, Teubner, 1886, p. 42-43.</ref>. Dans l'''Éthique à Nicomaque'', Aristote écrit ces mots célèbres : « Une recherche de ce genre est rendue difficile du fait que ce sont des amis qui ont introduit la doctrine des Idées. Mais on admettra peut-être qu'il est préférable, et c'est aussi pour nous une obligation, si nous voulons du moins sauvegarder la vérité, de sacrifier même nos sentiments personnels, surtout quand on est philosophe : vérité et amitié nous sont chères l'une et l'autre, mais c'est pour nous un devoir sacré d'accorder la préférence à la vérité »<ref>Aristote, ''Éthique à Nicomaque'', I, 4, 1096 a 12-17, trad. J. Tricot, Paris, Vrin, 1959, p. 44.</ref><ref>Cette phrase a été traduite en latin par l'adage ''Amicus Plato, sed magis amica veritas'' : « ami de Platon, mais plus encore ami de la vérité ». Voir Ammonius, ''In Categorias'', éd. Busse, Berlin, Reimer, 1895, p. 79.</ref>. Aristote participa activement à l'enseignement de l'Académie. On lui attribue notamment la charge du cours de rhétorique, qu'il aurait inauguré, selon certains témoignages, par cette formule provocatrice : « Il serait honteux de se taire et de laisser parler Isocrate »<ref>Cette boutade visait Isocrate, le rival de Platon en matière d'enseignement rhétorique. Voir Werner Jaeger, ''Aristote'', op. cit., p. 180-182.</ref><ref>Philodème, ''Volumina rhetorica'', II, 36, 3-5, éd. Siegfried Sudhaus, Leipzig, Teubner, 1892-1896, vol. II, p. 50 ; Cicéron, ''De Oratore'', III, 35, 141 ; Quintilien, ''Institutio oratoria'', III, 1, 14.</ref>. Cette période fut également celle de ses premières publications, sous forme de dialogues à la manière platonicienne, dont la plupart sont aujourd'hui perdus<ref>Cicéron loua plus tard la beauté littéraire de ces dialogues, parlant d'un ''flumen orationis aureum'' : « un fleuve d'or de paroles ». Cicéron, ''Academica priora'', II, 38, 119.</ref>. ==== Voyages et maturation (348-335 av. J.-C.) ==== À la mort de Platon en 347, c'est le neveu du Maître, Speusippe, qui prend la direction de l'Académie. Aristote, qui peut-être avait espéré cette succession, quitte alors Athènes. Il se rend à Atarnée et Assos, en Troade (Asie Mineure), auprès de son ami Hermias, ancien condisciple de l'Académie devenu tyran d'Atarnée<ref>Hermias avait été esclave, puis affranchi, avant de devenir maître d'Atarnée et d'Assos. Voir Ingemar Düring, ''Aristotle in the Ancient Biographical Tradition'', op. cit., p. 272-277.</ref>. Aristote y séjourne pendant trois ans (347-345), accompagné probablement de Xénocrate, autre disciple de Platon<ref>Strabon, ''Géographie'', XIII, 1, 57, trad. F. Lasserre, Paris, Les Belles Lettres, 1981, p. 84-85.</ref>. Il y fonde une sorte de succursale de l'Académie et poursuit ses recherches philosophiques et biologiques<ref>C'est durant ce séjour qu'Aristote aurait entrepris d'importantes observations sur la faune marine de la côte d'Asie Mineure, dont témoignent l'''Histoire des Animaux'' et d'autres traités zoologiques. Voir David M. Balme, « The Place of Biology in Aristotle's Philosophy », dans Allan Gotthelf & James G. Lennox (éd.), ''Philosophical Issues in Aristotle's Biology'', Cambridge, Cambridge University Press, 1987, p. 9-20.</ref>. Hermias, pris dans les luttes entre la Macédoine et la Perse, fut capturé en 341 av. J.-C. par trahison sur ordre d'Artaxerxès III, le roi de Perse, torturé et finalement exécuté<ref>Malgré les tortures, Hermias refusa de trahir ses liens avec Philippe II de Macédoine. Voir Werner Jaeger, ''Aristote'', op. cit., p. 138-142.</ref><ref>Diodore de Sicile, ''Bibliothèque historique'', XVI, 52, trad. P. Goukowsky, Paris, Les Belles Lettres, 1976, p. 164-165.</ref>. Aristote, profondément affecté par cette mort héroïque, composa un célèbre Hymne à la Vertu en son honneur et fit ériger une statue à Delphes portant une épigramme à sa mémoire<ref>Fragments 624 et 625 (Rose), conservés par Diogène Laërce (V, 7-8) et Athénée (''Deipnosophistes'', XV, 696 a-c). Voir Valentin Rose, ''Aristotelis qui ferebantur librorum fragmenta'', op. cit., p. 43-44.</ref>. Il épousa également Pythias, nièce ou fille adoptive d'Hermias, dont il aura une fille du même nom<ref>Dans son testament, Aristote prescrit que ses cendres soient mêlées à celles de Pythias, témoignant de l'attachement profond qu'il lui porta. Diogène Laërce, ''Vies'', V, 16, op. cit., p. 582.</ref>. Après la mort d'Hermias, Aristote quitte Assos pour se rendre à Mytilène, sur l'île de Lesbos, en 345-344 av. J.-C. Il y poursuit pendant deux à trois ans ses observations biologiques et zoologiques, notamment sur les lagunes de Pyrrha<ref>De nombreuses observations précises de l'''Histoire des Animaux'' se rapportent à la faune de Lesbos. Voir Pierre Louis, « Introduction », dans Aristote, ''Histoire des Animaux'', tome I, Paris, Les Belles Lettres, 1964, p. XIV-XVIII.</ref>. ==== Précepteur d'Alexandre le Grand (343-336 av. J.-C.) ==== En 343-342 av. J.-C., Philippe II de Macédoine, qui avait connu Aristote dans sa jeunesse grâce à Nicomaque, appelle le philosophe à la cour de Pella pour devenir le précepteur de son fils, le jeune prince Alexandre, alors âgé de treize ans<ref>Le choix d'Aristote par Philippe s'explique à la fois par leurs relations antérieures et par la réputation encyclopédique qu'avait acquise le philosophe. Voir Werner Jaeger, ''Aristote'', op. cit., p. 145-148.</ref><ref>Plutarque, ''Vie d'Alexandre'', 7-8, trad. R. Flacelière & É. Chambry, Paris, Les Belles Lettres, 1975, p. 33-34.</ref>. Aristote enseigne au prince pendant deux ou trois ans, probablement à Miéza, ville où Philippe avait installé son fils<ref>Miéza était située à l'ouest de Pella, dans un environnement champêtre propice aux études. Voir Jean Brun, ''Aristote et le Lycée'', op. cit., p. 15-16.</ref>. L'enseignement donné à Alexandre portait essentiellement sur la politique, l'éthique, la poésie et les lettres grecques<ref>Aristote aurait fait connaître à Alexandre une édition annotée de l'''Iliade'' d'Homère, que le conquérant emportera dans toutes ses expéditions. Plutarque, ''Vie d'Alexandre'', 8, op. cit., p. 34.</ref><ref>Plutarque rapporte qu'Alexandre disait aimer Aristote autant que son père car, disait-il, « si je dois la vie à l'un, je dois à l'autre de savoir bien vivre ». Plutarque, ''Vie d'Alexandre'', 8, op. cit., p. 34.</ref>. Aristote composa sans doute à cette époque un traité ''Sur la Royauté'' (''Peri basileias''), aujourd'hui perdu, pour l'édification du jeune prince<ref>Ce traité devait exposer la conception aristotélicienne du bon gouvernement monarchique. Voir Paul Moraux, ''Les listes anciennes des ouvrages d'Aristote'', Louvain, Éditions universitaires, 1951, p. 157-158.</ref>. Toutefois, si les relations entre le maître et l'élève furent marquées par une affection réciproque, Alexandre ne suivit que fort peu les conseils de son précepteur. Alors qu'Aristote prêchait la modération et la distinction entre Grecs et Barbares<ref>Aristote considérait les Barbares comme naturellement faits pour être gouvernés par les Grecs. Aristote, ''Politique'', I, 2, 1252 b 5-9, trad. J. Aubonnet, Paris, Les Belles Lettres, 1960, p. 10-11.</ref>, Alexandre adopta une politique d'intégration des peuples conquis et de fusion entre Grecs et Orientaux, allant jusqu'à adopter certains usages perses et prétendre à une origine divine<ref>Ces orientations politiques d'Alexandre furent en contradiction complète avec la pensée politique d'Aristote. Voir Werner Jaeger, ''Aristote'', op. cit., p. 148-152.</ref>. En 335 av. J.-C., lorsque Alexandre part pour la conquête de l'Asie, Aristote propose son neveu Callisthène pour l'accompagner comme conseiller et historiographe<ref>Callisthène, fils de Damastos et neveu d'Aristote, avait collaboré avec le philosophe pour l'établissement de la liste des vainqueurs aux Jeux Pythiques de Delphes. Voir Felix Jacoby, ''Die Fragmente der griechischen Historiker'', Berlin, Weidmann, 1923-1958, 124 F 1.</ref>. Mais Callisthène, refusant d'adopter la proskynèse (prosternation devant le roi) exigée par Alexandre, encourut sa disgrâce et fut exécuté vers 327 av. J.-C., probablement sous l'accusation d'avoir participé à une conjuration<ref>Diogène Laërce (V, 5) et Plutarque (''Vie d'Alexandre'', 55) rapportent cette fin tragique. Voir Diogène Laërce, ''Vies'', op. cit., p. 575-576 ; Plutarque, ''Vie d'Alexandre'', op. cit., p. 81-83.</ref>. ==== Fondation du Lycée et enseignement à Athènes (335-323 av. J.-C.) ==== En 335-334 av. J.-C., après la défaite définitive d'Athènes face à la Macédoine à Chéronée (338 av. J.-C.) et l'affermissement de l'hégémonie macédonienne sur la Grèce, Aristote retourne à Athènes. Il y fonde sa propre école, le Lycée (''Lykeion''), ainsi nommé parce que situé près du temple d'Apollon Lycien<ref>Le Lycée était un gymnase entouré de jardins et de promenoirs couverts (''peripatoi''), d'où le nom d'« école péripatéticienne » donné aux disciples d'Aristote. Voir Jean Brun, ''Aristote et le Lycée'', op. cit., p. 21-24.</ref><ref>Le site archéologique du Lycée fut découvert fortuitement en 1996 lors de travaux au centre d'Athènes et ouvert au public en 2014. Voir Effi Lygouri, « The Lyceum of Aristotle in Athens », dans ''Acta Musei Nationalis Pragae. Series A – Historia'', 60, 1-2, 2006, p. 47-52.</ref>. Aristote y enseigne pendant douze ou treize ans, de 335 à 323 av. J.-C., période d'intense activité intellectuelle, où il poursuit ses cours, dirige des recherches collectives et rédige la plupart de ses traités systématiques (''pragmateiai'') qui nous sont parvenus<ref>Ces traités, dits « acroamatiques » ou « ésotériques », étaient destinés à l'enseignement interne, par opposition aux dialogues « exotériques » écrits pour un large public. Voir Paul Moraux, ''Les listes anciennes des ouvrages d'Aristote'', op. cit., p. 3-85.</ref>. Son enseignement se déployait, selon les témoignages antiques, selon deux modalités : le matin, il dispensait des leçons approfondies sur la philosophie, la physique et la métaphysique, réservées aux initiés (enseignement acroamatique ou ésotérique) ; l'après-midi, il donnait des cours plus accessibles de rhétorique et de dialectique, ouverts à un public plus large (enseignement exotérique)<ref>Aulu-Gelle, ''Nuits attiques'', XX, 5, trad. R. Marache, Paris, Les Belles Lettres, 1978, tome IV, p. 185-186.</ref>. Aristote professait en se promenant (''peripatein'') avec ses disciples dans les allées du gymnase, d'où le nom d'école péripatéticienne donné à son école<ref>Diogène Laërce, ''Vies'', V, 2, op. cit., p. 574.</ref>. Il constitua au Lycée une vaste bibliothèque, la première grande bibliothèque privée du monde grec, et des collections d'histoire naturelle (animaux, plantes, minéraux, cartes géographiques)<ref>Alexandre aurait aidé financièrement son ancien maître en lui envoyant d'Asie des spécimens zoologiques et botaniques. Pline l'Ancien, ''Histoire naturelle'', VIII, 17, trad. A. Ernout, Paris, Les Belles Lettres, 1952, p. 23.</ref>. Il entreprit également avec ses collaborateurs la rédaction monumentale des 158 Constitutions (''Politeiai'') des cités grecques et barbares, dont seule la ''Constitution d'Athènes'' nous est parvenue<ref>Cette œuvre colossale de recherche historique et politique fut probablement un travail collectif sous la direction d'Aristote. Voir Mortimer Chambers, « Aristotle's Forms of Democracy », dans ''Transactions and Proceedings of the American Philological Association'', 92, 1961, p. 20-36.</ref>. ==== Exil et mort (323-322 av. J.-C.) ==== À la mort d'Alexandre le Grand, survenue à Babylone en juin 323 av. J.-C., une violente réaction antimacédonienne éclate à Athènes, dirigée par le parti nationaliste de Démosthène et d'Hypéride<ref>Cette réaction avait été préparée pendant la captivité de Démosthène et éclata au grand jour à la nouvelle de la mort d'Alexandre. Voir Werner Jaeger, ''Aristote'', op. cit., p. 387-390.</ref>. Aristote, en tant que métèque (étranger domicilié) proche de la Macédoine, se trouve menacé<ref>En tant que métèque, Aristote n'avait jamais eu le droit de cité athénien ni le droit de participer à la vie publique. Voir Jean Brun, ''Aristote et le Lycée'', op. cit., p. 32-33.</ref>. Un certain Démophile, peut-être prêtre d'Éleusis, l'accuse d'impiété (''asebeia''), lui reprochant d'avoir composé l'Hymne à la Vertu en l'honneur d'Hermias, honneur normalement réservé aux dieux<ref>Cette accusation d'impiété rappelait évidemment le procès de Socrate en 399 av. J.-C. Voir Ingemar Düring, ''Aristotle in the Ancient Biographical Tradition'', op. cit., p. 346-350.</ref><ref>Diogène Laërce, ''Vies'', V, 5-6, op. cit., p. 575-576.</ref>. Sans attendre le jugement qui le condamnerait certainement, Aristote quitte précipitamment Athènes et se réfugie à Chalcis, en Eubée, ville natale de sa mère, où il possédait des biens hérités par elle<ref>En partant, Aristote aurait déclaré, selon Aulu-Gelle, vouloir « empêcher les Athéniens de commettre un second crime contre la philosophie », allusion évidente au procès de Socrate. Aulu-Gelle, ''Nuits attiques'', III, 3, 10, op. cit., tome I, p. 151.</ref>. Il confie la direction du Lycée à Théophraste d'Érèse, son plus fidèle disciple et collaborateur<ref>Théophraste dirigera le Lycée pendant trente-cinq ans, jusqu'en 288-287 av. J.-C., poursuivant l'œuvre encyclopédique de son maître. Voir Diogène Laërce, ''Vies'', V, 36-57, op. cit., p. 590-601.</ref>. Aristote meurt à Chalcis en 322 av. J.-C., à l'âge de soixante-deux ans, très probablement des suites de la maladie d'estomac chronique dont il avait souffert toute sa vie<ref>La légende d'un suicide par absorption de ciguë, rapportée par certaines sources tardives, est rejetée par les historiens modernes. Voir Ingemar Düring, ''Aristotle in the Ancient Biographical Tradition'', op. cit., p. 350-355.</ref><ref>Son testament, conservé intégralement par Diogène Laërce (V, 11-16), témoigne de sa sollicitude envers tous les membres de sa maisonnée, esclaves compris, et prescrit que ses cendres soient mêlées à celles de sa première épouse Pythias. Voir Diogène Laërce, ''Vies'', op. cit., p. 579-582.</ref>. Ses cendres furent inhumées à Stagire, sa ville natale, et les citoyens de Stagire érigèrent en son honneur un monument funéraire (l'''Aristoteleion'') qui devint un lieu de rassemblement civique<ref>Les habitants de Stagire instituèrent également des fêtes annuelles en l'honneur de leur illustre concitoyen. Voir Ingemar Düring, ''Aristotle in the Ancient Biographical Tradition'', op. cit., p. 355-358.</ref>. Aristote laissait deux enfants : Pythias, née de son premier mariage avec Pythias, destinée à épouser Nicanor, le fils adoptif du philosophe, et Nicomaque, né de son second mariage avec Herpyllis d'Assos, qui lui donna également une fille<ref>Nicomaque mourut jeune, mais c'est à lui que fut dédiée l'''Éthique à Nicomaque'', peut-être publiée après sa mort par Nicomaque lui-même ou par un disciple. Voir Jean Brun, ''Aristote et le Lycée'', op. cit., p. 35-36.</ref><ref>Dans son testament, Aristote témoigne d'une grande reconnaissance envers Herpyllis, la mère de Nicomaque, lui assurant des moyens d'existence confortables et la recommandant aux bons soins de ses amis. Diogène Laërce, ''Vies'', V, 12-13, op. cit., p. 580.</ref>. === L'œuvre aristotélicienne : constitution et transmission === La question de la constitution et de la transmission de l'œuvre d'Aristote représente l'un des problèmes les plus complexes de l'histoire de la philosophie antique. Notre accès actuel à la pensée du Stagirite est le résultat d'un long processus historique marqué par des péripéties qui ont profondément affecté la forme sous laquelle nous lisons aujourd'hui ses écrits. ==== La double nature de l'œuvre aristotélicienne ==== L'œuvre d'Aristote se composait à l'origine de deux types d'écrits bien distincts, qui correspondent à deux publics différents et à deux modes d'exposition philosophique. D'une part, les '''écrits exotériques''' (ἐξωτερικοὶ λόγοι), destinés au grand public et composés sous forme de dialogues à la manière platonicienne entre 360 et 345 av. J.-C.<ref>Voir Aristote, ''Œuvres'', éd. Firmin-Didot, Paris, 1854-1869 ; cf. aussi Diogène Laërce, ''Vies et doctrines des philosophes illustres'', V, 22-27</ref>. Ces dialogues de jeunesse, dont Cicéron vantait les qualités littéraires en qualifiant le style d'Aristote de « fleuve d'or » (''flumen aureum'')<ref>Cicéron, ''Academica'', II, 38, 119</ref>, comprenaient notamment le ''Gryllos ou De la rhétorique'' (361 av. J.-C.), l'''Eudème ou De l'âme'' (354 av. J.-C.) sur l'immortalité de l'âme, le ''Protreptique'' (353 av. J.-C.) et le traité ''Sur la philosophie'' (348-345 av. J.-C.)<ref>Fragments rassemblés dans V. Rose, ''Aristotelis qui ferebantur librorum fragmenta'', Leipzig, Teubner, 1886</ref>. Ces œuvres, mieux connues durant l'Antiquité que les traités techniques, ne nous sont malheureusement parvenues que sous forme de fragments épars. D'autre part, les '''écrits acroamatiques''' ou '''ésotériques''' (ἀκροαματικοί, ἐσωτερικοί), réservés aux disciples avancés du Lycée et destinés à un usage interne à l'école<ref>Sur cette distinction, voir Aulu-Gelle, ''Nuits attiques'', XX, 5 ; Plutarque, ''Vie d'Alexandre'', 7</ref>. Ces textes, qui constituent l'essentiel du corpus aristotélicien tel que nous le connaissons aujourd'hui, prenaient probablement la forme de notes de cours, de résumés ou de mémoires de recherche élaborés par Aristote et ses collaborateurs au sein du Lyce. La différence entre ces deux catégories ne portait pas tant sur les questions traitées que sur la forme et les procédés d'argumentation et d'exposition<ref>Simplicius, ''In Physica'', CAG IX, p. 8, 15-23</ref>. ==== Le sort de la bibliothèque d'Aristote ==== L'histoire de la transmission des œuvres d'Aristote est intimement liée au destin de sa bibliothèque personnelle, qui contenait ses propres écrits ainsi que ceux de son école. À sa mort en 322 av. J.-C., Aristote légua sa bibliothèque à son successeur à la tête du Lycée, Théophraste d'Érèse<ref>Diogène Laërce, ''Vies'', V, 51-52</ref>. Selon le récit de Strabon, à la mort de Théophraste vers 288 av. J.-C., la bibliothèque fut léguée à '''Nélée de Scepsis''', fils de Coriscos et disciple d'Aristote et de Théophraste<ref>Strabon, ''Géographie'', XIII, 1, 54 (608-609 C) ; voir aussi Plutarque, ''Sylla'', 26, 1-3</ref>. Nélée, en quittant Athènes pour retourner dans sa cité natale de Scepsis en Troade, emporta avec lui les manuscrits autographes d'Aristote et de Théophraste. Ses descendants, gens peu instruits selon Strabon, auraient caché ces livres dans une cave pour éviter qu'ils ne soient confisqués par les rois Attalides de Pergame, qui cherchaient à enrichir leur célèbre bibliothèque<ref>Strabon, ''Géographie'', XIII, 1, 54 : « Lorsqu'ils vinrent à savoir quelle ardeur mettaient les Attalides, auxquels leur ville obéissait, à assembler des livres pour la bibliothèque de Pergame, ils cachèrent les leurs sous terre, dans une cave, où ils furent gâtés par l'humidité et par les vers »</ref>. Les ouvrages restèrent ainsi enfouis pendant près de deux siècles, dans des conditions qui endommagèrent gravement les manuscrits. Ce n'est qu'au début du Ier siècle av. J.-C. que ces textes furent redécouverts et achetés par '''Apellicon de Téos''', un riche bibliophile athénien<ref>Athénée, ''Deipnosophistes'', V, 214d = Posidonios, FGrHist 87 F 36</ref>. Apellicon, davantage amateur de livres (φιλόβιβλος) que véritable philosophe, entreprit de restaurer les manuscrits endommagés. Cependant, en tentant de combler les lacunes et de reconstituer les passages illisibles, il commit de nombreuses erreurs qui altérèrent le texte original<ref>Strabon, ''Géographie'', XIII, 1, 54 : « Apellicon était un amoureux des livres plutôt qu'un philosophe ; c'est pourquoi, en cherchant à réparer les dégâts, il fit de nouvelles copies en comblant mal les lacunes, et publia les livres pleins d'erreurs »</ref>. ==== Le transfert à Rome et l'édition d'Andronicos ==== En 86 av. J.-C., lors de la prise d'Athènes par les troupes romaines, le général '''Sylla''' s'empara de la bibliothèque d'Apellicon et la fit transporter à Rome<ref>Plutarque, ''Sylla'', 26, 2 ; Strabon, ''Géographie'', XIII, 1, 54</ref>. À Rome, ces manuscrits furent confiés au grammairien '''Tyrannion d'Amisos''', qui en fit des copies et commença un travail préliminaire d'organisation<ref>Strabon, ''Géographie'', XIII, 1, 54 ; Cicéron, ''Lettres à Atticus'', IV, 10, 1</ref>. C'est finalement '''Andronicos de Rhodes''', onzième scholarque du Lycée et philosophe péripatéticien actif vers 60 av. J.-C., qui établit la première édition systématique des œuvres d'Aristote et de Théophraste<ref>Porphyre, ''Vie de Plotin'', 24 : « Andronicos divisa les œuvres d'Aristote et de Théophraste en traités, rassemblant les matériaux connexes au même endroit » ; voir aussi David l'Arménien, ''Prolégomènes'', CAG XVIII, 2, p. 32-33</ref>. Andronicos ne se contenta pas de publier les textes : il les organisa selon un ordre thématique qui n'était probablement pas celui d'Aristote lui-même, en regroupant les traités par affinité de contenu. C'est ainsi que naquit la division traditionnelle du corpus en écrits logiques (réunis plus tard sous le titre d''''Organon'''), physiques, biologiques, métaphysiques, éthiques et politiques<ref>Voir Simplicius, ''In Categorias'', CAG VIII, p. 4, 28-5, 4 ; Alexandre d'Aphrodise, ''In Metaphysica'', CAG I, p. 171, 5-8</ref>. C'est également à Andronicos que l'on doit le titre de '''Métaphysique''' (τὰ μετὰ τὰ φυσικά), qui signifie littéralement « ce qui vient après les [livres] physiques », titre qu'il donna à un ensemble de quatorze livres qu'il plaça après les traités de physique dans son édition<ref>Voir Métaphysique, éd. W.D. Ross, Oxford, Clarendon Press, 1924, vol. I, p. XXXII-XLIII ; bien que le terme apparaisse déjà chez Théophraste, c'est l'édition d'Andronicos qui l'a consacré</ref>. Aristote lui-même n'utilisait jamais ce terme, préférant parler de « philosophie première » (πρώτη φιλοσοφία) ou de « science théologique » (θεολογική)<ref>Aristote, ''Métaphysique'', E (VI), 1, 1026a19-23 ; K (XI), 7, 1064b1-3</ref>. ==== Les conséquences pour notre accès au texte ==== Cette histoire mouvementée a eu des conséquences majeures pour notre connaissance de la pensée d'Aristote. D'abord, la perte quasi-totale des écrits exotériques nous prive d'un pan entier de son œuvre, celui qui était le mieux connu dans l'Antiquité et qui témoignait probablement d'une phase plus platonicienne de sa pensée. Ensuite, l'état fragmentaire et endommagé des manuscrits retrouvés, aggravé par les restaurations maladroites d'Apellicon, a introduit de nombreuses corruptions dans le texte. Enfin, l'organisation systématique imposée par Andronicos, bien qu'elle ait permis la préservation et la diffusion de l'œuvre, ne correspond probablement pas à l'ordre de composition ni à l'organisation originelle voulue par Aristote. Il faut également noter que contrairement à la légende propagée par certaines sources antiques, les œuvres d'Aristote n'étaient pas totalement inconnues entre la mort de Théophraste et leur « redécouverte » au Ier siècle av. J.-C. Des témoignages indiquent que certains textes circulaient et étaient étudiés, notamment à Alexandrie<ref>Athénée, ''Deipnosophistes'', I, 3a-b, affirme que Ptolémée II Philadelphe aurait acheté de Nélée les livres d'Aristote et de Théophraste pour la bibliothèque d'Alexandrie, contredisant ainsi Strabon</ref>. Les philosophes hellénistiques, notamment les stoïciens et les épicuriens, connaissaient et discutaient certaines thèses aristotéliciennes, ce qui suppose un accès au moins partiel à ses écrits<ref>Voir P. Moraux, ''Der Aristotelismus bei den Griechen'', vol. I, Berlin-New York, De Gruyter, 1973, p. 3-31</ref>. Le texte d'Aristote tel que nous le connaissons aujourd'hui est donc le produit d'une longue chaîne de transmission qui a commencé avec l'édition d'Andronicos et s'est poursuivie à travers les commentateurs néoplatoniciens de l'Antiquité tardive (Alexandre d'Aphrodise, Simplicius, Philopon), la tradition arabe médiévale (Al-Fârâbî, Avicenne, Averroès), puis la redécouverte occidentale à partir du XIIIe siècle grâce aux traductions latines<ref>Pour l'histoire de la transmission médiévale, voir F.E. Peters, ''Aristoteles Arabus'', Leiden, Brill, 1968 ; L. Minio-Paluello, ''Opuscula. The Latin Aristotle'', Amsterdam, Hakkert, 1972</ref>. Chaque étape de cette transmission a laissé sa marque sur le texte et son interprétation, faisant de l'œuvre aristotélicienne un palimpseste dont il est parfois difficile de retrouver la strate originelle. === Aristote et Platon : continuité et rupture === La relation intellectuelle entre Aristote et Platon demeure l'une des questions les plus débattues de l'histoire de la philosophie. Deux traditions interprétatives se sont longtemps affrontées. Les commentateurs néoplatoniciens, de Porphyre à Simplicius, insistaient sur l'harmonie fondamentale entre les deux philosophes<ref>Simplicius, ''In Categorias'', CAG VIII, éd. K. Kalbfleisch, Berlin, Reimer, 1907, p. 7, 23-32</ref>. À l'inverse, la tradition occidentale, depuis la Renaissance, a mis l'accent sur leurs divergences profondes, au point que le poète anglais Samuel Taylor Coleridge affirmait : « Tout homme est né aristotélicien ou platonicien »<ref>Samuel Taylor Coleridge, ''Table Talk'', 2 juillet 1830, dans ''The Table Talk and Omniana of Samuel Taylor Coleridge'', Londres, Oxford University Press, 1917, p. 90</ref>. La vérité se situe entre ces deux extrêmes. Si Aristote s'oppose à Platon sur des questions ontologiques fondamentales, il demeure profondément tributaire de l'héritage platonicien et socratique. Comme il l'écrit lui-même dans un passage célèbre de l'''Éthique à Nicomaque'' : « Une recherche de ce genre est rendue difficile du fait que ce sont des amis qui ont introduit la doctrine des Idées. Mais on admettra peut-être qu'il est préférable, et c'est aussi pour nous une obligation, si nous voulons du moins sauvegarder la vérité, de sacrifier même nos sentiments personnels, surtout quand on est philosophe : vérité et amitié nous sont chères l'une et l'autre, mais c'est pour nous un devoir sacré d'accorder la préférence à la vérité »<ref>Aristote, ''Éthique à Nicomaque'', I, 4, 1096a11-17, trad. J. Tricot, Paris, Vrin, 1959, p. 44</ref>. ==== La critique aristotélicienne des Idées platoniciennes ==== ===== Le rejet de la séparation (''chôrismos'') ===== L'un des points de rupture les plus décisifs entre Aristote et Platon concerne le statut ontologique des Idées ou Formes (''eidos''). Pour Platon, les Idées constituent un monde intelligible séparé (''chôristos'') du monde sensible. Elles sont immuables, éternelles, et possèdent une existence autonome et transcendante. Les choses sensibles ne sont que des copies imparfaites qui participent (''methexis'') aux Idées ou les imitent (''mimêsis'')<ref>Platon, ''Phédon'', 74a-75d, 100c-102a ; ''République'', VI, 509d-511e, trad. É. Chambry, Paris, Les Belles Lettres, 1932-1934</ref>. Dans le ''Phédon'', Platon affirme ainsi que « rien d'autre ne rend belle une chose que la présence ou la communion (on ne sait comment elle se fait) avec le Beau en soi »<ref>Platon, ''Phédon'', 100d, trad. M. Dixsaut, Paris, Flammarion, 1991, p. 315</ref>. Aristote refuse catégoriquement cette séparation des Idées. Dans la ''Métaphysique'', il consacre plusieurs chapitres à une critique systématique et parfois virulente de la théorie platonicienne<ref>Aristote, ''Métaphysique'', A, 6, 987a29-988a17 ; A, 9, 990a34-993a10 ; M, 4-5, 1078b7-1080a11 ; N, 2, 1088b35-1090a2</ref>. Ses objections portent sur trois points principaux. Premièrement, l'inutilité explicative des Idées séparées. Aristote soutient que les Idées ne peuvent rendre compte ni de l'existence ni du devenir des choses sensibles. Comme il l'écrit avec ironie : « Dire que les Idées sont des paradigmes et que les autres choses y participent, c'est prononcer des mots vides et faire des métaphores poétiques »<ref>Aristote, ''Métaphysique'', A, 9, 991a20-22, trad. J. Tricot, Paris, Vrin, 1953, t. I, p. 61</ref>. Les Idées platoniciennes, étant immobiles et éternelles, ne peuvent expliquer le mouvement et le changement que nous observons constamment dans le monde sensible. Platon n'a pas identifié la cause efficiente (''archê tês kinêseôs''), c'est-à-dire le principe qui déclenche effectivement le changement<ref>Aristote, ''Métaphysique'', A, 9, 992a24-29</ref>. Deuxièmement, l'argument du « Troisième Homme ». Aristote reprend ici une objection déjà formulée dans le ''Parménide'' de Platon lui-même<ref>Platon, ''Parménide'', 132a-b</ref>. Si l'on postule une Idée séparée de l'Homme pour expliquer ce que plusieurs hommes ont en commun, alors on doit également postuler une troisième Idée pour expliquer ce que l'Idée de l'Homme et les hommes particuliers ont en commun, et ainsi de suite à l'infini<ref>Aristote, ''Métaphysique'', A, 9, 990b15-17 ; Z, 13, 1039a2-3 ; ''De la Sophistique'', 22, 178b36-179a10</ref>. Cette régression infinie montre l'absurdité de poser des Idées séparées comme principes explicatifs. Troisièmement, le dédoublement inutile du monde. Postuler un monde intelligible d'Idées séparées revient à « doubler les difficultés » sans les résoudre<ref>Aristote, ''Métaphysique'', A, 9, 990b1-4</ref>. Au lieu d'expliquer le monde sensible, on crée un second monde qui requiert lui-même une explication. Aristote estime que c'est là une violation du principe d'économie ontologique : « Les Platoniciens font exister autant de substances séparées qu'il y a de choses naturelles, comme si quelqu'un, voulant compter des objets, estimait qu'il ne le pourrait pas quand ils sont en petit nombre, et croyait y parvenir en les multipliant »<ref>Aristote, ''Métaphysique'', A, 9, 990b4-8, trad. J. Tricot, op. cit., p. 60</ref>. ===== L'hylémorphisme aristotélicien ===== Face à la théorie platonicienne de la participation, Aristote propose une solution originale : la théorie hylémorphique. Selon cette conception, les êtres sensibles ne sont pas des copies imparfaites d'Idées transcendantes, mais des composés (''sunolon'') de matière (''hulê'') et de forme (''morphê'' ou ''eidos'')<ref>Aristote, ''Métaphysique'', Z, 3, 1029a1-7 ; H, 1-3, 1042a24-1043b23 ; ''Physique'', II, 1-2, 192b8-194a12</ref>. La forme aristotélicienne n'est pas séparée des choses sensibles : elle est immanente à la matière qu'elle informe. Par exemple, la forme du cheval n'existe pas dans un monde intelligible séparé, mais uniquement dans les chevaux individuels concrets. La forme est ce qui fait qu'une chose est ce qu'elle est, son essence (''to ti ên einai'', littéralement « ce qu'était le fait d'être »)<ref>Aristote, ''Métaphysique'', Z, 4-6, 1029b13-1031b14 ; Z, 7, 1032b1-2</ref>. Comme l'explique Aristote : « Par forme, j'entends l'essence de chaque chose et sa substance première »<ref>Aristote, ''Métaphysique'', Z, 7, 1032b1-2, trad. J. Tricot, op. cit., t. I, p. 372</ref>. Cette conception hylémorphique permet à Aristote de résoudre plusieurs problèmes que posait la théorie platonicienne. D'abord, elle explique le devenir : la génération d'une substance est le passage de la puissance (''dunamis'') à l'acte (''energeia''), l'actualisation d'une forme dans une matière. La matière possède en puissance la forme qui s'actualisera en elle, et ce passage s'effectue grâce à une cause efficiente qui possède déjà cette forme en acte<ref>Aristote, ''Métaphysique'', Θ, 6-8, 1048a25-1050b6 ; ''Physique'', III, 1, 201a10-11</ref>. Ensuite, elle sauvegarde l'individualité des substances premières. Pour Aristote, les véritables réalités sont les individus concrets (''tode ti'', « ceci particulier »), comme « cet homme-ci » ou « ce cheval-ci »<ref>Aristote, ''Catégories'', 5, 2a11-19 ; ''Métaphysique'', Z, 13, 1038b23-1039a3</ref>. La forme n'existe que dans les individus qu'elle informe, et non dans un monde séparé. Enfin, elle unifie forme et finalité. La forme est à la fois cause formelle et cause finale, car elle est ce vers quoi tend le développement naturel de la chose. « La nature, en tant que génération, est un chemin vers la nature » au sens de forme achevée<ref>Aristote, ''Physique'', II, 1, 193b12-13 ; II, 7, 198a24-27</ref>. Dans la génération d'un chêne à partir d'un gland, la forme du chêne est à la fois le principe structurant qui guide le développement et la fin vers laquelle tend ce processus. ==== La critique de la participation et de l'imitation ==== ===== L'ambiguïté conceptuelle de la participation ===== Platon utilise principalement deux concepts pour expliquer la relation entre les choses sensibles et les Idées : la participation (''methexis'') et l'imitation (''mimêsis''). Aristote critique vivement ces notions, les considérant comme métaphoriques et vides de sens explicatif<ref>Aristote, ''Métaphysique'', A, 9, 991a20-22 ; M, 5, 1079b24-26</ref>. La notion de participation pose en effet de redoutables problèmes conceptuels. Platon lui-même les a identifiés dans le ''Parménide'', dialogue où le vieux Parménide soumet le jeune Socrate à un interrogatoire serré sur la théorie des Idées<ref>Platon, ''Parménide'', 130e-134e</ref>. Comment une chose participe-t-elle d'une Idée ? Si l'Idée tout entière est présente en chaque chose qui y participe, alors l'Idée, qui est une, se trouve séparée d'elle-même. Mais si c'est seulement une partie de l'Idée qui est présente en chaque chose, alors l'Idée se divise, ce qui contredit son unité et son indivisibilité<ref>Platon, ''Parménide'', 131a-e</ref>. Aristote estime que Platon n'a jamais résolu ces difficultés et s'est contenté d'images poétiques. « Dire que les Idées sont des paradigmes et que les autres choses y participent, c'est ne rien dire, car participer, qu'est-ce que c'est ? »<ref>Aristote, ''Métaphysique'', A, 9, 991a20-21</ref>. Pour Aristote, la participation n'est qu'un nom différent pour désigner le même problème : « Ce que les Pythagoriciens appelaient "imitation", les Platoniciens l'ont appelé "participation", mais tous se bornent à changer le nom sans expliquer ce que peut bien être cette participation ou cette imitation des Idées »<ref>Aristote, ''Métaphysique'', A, 6, 987b10-13, trad. J. Tricot, op. cit., t. I, p. 49</ref>. ===== La ressemblance par la forme immanente ===== Pour Aristote, la ressemblance entre les individus d'une même espèce ne s'explique pas par la participation à une Idée transcendante, mais par la possession d'une même forme immanente. Cette forme se transmet dans la génération naturelle : « L'homme engendre l'homme » (''anthrôpos anthrôpon genna''), c'est-à-dire qu'un être possédant déjà la forme humaine en acte produit un autre être possédant cette même forme<ref>Aristote, ''Métaphysique'', Z, 7, 1032a25 ; Z, 8, 1033b32-1034a2 ; Λ, 3, 1070a8</ref>. Dans la génération, trois facteurs interviennent : la matière (fournie par la femelle), la forme (transmise par le mâle) et la privation (l'absence initiale de la forme dans la matière)<ref>Aristote, ''Physique'', I, 7, 190b10-191a7 ; ''De la Génération des Animaux'', I, 20-21, 729a9-730a32</ref>. Le géniteur, possédant la forme en acte, la transmet à la matière fournie par la génitrice, qui la possédait seulement en puissance. Ainsi, la ressemblance entre père et fils s'explique par la transmission d'une même forme immanente, sans qu'il soit besoin de recourir à une Idée séparée de l'Homme. La forme n'est donc pas un modèle extérieur que les choses imiteraient, mais un principe immanent de structuration et de développement qui se perpétue de génération en génération. Comme l'écrit Aristote : « La forme et la définition sont identiques dans les choses qui naissent les unes des autres »<ref>Aristote, ''Métaphysique'', Z, 8, 1034a5-8, trad. J. Tricot, op. cit., t. I, p. 385</ref>. ==== Substance, essence et ''ousia'' ==== ===== Les sens multiples de l'''ousia'' ===== Le terme grec ''ousia'', traduit en latin par ''substantia'' ou ''essentia'', possède des significations profondément différentes chez Platon et Aristote, bien que les deux philosophes l'utilisent pour désigner ce qui est véritablement réel. Chez Platon, l’''ousia'' désigne principalement l'Idée, la réalité véritable qui possède une existence stable, immuable et éternelle, par opposition aux choses sensibles qui sont en perpétuel devenir et ne possèdent qu'une existence dérivée et illusoire<ref>Platon, ''Phédon'', 78c-d ; ''République'', V, 477a-478e ; VI, 484b-486d</ref>. Dans le ''Phédon'', Socrate affirme que « chaque chose qui est en soi, l'ousia, est toujours identique à elle-même de la même manière »<ref>Platon, ''Phédon'', 78d, trad. M. Dixsaut, op. cit., p. 293</ref>. L'''ousia'' platonicienne est donc séparée du monde sensible et accessible uniquement par l'intellect pur. Chez Aristote, l'''ousia'' a plusieurs sens, mais désigne avant tout la substance première, c'est-à-dire l'individu concret qui existe réellement et par lui-même : « cet homme-ci », « ce cheval-ci »<ref>Aristote, ''Catégories'', 5, 2a11-19 ; ''Métaphysique'', Z, 3, 1028b33-1029a7</ref>. Dans les ''Catégories'', Aristote définit la substance première comme « ce qui n'est ni affirmé d'un sujet ni dans un sujet, par exemple, l'homme individuel ou le cheval individuel »<ref>Aristote, ''Catégories'', 5, 2a11-13, trad. F. Ildefonse & J. Lallot, Paris, Seuil, 2002, p. 15</ref>. L'''ousia'' aristotélicienne se caractérise par trois traits fondamentaux : # Elle n'est pas dans un sujet : elle ne se dit pas d'autre chose mais existe par elle-même # Elle est séparée (''chôriston'') : elle peut exister indépendamment des accidents qui l'affectent # Elle est déterminée (''tode ti'') : elle est un « ceci particulier », un individu identifiable<ref>Aristote, ''Métaphysique'', Z, 3, 1029a27-28 ; Δ, 8, 1017b23-26</ref> ===== Substance première et substance seconde ===== Aristote distingue, dans les ''Catégories'', entre substance première et substance seconde. La substance première est l'individu concret (Socrate, Bucéphale), tandis que la substance seconde est l'espèce ou le genre auquel appartient cet individu (l'homme, l'animal)<ref>Aristote, ''Catégories'', 5, 2a11-19, 2b7-29</ref>. Les substances secondes sont dites substances uniquement parce qu'elles sont prédiquées des substances premières et révèlent ce qu'elles sont essentiellement. Cette distinction marque une rupture ontologique majeure avec Platon. Pour ce dernier, l'universel (l'Idée) est la réalité première et véritable, tandis que l'individu sensible n'est qu'une ombre ou une copie imparfaite. Pour Aristote, c'est exactement l'inverse : l'individu est la substance première, et l'universel n'existe que dans les individus ou dans l'esprit qui les pense. Comme Aristote le formule catégoriquement dans la ''Métaphysique'' : « Aucun universel n'est une substance »<ref>Aristote, ''Métaphysique'', Z, 13, 1038b8-16 ; Z, 16, 1040b23-1041a5 ; M, 10, 1086b14-1087a25</ref>. Cette thèse a des conséquences considérables. Si seuls les individus existent véritablement, alors la science, qui porte sur l'universel, ne porte pas directement sur ce qui existe, mais sur les formes universelles abstraites à partir des individus. Aristote résout cette tension en distinguant l'ordre de l'être (où l'individu est premier) et l'ordre de la connaissance (où l'universel est premier)<ref>Aristote, ''Métaphysique'', Z, 3, 1029b3-12 ; ''Seconds Analytiques'', I, 2, 71b33-72a5</ref>. ===== La forme comme substance ===== Dans la ''Métaphysique'', Aristote développe une conception plus complexe où la forme (''eidos'') apparaît comme le meilleur candidat au titre de substance. Cette évolution s'explique par le fait qu'Aristote cherche à identifier ce qui, dans le composé hylémorphique, mérite le plus le nom de substance<ref>Aristote, ''Métaphysique'', Z, 3, 1028b33-1029a7 ; Z, 7, 1032b1-2 ; Z, 17, 1041b7-9</ref>. Cependant, cette forme n'est pas l'Idée platonicienne. Elle s'en distingue sur trois points essentiels : # Elle est immanente aux choses, non séparée. La forme du cheval n'existe que dans les chevaux individuels # Elle est le principe d'unité du composé matière-forme. Elle fait que le composé est un être et non une simple agrégation # Elle est l'actualité (''energeia'') de la chose, son essence en acte, par opposition à la matière qui est en puissance<ref>Aristote, ''Métaphysique'', H, 2, 1042b9-11 ; Θ, 8, 1050a15-16</ref> La forme aristotélicienne s'apparente davantage à la définition essentielle de la chose (''logos tês ousias'') qu'à une entité séparée existant par elle-même. Elle est « ce qui est signifié par la définition »<ref>Aristote, ''Métaphysique'', Z, 4, 1029b19-20 ; Z, 10, 1035b31-1036a12</ref>. Ainsi, la forme de l'homme est ce que définit la définition « animal rationnel mortel », et cette forme existe uniquement dans les hommes individuels qui la réalisent. ==== Éléments de continuité entre Platon et Aristote ==== ===== La primauté de la forme sur la matière ===== Malgré leurs divergences ontologiques fondamentales, Platon et Aristote partagent une conviction commune : la forme (ou l'Idée) est supérieure à la matière dans l'ordre de l'intelligibilité, de la réalité et de la perfection. Pour Platon, seules les Idées sont pleinement réelles (''ontôs onta'') et pleinement connaissables (''epistêta''), tandis que la matière, le réceptacle (''chôra'') du ''Timée'', est principe d'indétermination, de multiplicité et d'altération. Elle n'est ni intelligible ni perceptible, mais saisissable seulement par un « raisonnement bâtard »<ref>Platon, ''Timée'', 49a-52d ; 51a-52a</ref>. C'est elle qui explique pourquoi les choses sensibles sont changeantes, multiples et imparfaites. Pour Aristote également, la matière pure, prise en elle-même et abstraction faite de toute forme, est indéterminée (''aoriston'') et inconnaissable. « La matière en soi est inconnaissable »<ref>Aristote, ''Métaphysique'', Z, 10, 1036a8-9 ; cf. Z, 3, 1029a20-26</ref>. Seule la forme rend la chose intelligible et définissable. C'est elle qui fait qu'une chose est ce qu'elle est et qui la rend connaissable par la science. Comme l'écrit Aristote : « Ce que nous appelons la forme ou la substance, c'est ce dont l'énoncé est une définition »<ref>Aristote, ''Métaphysique'', Z, 7, 1032b1-2, trad. J. Tricot, op. cit., t. I, p. 372</ref>. Cette primauté de la forme se manifeste aussi dans le fait que, pour les deux philosophes, la perfection d'une chose consiste dans l'actualisation complète de sa forme, et que les déficiences s'expliquent par la résistance ou les limitations de la matière. Chez Platon, les choses sensibles sont imparfaites parce qu'elles ne peuvent réaliser parfaitement les Idées à cause de leur nature matérielle. Chez Aristote, les monstres et les anomalies s'expliquent par le fait que la matière ne se laisse pas toujours informer conformément à la forme spécifique<ref>Aristote, ''De la Génération des Animaux'', IV, 3, 767b5-15 ; 768b15-25</ref>. ===== La finalité dans la nature ===== Platon et Aristote partagent une vision téléologique de la nature, c'est-à-dire l'idée que les processus naturels sont orientés vers des fins et que le monde possède une structure rationnelle et ordonnée. Dans le ''Timée'', Platon présente le cosmos comme l'œuvre d'un Démiurge divin qui organise la matière chaotique en ayant les yeux fixés sur les Idées éternelles. Le Démiurge, « voulant que toutes choses soient bonnes et que rien ne soit mauvais dans la mesure du possible », façonne le monde sensible à l'image du monde intelligible<ref>Platon, ''Timée'', 28a-30c ; 29d-30a</ref>. Cette orientation vers le Bien confère au cosmos sa structure rationnelle et harmonieuse. Aristote reprend cette conception finaliste, mais l'intériorise et la naturalise. La nature elle-même agit en vue d'une fin (''hê phusis heneka tou poiei''), sans qu'il soit besoin de postuler un Démiurge extérieur ou des Idées transcendantes<ref>Aristote, ''Physique'', II, 8, 198b10-199a8 ; ''Des Parties des Animaux'', I, 1, 639b14-640a9</ref>. Chaque être naturel tend vers sa forme achevée comme vers sa fin naturelle : le gland tend à devenir chêne, l'enfant tend à devenir adulte. Comme l'écrit Aristote : « La nature ne fait rien en vain, et de toutes choses elle fait toujours la meilleure possible dans chaque genre »<ref>Aristote, ''Du Ciel'', II, 5, 288a2-3 ; cf. ''Des Parties des Animaux'', III, 1, 661b23-24</ref>. Dans les deux cas, la finalité garantit l'intelligibilité du monde naturel et permet de le comprendre non comme un chaos de mouvements aléatoires, mais comme un ordre structuré où chaque chose a sa place et sa raison d'être. La forme est à la fois modèle et fin du développement naturel. ===== La connaissance par les causes ===== Platon et Aristote s'accordent sur le fait que la véritable connaissance scientifique (''epistêmê'') consiste à connaître les causes (''aitiai'') des choses et non simplement à constater leur existence. Dans le ''Phédon'', Socrate raconte sa déception face aux philosophes de la nature présocratiques. Lorsqu'il apprit qu'Anaxagore posait l'Intelligence (''Nous'') comme principe du cosmos, il espéra trouver enfin des explications véritablement causales. Mais Anaxagore se contentait d'invoquer des causes mécaniques (l'air, l'éther, l'eau) sans jamais expliquer pourquoi l'Intelligence avait disposé chaque chose de la meilleure manière possible. Pour Socrate-Platon, une véritable explication doit remonter au Bien, cause finale de toute chose<ref>Platon, ''Phédon'', 96a-99d ; 97b-99c</ref>. Aristote approuve cette exigence et la systématise dans sa doctrine des quatre causes. Toute science véritable doit expliquer non seulement que quelque chose est (''hoti''), mais aussi pourquoi il est (''dioti''). Elle doit identifier la cause matérielle (ce dont une chose est faite), la cause formelle (ce qu'elle est essentiellement), la cause efficiente (ce qui l'a produite) et la cause finale (ce en vue de quoi elle existe)<ref>Aristote, ''Physique'', II, 3, 194b16-195b30 ; ''Seconds Analytiques'', I, 13, 78a22-b4 ; ''Métaphysique'', A, 3, 983a24-b6</ref>. Cependant, Aristote reproche à Platon de n'avoir effectivement utilisé que deux des quatre causes : la cause formelle (les Idées) et la cause matérielle (la matière ou le réceptacle). Platon aurait négligé la cause efficiente (le principe du mouvement) et la cause finale, ou du moins ne les aurait pas clairement distinguées de la cause formelle<ref>Aristote, ''Métaphysique'', A, 6, 988a8-17 ; A, 9, 992a24-29 ; Λ, 10, 1075b37-1076a4</ref>. C'est ce manque qui expliquerait l'incapacité du platonisme à rendre compte du devenir et du mouvement dans la nature. ===== La définition et l'universel ===== Platon et Aristote s'accordent sur le fait que la définition (''horismos'' ou ''logos'') porte sur l'universel et non sur le particulier. On ne peut définir un individu comme Socrate ou Callias, mais seulement l'espèce à laquelle il appartient (l'homme) ou le genre dont relève cette espèce (l'animal)<ref>Platon, ''Théétète'', 201c-210b ; ''Sophiste'', 218b-221c ; Aristote, ''Métaphysique'', Z, 15, 1039b27-1040a7 ; ''Seconds Analytiques'', II, 13, 97b26-98a17</ref>. Dans le ''Théétète'', Platon montre que la connaissance (''epistêmê'') ne peut être réduite à la sensation ni à l'opinion vraie, car la sensation porte sur le particulier changeant tandis que la connaissance requiert la saisie de l'universel stable. De même, dans le ''Sophiste'', la méthode de division dichotomique (''diairesis'') permet de définir une Idée en la divisant progressivement jusqu'à isoler son essence spécifique<ref>Platon, ''Sophiste'', 218b-221c ; 264c-268d</ref>. Aristote reprend cette exigence mais en tire des conclusions opposées. Pour Platon, le fait que la définition porte sur l'universel prouve que seul l'universel (l'Idée) est pleinement connaissable et réel. Pour Aristote, cela montre que la connaissance scientifique porte certes sur l'universel, mais que la réalité réside dans le particulier. La définition exprime la forme universelle (l'homme est un animal rationnel), mais cette forme n'existe qu'instanciée dans des individus concrets (Socrate, Callias, Platon)<ref>Aristote, ''Métaphysique'', Z, 13, 1038b8-16 ; Z, 15, 1039b27-1040a7 ; M, 10, 1086b14-1087a25</ref>. Ainsi, Aristote opère une distinction fondamentale entre l'ordre de l'être (où le particulier est premier) et l'ordre de la connaissance (où l'universel est premier). Cette distinction lui permet de concilier le réalisme (seuls les individus existent) avec l'exigence de scientificité (la science porte sur l'universel). ==== La dialectique et la méthode philosophique ==== ===== L'héritage socratique commun ===== Platon et Aristote sont tous deux héritiers de la méthode dialectique inaugurée par Socrate. Cette méthode consiste à progresser vers la vérité par l'examen critique des opinions (''doxai'') et par le dialogue argumenté qui met à l'épreuve les thèses avancées<ref>Platon, ''République'', VII, 533c-534e ; ''Sophiste'', 253b-254b ; Aristote, ''Topiques'', I, 1-2, 100a18-101b4</ref>. Dans les dialogues platoniciens, Socrate procède systématiquement par questions et réponses (''elenchus''), examinant les opinions de ses interlocuteurs pour en montrer les contradictions internes et les conduire progressivement vers la vérité. Cette méthode suppose que la vérité n'est pas simplement transmise du maître à l'élève, mais découverte par l'âme elle-même à travers un processus d'examen rationnel<ref>Platon, ''Ménon'', 81a-86c ; ''Théétète'', 148e-151d</ref>. Aristote conserve cette approche dialectique dans ses traités. Il commence presque toujours par recenser les opinions des prédécesseurs (''doxographia''), puis examine les difficultés (''aporiai'') que soulève le sujet, avant de proposer sa propre solution. Cette méthode apparaît clairement dans l'''Éthique à Nicomaque'' : « Comme pour les autres sujets, il faut, après avoir exposé les phénomènes et d'abord parcouru les difficultés, démontrer, si possible, la vérité de toutes les opinions relatives à ces affections de l'âme, ou du moins du plus grand nombre et des plus autorisées »<ref>Aristote, ''Éthique à Nicomaque'', VII, 1, 1145b2-7, trad. J. Tricot, op. cit., p. 317</ref>. Cependant, la portée de la dialectique diffère chez les deux philosophes. Pour Platon, la dialectique est la méthode suprême de la philosophie, celle qui permet de s'élever jusqu'à la contemplation des Idées et du Bien. Le dialecticien est le véritable philosophe<ref>Platon, ''République'', VII, 532a-535a ; 537c-539d</ref>. Pour Aristote, la dialectique est un outil préparatoire utile, notamment pour examiner les opinions reçues et identifier les apories, mais elle ne constitue pas une science à proprement parler, car elle ne démontre rien de façon apodictique<ref>Aristote, ''Topiques'', I, 2, 101a25-b4 ; ''Métaphysique'', Γ, 2, 1004b17-26 ; B, 1, 995a24-b4</ref>. La véritable science procède par démonstration à partir de principes premiers nécessaires et vrais. ===== Le rôle de l'expérience sensible ===== Un point de divergence majeur concerne le rôle de l'expérience sensible (''aisthêsis'') dans la connaissance. Pour Platon, les sens nous trompent et nous maintiennent dans l'ignorance (''agnoia'') ou au mieux dans l'opinion (''doxa''). La véritable connaissance (''epistêmê'') requiert de se détourner du sensible pour se tourner vers l'intelligible pur, accessible uniquement par l'intellect (''nous''). Dans le ''Phédon'', Socrate affirme que « tant que nous aurons notre corps et que notre âme sera pétrie avec cette chose mauvaise, jamais nous ne posséderons en suffisance l'objet de notre désir. Or, cet objet, nous l'avons dit, c'est le vrai »<ref>Platon, ''Phédon'', 66b, trad. M. Dixsaut, op. cit., p. 251</ref>. L'allégorie de la caverne dans la ''République'' illustre dramatiquement cette dévalorisation du sensible : les prisonniers enchaînés qui contemplent les ombres sur la paroi représentent l'humanité ordinaire, prisonnière des apparences sensibles<ref>Platon, ''République'', VII, 514a-517c</ref>. Aristote accorde au contraire une place fondamentale à l'expérience sensible. Certes, la science porte sur l'universel et les causes, mais cet universel est abstrait à partir des données sensibles par un processus d'induction (''epagôgê''). Comme Aristote l'affirme dans le célèbre chapitre final des ''Seconds Analytiques'' : « C'est donc évidemment par induction que nous devons connaître les principes premiers, car c'est aussi de cette façon que la sensation produit en nous l'universel »<ref>Aristote, ''Seconds Analytiques'', II, 19, 100b3-5, trad. J. Tricot, Paris, Vrin, 1947, p. 242</ref>. Toute connaissance commence donc par la sensation, et l'intellect ne possède rien qui ne soit d'abord passé par les sens<ref>Aristote, ''De l'Âme'', III, 8, 432a3-10</ref>. Cette différence d'attitude se manifeste particulièrement dans leurs travaux scientifiques. Platon construit des modèles mathématiques abstraits (comme dans le ''Timée'') sans se soucier outre mesure de leur adéquation aux observations empiriques. Aristote consacre au contraire une part immense de son œuvre à l'observation minutieuse des phénomènes naturels, notamment dans ses traités biologiques. Il reproche d'ailleurs aux Platoniciens de « négliger les faits » au profit de raisonnements abstraits : « Ceux qui, habitués aux raisonnements dialectiques, négligent les faits, après n'avoir considéré qu'un petit nombre de faits, légifèrent trop rapidement »<ref>Aristote, ''De la Génération et de la Corruption'', I, 2, 316a5-14, trad. C. Mugler, Paris, Les Belles Lettres, 1966, p. 6</ref>. ==== Conclusion : rupture dans la continuité ==== La relation entre Aristote et Platon illustre parfaitement cette formule célèbre, attribuée à Aristote bien qu'elle ne se trouve pas littéralement dans ses œuvres : « Platon m'est cher, mais la vérité m'est plus chère encore »<ref>Cette formule résume l'esprit du passage d'Aristote, ''Éthique à Nicomaque'', I, 6, 1096a11-17, traduit librement en latin par l'adage ''Amicus Plato, sed magis amica veritas''</ref>. Aristote rejette fondamentalement plusieurs thèses centrales du platonisme : la séparation des Idées, la participation comme mode de relation entre sensible et intelligible, la dévalorisation du monde sensible et de l'individu concret, l'autonomie ontologique de l'universel. Ces rejets ne sont pas de simples corrections de détail, mais impliquent une réorientation complète de la métaphysique. Pourtant, Aristote conserve et approfondit certaines intuitions fondamentales de Platon : la primauté de la forme sur la matière, la finalité dans la nature, l'exigence d'expliquer par les causes, la nécessité de définir l'universel pour fonder la science. Il estime avoir sauvé ce qu'il y a de vrai dans le platonisme tout en éliminant les erreurs métaphysiques qui en compromettaient la validité. Cette attitude n'est pas contradictoire. En ancrant la forme dans le monde sensible et en faisant de l'individu concret la substance première, Aristote pense avoir rendu compte de la structure rationnelle et intelligible du réel sans recourir à un dualisme métaphysique intenable. La forme n'est plus un modèle transcendant que les choses imiteraient imparfaitement, mais le principe immanent qui les structure et les fait être ce qu'elles sont. La rupture est donc profonde, mais elle s'opère dans la continuité d'un même questionnement sur l'être, l'essence, la connaissance et le Bien. En ce sens, l'aristotélisme peut être compris comme une naturalisation du platonisme : la forme descend du ciel des Idées pour s'incarner dans les choses mêmes, le Bien n'est plus au-delà de l'essence mais se réalise dans l'accomplissement de la nature propre de chaque être, et la philosophie, tout en restant science de l'universel, reconnaît que seuls existent les individus concrets dans lesquels cet universel se réalise. Cette tension créatrice entre continuité et rupture explique pourquoi la tradition philosophique ultérieure n'a jamais pu trancher définitivement entre platonisme et aristotélisme, mais a constamment oscillé entre ces deux pôles, cherchant tantôt à les concilier (comme les néoplatoniciens et Thomas d'Aquin), tantôt à radicaliser leur opposition (comme les nominalistes médiévaux ou les empiristes modernes). La querelle entre Platon et Aristote demeure ainsi l'une des plus fécondes de l'histoire de la métaphysique occidentale. == Première partie : L'Organon ou les outils de la pensée == === Chapitre I : Les Catégories – Les genres suprêmes de l'être === ==== La classification première de la réalité ==== Au commencement de son investigation philosophique, Aristote pose une question fondamentale : comment classer et ordonner la multiplicité des choses que nous rencontrons dans le monde ? Sa réponse prend la forme d'une doctrine des catégories (''katêgoriai''), terme qui signifie littéralement « prédicats » ou « attributions ». Ces catégories constituent les genres suprêmes de l'être, c'est-à-dire les classes les plus générales sous lesquelles tout ce qui existe peut être rangé<ref>Aristote, ''Catégories'', 4, 1b25-2a4, dans ''Œuvres complètes'', dir. P. Pellegrin, Flammarion, Paris, 2014</ref>. Dans le traité des ''Catégories'', Aristote présente une liste canonique de dix catégories qui détermine l'ensemble des modes selon lesquels l'être se dit. « Chacun des termes qui sont dits sans aucune combinaison », écrit-il, « indique soit une substance (''ousia''), soit une certaine quantité (''poson''), soit une certaine qualité (''poion''), soit un rapport à quelque chose (''pros ti''), soit quelque part (''pou''), soit un certain moment (''pote''), soit être dans une position (''keisthai''), soit posséder (''echein''), soit faire (''poiein''), soit subir (''paschein'') »<ref>Aristote, ''Catégories'', 4, 1b25-27, trad. R. Bodéüs, Les Belles Lettres, Paris, 2001</ref>. Cette énumération mérite qu'on s'y arrête. La substance, par exemple, désigne ce qu'est un être : un homme, un cheval. La quantité exprime l'extension : long de deux coudées, de trois coudées. La qualité caractérise la nature : blanc, instruit. La relation établit un rapport : double, moitié, plus grand. Le lieu situe dans l'espace : au Lycée, sur la place. Le temps inscrit dans la durée : hier, l'an dernier. La position indique l'attitude : être couché, être assis. La possession marque l'avoir : être chaussé, être armé. L'action et la passion expriment l'agir et le pâtir : couper ou être coupé, brûler ou être brûlé<ref>Aristote, ''Catégories'', 4, 1b25-2a10, dans P. Pellegrin dir., ''Dictionnaire Aristote'', Ellipses, Paris, 2007, p. 176-178</ref>. ==== La primauté ontologique de la substance ==== Parmi ces dix catégories, l'une occupe une place privilégiée : la substance. Aristote affirme sans ambages que « la substance est ce qui se dit proprement, premièrement et avant tout ; ce qui à la fois ne se dit pas d'un certain sujet et n'est pas dans un certain sujet ; par exemple tel homme ou tel cheval »<ref>Aristote, ''Catégories'', 5, 2a11-14, trad. R. Bodéüs, op. cit.</ref>. Cette définition capitale établit la substance première (''prôtê ousia'') comme le fondement de toute réalité. Pourquoi cette primauté ? Parce que toutes les autres catégories présupposent l'existence de la substance. On ne peut concevoir une couleur sans un corps coloré, une grandeur sans quelque chose de grand, une action sans un agent. « Tous les autres termes, ou bien se disent de sujets qui sont les substances premières, ou bien sont dans des sujets qui sont ces mêmes substances », explique le Stagirite<ref>Aristote, ''Catégories'', 5, 2a34-2b6</ref>. Si les substances premières n'existaient pas, il serait impossible que quoi que ce soit d'autre existe. La substance est donc le substrat ultime (''hypokeimenon'') : ce dont tout se dit mais qui n'est dit de rien d'autre<ref>Aristote, ''Métaphysique'', Z, 3, 1028b36-37, trad. J. Tricot, Vrin, Paris, 1953</ref>. ==== Substance première et substance seconde ==== Aristote introduit cependant une distinction cruciale au sein même de la notion de substance. À côté de la substance première, il reconnaît l'existence de substances secondes (''deuterai ousiai''). « Se disent par ailleurs une seconde sorte de substances, les espèces auxquelles appartiennent les substances dites au sens premier — celles-là, et aussi les genres de ces espèces »<ref>Aristote, ''Catégories'', 5, 2a14-17</ref>. La substance première désigne l'individu concret, singulier, unique : cet homme-ci (par exemple Socrate), ce cheval-là (par exemple Bucéphale). Elle possède une double caractéristique négative : elle n'est ni dite d'un sujet, ni dans un sujet. Socrate n'est attribué à rien d'autre — on ne dit pas qu'autre chose « est Socrate » — et il n'existe pas dans un autre être comme une propriété. Les substances premières sont des individus numériquement uns (''arithmôi hen''), des « ceci » (''tode ti'') déterminés et séparés<ref>J. Brun, ''Aristote et le Lycée'', PUF, « Que sais-je ? », Paris, 1961, p. 95-96</ref>. Les substances secondes, en revanche, sont les espèces et les genres auxquels appartiennent les substances premières. L'homme (espèce) et l'animal (genre) sont des substances secondes parce qu'ils se disent des substances premières : Socrate est un homme, l'homme est un animal. Aristote précise qu'« parmi les substances secondes, l'espèce est plus substance que le genre, car elle est plus proche de la substance première »<ref>Aristote, ''Catégories'', 5, 2b7-8</ref>. Si l'on veut rendre compte de ce qu'est Socrate, on sera plus précis en disant que c'est un homme plutôt qu'un animal. Cette hiérarchie révèle une structure ontologique fondamentale : les substances premières sont pré supposées comme sujets par toutes les autres réalités, et ce que les substances premières sont par rapport à tout le reste, les espèces le sont par rapport aux genres<ref>Aristote, ''Catégories'', 5, 2b15-22</ref>. Seules les espèces et les genres méritent d'être appelés substances secondes, car eux seuls, parmi tous les prédicats, indiquent la substance première de manière essentielle<ref>S. Bassu, ''Aristote'', Ellipses, Paris, 2016, p. 62-63</ref>. ==== Les propriétés caractéristiques de la substance ==== Aristote dégage plusieurs propriétés topiques qui permettent d'identifier la substance et de la distinguer des accidents. Premièrement, toute substance n'est pas dans un sujet. Cette caractéristique sépare radicalement la substance des qualités, quantités et relations qui, elles, existent nécessairement dans un substrat<ref>Aristote, ''Catégories'', 5, 3a7-21</ref>. Deuxièmement, la substance et ses différences se disent de façon synonyme (''sunônuma''). Tout ce qui est attribué à partir d'une substance l'est avec le même nom et la même définition : quand on dit de Socrate qu'il est un homme et de l'homme qu'il est un animal, le terme « est » conserve le même sens fondamental<ref>Aristote, ''Catégories'', 5, 3a33-3b9</ref>. Troisièmement, toute substance « indique un certain ceci » (''tode ti''). Les substances premières désignent indiscutablement quelque chose d'individuel et de numériquement un. Les substances secondes, bien que leur forme grammaticale suggère qu'elles indiquent également un « ceci », désignent en réalité plutôt « une certaine sorte d'objet » (''poion ti'') : l'homme n'est pas un individu unique mais une espèce comprenant une multiplicité de sujets<ref>Aristote, ''Catégories'', 5, 3b10-21, dans F. Stirn, ''Aristote'', Armand Colin, Paris, 1999, p. 91-95</ref>. Quatrièmement, la substance n'admet pas le plus ou le moins. Un homme n'est pas « plus homme » qu'un autre, ni plus homme à un moment qu'à un autre. Cette propriété distingue la substance de la qualité : un corps peut être plus ou moins blanc, mais Socrate ne peut être plus ou moins Socrate<ref>Aristote, ''Catégories'', 5, 3b33-4a9</ref>. Enfin, et c'est là « ce que l'on considère surtout comme propre à la substance », celle-ci est capable de recevoir les contraires tout en restant la même et numériquement une. Un même homme, restant identique à lui-même, devient tantôt pâle tantôt bronzé, tantôt chaud tantôt froid, tantôt bon tantôt mauvais. Aucune autre catégorie ne possède cette capacité remarquable de demeurer identique tout en subissant des modifications contraires<ref>Aristote, ''Catégories'', 5, 4a10-21</ref>. ==== Les catégories et la structure du langage ==== Cette doctrine des catégories révèle un lien profond entre l'ontologie et le langage. Les catégories ne sont pas seulement des divisions de l'être, mais aussi des types de prédication. Comme l'explique Aristote, « l'être se dit en autant de sens qu'il y a de catégories »<ref>Aristote, ''Métaphysique'', Δ, 7, 1017a22-24</ref>. Dire que « Socrate est un homme » (substance), que « Socrate est grand » (quantité), que « Socrate est sage » (qualité), ou que « Socrate est au Lycée » (lieu), ce sont autant de manières différentes d'attribuer l'être. Cette correspondance entre langage et réalité n'est cependant pas fortuite. Les catégories ne découlent pas d'une analyse arbitraire du discours, mais reflètent la structure même du réel. Elles constituent les « genres de l'être » (''ta genê tou ontos''), c'est-à-dire les divisions fondamentales selon lesquelles la réalité s'ordonne<ref>R. Bodéüs, Introduction aux ''Catégories'', Les Belles Lettres, Paris, 2001, p. CXVIII-CXXV</ref>. Aristote fonde ainsi une ontologie catégoriale qui demeurera pendant des siècles la référence de la pensée occidentale. ==== L'unité problématique de l'être ==== La doctrine des catégories soulève néanmoins une difficulté majeure : si l'être se dit en plusieurs sens irréductibles, comment préserver l'unité de son concept ? Comment parler de « l'être en tant qu'être » s'il n'y a pas un sens unique mais dix sens différents de l'être ? Aristote ne dissimule pas cette aporie. L'être n'est pas un genre au sens propre, car un genre possède une définition unique qui s'applique uniformément à toutes ses espèces<ref>Aristote, ''Métaphysique'', B, 3, 998b22</ref>. La solution aristotélicienne consiste à reconnaître que l'être est dit selon une pluralité ordonnée. Bien que l'être n'ait pas un sens univoque, ses différentes acceptions ne sont pas non plus purement équivoques. Elles se rapportent toutes à un terme premier : la substance. « L'être se dit en multiples acceptions, mais toujours relativement à un terme unique, à une nature déterminée », affirme Aristote dans un passage capital de la ''Métaphysique''<ref>Aristote, ''Métaphysique'', Γ, 2, 1003a33-b10</ref>. De même que tout ce qui est « sain » se rapporte à la santé — tel aliment la produit, tel exercice la conserve, telle couleur l'indique — de même tout ce qui « est » se rapporte à la substance. C'est ce que la tradition appellera l'analogie de l'être : une unité relative, fondée sur une référence commune, qui préserve la diversité des sens tout en maintenant leur cohérence<ref>P. Aubenque, ''Le problème de l'être chez Aristote'', PUF, Paris, 1962, p. 178-195</ref>. Ainsi, les dix catégories ne forment pas une liste hétéroclite mais un système hiérarchisé dont la substance constitue le centre. Cette architecture conceptuelle permet à Aristote d'affirmer simultanément la multiplicité des sens de l'être et l'unité de la science qui l'étudie : la philosophie première ou métaphysique, science de l'être en tant qu'être<ref>Aristote, ''Métaphysique'', Γ, 1, 1003a20-32, dans C. Cerami dir., ''Génération et substance : Aristote et Averroès'', De Gruyter, Berlin, 2015, p. 167-172</ref>. === Chapitre II : Le ''De interpretatione'' – Le jugement et la proposition === Le ''De interpretatione'' (''Peri hermeneias'') constitue le deuxième traité de l'Organon aristotélicien et offre une analyse minutieuse de la proposition comme lieu privilégié de la vérité et de la fausseté. Aristote y examine la structure du discours déclaratif, les conditions de possibilité du jugement et les rapports entre langage, pensée et réalité. Cette œuvre, d'une densité conceptuelle remarquable, pose les fondements de la logique propositionnelle et s'attache à déterminer comment le ''logos'' peut manifester l'être<ref>Aristote, ''De l'interprétation'', 1, 16a3-8, trad. J. Tricot, Paris, Vrin, 1969, p. 77</ref>. ==== Le langage comme système symbolique ==== Aristote ouvre son traité par une réflexion sur le statut du langage qui marque une rupture décisive avec les conceptions sophistiques. Les sons émis par la voix (''ta en tè phonè'') sont, écrit-il, les symboles (''symbola'') des affections de l'âme (''ta en tè psychè pathèmata''), et les mots écrits sont à leur tour les symboles des mots prononcés<ref>Aristote, ''De l'interprétation'', 1, 16a3-8</ref>. Cette conception stratifiée du signe linguistique établit trois niveaux distincts : les choses (''pragmata''), les affections ou états de l'âme, et les signes vocaux ou écrits. Cette théorie sémiotique, qui sera reprise et développée par la scolastique médiévale, présente plusieurs caractéristiques essentielles. D'abord, elle affirme le caractère conventionnel (''kata synthèkèn'') du rapport entre le signifiant linguistique et le signifié mental : « De même que l'écriture n'est pas la même pour tous les hommes, les mots parlés ne sont pas non plus les mêmes, alors que les états de l'âme dont ces expressions sont immédiatement les signes sont identiques chez tous, comme sont identiques aussi les choses dont ces états sont les images »<ref>''Ibid.'', 16a5-8</ref>. La diversité des langues manifeste ainsi le caractère artificiel du lien entre ''phone'' et ''pathèma'', tandis que l'universalité des états mentaux et leur rapport de ressemblance (''homoiômata'') avec les choses garantit la possibilité d'une communication véridique. Cette analyse permet à Aristote de réfuter la position sophistique, particulièrement celle de Cratyle, selon laquelle les noms seraient naturellement justes. Contre l'idée d'une adhérence immédiate entre le mot et la chose, Aristote introduit une médiation nécessaire : celle de la pensée. Le langage ne manifeste pas directement l'être, mais signifie d'abord les concepts par lesquels nous appréhendons l'être<ref>Voir Pierre Aubenque, ''Le problème de l'être chez Aristote'', Paris, PUF, 1962, p. 100-110</ref>. ==== Les éléments du discours déclaratif : nom et verbe ==== Avant d'examiner la proposition proprement dite, Aristote analyse ses constituants élémentaires. Le nom (''onoma'') est défini comme « un son vocal possédant une signification conventionnelle, sans référence au temps, et dont aucune partie, prise isolément, n'est significative par elle-même »<ref>Aristote, ''De l'interprétation'', 2, 16a19-21</ref>. Cette définition insiste sur quatre caractères : la nature sonore (''phonè''), la signification conventionnelle (''kata synthèkèn''), l'atemporalité, et l'absence de signification des parties considérées isolément. Ce dernier point vaut pour les noms simples ; dans les noms composés comme « Callippos », les parties (''kallos'', « beauté », et ''hippos'', « cheval ») ne contribuent pas à la signification du tout<ref>''Ibid.'', 16a21-29</ref>. Aristote précise que « rien n'est par nature un nom, mais seulement quand il devient symbole »<ref>''Ibid.'', 16a27-28</ref>. Cette conventionnalité radicale distingue le langage humain des cris inarticulés des animaux : même lorsque ceux-ci signifient quelque chose (par exemple la douleur), ils ne constituent pas des noms car ils n'impliquent pas la médiation d'une institution symbolique. Le nom renvoie donc à une réalité doublement humaine : comme production vocale conventionnelle et comme instrument de la vie sociale et politique. Aristote mentionne également les noms indéfinis (''aoriston onoma''), comme « non-homme », qui ne sont pas véritablement des noms car ils peuvent s'appliquer indifféremment à tout ce qui n'est pas homme, sans détermination positive<ref>''Ibid.'', 2, 16a30-32</ref>. Cette analyse des termes privatifs ou négatifs anticipe les difficultés ultérieures concernant le statut logique de la négation. Le verbe (''rhèma'') se distingue du nom par deux caractères spécifiques : « Le verbe est ce qui ajoute à sa propre signification celle du temps ; aucune de ses parties n'est significative prise séparément, et il indique toujours quelque chose d'affirmé de quelque chose d'autre »<ref>''Ibid.'', 3, 16b6-8</ref>. La première caractéristique, la consignification du temps (''prosèmainei chronon''), fait du verbe l'élément qui ancre le discours dans la temporalité et permet de distinguer le présent, le passé et le futur. Mais c'est la seconde caractéristique qui importe le plus du point de vue logique : le verbe signifie toujours « quelque chose dit au sujet de quelque chose d'autre », c'est-à-dire qu'il introduit essentiellement une relation prédicative. Cependant, Aristote ajoute une précision capitale : les verbes « dits par eux-mêmes » (''kath' hauta legomena''), c'est-à-dire pris isolément sans sujet explicite, « sont des noms et signifient quelque chose », mais « ils ne signifient pas encore si la chose est ou n'est pas »<ref>''Ibid.'', 16b19-22</ref>. Ainsi, le verbe « court » ou « marche », prononcé seul, arrête certes la pensée de l'auditeur et produit un sens, mais il ne constitue pas encore un jugement, il n'affirme ni ne nie rien. Cette observation conduit à la question cruciale du rôle de la copule « être ». Le verbe « être » occupe une position singulière dans l'analyse aristotélicienne. Pris en lui-même, « être ou ne pas être n'est pas un signe de la chose réelle, pas même si tu dis 'étant' tout court ; car en soi, ce n'est rien, mais il consignifie une certaine composition qu'on ne peut concevoir sans les composants »<ref>''Ibid.'', 3, 16b22-25</ref>. Cette remarque énigmatique a suscité d'innombrables commentaires. Elle suggère que le verbe « être », dans sa fonction copulative, ne possède pas de contenu sémantique autonome, mais signifie l'acte même de liaison ou de synthèse (''synthesis'') entre le sujet et le prédicat. Ce n'est donc ni un nom, ni vraiment un verbe au sens plein, mais le marqueur linguistique de l'opération judicative elle-même<ref>Voir Octave Hamelin, ''Le système d'Aristote'', Paris, Vrin, 1920, p. 159-160</ref>. ==== La proposition déclarative (''logos apophantikos'') ==== Tout discours (''logos'') n'est pas une proposition. La prière (''euchè''), par exemple, est bien un discours, mais elle n'est ni vraie ni fausse, et son étude relève plutôt de la rhétorique ou de la poétique<ref>Aristote, ''De l'interprétation'', 4, 17a2-7</ref>. Aristote définit la proposition déclarative (''logos apophantikos'') comme « le discours en lequel réside le vrai ou le faux »<ref>''Ibid.'', 17a2-3</ref>. Ce n'est donc pas n'importe quel énoncé, mais celui qui possède une valeur de vérité, celui qui « manifeste » (''apophainein'') un état de choses et peut être confronté à la réalité. La proposition élémentaire se divise en affirmation (''kataphasis'') et négation (''apophasis''). L'affirmation est « la déclaration de quelque chose au sujet de quelque chose » (''apophansis tinos kata tinos''), tandis que la négation est « la déclaration de quelque chose séparé de quelque chose » (''apophansis tinos apo tinos'')<ref>''Ibid.'', 5, 17a25-26</ref>. Cette structure binaire – affirmer ou nier un prédicat d'un sujet – constitue la forme canonique du jugement aristotélicien. Toute proposition déclarative se ramène ultimement à la formule « S est P » (affirmation) ou « S n'est pas P » (négation). Aristote insiste sur le fait que l'affirmation et la négation concernent nécessairement la liaison ou la séparation d'un sujet et d'un prédicat. Une proposition n'est pas une simple juxtaposition de mots, mais suppose une ''synthesis'' (dans l'affirmation) ou une ''diairesis'' (dans la négation). C'est précisément cette opération de composition ou de division qui rend possible la vérité ou la fausseté. Comme Aristote l'expliquera dans la ''Métaphysique'', « dire que ce qui est est et que ce qui n'est pas n'est pas, c'est le vrai ; dire que ce qui est n'est pas ou que ce qui n'est pas est, c'est le faux »<ref>Aristote, ''Métaphysique'', Γ (IV), 7, 1011b26-27</ref>. Cette définition de la vérité comme adéquation (ou conformité) marque un tournant décisif dans l'histoire de la philosophie. Elle établit que la vérité ne réside ni dans les choses elles-mêmes, ni dans la pensée isolée, mais dans le jugement en tant qu'il assemble ou sépare des déterminations conformément à ce qui est assemblé ou séparé dans la réalité. La vérité est donc toujours relative à une opération prédicative : elle suppose la dualité du sujet et du prédicat, et l'acte qui les relie ou les disjoint. ==== Quantité et qualité des propositions ==== Aristote distingue les propositions selon leur quantité et leur qualité. Du point de vue de la quantité, les propositions sont universelles (''katholou''), particulières (''en merei''), singulières (portant sur un individu) ou indéterminées (''aoristoi'')<ref>Aristote, ''De l'interprétation'', 7, 17a38-17b12</ref>. Une proposition universelle affirme ou nie quelque chose de « tout » son sujet (''pan'', ''oudens'') : « Tout homme est blanc », « Nul homme n'est blanc ». Une proposition particulière affirme ou nie quelque chose de « quelque » sujet : « Quelque homme est blanc », « Quelque homme n'est pas blanc ». Les propositions indéterminées sont celles qui ne comportent aucune marque de quantité : « L'homme est blanc », « L'homme n'est pas blanc ». Du point de vue de la qualité, les propositions sont affirmatives ou négatives. En croisant ces deux critères, on obtient quatre formes fondamentales de propositions : l'universelle affirmative (A), l'universelle négative (E), la particulière affirmative (I), et la particulière négative (O) – désignations qui ne sont pas aristotéliciennes mais scolastiques, tirées des voyelles des mots latins ''Affirmo'' et ''nEgO''. Cette classification quantitative revêt une importance considérable pour la théorie du syllogisme développée dans les ''Premiers Analytiques''. Elle permet notamment de définir les relations d'opposition entre propositions : contradiction, contrariété, subcontrariété et subalternation. Deux propositions sont contradictoires lorsqu'elles diffèrent à la fois en quantité et en qualité : « Tout homme est juste » et « Quelque homme n'est pas juste ». Elles sont contraires lorsqu'elles sont toutes deux universelles mais de qualité opposée : « Tout homme est juste » et « Nul homme n'est juste »<ref>''Ibid.'', 7, 17b16-26</ref>. ==== Le problème des futurs contingents ==== Le chapitre 9 du ''De interpretatione'' soulève l'une des questions les plus débattues de l'histoire de la logique et de la métaphysique : celle du statut des propositions portant sur les futurs contingents. Aristote se demande si le principe de bivalence – selon lequel toute proposition est soit vraie, soit fausse – s'applique aux propositions concernant des événements futurs qui ne sont ni nécessaires ni impossibles. Le problème se formule ainsi : « Il y aura demain une bataille navale » et « Il n'y aura pas demain une bataille navale » sont deux propositions contradictoires. D'après le principe du tiers exclu, l'une doit être vraie et l'autre fausse. Mais si l'une est dès maintenant vraie (par exemple la première), alors il est nécessaire qu'il y ait demain une bataille navale, car « il n'est pas possible que ce qui a été dit de vrai se produise autrement »<ref>Aristote, ''De l'interprétation'', 9, 18b9-16</ref>. On aboutit ainsi à un fatalisme logique : tout ce qui arrivera devait nécessairement arriver, et la contingence est abolie. La solution d'Aristote est subtile. Il maintient que, pour les propositions portant sur le présent et le passé, le principe de bivalence s'applique sans restriction : de deux contradictoires, l'une est nécessairement vraie et l'autre fausse. Mais pour les futurs contingents, il faut modifier cette règle : « Il est nécessaire que l'affirmation ou la négation soit vraie, mais non que celle-ci déterminément ou celle-là déterminément soit vraie »<ref>''Ibid.'', 19a36-39</ref>. Autrement dit, il est nécessaire que l'une des deux propositions soit vraie et l'autre fausse, mais il n'est pas nécessaire que ce soit celle-ci plutôt que celle-là. La disjonction est nécessaire, mais chacun des disjoints reste contingent. Cette distinction entre nécessité « divisée » et nécessité « composée » (pour reprendre la terminologie scolastique) préserve à la fois le principe logique fondamental et la réalité de la contingence dans le monde. « Les discours sont vrais de la même manière que les choses », écrit Aristote<ref>''Ibid.'', 9, 19a33</ref> : si les événements futurs sont contingents, les propositions qui les concernent ne peuvent avoir dès maintenant une vérité ou une fausseté déterminée. Cette doctrine a suscité des controverses innombrables, de l'Antiquité (les Stoïciens) au Moyen Âge (les discussions sur la prescience divine) jusqu'à la logique contemporaine (la logique modale et les logiques plurivalentes). ==== Les propositions modales ==== Les chapitres 12 et 13 du ''De interpretatione'' examinent les propositions modales, c'est-à-dire celles qui n'affirment pas simplement que quelque chose est ou n'est pas, mais qu'il est possible, impossible, contingent ou nécessaire que quelque chose soit<ref>Aristote, ''De l'interprétation'', 12-13, 21a34-23a26</ref>. Aristote distingue quatre modalités fondamentales : le possible (''dynaton'', ''endechomenon''), l'impossible (''adynaton''), le contingent (également ''endechomenon'' dans un sens plus restreint) et le nécessaire (''anankaion''). L'analyse des rapports entre ces modalités pose des difficultés considérables. Aristote établit que « Il est possible que S soit P » ne se convertit pas comme une proposition assertorique : de « Il est possible que tout B soit A », on ne peut pas conclure « Il est possible que tout A soit B ». En revanche, la proposition « Il est possible que S soit P » implique « Il est possible que S ne soit pas P », car la possibilité véritable suppose l'indétermination : ce qui peut être peut aussi ne pas être<ref>''Ibid.'', 13, 22a14-23</ref>. Le nécessaire, au contraire, exclut la possibilité opposée : si « Il est nécessaire que S soit P », alors il est impossible que S ne soit pas P. Aristote définit le nécessaire comme « ce qui ne peut pas ne pas être » (''to mè endechomenon mè einai'')<ref>''Ibid.'', 12, 21b35-36</ref>. Cette définition sera reprise par toute la tradition scolastique et restera au centre des débats modaux jusqu'à Leibniz et au-delà. Les propositions modales jouent un rôle essentiel dans la théorie aristotélicienne de la science exposée dans les ''Seconds Analytiques'' : la science porte sur le nécessaire, non sur le contingent. Seul ce qui est toujours ou le plus souvent peut faire l'objet d'une démonstration rigoureuse. L'accident pur, ce qui arrive parfois mais pas régulièrement, échappe à la connaissance scientifique<ref>Aristote, ''Seconds Analytiques'', I, 30, 87b19-27</ref>. ==== L'unité et la multiplicité des propositions ==== Le chapitre 11 examine la question de savoir ce qui fait l'unité d'une proposition. Aristote distingue les propositions véritablement unes de celles qui ne sont unes qu'en apparence. Une proposition est une si elle affirme ou nie « une seule chose d'une seule chose » (''hen kata henos'')<ref>Aristote, ''De l'interprétation'', 11, 20b12-13</ref>. Mais cette unité peut être entendue de deux manières : ou bien le prédicat constitue véritablement une unité (par exemple « animal bipède » dans « L'homme est un animal bipède »), ou bien on relie par accident plusieurs prédicats à un même sujet. La difficulté est de déterminer dans quels cas plusieurs prédicats forment véritablement une unité. « Homme », « blanc » et « musicien » peuvent certes être attribués ensemble à Socrate (« Socrate est un homme blanc et musicien »), mais ils ne forment pas une essence unique. En revanche, « animal » et « bipède » constituent ensemble la définition de l'homme et forment donc une unité substantielle<ref>''Ibid.'', 11, 20b31-21a7</ref>. Cette distinction anticipe l'analyse des différents types de prédication (essentielle, propre, accidentelle) développée dans les ''Topiques'' et les ''Seconds Analytiques''. Cette réflexion sur l'unité de la proposition révèle la préoccupation constante d'Aristote : garantir que le discours ne se perde pas dans une multiplicité infinie et indéterminée, mais conserve une structure rationnelle permettant la connaissance. Le langage doit être régulé par les articulations mêmes de l'être : les genres, les espèces, les accidents par soi, etc. C'est seulement à cette condition que le ''logos apophantikos'' peut manifester véritablement ce qui est. ==== Portée philosophique du ''De interpretatione'' ==== L'importance du ''De interpretatione'' dépasse largement le cadre d'un traité de logique formelle. Aristote y pose les bases d'une ontologie du jugement qui dominera toute la philosophie occidentale jusqu'à Kant et au-delà. En établissant que la vérité réside dans la composition et la division opérées par l'intellect, et non dans les choses elles-mêmes ni dans les concepts isolés, Aristote fait du jugement le lieu privilégié de la manifestation de l'être. Cette doctrine a plusieurs implications majeures. D'abord, elle implique que la vérité est toujours relationnelle : elle suppose un rapport entre au moins deux termes (sujet et prédicat), et entre la pensée et la réalité. Il n'y a pas de vérité absolue et simple, mais seulement des vérités de composition. Ensuite, elle établit que l'erreur est possible : là où il y a composition, il peut y avoir fausse composition. L'erreur naît précisément de l'écart possible entre la liaison que nous posons dans le jugement et la liaison réelle dans les choses<ref>Aristote, ''Métaphysique'', Θ (IX), 10, 1051b1-9</ref>. Enfin, le ''De interpretatione'' pose la question des limites du langage déclaratif. En distinguant le ''logos apophantikos'' des autres formes de discours (prière, commandement, souhait), Aristote délimite le domaine propre de la logique et de la science : celui des énoncés susceptibles d'être vrais ou faux. Mais cette délimitation implique aussi une reconnaissance : il existe des usages légitimes du langage qui échappent à la juridiction de la logique. Cette reconnaissance ouvre un espace pour la rhétorique, la poétique et, plus généralement, pour toutes les formes de discours qui ne visent pas la vérité théorique mais d'autres fins – persuader, émouvoir, prescrire, célébrer. Le ''De interpretatione'' demeure ainsi une œuvre fondatrice, non seulement pour la logique et la philosophie du langage, mais pour toute réflexion sur les rapports entre pensée, langage et réalité. Sa richesse conceptuelle et les difficultés qu'il soulève continuent de nourrir les débats contemporains en philosophie analytique, en linguistique et en logique formelle. === Chapitre III : Les Analytiques – La science démonstrative === Les deux traités des ''Analytiques'' – ''Premiers Analytiques'' (''Analytica priora'') et ''Seconds Analytiques'' (''Analytica posteriora'') – constituent le cœur de l'édifice logique aristotélicien et exposent la théorie de la science démonstrative. Tandis que les ''Premiers Analytiques'' s'attachent à la forme du syllogisme en général, indépendamment de la vérité des prémisses, les ''Seconds Analytiques'' traitent du syllogisme scientifique ou démonstratif (''apodeixis''), dont les prémisses doivent être vraies, premières et causales<ref>Aristote, ''Seconds Analytiques'', I, 2, 71 b 9-16</ref>. Cette distinction fondamentale marque la différence entre la logique formelle et l'épistémologie : les ''Premiers Analytiques'' étudient la validité formelle des raisonnements, les ''Seconds Analytiques'' examinent les conditions de la connaissance scientifique<ref>Jonathan Barnes, ''Aristotle's Posterior Analytics'', 2e éd., Oxford, Clarendon Press, 1993, p. xviii-xix</ref>. ==== Le syllogisme : structure et figures ==== Le syllogisme (''syllogismos'') est défini par Aristote comme « un discours dans lequel, certaines choses étant posées, quelque chose d'autre que ces données en résulte nécessairement par le seul fait de ces données »<ref>Aristote, ''Premiers Analytiques'', I, 1, 24 b 18-20</ref>. Cette définition insiste sur trois caractères essentiels : la pluralité des prémisses (au moins deux), la nécessité de la conclusion, et le fait que cette nécessité découle uniquement de la position des prémisses, sans recours à aucune autre donnée. Le syllogisme est ainsi la forme canonique du raisonnement déductif, celui qui permet de passer de propositions données à une conclusion qui en découle avec rigueur<ref>David Bronstein, ''Aristotle on Knowledge and Learning: The Posterior Analytics'', Oxford, Oxford University Press, 2016, p. 31-32</ref>. La structure minimale du syllogisme comporte trois termes : le grand extrême ou majeur (''to mega akron''), le petit extrême ou mineur (''to mikron akron''), et le moyen terme (''to meson'') qui assure la liaison entre les deux extrêmes sans apparaître dans la conclusion<ref>Aristote, ''Premiers Analytiques'', I, 4, 25 b 32-36</ref>. Ces trois termes entrent dans deux prémisses – la majeure et la mineure – pour produire une conclusion. Par exemple : « Tout homme est mortel » (majeure), « Or Socrate est un homme » (mineure), « Donc Socrate est mortel » (conclusion). Le moyen terme « homme » relie le prédicat « mortel » au sujet « Socrate », mais disparaît dans la conclusion. Aristote distingue trois figures du syllogisme selon la position du moyen terme dans les prémisses. Dans la première figure, le moyen terme est sujet de la majeure et prédicat de la mineure. Dans la deuxième figure, il est prédicat dans les deux prémisses. Dans la troisième figure, il est sujet dans les deux prémisses<ref>Aristote, ''Premiers Analytiques'', I, 4-6, 25 b 26-28 b 14</ref>. La première figure est considérée comme la plus parfaite, car elle seule permet de conclure dans tous les modes (universelles affirmatives et négatives, particulières affirmatives et négatives), tandis que les deux autres figures ne produisent que des conclusions limitées : la deuxième ne donne que des conclusions négatives, la troisième que des conclusions particulières<ref>Aristote, ''Premiers Analytiques'', I, 7, 29 a 30 ; I, 14, 32 b 38-33 a 5</ref>. À l'intérieur de chaque figure, Aristote détermine les modes valables en combinant les différentes qualités (affirmative/négative) et quantités (universelle/particulière) des propositions. Dans la première figure, quatre modes sont concluants : Barbara (deux universelles affirmatives donnant une universelle affirmative), Celarent (universelle négative et universelle affirmative donnant une universelle négative), Darii et Ferio pour les particulières<ref>Aristote, ''Premiers Analytiques'', I, 4, 25 b 37-26 b 34</ref>. Les syllogismes des deuxième et troisième figures, dits « imparfaits », doivent être ramenés par conversion des prémisses aux syllogismes parfaits de la première figure pour manifester leur validité. Cette réduction opère par transformation des propositions selon les lois de conversion : l'universelle négative se convertit simplement (« Nul A n'est B » devient « Nul B n'est A »), l'universelle affirmative se convertit par accident (« Tout A est B » devient « Quelque B est A »), la particulière affirmative se convertit simplement<ref>Aristote, ''Premiers Analytiques'', I, 2-3, 25 a 1-26</ref>. ==== La démonstration scientifique (''apodeixis'') ==== Si les ''Premiers Analytiques'' étudient le syllogisme en général, abstraction faite de la vérité des prémisses, les ''Seconds Analytiques'' s'attachent au syllogisme scientifique ou démonstratif. Aristote ouvre ce traité par une déclaration solennelle : « Nous estimons posséder la science d'une chose d'une manière absolue, et non pas, à la façon des Sophistes, d'une manière purement accidentelle, quand nous croyons que nous connaissons la cause par laquelle la chose est, que nous savons que cette cause est celle de la chose, et qu'en outre il n'est pas possible que la chose soit autre qu'elle n'est »<ref>Aristote, ''Seconds Analytiques'', I, 2, 71 b 9-12</ref>. La science (''epistèmè'') se définit donc triplement : elle est connaissance de la cause, certitude de l'adéquation entre la cause et l'effet, et conscience de la nécessité de cette liaison<ref>Jonathan Barnes, ''Aristotle's Posterior Analytics'', op. cit., p. 90-93</ref>. La démonstration est le syllogisme qui produit la science. Elle doit partir de prémisses qui satisfont plusieurs exigences cumulatives : elles doivent être vraies (car on ne peut savoir ce qui n'est pas), premières (indémontrables, car la chaîne des démonstrations ne peut remonter à l'infini), immédiates (sans moyen terme intermédiaire), plus connues que la conclusion, antérieures à elle, et causes de celle-ci<ref>Aristote, ''Seconds Analytiques'', I, 2, 71 b 20-25</ref>. Ces conditions garantissent que la conclusion ne sera pas seulement vraie et nécessaire, mais qu'elle sera sue avec certitude comme découlant de ses causes. Sans ces conditions, on peut avoir un syllogisme valide, mais non une démonstration scientifique<ref>David Bronstein, ''Aristotle on Knowledge and Learning'', op. cit., p. 35-42</ref>. Le moyen terme joue dans la démonstration un rôle crucial : il n'est pas seulement l'élément logique qui relie les extrêmes, il est la cause (''aitia'') de l'attribution du prédicat au sujet. « Le moyen terme est la cause », écrit Aristote<ref>Aristote, ''Seconds Analytiques'', II, 2, 90 a 6</ref>. Par exemple, pour démontrer que la Lune subit des éclipses, on utilisera comme moyen terme « interposition de la Terre », qui n'est pas seulement un lien logique mais la cause physique du phénomène. Aristote distingue ainsi le syllogisme du fait (''hoti'') et le syllogisme du pourquoi (''dioti'') : le premier prouve que quelque chose est, le second pourquoi c'est<ref>Aristote, ''Seconds Analytiques'', I, 13, 78 a 22-b 13</ref>. La vraie science s'obtient par le syllogisme du pourquoi, qui exhibe la cause à travers le moyen terme<ref>Jonathan Barnes, ''Aristotle's Posterior Analytics'', op. cit., p. 214-220</ref>. Aristote précise que les causes dont traite la démonstration correspondent aux quatre types de causes exposés dans la ''Physique'' : la cause formelle (l'essence), la cause matérielle (ce dont une chose est faite), la cause efficiente (ce qui produit le changement) et la cause finale (ce en vue de quoi)<ref>Aristote, ''Seconds Analytiques'', II, 11, 94 a 20-24 ; cf. ''Physique'', II, 3, 194 b 23-195 b 30</ref>. Toutefois, dans les sciences théoriques comme les mathématiques, c'est surtout la cause formelle qui intervient, car les objets mathématiques ne subissent ni génération ni corruption. Dans les sciences physiques et biologiques, en revanche, les quatre causes peuvent être invoquées selon les circonstances<ref>Christopher Shields (éd.), ''The Oxford Handbook of Aristotle'', Oxford, Oxford University Press, 2012, p. 176-180</ref>. ==== Les principes de la science : axiomes, hypothèses et définitions ==== Toute démonstration présuppose des principes (''archai'') qui, eux-mêmes, ne peuvent être démontrés. Aristote refuse vigoureusement l'idée d'une régression à l'infini dans la chaîne des démonstrations, de même qu'il rejette la démonstration circulaire où l'on prouverait A par B et B par A<ref>Aristote, ''Seconds Analytiques'', I, 3, 72 b 5-18</ref>. Il faut donc admettre l'existence de principes premiers, qui sont connus sans démonstration. Ces principes se divisent en trois catégories<ref>Jonathan Barnes, ''Aristotle's Posterior Analytics'', op. cit., p. 94-99</ref>. D'abord, les axiomes (''axiômata'') ou principes communs, qui s'appliquent à toutes les sciences. Le plus fondamental est le principe de non-contradiction : « Il est impossible que le même attribut appartienne et n'appartienne pas en même temps au même sujet et sous le même rapport »<ref>Aristote, ''Métaphysique'', Γ (IV), 3, 1005 b 19-20</ref>. Ce principe n'est pas démontrable, car toute démonstration le présupposerait, mais il peut être défendu par réfutation de ceux qui le nient. Le principe du tiers exclu – « De deux propositions contradictoires, l'une est nécessairement vraie et l'autre fausse » – constitue un autre axiome fondamental. Ces principes, Aristote le précise dans les ''Seconds Analytiques'', sont utilisés par toutes les sciences, mais chacune en fait l'usage que requiert son genre propre<ref>Aristote, ''Seconds Analytiques'', I, 11, 77 a 26-31</ref>. Ensuite, les hypothèses (''hypotheseis'') et les thèses (''theseis''), qui affirment l'existence ou la non-existence de certaines entités propres à chaque science. Une hypothèse suppose l'existence de l'objet d'étude (par exemple, l'arithmétique suppose l'existence de l'unité, la géométrie celle du point et de la ligne), tandis qu'une thèse affirme quelque chose sans le prouver, soit à titre de postulat contraire à l'opinion commune, soit à titre de principe accepté par l'élève<ref>Aristote, ''Seconds Analytiques'', I, 2, 72 a 14-24 ; I, 10, 76 b 23-34</ref>. Ces principes délimitent le domaine d'une science : on ne peut démontrer les propriétés des triangles sans avoir d'abord posé l'existence des figures géométriques. Enfin, les définitions (''horoi'') énoncent ce qu'est une chose, son essence (''ti esti''). La définition n'est pas une démonstration, car elle ne prouve rien : elle se contente de manifester l'essence<ref>Aristote, ''Seconds Analytiques'', II, 3, 90 b 24-27</ref>. Cependant, elle entretient avec la démonstration des rapports complexes. Aristote examine longuement la question de savoir si l'on peut démontrer l'essence, et il conclut négativement : la définition et la démonstration sont deux opérations distinctes<ref>Aristote, ''Seconds Analytiques'', II, 4, 91 a 35-b 11</ref>. On ne démontre pas « ce qu'est l'homme », on le définit. Toutefois, certaines définitions peuvent découler de démonstrations : c'est le cas des définitions causales, qui expliquent pourquoi une chose a telle propriété essentielle. Par exemple, la définition du tonnerre comme « bruit dans les nuages causé par l'extinction du feu » révèle à la fois l'essence et la cause du phénomène<ref>Aristote, ''Seconds Analytiques'', II, 8, 93 b 7-14</ref>. ==== Unité et diversité des sciences ==== Un principe capital de l'épistémologie aristotélicienne est l'interdiction du passage d'un genre à un autre (''metabasis eis allo genos''). Chaque science se définit par son genre propre, c'est-à-dire par le domaine d'objets qu'elle étudie, et elle ne peut emprunter ses principes ou ses démonstrations à une autre science<ref>Aristote, ''Seconds Analytiques'', I, 7, 75 a 38-b 2</ref>. On ne peut pas, par exemple, démontrer un théorème géométrique par des principes arithmétiques, car le nombre et la grandeur constituent deux genres différents. Cette règle garantit l'autonomie des sciences et interdit toute confusion entre les domaines du savoir<ref>David Bronstein, ''Aristotle on Knowledge and Learning'', op. cit., p. 177-183</ref>. Aristote admet cependant trois exceptions à cette règle. D'abord, lorsque les objets d'une science tombent sous ceux d'une autre comme des espèces sous un genre commun : ainsi l'optique et la mécanique utilisent les principes de la géométrie, l'harmonique ceux de l'arithmétique<ref>Aristote, ''Seconds Analytiques'', I, 7, 75 b 14-17</ref>. On dit alors que ces sciences sont « subordonnées » (''hypo'') aux sciences plus générales. Ensuite, les sciences peuvent partager les axiomes communs, bien que chacune ne les utilise que dans la mesure où ils s'appliquent à son genre propre. Enfin, il existe des sciences mixtes ou « moyennes » (''metaxu''), comme l'astronomie mathématique, qui appliquent les principes mathématiques aux phénomènes physiques<ref>Aristote, ''Physique'', II, 2, 194 a 7-12</ref>. Cette théorie de l'unité et de la diversité des sciences fonde l'organisation aristotélicienne du savoir. Chaque science possède une méthode adaptée à son objet : les mathématiques procèdent par démonstration nécessaire à partir de principes évidents, la physique doit tenir compte du changement et de la contingence, la biologie observe les régularités qui se produisent « dans la plupart des cas » (''hôs epi to poly''), l'éthique et la politique se contentent de vérités approximatives car « le bien et le juste comportent de grandes divergences et incertitudes »<ref>Aristote, ''Éthique à Nicomaque'', I, 3, 1094 b 14-16</ref>. Il n'y a donc pas de science unique et universelle, mais une pluralité de sciences régies par les principes que leur impose leur objet propre<ref>Christopher Shields (éd.), ''The Oxford Handbook of Aristotle'', op. cit., p. 173-176</ref>. ==== Le problème de la connaissance des principes : induction et intellection ==== La théorie de la démonstration soulève un problème redoutable : si toute science procède de principes indémontrables, comment connaître ces principes eux-mêmes ? Aristote refuse les deux solutions extrêmes : soit dire qu'il n'y a pas de science possible puisque les principes sont inconnaissables, soit affirmer que tout est démontrable et accepter une régression à l'infini<ref>Aristote, ''Seconds Analytiques'', I, 3, 72 b 5-18</ref>. Il faut donc admettre qu'il existe un mode de connaissance des principes distinct de la démonstration<ref>Jonathan Barnes, ''Aristotle's Posterior Analytics'', op. cit., p. 259-267</ref>. Le célèbre et difficile chapitre final des ''Seconds Analytiques'' (II, 19) expose la solution d'Aristote. La connaissance des principes résulte d'un processus qui part de la sensation (''aisthèsis''), passe par la mémoire (''mnèmè''), puis par l'expérience (''empeiria''), pour aboutir à la saisie de l'universel. « De la sensation naît la mémoire, et de la mémoire fréquemment répétée d'une même chose naît l'expérience ; car des souvenirs en grand nombre constituent une seule expérience. De l'expérience, ou de tout universel qui s'est fixé dans l'âme, de l'un en dehors du multiple, de ce qui est identique en tous ces cas particuliers, provient alors le principe de l'art et de la science »<ref>Aristote, ''Seconds Analytiques'', II, 19, 100 a 3-9</ref>. Ce processus est appelé induction (''epagôgè''). L'induction n'est pas ici la simple énumération de cas particuliers conduisant à une généralisation hasardeuse, mais le mouvement naturel de l'âme qui, à partir d'une multiplicité sensible, dégage progressivement l'universel qui s'y trouve incarné. Aristote compare ce processus à une armée en déroute où, un soldat s'étant arrêté, un autre s'arrête auprès de lui, puis un autre, jusqu'à ce que l'ordre primitif soit rétabli : de même, à partir des sensations éparses, l'âme reconstitue l'unité de la forme universelle<ref>Aristote, ''Seconds Analytiques'', II, 19, 100 a 12-14</ref>. Cette métaphore suggère que l'induction n'est pas une opération purement logique, mais un mouvement ontologique de l'âme vers l'intelligible<ref>David Bronstein, ''Aristotle on Knowledge and Learning'', op. cit., p. 225-247</ref>. La saisie ultime des principes premiers relève d'une faculté spécifique qu'Aristote nomme intellect (''nous'') ou intuition intellectuelle. L'intellect est « la disposition par laquelle nous connaissons les principes »<ref>Aristote, ''Seconds Analytiques'', II, 19, 100 b 12</ref>. Il ne s'agit pas d'une faculté mystérieuse ou d'une illumination surnaturelle, mais de l'achèvement naturel du processus cognitif humain. Aristote affirme que nous possédons en puissance la capacité de connaître les principes, et que cette capacité s'actualise progressivement par l'exercice de la sensation et de la pensée. L'intellect est ainsi la plus haute forme de connaissance, supérieure même à la science démonstrative, car il appréhende directement et sans intermédiaire les vérités premières dont toute démonstration dépend<ref>Aristote, ''Éthique à Nicomaque'', VI, 6, 1141 a 7-8</ref>. Cette solution pose toutefois de nombreuses difficultés. Comment l'intellect peut-il être à la fois une capacité innée et le résultat d'un processus d'apprentissage ? Comment peut-il appréhender l'universel sans passer par le raisonnement ? Aristote ne répond pas clairement à ces questions, et ses successeurs – Alexandre d'Aphrodise, Avicenne, Averroès, Thomas d'Aquin – proposeront des interprétations divergentes du statut de l'intellect et de son rapport à l'expérience sensible. Ce qui demeure certain, c'est qu'Aristote refuse tout innéisme platonicien : nous ne naissons pas avec la connaissance des principes, nous l'acquérons progressivement à partir de l'expérience du monde sensible<ref>Orna Harari, ''Knowledge and Demonstration: Aristotle's Posterior Analytics'', Dordrecht, Springer, 2004, p. 26-31</ref>. ==== Les limites de la science démonstrative ==== Aristote ne prétend pas que toute connaissance relève de la démonstration. Au contraire, il délimite soigneusement le domaine de la science démonstrative et reconnaît l'existence d'autres formes légitimes de savoir. D'abord, tout ce qui relève de l'accident pur échappe à la science : ce qui arrive parfois mais pas toujours, ce qui est contingent et irrégulier, ne peut faire l'objet d'une démonstration nécessaire<ref>Aristote, ''Seconds Analytiques'', I, 30, 87 b 19-27</ref>. La science porte sur le nécessaire ou, à défaut, sur ce qui se produit « dans la plupart des cas ». Ensuite, il existe des propositions immédiates (''amesa'') qui, bien que vraies et universelles, ne peuvent être démontrées parce qu'il n'y a pas de moyen terme entre leur sujet et leur prédicat. Ces propositions constituent précisément les principes de la démonstration<ref>Aristote, ''Seconds Analytiques'', I, 2, 72 a 7-8</ref>. Les axiomes, les définitions, les hypothèses d'existence ne se démontrent pas : on les saisit par intuition intellectuelle ou on les pose comme conditions de la science<ref>Jonathan Barnes, ''Aristotle's Posterior Analytics'', op. cit., p. 179-182</ref>. Enfin, Aristote reconnaît que certaines formes de raisonnement, bien que n'étant pas des démonstrations au sens strict, jouent un rôle important dans la recherche scientifique. L'induction, comme on l'a vu, permet d'accéder aux principes. L'exemple (''paradeigma''), qui consiste à raisonner du particulier au particulier par l'intermédiaire d'un universel, est utile en rhétorique et en délibération politique<ref>Aristote, ''Premiers Analytiques'', II, 24, 68 b 38-69 a 19</ref>. Le raisonnement par signes (''sèmeion''), qui conclut de l'effet à la cause ou inversement, peut fournir des indications précieuses même s'il ne garantit pas la nécessité. Tous ces modes de raisonnement, bien qu'imparfaits du point de vue démonstratif, témoignent de la richesse et de la diversité de la pensée rationnelle. ==== Portée et postérité de la théorie démonstrative ==== La théorie aristotélicienne de la science démonstrative a exercé une influence considérable sur toute l'histoire de la philosophie et des sciences. Elle a fourni le modèle épistémologique dominant pendant deux millénaires, de l'Antiquité grecque à la Renaissance, en passant par la scolastique médiévale. Les mathématiques d'Euclide, avec leur méthode axiomatique-déductive, constituent l'incarnation parfaite de l'idéal aristotélicien : des définitions claires, des axiomes évidents, des démonstrations rigoureuses enchaînant les théorèmes selon un ordre nécessaire<ref>David Bronstein, ''Aristotle on Knowledge and Learning'', op. cit., p. 3-10</ref>. Cependant, la théorie aristotélicienne a aussi fait l'objet de critiques sévères, particulièrement à partir de la révolution scientifique moderne. Descartes reproche au syllogisme de ne rien apprendre de nouveau, puisque la conclusion est déjà contenue dans les prémisses. Bacon dénonce la stérilité de la logique aristotélicienne et lui oppose sa méthode inductive fondée sur l'observation et l'expérimentation. Les sciences modernes – physique galiléenne, mécanique newtonienne, biologie darwinienne – ne se conforment pas au schéma aristotélicien : elles procèdent par hypothèses provisoires, expérimentations, inférences probabilistes, et non par démonstrations nécessaires à partir de principes évidents<ref>Jonathan Barnes, ''Aristotle's Posterior Analytics'', op. cit., p. xviii-xix</ref>. Ces critiques, pour fondées qu'elles soient, ne doivent pas faire oublier les apports décisifs d'Aristote. Il a établi les règles de la déduction valide, distingué clairement la forme et la matière du raisonnement, montré la nécessité des principes premiers et l'impossibilité d'une science unique et universelle. Sa théorie des causes et sa conception de l'explication scientifique comme connaissance du « pourquoi » restent fécondes. Surtout, il a compris que la science n'est pas un simple catalogue de faits, mais un système organisé de propositions reliées par des rapports logiques et causaux. Cette exigence de systématicité et de rigueur demeure au cœur de toute entreprise scientifique, même si les formes concrètes de la rationalité scientifique ont profondément évolué depuis Aristote<ref>Christopher Shields (éd.), ''The Oxford Handbook of Aristotle'', op. cit., p. 169-202</ref>. En définitive, les ''Analytiques'' représentent l'effort le plus abouti de l'Antiquité pour penser les conditions de la connaissance véritable. Aristote y pose les questions fondamentales de toute épistémologie : qu'est-ce que savoir ? Comment passe-t-on de l'ignorance à la science ? Quels sont les rapports entre la sensation, l'expérience et la raison ? Quelle est la nature des principes premiers ? Ces questions, loin d'être résolues une fois pour toutes, continuent de nourrir la réflexion philosophique contemporaine sur la science, ses méthodes et ses fondements. === Chapitre IV : Les ''Topiques'' – L'art de la dialectique === ==== La dialectique et les lieux communs ==== Si les ''Analytiques'' traitent de la démonstration scientifique, les ''Topiques'' concernent le raisonnement dialectique. La dialectique part de prémisses probables (''endoxa''), c'est-à-dire d'opinions généralement admises par tous, ou par les plus nombreux, ou par les sages<ref>Aristote, ''Topiques'', I, 1, 100a29-b23</ref>. Elle ne vise pas la certitude scientifique mais l'art de discuter de manière raisonnable sur toute question. Les ''topoi'' ou lieux sont des schèmes d'argumentation valables dans de multiples domaines. Par exemple, le lieu « du plus et du moins » permet d'argumenter ainsi : si ce qui est plus susceptible d'appartenir à quelque chose ne lui appartient pas, ce qui l'est moins ne lui appartiendra pas non plus<ref>Aristote, ''Topiques'', II, 10, 114b37-115a3</ref>. Aristote recense systématiquement ces lieux et les classe selon les catégories. ==== L'utilité de la dialectique ==== Aristote assigne trois usages à la dialectique. D'abord, elle sert d'entraînement intellectuel, développant la capacité d'argumenter sur n'importe quel sujet. Ensuite, elle est utile dans les rencontres avec autrui, permettant de discuter à partir d'opinions communes. Enfin, et surtout, elle sert aux sciences philosophiques elles-mêmes<ref>Aristote, ''Topiques'', I, 2, 101a25-b4</ref>. En effet, la dialectique permet d'examiner de manière critique (''peirastike'') les principes des sciences, en confrontant les opinions opposées sur un sujet. Elle permet aussi de parcourir les difficultés (''aporiai'') et de dégager progressivement les principes véritables. C'est cette méthode dialectique qu'Aristote met systématiquement en œuvre dans ses traités, commençant par recenser les opinions des prédécesseurs, examinant les difficultés, avant de proposer sa propre solution<ref>Aristote, ''Éthique à Nicomaque'', VII, 1, 1145b2-7</ref>. === Chapitre V : Les ''Réfutations sophistiques'' – Les paralogismes et leur réfutation === ==== Les sophismes et leurs types ==== Les ''Réfutations sophistiques'' complètent les ''Topiques'' en examinant les raisonnements fallacieux utilisés par les sophistes. Aristote distingue treize types de sophismes, dont six dépendent du langage (l'homonymie, l'amphibologie, la composition, la division, l'accentuation, la forme de l'expression) et sept sont indépendants du langage (l'accident, le passage du qualifié à l'absolu, l'ignorance de la réfutation, la pétition de principe, le faux enchaînement, la prise de l'antécédent comme conséquent, la réunion de plusieurs questions en une)<ref>Aristote, ''Réfutations sophistiques'', 4, 165b23-166b19</ref>. L'exemple du sophisme par accident est célèbre : « Coriskos est différent de Socrate / Or Socrate est un homme / Donc Coriskos est différent d'un homme ». Ce raisonnement est vicieux car il confond « être différent de Socrate » et « être différent d'un homme », alors que la première propriété appartient à Coriskos par accident, en tant qu'il se trouve que Socrate est l'homme en question<ref>Aristote, ''Réfutations sophistiques'', 5, 166b28-36</ref>. ==== La portée philosophique de la réfutation des sophismes ==== Au-delà de son utilité pratique pour déjouer les pièges argumentatifs, cette étude des sophismes a une portée philosophique profonde. Elle montre qu'il existe des règles objectives du raisonnement correct, indépendantes de l'habileté rhétorique. Contre le relativisme sophistique, qui prétendait qu'on peut également bien défendre n'importe quelle thèse et son contraire, Aristote établit qu'il y a des critères rationnels pour distinguer le vrai du faux, le raisonnement valide de l'invalide<ref>Aristote, ''Réfutations sophistiques'', 11, 171b34-172a7</ref>. Cette défense de la possibilité d'une vérité objective et d'une rationalité universelle contre le relativisme sophiste constitue l'un des enjeux majeurs de la philosophie aristotélicienne, qui se prolongera dans sa défense du principe de non-contradiction contre ceux qui, comme Héraclite selon Aristote, semblaient le nier<ref>Aristote, ''Métaphysique'', Γ, 3-4, 1005b19-1008b31</ref>. == Deuxième partie : La philosophie de la nature == === Chapitre I : La ''Physique'' – Les principes du mouvement et du changement === ==== La nature comme principe interne de mouvement ==== La physique aristotélicienne n'est pas la physique au sens moderne, science mathématisée des phénomènes matériels. Elle est la science des êtres qui possèdent en eux-mêmes un principe de mouvement et de repos<ref>Aristote, ''Physique'', II, 1, 192b13-23</ref>. Un être naturel (''phusei on'') se distingue ainsi d'un être artificiel : une plante croît et se reproduit par sa propre nature, tandis qu'un lit, fait de bois, ne produit pas naturellement d'autres lits. Cette définition fonde l'autonomie de la nature et de son étude. Les êtres naturels ont leurs principes en eux-mêmes et ne dépendent pas de causes extérieures pour leur mouvement essentiel. C'est pourquoi le physicien doit étudier non seulement la forme des choses, mais aussi leur matière, car la matière est le principe des mouvements naturels<ref>Aristote, ''Physique'', II, 2, 194a12-27</ref>. En cela, Aristote se distingue du platonisme, qui tendait à négliger la matière et à ramener l'explication physique aux formes intelligibles séparées. ==== Les quatre causes ==== Pour comprendre pleinement un être naturel, il faut connaître ses quatre causes. La cause matérielle est ce dont une chose est faite (le bronze est la cause matérielle de la statue). La cause formelle est l'essence ou la définition de la chose (la forme de l'Hermès est la cause formelle de la statue d'Hermès). La cause efficiente est ce qui a produit la chose (le sculpteur est la cause efficiente de la statue). La cause finale est ce en vue de quoi la chose existe (la décoration du temple est la cause finale de la statue)<ref>Aristote, ''Physique'', II, 3, 194b23-195a3</ref>. Ces quatre causes ne s'excluent pas mais se complètent. Souvent même, plusieurs causes coïncident : la forme, le moteur et la fin peuvent être identiques. Dans la génération naturelle d'un homme, c'est la forme d'homme qui est à la fois dans le géniteur (cause efficiente), dans l'engendré (cause formelle), et visée comme fin du processus de génération (cause finale)<ref>Aristote, ''Physique'', II, 7, 198a24-27</ref>. ==== Le mouvement et ses espèces ==== Le mouvement (''kinesis'') ou changement est défini comme « l'actualité de ce qui est en puissance en tant que tel »<ref>Aristote, ''Physique'', III, 1, 201a10-11</ref>. Cette définition énigmatique signifie que le mouvement est l'acte d'un être qui n'est pas encore pleinement actualisé, mais en voie de l'être. C'est un acte imparfait, par opposition à l'acte parfait (''energeia'') qui est l'actualité pleine et achevée d'une chose. Aristote distingue quatre espèces de changement selon les catégories : le changement substantiel (génération et corruption), le changement qualitatif (altération), le changement quantitatif (accroissement et diminution), et le changement local (déplacement ou translation)<ref>Aristote, ''Physique'', V, 1, 225a34-225b9</ref>. Parmi ces changements, le mouvement local est premier en nature et en dignité, car il peut exister sans les autres, tandis que les autres le présupposent<ref>Aristote, ''Physique'', VIII, 7, 260a26-260b7</ref>. ==== L'infinité du mouvement et le Premier Moteur ==== Le mouvement est-il éternel ou a-t-il commencé ? Aristote démontre qu'il doit être éternel, car toute génération du mouvement supposerait un mouvement antérieur<ref>Aristote, ''Physique'', VIII, 1, 251a8-252b6</ref>. Or tout ce qui est mû est mû par autre chose<ref>Aristote, ''Physique'', VII, 1, 241b24-242a15</ref>. Il faut donc, pour éviter une régression à l'infini des moteurs, qu'existe un Premier Moteur absolument immobile qui meut éternellement. Ce Premier Moteur meut sans être mû, à la manière dont l'objet du désir meut le désirant. Il est donc cause finale plutôt qu'efficiente du mouvement éternel du ciel<ref>Aristote, ''Physique'', VIII, 6, 259b20-260a19</ref>. Cette preuve physique du Premier Moteur prépare les développements métaphysiques du livre Λ de la ''Métaphysique'' sur Dieu comme Pensée de la Pensée. === Chapitre II : Le ''De caelo'' – Le ciel et les éléments === ==== La cinquième essence et l'éternité du monde ==== Le traité ''Du ciel'' étudie d'abord le mouvement circulaire du ciel et sa substance. Aristote établit qu'il existe un cinquième élément, l'éther (''aither''), distinct des quatre éléments sublunaires, qui est animé d'un mouvement circulaire naturel et éternel<ref>Aristote, ''Du ciel'', I, 2, 269a2-269b17</ref>. Cette cinquième essence ou quintessence ne connaît ni génération ni corruption, ni accroissement ni diminution, ni altération. Le ciel est donc éternel, sans commencement ni fin. Aristote réfute les cosmogonies qui, comme celle du ''Timée'' de Platon, faisaient naître le monde dans le temps<ref>Aristote, ''Du ciel'', I, 10-12, 279b4-284a35</ref>. Cette thèse de l'éternité du monde entrera en conflit, au Moyen Âge, avec le dogme chrétien de la création ex nihilo, donnant lieu à de subtiles tentatives de conciliation comme celles de Thomas d'Aquin. ==== La structure du cosmos ==== Aristote défend une cosmologie géocentrique. La Terre, sphérique, est immobile au centre de l'univers<ref>Aristote, ''Du ciel'', II, 14, 296b6-297b21</ref>. Autour d'elle se trouvent les sphères concentriques des quatre éléments sublunaires (terre, eau, air, feu), puis les sphères célestes portant les astres. Chaque planète est portée par une sphère animée d'un mouvement circulaire uniforme, et l'ensemble de ces sphères forme un système dont le mouvement est communiqué par le Premier Moteur immobile situé à la périphérie de l'univers<ref>Aristote, ''Du ciel'', II, 12, 292a14-293a11</ref>. Cette cosmologie restera dominante jusqu'à Copernic, Kepler et Galilée. Elle repose sur la distinction ontologique entre monde supralunaire, parfait et immuable, et monde sublunaire, siège de la génération et de la corruption. Cette distinction fondamentale sera ébranlée par la découverte, au début du XVIIe siècle, des taches solaires et de la nature non éthérée des comètes. ==== Les quatre éléments et leurs mouvements naturels ==== Dans le monde sublunaire, les quatre éléments possèdent des mouvements naturels rectilignes. La terre et l'eau, éléments lourds, se dirigent naturellement vers le bas, c'est-à-dire vers le centre de l'univers. L'air et le feu, éléments légers, se dirigent naturellement vers le haut, c'est-à-dire vers la périphérie du monde sublunaire<ref>Aristote, ''Du ciel'', IV, 4, 311a15-312a21</ref>. Ces mouvements naturels expliquent la disposition concentrique des éléments : la terre au centre, recouverte par l'eau, puis l'air, puis le feu. Ils fondent aussi la dynamique aristotélicienne, selon laquelle chaque corps tend naturellement vers son lieu propre avec une vitesse proportionnelle à son poids et inversement proportionnelle à la résistance du milieu<ref>Aristote, ''Du ciel'', I, 8, 277a27-277b8</ref>. Cette physique sera critiquée par la révolution galiléenne, qui montrera que dans le vide tous les corps tombent à la même vitesse. === Chapitre III : Le ''De generatione et corruptione'' – Génération, corruption et transformation === ==== Les deux espèces de génération ==== La génération absolue (''genesis haplôs'') est le passage du non-être à l'être d'une substance : un homme naît. La génération relative (''genesis ti'') est l'altération d'une substance qui demeure : un homme devient musicien<ref>Aristote, ''De la génération et la corruption'', I, 3, 317a17-b18</ref>. Aristote cherche à éviter deux écueils : l'éléatisme de Parménide, qui niait tout devenir parce qu'il serait passage du non-être à l'être, et l'atomisme de Démocrite, qui réduisait toute génération à une simple agrégation d'atomes éternels. La solution réside dans les concepts de matière et de forme. Dans toute génération, quelque chose persiste (la matière) tandis que quelque chose advient (la forme). Dans la génération absolue, la matière première indéterminée reçoit une nouvelle forme substantielle. Dans la génération relative, la substance demeure identique tandis que ses accidents changent<ref>Aristote, ''De la génération et la corruption'', I, 4, 319b6-320a7</ref>. ==== L'action et la passion – Le contact et le mélange ==== Pour qu'il y ait génération, il faut qu'un agent agisse sur un patient. Mais l'action n'est pas séparée de la passion : ce sont deux aspects d'un même changement, considéré tantôt du point de vue de l'agent, tantôt de celui du patient<ref>Aristote, ''De la génération et la corruption'', I, 7, 323b18-324a19</ref>. Le mélange (''mixis'') se distingue de la simple juxtaposition par le fait que les composants, tout en formant un nouveau corps homogène, peuvent être restitués par analyse. Aristote critique l'atomisme qui ramène le mélange à un simple entrelacement d'atomes indivisibles : si les composants demeuraient inchangés, il n'y aurait pas véritable mélange mais juxtaposition<ref>Aristote, ''De la génération et la corruption'', I, 10, 327a30-328a5</ref>. Dans le véritable mélange, les qualités des composants s'unissent pour former une nouvelle qualité intermédiaire. === Chapitre IV : Les ''Météorologiques'' – Les phénomènes du monde sublunaire === ==== Le programme de la science naturelle ==== Les ''Météorologiques'' s'insèrent dans le vaste programme de la physique aristotélicienne. Après avoir traité des principes du mouvement dans la ''Physique'', de la structure de l'univers dans le ''De caelo'', et des transformations des éléments dans le ''De generatione'', Aristote étudie maintenant les phénomènes qui résultent de l'action du ciel sur le monde sublunaire<ref>Aristote, ''Météorologiques'', I, 1, 338a20-339a5</ref>. Le terme « météorologique » a ici un sens beaucoup plus large qu'aujourd'hui. Il désigne tous les phénomènes qui se produisent dans la région entre la surface de la terre et la sphère de la lune : non seulement les phénomènes atmosphériques (pluie, vent, tonnerre), mais aussi les comètes, la Voie Lactée, les tremblements de terre, la mer et ses marées, les fleuves et leurs crues, etc. ==== Les deux exhalaisons et l'explication des météores ==== Le principe explicatif fondamental est celui des deux exhalaisons. Le soleil, par son mouvement et sa chaleur, produit deux sortes d'évaporations terrestres : une exhalaison humide (vapeur d'eau) et une exhalaison sèche (exhalations terrestres combustibles)<ref>Aristote, ''Météorologiques'', I, 3, 340b19-341a12</ref>. Ces deux exhalaisons, s'élevant dans l'atmosphère, donnent naissance à la plupart des phénomènes météorologiques. L'exhalaison humide, refroidie dans les régions supérieures, retombe en pluie. L'exhalaison sèche, enflammée au contact de la sphère du feu, produit les phénomènes ignés : étoiles filantes, comètes, Voie Lactée<ref>Aristote, ''Météorologiques'', I, 4-8, 341b6-346a34</ref>. Ces explications, souvent fausses, témoignent néanmoins d'une démarche rationnelle qui cherche à rendre compte des phénomènes par des causes naturelles, sans recourir au mythe ou au surnaturel. === Chapitre V : Le ''De anima'' – L'âme, forme du corps vivant === ==== Définition de l'âme ==== Le traité ''De l'âme'' appartient à la fois à la physique, puisque l'âme est principe de mouvement du corps vivant, et à ce que nous appellerions aujourd'hui psychologie ou philosophie de l'esprit. Après avoir réfuté les conceptions de ses prédécesseurs, Aristote définit l'âme comme « l'entéléchie première d'un corps naturel ayant la vie en puissance »<ref>Aristote, ''De l'âme'', II, 1, 412a27-28</ref>. Cette définition signifie que l'âme est la forme du corps vivant, son acte premier, principe de toutes ses opérations vitales. Elle n'est ni une substance séparée qui habiterait le corps comme un pilote dans son navire (critique du dualisme platonicien), ni une simple harmonie ou arrangement du corps (critique du matérialisme), mais la forme substantielle indissociable de son corps propre<ref>Aristote, ''De l'âme'', II, 1, 413a3-10</ref>. ==== Les trois âmes et leurs facultés ==== Il y a trois niveaux d'âme, correspondant aux trois types de vivants. L'âme nutritive, commune à tous les vivants (plantes, animaux, hommes), assure les fonctions de nutrition, croissance et reproduction. L'âme sensitive, propre aux animaux, ajoute la sensation, le désir et le mouvement local. L'âme intellective, propre à l'homme, ajoute la pensée rationnelle<ref>Aristote, ''De l'âme'', II, 2-3, 413a20-414b28</ref>. Ces trois âmes ne sont pas trois substances séparées dans le vivant, mais trois niveaux de capacités, les supérieures incluant les inférieures. Un animal possède toutes les capacités de la plante plus les siennes propres ; l'homme possède celles de la plante et de l'animal plus l'intelligence. ==== La sensation et l'intellection ==== La sensation est la réception de la forme sensible sans la matière, comme la cire reçoit l'empreinte du sceau sans recevoir le fer ou l'or dont il est fait<ref>Aristote, ''De l'âme'', II, 12, 424a17-21</ref>. Chaque sens a son objet propre (la couleur pour la vue, le son pour l'ouïe), qu'il perçoit infailliblement. Il existe aussi un sens commun qui perçoit les sensibles communs (mouvement, repos, nombre, figure) et permet de comparer les données de différents sens<ref>Aristote, ''De l'âme'', III, 1-2, 425a13-427a16</ref>. L'intellect (''nous'') est la faculté de penser les formes intelligibles. Aristote distingue l'intellect patient, qui reçoit les formes, et l'intellect agent, qui les abstrait à partir des images sensibles<ref>Aristote, ''De l'âme'', III, 5, 430a10-25</ref>. Ce dernier est séparé, impassible, sans mélange avec le corps, et Aristote suggère qu'il pourrait être immortel, bien que cette thèse reste obscure et ait donné lieu à d'innombrables controverses dans la tradition commentatoriale arabe et latine. === Chapitre VI : Les ''Parva Naturalia'' – Les fonctions vitales === ==== Organisation et contenu des Petits traités d'histoire naturelle ==== Les ''Parva Naturalia'' regroupent un ensemble de courts traités sur diverses fonctions vitales et leurs organes. Ils complètent le ''De anima'' en étudiant non plus l'âme en général mais ses opérations particulières, considérées dans leur rapport au corps<ref>Aristote, ''De la sensation'', 1, 436a1-17</ref>. Le ''De sensu'' traite des sensibles et des organes des sens. Le ''De memoria'' examine la mémoire et la réminiscence, faculté de retrouver volontairement un souvenir. Le ''De somno'' et le ''De insomniis'' étudient le sommeil et les rêves, expliqués par les mouvements qui persistent dans les organes sensoriels après la disparition des stimuli externes<ref>Aristote, ''Du sommeil'', 2, 455a12-455b2</ref>. Le ''De divinatione'' traite de la divination dans les songes, dont Aristote nie la valeur prophétique, y voyant de simples coïncidences. ==== Le cœur, principe de la vie ==== Aristote localise le principe vital et le sens commun dans le cœur plutôt que dans le cerveau<ref>Aristote, ''Des parties des animaux'', III, 4, 666a11-18</ref>. C'est dans le cœur que réside la chaleur innée, principe de toutes les opérations vitales. Le cerveau, froid et humide, sert seulement à refroidir la chaleur excessive du cœur<ref>Aristote, ''Des parties des animaux'', II, 7, 652a24-652b34</ref>. Cette erreur anatomique, que Galien corrigera partiellement en reconnaissant au cerveau un rôle dans les sensations et les mouvements volontaires, illustre les limites de la science aristotélicienne, tributaire des moyens techniques de son époque. Elle n'enlève rien à la valeur de la démarche méthodologique d'Aristote, qui cherche à expliquer les phénomènes vitaux par des causes naturelles observables. == Troisième partie : La biologie et la connaissance du vivant == === Chapitre I : L'''Histoire des animaux'' – L'enquête empirique === ==== Méthode et organisation de l'œuvre ==== L'''Histoire des animaux'' (''Peri ta zôia historiai''), en dix livres dont trois sont probablement apocryphes, constitue un immense travail de collection et de description des faits concernant les animaux<ref>Aristote, ''Histoire des animaux'', I, 6, 491a7-14</ref>. Le terme ''historia'' signifie ici « enquête » ou « recherche », non pas histoire au sens temporel. Il s'agit d'un exposé systématique des données empiriques qui servira de fondement aux explications causales développées dans les autres traités biologiques. Aristote y décrit avec une précision remarquable l'anatomie externe et interne, le mode de reproduction, le régime alimentaire, les habitats, les comportements de plusieurs centaines d'espèces animales. Cette œuvre témoigne d'observations personnelles minutieuses, complétées par des témoignages de pêcheurs, chasseurs et éleveurs. C'est le premier grand ouvrage de zoologie systématique de l'histoire occidentale<ref>D'Arcy Wentworth Thompson, introduction à sa traduction de l'''Histoire des animaux'', Oxford, 1910</ref>. ==== Principes de classification ==== Aristote organise les animaux selon une classification qui prend en compte de multiples critères : mode de reproduction (vivipares, ovipares, larvipares), habitat (terrestres, aquatiques, aériens), régime alimentaire (carnivores, herbivores), présence ou absence de sang<ref>Aristote, ''Histoire des animaux'', I, 6, 490b7-491a6</ref>. La division fondamentale oppose les animaux sanguins (en gros, les vertébrés) et les animaux non-sanguins (les invertébrés). Contrairement à une idée reçue, Aristote ne se contente pas d'une classification dichotomique rigide. Il reconnaît que les espèces forment un continuum, avec des formes intermédiaires qui rendent difficile le tracé de frontières nettes. Ainsi, les éponges occupent-elles une position intermédiaire entre les plantes et les animaux<ref>Aristote, ''Histoire des animaux'', I, 1, 487b6-10</ref>. Cette intuition d'une scala naturae, d'une échelle de la nature, influencera durablement la pensée biologique jusqu'à Lamarck et Darwin. === Chapitre II : Les ''Parties des animaux'' – La causalité finale dans le vivant === ==== Méthode de la biologie : de la description à l'explication ==== Le traité ''Des parties des animaux'' se propose d'expliquer pourquoi les animaux possèdent telles parties avec telles configurations. Après l'''Historia'', qui répond à la question « qu'est-ce qui est ? », vient l'étude qui répond à « pourquoi est-ce ainsi ? »<ref>Aristote, ''Des parties des animaux'', I, 1, 639b7-640a9</ref>. Le livre I constitue un véritable traité de méthodologie scientifique. Aristote y défend la légitimité de l'étude de la nature, même si celle-ci n'atteint pas la dignité des réalités éternelles. Il affirme qu'« il faut étudier chaque être sans dégoût, car en chacun réside quelque chose de naturel et de beau »<ref>Aristote, ''Des parties des animaux'', I, 5, 645a15-17</ref>. Cette célébration de la beauté et de l'ordre de la nature, même dans ses manifestations les plus humbles, exprime l'émerveillement du savant devant l'infinie diversité du vivant. ==== La priorité de la cause finale ==== Dans l'étude des vivants, la cause finale doit primer. C'est en vue d'une fonction déterminée que chaque organe est configuré d'une certaine manière. Les dents de devant sont tranchantes pour couper, les molaires larges et plates pour broyer, non par hasard, mais parce que telle est leur fonction dans la nutrition<ref>Aristote, ''Des parties des animaux'', III, 1, 661b23-662a8</ref>. Aristote critique vigoureusement le mécanisme d'Empédocle et des atomistes, qui expliquaient les organes par des rencontres fortuites de particules matérielles. Comment le hasard pourrait-il produire la régularité observée dans la nature, où chaque espèce reproduit invariablement la même organisation ? La matière joue certes un rôle, par ses propriétés et ses nécessités, mais elle est toujours au service de la forme et de la fin<ref>Aristote, ''Des parties des animaux'', I, 1, 639b11-640a9</ref>. ==== L'unité fonctionnelle de l'organisme ==== Chaque animal forme une totalité organisée dont toutes les parties coopèrent en vue de la vie de l'ensemble. Les poumons existent en vue de la respiration, qui existe en vue du refroidissement du cœur, qui existe en vue de la vie de l'animal. Réciproquement, la possession de poumons impose certaines autres caractéristiques : un animal pourvu de poumons doit avoir un sang et un cœur, doit respirer, donc doit avoir des voies aériennes, etc.<ref>Aristote, ''Des parties des animaux'', III, 6, 668b33-669a13</ref> Cette conception systémique de l'organisme, où chaque partie trouve sa raison d'être dans sa contribution au tout, annonce certaines perspectives de la biologie moderne, notamment l'approche fonctionnaliste. Elle fonde aussi la possibilité d'une connaissance rationnelle du vivant : malgré la complexité quasi infinie des formes de vie, on peut les comprendre en dégageant les rapports fonctionnels entre les parties et le tout. === Chapitre III : La ''Génération des animaux'' – Reproduction et hérédité === ==== Les modes de reproduction ==== Le traité ''De la génération des animaux'' étudie la reproduction sexuée et asexuée, la formation de l'embryon, l'hérédité. Aristote distingue plusieurs modes de génération : par accouplement (la plupart des animaux), par génération spontanée (certains insectes, les anguilles qu'on croyait naître de la vase), par bourgeonnement (certaines plantes)<ref>Aristote, ''De la génération des animaux'', I, 1, 715a1-18</ref>. Dans la génération sexuée, le mâle apporte la forme et le principe du mouvement (la semence), tandis que la femelle apporte la matière (les menstrues chez les vivipares, l'œuf chez les ovipares)<ref>Aristote, ''De la génération des animaux'', I, 20, 729a9-b21</ref>. Cette théorie, qui reflète les préjugés androcentrés de l'époque, dénie à la femelle tout rôle actif dans la génération, la réduisant à fournir le substrat matériel informé par la semence mâle. ==== L'embryogenèse et la formation des parties ==== Aristote décrit avec précision le développement de l'embryon de poulet, qu'il a observé en ouvrant des œufs à différents stades d'incubation. Il voit d'abord se former le cœur, qui bat dès le troisième jour, puis progressivement les autres organes<ref>Aristote, ''Histoire des animaux'', VI, 3, 561a4-561b13</ref>. Cette description remarquable fonde la conception épigénétique de l'embryologie : l'embryon se construit progressivement, par différenciation graduelle, et non par simple croissance d'un organisme préformé miniature. La cause de cette différenciation progressive est la chaleur vitale contenue dans la semence, qui cuit et façonne la matière menstruelle comme le présure fait cailler le lait<ref>Aristote, ''De la génération des animaux'', II, 4, 739b20-740a4</ref>. Ce processus n'est pas mécanique mais finalisé : c'est en vue de telle forme déterminée (l'homme adulte, le poulet adulte) que les parties se forment dans tel ordre et avec telles caractéristiques. ==== L'hérédité et la ressemblance ==== Pourquoi les enfants ressemblent-ils à leurs parents ? Pourquoi parfois ressemblent-ils plutôt aux grands-parents ou à des ancêtres plus lointains ? Aristote explique ces phénomènes par le plus ou moins grand degré de cuisson et de maîtrise qu'exerce la semence mâle sur la matière femelle. Si la cuisson est parfaite, l'enfant ressemble au père ; si elle est un peu moins parfaite, il ressemble à la mère ; si elle l'est encore moins, aux grands-parents, etc.<ref>Aristote, ''De la génération des animaux'', IV, 3, 767b15-768a14</ref> Ces explications, bien que rudimentaires et souvent erronées dans le détail, témoignent d'un effort remarquable pour rendre compte rationnellement de phénomènes que d'autres cultures expliquaient par l'intervention divine ou la magie. Aristote pose les questions fondamentales de la biologie (qu'est-ce que la vie ? comment les formes se transmettent-elles ? pourquoi existe-t-il une telle diversité d'espèces ?) et invente des méthodes pour y répondre, même si les réponses devront être révisées par la science ultérieure. === Chapitre IV : Le ''De motu animalium'' et le ''De incessu animalium'' – Le mouvement animal === ==== Le principe du mouvement volontaire ==== Le ''De motu animalium'' examine comment les animaux se meuvent localement par eux-mêmes. Aristote établit qu'un animal ne peut se mouvoir que s'il s'appuie sur quelque chose d'immobile : de même qu'on ne peut pousser un bateau depuis l'intérieur du bateau, l'animal doit prendre appui sur le sol, l'eau ou l'air pour se déplacer<ref>Aristote, ''Du mouvement des animaux'', 2, 698b7-699a11</ref>. Le principe moteur interne est le désir, excité par la sensation ou l'imagination d'un objet désirable. Le désir entraîne un réchauffement dans la région du cœur, qui se communique aux membres et produit leur contraction ou extension<ref>Aristote, ''Du mouvement des animaux'', 10, 703a4-b2</ref>. Ainsi, le mouvement animal, bien que produit de l'intérieur, a toujours une cause finale externe : l'objet désiré qui meut sans être mû, comme le Premier Moteur meut le ciel. ==== Les modes de locomotion ==== Le ''De incessu animalium'' décrit et explique les différents modes de locomotion : marche, course, vol, nage. Aristote établit des lois générales : les animaux sanguins ne peuvent avoir plus de quatre points d'appui, car la nature ne fait rien en vain, et quatre suffisent à assurer la stabilité<ref>Aristote, ''De la marche des animaux'', 8, 708a9-708b9</ref>. Les oiseaux ont des ailes parce qu'ils ont le corps léger et la poitrine large et musculeuse, configurations qui rendent le vol possible<ref>Aristote, ''De la marche des animaux'', 10, 710a10-710b7</ref>. Ces études, qui mêlent observations précises et raisonnements mécaniques, relèvent de ce que nous appellerions aujourd'hui biomécanique. Elles montrent qu'Aristote ne se contente pas de contempler les formes vivantes, mais cherche à en expliquer le fonctionnement par des causes physiques vérifiables, sans abandonner toutefois le cadre téléologique général qui voit partout dans la nature l'action providentielle d'une finalité immanente. == Quatrième partie : La philosophie première ou métaphysique == === Chapitre I : Objet et divisions de la métaphysique === ==== La science de l'être en tant qu'être ==== La métaphysique, qu'Aristote nomme « philosophie première » ou « sagesse », est définie comme la science de l'être en tant qu'être (''on hêi on'') et de ce qui lui appartient essentiellement<ref>Aristote, ''Métaphysique'', Γ, 1, 1003a21-22</ref>. Cette formulation distingue la métaphysique de toutes les sciences particulières. Celles-ci étudient une partie ou un aspect de l'être : la géométrie étudie l'être en tant que continu, la biologie l'être en tant que vivant. Seule la métaphysique considère l'être dans sa totalité et sa généralité, recherchant ce qui appartient à tout être du seul fait qu'il est. Cette science est « première » en deux sens : elle porte sur ce qui est premier dans l'ordre de l'être (les principes et causes premiers), et elle fonde toutes les autres sciences en étudiant les principes communs à toutes, comme le principe de non-contradiction<ref>Aristote, ''Métaphysique'', Γ, 3, 1005a19-b8</ref>. Elle est aussi la plus divine des sciences, car elle porte sur ce qu'il y a de plus divin, Dieu comme Premier Moteur immobile<ref>Aristote, ''Métaphysique'', Α, 2, 983a5-10</ref>. ==== La question de l'unité de la métaphysique ==== Comment la métaphysique peut-elle être à la fois ontologie générale, étude de l'être en tant qu'être, et théologie, étude d'un être particulier, Dieu ? Cette apparente tension a donné lieu à deux interprétations dans l'histoire de la philosophie. Pour certains, il y aurait deux métaphysiques chez Aristote, l'une générale, l'autre spéciale, maladroitement juxtaposées. Pour d'autres, dont Thomas d'Aquin<ref>Thomas d'Aquin, ''In Metaphysicam Aristotelis Commentaria'', Prooemium</ref>, l'unité est assurée par le fait que Dieu est principe de tout être, de sorte qu'en étudiant Dieu on étudie la cause première de tout ce qui est. Une troisième solution reconnaît que l'être se dit en plusieurs sens (substance, qualité, quantité, etc.), mais que tous ces sens se rapportent à un sens premier, la substance. Et parmi les substances, la substance divine est première. Ainsi, la métaphysique étudie-t-elle l'être en étudiant ce qui est premier dans l'être : la substance, et plus particulièrement la substance immobile séparée<ref>Aristote, ''Métaphysique'', Ε, 1, 1026a23-32</ref>. ==== Les apories de la métaphysique ==== Le livre Β de la ''Métaphysique'' expose méthodiquement les apories, les difficultés que rencontre toute tentative de construire une science de l'être et des principes premiers. Y a-t-il une science unique de toutes les causes, ou plusieurs sciences ? La science de l'être porte-t-elle aussi sur les principes de la démonstration ? Y a-t-il seulement des substances sensibles, ou existe-t-il aussi des substances séparées immobiles ? Les principes sont-ils universels ou particuliers ? Sont-ils en acte ou en puissance ?<ref>Aristote, ''Métaphysique'', Β, 1, 995b4-996a17</ref> Cette méthode aporétique, qui consiste à commencer par rassembler les difficultés et à examiner les arguments dans les deux sens avant de proposer une solution, est caractéristique de la démarche aristotélicienne. Elle empêche le dogmatisme et oblige à prendre en compte toute la complexité des problèmes. « Il faut, pour bien rechercher, avoir auparavant développé les apories »<ref>Aristote, ''Métaphysique'', Β, 1, 995a24-27</ref>. === Chapitre II : La substance (livres Z-H) === ==== Qu'est-ce que la substance ? ==== Si l'être se dit en plusieurs sens, et si la substance est le sens premier de l'être, la question centrale de la métaphysique devient : qu'est-ce que la substance (''ousia'') ?<ref>Aristote, ''Métaphysique'', Ζ, 1, 1028a10-1028b2</ref> Quatre candidats se présentent : l'essence (''to ti ên einai''), l'universel (''to katholou''), le genre (''to genos''), et le substrat (''to hupokeimenon''). Ce dernier peut être entendu en trois sens : la matière, la forme, ou le composé des deux. Aristote élimine d'abord l'universel et le genre comme candidats à la substantialité. Ils ne peuvent être substances parce qu'ils se prédiquent de plusieurs choses, alors que la substance de chaque chose lui est propre<ref>Aristote, ''Métaphysique'', Ζ, 13, 1038b8-16</ref>. Cette exclusion vise clairement la théorie platonicienne des Idées, qui faisait de l'Homme en soi, universel, la véritable substance dont les hommes particuliers n'étaient que des copies. ==== La forme, substance première ==== Le substrat a plus de titres à être appelé substance, puisqu'il est ce dont tout le reste se dit. Mais si l'on identifiait la substance à la matière pure, support ultime de toutes les déterminations, on aboutirait au paradoxe qu'une substance pourrait exister sans aucune détermination, ce qui est absurde<ref>Aristote, ''Métaphysique'', Ζ, 3, 1029a10-26</ref>. C'est donc la forme (''morphê'', ''eidos'') qui est substance au sens le plus propre. La forme est ce qui fait qu'une chose est ce qu'elle est, son essence. Elle est aussi acte (''energeia'') par opposition à la matière qui est puissance (''dynamis''). Un morceau d'airain est en puissance une statue ; quand le sculpteur lui a donné la forme de l'Hermès, il est en acte une statue. La forme actualise la matière et lui confère l'être déterminé<ref>Aristote, ''Métaphysique'', Η, 2, 1042b9-1043a28</ref>. ==== Le composé de matière et de forme ==== La substance concrète, l'individu réel, n'est ni la matière seule ni la forme seule, mais leur composé (''synolon''). Callias n'est ni simplement de la chair et des os, ni simplement la forme de l'homme, mais cet homme-ci composé de cette chair-ci et de ces os-ci structurés selon la forme humaine<ref>Aristote, ''Métaphysique'', Ζ, 10, 1035b14-27</ref>. Toutefois, la forme demeure première en un sens, car c'est elle qui confère l'unité au composé. Sans la forme, la matière ne serait qu'un amas d'éléments disparates. C'est l'âme, forme du corps vivant, qui fait que ce corps est un organisme uni et non un simple agrégat. Dans l'ordre de la connaissance aussi, c'est par la forme qu'on définit et connaît la substance : on définit l'homme par sa forme (animal rationnel), non par sa matière (chair et os)<ref>Aristote, ''Métaphysique'', Ζ, 11, 1037a21-33</ref>. === Chapitre III : Puissance et acte (livre Θ) === ==== La distinction de la puissance et de l'acte ==== La distinction entre puissance (''dynamis'') et acte (''energeia'', ''entelecheia'') est l'une des contributions majeures d'Aristote à la philosophie. Elle permet de résoudre les apories de Parménide et de Platon concernant le devenir et la participation. Comment une chose peut-elle devenir ce qu'elle n'est pas ? En étant en puissance ce qu'elle devient en acte. Le bronze n'est pas une statue, mais il est en puissance une statue, de sorte qu'il peut le devenir sans passer du non-être à l'être<ref>Aristote, ''Métaphysique'', Θ, 6, 1048a30-1048b17</ref>. Une chose en puissance n'a pas encore telle propriété, mais elle a la capacité de l'acquérir. La capacité n'est ni l'absence pure (le bronze ne peut pas devenir homme), ni la présence actuelle (ce qui est actuellement chaud n'a plus la capacité de le devenir). Elle est un mode d'être intermédiaire<ref>Aristote, ''Métaphysique'', Θ, 7, 1049a1-18</ref>. ==== La priorité de l'acte sur la puissance ==== Aristote établit que l'acte est antérieur à la puissance en plusieurs sens. Dans l'ordre de la définition, car on définit la puissance par l'acte correspondant (ce qui peut voir est ce qui peut exercer l'acte de la vision). Dans l'ordre de la connaissance, car on connaît la puissance en connaissant son actualisation. Dans l'ordre de l'essence et du temps, car l'acte est la fin de la puissance<ref>Aristote, ''Métaphysique'', Θ, 8, 1049b10-1050a3</ref>. Plus profondément encore, ce qui est éternellement en acte est antérieur à ce qui peut être tantôt en acte, tantôt en puissance. Or les substances divines sont éternellement en acte, tandis que les substances sensibles passent de la puissance à l'acte et de l'acte à la puissance. Les substances immobiles éternelles sont donc absolument premières<ref>Aristote, ''Métaphysique'', Θ, 8, 1050b6-28</ref>. Ce raisonnement fonde métaphysiquement la primauté de la théologie dans la philosophie première. === Chapitre IV : L'Un et le Multiple (livres I-K) === ==== L'un se dit en plusieurs sens ==== Parallèlement à l'être, l'un aussi se dit en plusieurs sens. Il y a l'un par accident (le musicien et l'homme sont un quand le musicien est homme). Il y a l'un par soi, qui se divise en un par continuité (une route), un par indivisibilité en espèce (tout homme est un par l'espèce), un par le concept et la définition (être et un sont convertibles)<ref>Aristote, ''Métaphysique'', Ι, 1, 1052a15-1052b14</ref>. Les pythagoriciens et Platon avaient fait de l'Un un principe suprême, antérieur même à l'être. Aristote critique cette position : l'un n'est pas une substance séparée, mais une propriété convertible avec l'être<ref>Aristote, ''Métaphysique'', Ι, 2, 1053b16-1054a13</ref>. Dire qu'une chose est et dire qu'elle est une, c'est la même chose. L'un n'ajoute rien à l'être, il exprime simplement son indivisibilité. ==== La pluralité et le nombre ==== La pluralité s'oppose à l'un comme le divisible à l'indivisible. Le nombre est une pluralité mesurée par l'un<ref>Aristote, ''Métaphysique'', Ι, 1, 1053a18-24</ref>. Mais qu'est-ce que cette unité qui mesure ? Dans les nombres arithmétiques, c'est l'unité absolument indivisible. Dans les choses sensibles, c'est une unité relative à un genre : pour mesurer des longueurs, on prend une longueur comme unité ; pour mesurer des poids, un poids. Cette analyse du nombre et de la mesure fonde la possibilité des mathématiques et des sciences quantitatives. Elle montre aussi qu'il ne peut y avoir de science mathématique absolument universelle, puisque chaque genre de grandeur requiert sa propre unité de mesure. === Chapitre V : Le Premier Moteur immobile (livre Λ) === ==== La démonstration de l'existence du Premier Moteur ==== Le livre Λ de la ''Métaphysique'' constitue le sommet de la philosophie première aristotélicienne. Après avoir établi que les substances sont premières parmi les êtres, Aristote examine quelles substances existent. Il y a évidemment les substances sensibles, soumises au changement. Mais existe-t-il aussi une substance éternelle immobile ?<ref>Aristote, ''Métaphysique'', Λ, 6, 1071b3-5</ref> La démonstration procède à partir du mouvement. Il existe du mouvement, cela est manifeste. Or tout ce qui est mû est mû par autre chose. Ou bien il y a un premier moteur immobile, ou bien la série des moteurs remonte à l'infini. Mais une série infinie de causes ne peut rien expliquer, car en l'absence d'un premier il n'y aurait ni intermédiaire ni dernier<ref>Aristote, ''Métaphysique'', Λ, 7, 1072a19-1072b4</ref>. Il doit donc exister un premier moteur qui meut sans être mû. ==== La nature du Premier Moteur ==== Ce Premier Moteur est substance, acte pur, éternel, sans parties, sans grandeur. Il meut comme objet de désir et d'intellection : l'intellect désire le bien et se meut vers lui. Le Premier Moteur, étant le bien suprême et l'intelligible suprême, attire vers lui le premier ciel qui l'imite par son mouvement circulaire éternel<ref>Aristote, ''Métaphysique'', Λ, 7, 1072a23-1072b4</ref>. Quelle est la vie de ce Premier Moteur ? Il est Pensée, mais que pense-t-il ? Il doit penser ce qu'il y a de plus excellent, donc lui-même. Il est donc Pensée de la Pensée (''noêsis noêseôs noêsis''), intellection qui se pense elle-même dans une contemplation éternellement bienheureuse<ref>Aristote, ''Métaphysique'', Λ, 9, 1074b33-35</ref>. Cette vie divine est la plus haute qui soit, et la vie philosophique est bienheureuse dans la mesure où elle participe à cette activité contemplative. ==== Les moteurs des sphères ==== Le mouvement complexe des astres requiert, outre le Premier Moteur qui meut la sphère des fixes, une pluralité de moteurs immobiles pour les sphères des planètes. Aristote, se fondant sur l'astronomie d'Eudoxe et de Callippe, compte cinquante-cinq sphères, donc cinquante-cinq moteurs<ref>Aristote, ''Métaphysique'', Λ, 8, 1073a14-1074b14</ref>. Ces moteurs ne sont pas subordonnés les uns aux autres comme dans une hiérarchie, mais chacun meut directement sa sphère. Cette prolifération de dieux cosmiques, nécessaire pour sauver les phénomènes astronomiques, contredit-elle le monothéisme du Premier Moteur unique ? La tradition péripatéticienne et la scolastique médiévale tenteront de concilier l'unité divine avec la pluralité des intelligences motrices, en faisant de celles-ci des substances secondes subordonnées au Premier Moteur. === Chapitre VI : Critique de la théorie platonicienne des Idées (livres M-N) === ==== Les arguments contre les Idées séparées ==== Les livres M et N de la ''Métaphysique'' reprennent et développent la critique de la théorie platonicienne des Idées, déjà amorcée dans le livre A. Aristote reconnaît à Platon le mérite d'avoir affirmé l'existence de réalités intelligibles éternelles, condition de toute science. Mais il lui reproche d'avoir séparé ces formes intelligibles des choses sensibles et d'en avoir fait des substances subsistant par elles-mêmes<ref>Aristote, ''Métaphysique'', Μ, 4-5, 1078b7-1080a8</ref>. Cette séparation est inutile et impossible. Inutile, car les Idées ne servent ni à la connaissance des sensibles (puisqu'elles sont séparées d'eux), ni à leur existence (puisqu'elles ne sont pas en eux), ni à leur devenir (puisqu'elles sont immobiles)<ref>Aristote, ''Métaphysique'', Α, 9, 991a8-991b9</ref>. Impossible, car elle conduit à des apories insolubles, comme l'argument du Troisième Homme : si Socrate ressemble à l'Homme en soi, il faut une troisième Idée pour fonder cette ressemblance, et ainsi à l'infini<ref>Aristote, ''Métaphysique'', Α, 9, 990b17-991a8</ref>. ==== Le statut des êtres mathématiques ==== Platon situait les êtres mathématiques (nombres, figures) dans un plan intermédiaire entre les Idées et les sensibles. Aristote refuse cette ontologie tripartite. Les objets mathématiques n'existent pas séparément, mais par abstraction<ref>Aristote, ''Métaphysique'', Μ, 3, 1078a21-31</ref>. Le mathématicien considère les sensibles non en tant que sensibles mais en tant que continus ou quantifiés, faisant abstraction de leurs autres propriétés. Il n'étudie pas des êtres qui existeraient dans un monde séparé, mais des aspects abstraits des êtres sensibles. Cette solution préserve l'objectivité des mathématiques sans multiplier les êtres. Les vérités mathématiques sont nécessaires et universelles parce qu'elles portent sur des propriétés que les choses sensibles possèdent nécessairement, même si ces propriétés ne sont jamais réalisées dans les sensibles avec la perfection qu'étudie le mathématicien<ref>Aristote, ''Métaphysique'', Μ, 2, 1077a9-1077b17</ref>. == Cinquième partie : La philosophie pratique == === Chapitre I : L'''Éthique à Nicomaque'' – La vie bonne === ==== Le souverain bien et le bonheur ==== L'''Éthique à Nicomaque'' s'ouvre sur l'affirmation que toute action et toute recherche vise un bien. Parmi les biens, il y en a un qui est visé pour lui-même et en vue duquel tous les autres sont poursuivis : c'est le souverain bien, que tous appellent bonheur (''eudaimonia'')<ref>Aristote, ''Éthique à Nicomaque'', I, 4, 1095a14-26</ref>. Mais en quoi consiste le bonheur ? Les hommes en donnent des définitions différentes : le plaisir, les honneurs, la richesse. Pour déterminer le bonheur véritable, Aristote recourt à la notion de fonction (''ergon'') : le bien pour chaque être réside dans l'accomplissement de sa fonction propre. La fonction propre de l'homme est l'activité de l'âme rationnelle selon l'excellence. Le bonheur est donc « une activité de l'âme selon la vertu parfaite, dans une vie complète »<ref>Aristote, ''Éthique à Nicomaque'', I, 7, 1098a16-18</ref>. ==== Les vertus éthiques et la doctrine du juste milieu ==== Aristote distingue les vertus intellectuelles (sagesse théorique, prudence) des vertus éthiques ou morales (courage, tempérance, justice). Les vertus morales sont des dispositions acquises par l'habitude à choisir le juste milieu entre deux extrêmes vicieux<ref>Aristote, ''Éthique à Nicomaque'', II, 6, 1106b36-1107a2</ref>. Ainsi, le courage est le juste milieu entre la lâcheté (défaut de crainte) et la témérité (excès d'audace). La tempérance est le milieu entre l'insensibilité et l'intempérance. La libéralité est le milieu entre l'avarice et la prodigalité<ref>Aristote, ''Éthique à Nicomaque'', II, 7, 1107a33-1108b10</ref>. Ce milieu n'est pas arithmétique mais relatif à nous : il dépend des circonstances et de l'agent. Toute l'œuvre de la vertu consiste à trouver ce milieu, guidée par la droite raison (''orthos logos'') qu'incarne la prudence<ref>Aristote, ''Éthique à Nicomaque'', II, 6, 1107a1-2</ref>. Cette doctrine du juste milieu a connu une fortune considérable, devenant l'un des principes les plus populaires de la philosophie morale. Elle exprime une sagesse pratique qui fuit les extrêmes et recherche l'équilibre, tout en reconnaissant la complexité du jugement moral qui doit tenir compte de la situation concrète. ==== L'amitié ==== Les livres VIII et IX de l'''Éthique à Nicomaque'' traitent de l'amitié (''philia''), vertu sociale essentielle. Aristote distingue trois espèces d'amitié : l'amitié utile, fondée sur l'intérêt réciproque ; l'amitié agréable, fondée sur le plaisir ; et l'amitié parfaite, celle des hommes vertueux qui s'aiment pour eux-mêmes<ref>Aristote, ''Éthique à Nicomaque'', VIII, 3, 1156a6-1156b7</ref>. Seule la dernière est véritablement amitié, car elle est durable et réciproque. Les deux premières sont instables, car elles cessent quand cesse l'utilité ou le plaisir. L'amitié parfaite requiert du temps et de l'intimité : on ne peut être ami de beaucoup, car l'amitié véritable exige une vie partagée<ref>Aristote, ''Éthique à Nicomaque'', VIII, 6, 1158a10-15</ref>. L'amitié n'est pas seulement agréable, elle est nécessaire à la vie bonne. L'homme est un animal politique qui ne peut s'accomplir dans l'isolement. L'ami est un autre soi-même : en conversant avec lui, en partageant ses joies et ses peines, nous nous connaissons et nous accomplissons nous-mêmes<ref>Aristote, ''Éthique à Nicomaque'', IX, 9, 1169b16-1170a4</ref>. ==== La vie contemplative ==== Le livre X couronne l'éthique en montrant que le bonheur suprême réside dans la vie contemplative. L'activité théorétique de l'intellect est la plus haute et la plus autosuffisante. Elle nous apparente aux dieux, qui passent l'éternité dans la contemplation<ref>Aristote, ''Éthique à Nicomaque'', X, 7, 1177a12-18</ref>. Certes, cette vie purement intellectuelle dépasse les forces humaines prises isolément. Mais dans la mesure où il y a en nous quelque chose de divin, l'intellect, nous devons tendre vers cette vie divine<ref>Aristote, ''Éthique à Nicomaque'', X, 7, 1177b30-34</ref>. La vie selon les vertus morales est bonne et humaine, mais la vie selon l'intellect est divine. Le sage qui la mène est le plus heureux des hommes, celui que les dieux aiment le plus. === Chapitre II : Les ''Politiques'' – La cité et les constitutions === ==== L'homme, animal politique ==== Les ''Politiques'' commencent par affirmer que la cité (''polis'') existe par nature et que l'homme est par nature un animal politique<ref>Aristote, ''Politiques'', I, 2, 1252b27-1253a3</ref>. Cette affirmation s'oppose à la fois aux sophistes, qui faisaient de la société une convention artificielle, et aux cyniques, qui prônaient l'autarcie individuelle. L'homme ne peut vivre ni bien vivre en dehors de la cité, car il a besoin des autres pour satisfaire ses besoins matériels et pour actualiser sa nature rationnelle et morale. La cité n'est pas une simple alliance défensive ou commerciale, mais une communauté de vie bonne, ayant pour fin le bonheur de ses membres<ref>Aristote, ''Politiques'', III, 9, 1280b39-1281a4</ref>. ==== Les formes de constitution ==== Aristote distingue six formes de constitution, selon deux critères : le nombre de gouvernants (un seul, quelques-uns, la multitude) et la fin poursuivie (l'intérêt commun ou l'intérêt privé des gouvernants). Les formes droites sont la royauté (gouvernement d'un seul pour le bien commun), l'aristocratie (gouvernement de quelques-uns pour le bien commun), et la république ou politeia (gouvernement de la multitude pour le bien commun). Les formes déviées sont la tyrannie, l'oligarchie et la démocratie (au sens péjoratif de démagogie)<ref>Aristote, ''Politiques'', III, 7, 1279a22-1279b10</ref>. Aucune de ces constitutions n'est absolument la meilleure en tout lieu et en tout temps. La meilleure constitution pour une cité donnée dépend des circonstances : étendue du territoire, richesses, mœurs des habitants, etc. Aristote fait preuve ici d'un réalisme politique qui contraste avec l'utopisme de la ''République'' de Platon. ==== La constitution mixte et la classe moyenne ==== La constitution la plus stable est celle qui mêle des éléments oligarchiques et démocratiques, donnant le pouvoir à la classe moyenne<ref>Aristote, ''Politiques'', IV, 11, 1295a25-1295b1</ref>. Les classes moyennes ne sont ni assez pauvres pour envier les riches, ni assez riches pour mépriser les pauvres. Elles recherchent la stabilité et l'égalité plutôt que les révolutions. Cette théorie de la classe moyenne et de la constitution mixte influencera durablement la pensée politique occidentale, de Polybe à Montesquieu. Elle exprime un idéal de modération et d'équilibre, caractéristique de la sagesse pratique aristotélicienne. ==== L'éducation ==== Les derniers livres des ''Politiques'' sont consacrés à l'éducation. Celle-ci ne peut être laissée aux familles privées, car la cité a pour fin de rendre les citoyens vertueux. Il doit donc y avoir une éducation publique commune, qui forme le caractère et l'intelligence des jeunes en vue de la vie civique et de la vie bonne<ref>Aristote, ''Politiques'', VIII, 1, 1337a11-21</ref>. Cette éducation comprend la gymnastique pour le corps, la musique pour l'âme, les lettres pour l'usage pratique, et le dessin pour apprécier la beauté des œuvres. Mais son but ultime n'est pas l'utilité mais le loisir bien employé (''scholê''), c'est-à-dire l'activité libre de l'esprit dans la contemplation et les arts libéraux<ref>Aristote, ''Politiques'', VIII, 3, 1337b28-1338a13</ref>. === Chapitre III : La ''Rhétorique'' – L'art de persuader === ==== La rhétorique comme art ==== La ''Rhétorique'' étudie les moyens de persuasion dans tous les genres de discours. Elle est l'homologue (''antistrophos'') de la dialectique : de même que la dialectique enseigne à raisonner sur toute question à partir de prémisses probables, la rhétorique enseigne à persuader tout auditoire sur tout sujet<ref>Aristote, ''Rhétorique'', I, 1, 1354a1-11</ref>. Aristote défend la rhétorique contre les attaques de Platon qui, dans le ''Gorgias'', en faisait une simple flatterie. Bien utilisée, elle est un art (''technê'') légitime et utile. Elle sert à défendre la vérité et la justice, car il serait absurde que l'incapacité de se servir de ses forces corporelles soit honteuse, mais non l'incapacité de se servir de la parole<ref>Aristote, ''Rhétorique'', I, 1, 1355a20-24</ref>. ==== Les trois genres rhétoriques et leurs topiques ==== Aristote distingue trois genres de rhétorique. Le genre délibératif conseille ou dissuade concernant l'avenir (devons-nous faire la guerre ?). Le genre judiciaire accuse ou défend concernant le passé (X a-t-il commis ce crime ?). Le genre épidictique loue ou blâme concernant le présent (célébrons la vertu du héros)<ref>Aristote, ''Rhétorique'', I, 3, 1358b2-29</ref>. Chaque genre a ses lieux propres. Le délibératif porte sur l'utile et le nuisible, recourant aux arguments sur les conséquences. Le judiciaire porte sur le juste et l'injuste, s'appuyant sur les lois et les témoignages. L'épidictique porte sur le beau et le laid moral, utilisant l'amplification<ref>Aristote, ''Rhétorique'', I, 3, 1358b29-1359a5</ref>. ==== Les preuves rhétoriques : ethos, pathos, logos ==== Aristote distingue trois moyens de persuasion : l'ethos (le caractère de l'orateur), le pathos (les émotions de l'auditoire), et le logos (le raisonnement lui-même)<ref>Aristote, ''Rhétorique'', I, 2, 1356a1-13</ref>. L'ethos persuade en inspirant confiance : l'orateur doit paraître prudent, vertueux et bienveillant. Le pathos persuade en suscitant des émotions favorables à la thèse défendue. Le livre II étudie systématiquement les émotions (colère, pitié, crainte, envie, etc.), leurs causes et leurs manifestations. Le logos persuade par les raisonnements, notamment l'enthymème (syllogisme rhétorique à partir de prémisses probables) et l'exemple (induction rhétorique)<ref>Aristote, ''Rhétorique'', I, 2, 1356a35-1356b5</ref>. Cette analyse des moyens de persuasion fonde la rhétorique comme discipline rationnelle et systématique, loin de l'empirisme des sophistes. Elle restera la base de l'enseignement rhétorique jusqu'à l'époque moderne. === Chapitre IV : La ''Potique'' – La création artistique === ==== La mimêsis et la catharsis ==== La ''Poétique'' étudie l'art poétique, principalement la tragédie et l'épopée. La poésie, comme tous les arts, est imitation (''mimêsis''). Mais elle n'imite pas les choses telles qu'elles sont (c'est le rôle de l'histoire), mais telles qu'elles pourraient ou devraient être selon la vraisemblance ou la nécessité<ref>Aristote, ''Poétique'', 9, 1451a36-1451b5</ref>. C'est pourquoi la poésie est plus philosophique que l'histoire : elle dit l'universel, alors que l'histoire dit le particulier. La tragédie représente une action sérieuse et complète, en suscitant pitié et crainte, pour opérer la purification (''catharsis'') de ces émotions<ref>Aristote, ''Poétique'', 6, 1449b24-28</ref>. Cette doctrine de la catharsis, qui répond à l'accusation platonicienne selon laquelle la tragédie corrompt l'âme en excitant les passions, est demeurée énigmatique. Peut-être Aristote veut-il dire que la tragédie, en permettant de ressentir ces émotions dans un cadre fictif, les évacue ou les modère, préparant ainsi le spectateur à mieux les gérer dans la vie réelle. ==== La structure de la tragédie ==== Aristote analyse avec précision la structure de la tragédie. Celle-ci doit former un tout avec commencement, milieu et fin. L'intrigue (''mythos'') est l'âme de la tragédie, plus importante que les caractères. L'intrigue idéale comprend une péripétie (''peripeteia''), renversement de l'action en sens contraire, et une reconnaissance (''anagnôrisis''), passage de l'ignorance à la connaissance<ref>Aristote, ''Poétique'', 11, 1452a15-32</ref>. Le héros tragique ne doit être ni parfaitement vertueux ni complètement vicieux, mais occuper une position intermédiaire. Son malheur doit résulter non d'un vice mais d'une erreur (''hamartia'')<ref>Aristote, ''Poétique'', 13, 1453a7-10</ref>. Ainsi Œdipe qui, sans le savoir, tue son père et épouse sa mère, inspire-t-il pitié plutôt que répulsion, parce que son crime procède de l'ignorance et non de la méchanceté. Ces analyses ont profondément influencé la théorie littéraire occidentale, de la Renaissance (avec la redécouverte de la ''Poétique'' et son influence sur le théâtre classique français) jusqu'à nos jours. == Conclusion : L'héritage d'Aristote == === La transmission et l'influence historique === L'influence d'Aristote sur l'histoire de la pensée occidentale et orientale est sans égale. Dans l'Antiquité tardive, les commentateurs néoplatoniciens (Porphyre, Alexandre d'Aphrodise, Simplicius, Philopon) étudient et expliquent ses œuvres, souvent en tentant de les harmoniser avec le platonisme. Au IXe siècle, les philosophes arabes traduisent Aristote en syriaque puis en arabe. Al-Fârâbî (872-950), Avicenne (980-1037) et surtout Averroès (1126-1198), surnommé « le Commentateur », développent un aristotélisme islamique qui influencera en retour l'Occident latin. À partir du XIIe siècle, les œuvres d'Aristote, accompagnées des commentaires arabes, sont traduites en latin. Elles révolutionnent l'université médiévale. Albert le Grand (1200-1280) et surtout Thomas d'Aquin (1225-1274) christianisent Aristote, montrant la compatibilité de sa philosophie avec la foi chrétienne. Le thomisme devient la philosophie officielle de l'Église catholique. Mais cette domination scolastique d'Aristote provoque aussi, à la Renaissance et à l'époque moderne, un puissant mouvement de rejet. La science moderne, de Galilée à Newton, se construit explicitement contre la physique aristotélicienne. === La pertinence contemporaine d'Aristote === Après trois siècles d'éclipse relative, le XXe siècle voit un remarquable renouveau de l'intérêt pour Aristote. En éthique, l'éthique des vertus revient au premier plan après la domination du kantisme et de l'utilitarisme, grâce aux travaux d'Anscombe, MacIntyre, Nussbaum. En philosophie de l'esprit, on redécouvre la pertinence du hylémorphisme aristotélicien face aux apories du dualisme cartésien et du matérialisme réductionniste. En métaphysique, Aristote inspire un renouveau de l'ontologie réaliste, attentive aux structures catégorielles de la réalité. En philosophie de la biologie, sa téléologie immanente et sa conception de l'organisme comme totalité fonctionnelle retrouvent une actualité face aux limites du mécanisme et du réductionnisme génétique. Plus largement, Aristote demeure une référence incontournable pour quiconque s'interroge sur les grandes questions philosophiques : qu'est-ce que l'être ? qu'est-ce que la connaissance ? comment vivre bien ? quelle est la meilleure constitution politique ? Son œuvre, par sa richesse, sa rigueur et son ampleur encyclopédique, continue de nourrir la réflexion philosophique contemporaine. == Notes et références == {{references}} == Bibliographie == '''Éditions de référence :''' * Pierre Pellegrin (dir.), ''Aristote. Œuvres complètes'', Paris, Flammarion, 2014 * Bekker, Immanuel (éd.), ''Aristotelis Opera'', Berlin, Reimer, 1831-1870 '''Études générales :''' * Pierre Aubenque, ''Le problème de l'être chez Aristote'', Paris, PUF, 1962 * Jonathan Barnes (éd.), ''The Cambridge Companion to Aristotle'', Cambridge, Cambridge University Press, 1995 * Werner Jaeger, ''Aristote. Fondements pour une histoire de son évolution'', trad. fr. O. Sedeyn, Paris, L'Éclat, 1997 (1923) * Pierre Pellegrin, ''Aristote'', Paris, Bordas, 1990 '''Études spécialisées :''' * Richard Bodéüs, ''Aristote et la théologie des vivants immortels'', Montréal-Paris, Bellarmin-Les Belles Lettres, 1992 * Pierre-Marie Morel, ''Aristote. Une philosophie de l'activité'', Paris, Flammarion, 2003 * David Ross, ''Aristotle'', London, Methuen, 1923 {{Autocat}} [[Catégorie:Philosophe]] f3vy2j9wtid4xxc6b7r1zlratyiumoy Mathc initiation/004y 0 83655 764739 761327 2026-04-23T22:52:13Z Xhungab 23827 764739 wikitext text/x-wiki __NOTOC__ [[Catégorie:Mathc initiation (livre)]] : [[Mathc initiation/005i| Sommaire]] : {{Partie{{{type|}}}| Le théorème de Stoke (version I) }} En mathématiques, et plus particulièrement en géométrie différentielle, le théorème de Stokes est un résultat central sur l'intégration des formes différentielles, qui généralise le second théorème fondamental de l'analyse, ainsi que de nombreux théorèmes d'analyse vectorielle. [https://fr.khanacademy.org/math/multivariable-calculus/greens-theorem-and-stokes-theorem/stokes-theorem/v/stokes-theorem-intuition Khanacademy : stokes-theorem-intuition] ... [https://fr.khanacademy.org/math/multivariable-calculus/greens-theorem-and-stokes-theorem/stokes-proof/v/stokes-theorem-proof-part-1 Khanacademy : stokes-theorem-proof] Copier la bibliothèque dans votre répertoire de travail : * [[Mathc initiation/0057|x_afile.h ............ Déclaration des fichiers h]] * [[Mathc initiation/Fichiers h : c30a2|x_def.h .............. Déclaration des utilitaires]] * [[Mathc initiation/Fichiers c : c47ca|x_strcp.h ........... Déclaration des structures (points, vecteurs)]] * [[Mathc initiation/Fichiers h : c23a3|x_fx.h ................ Calculer les dérivées]] * [[Mathc initiation/Fichiers h : c25a4|x_fxy.h]] * [[Mathc initiation/Fichiers h : c26a4|x_fxyz.h]] * [[Mathc initiation/Fichiers h : c59a7|x_l3d_dx.h ......... L'intégrale curviligne 3d]] * [[Mathc initiation/Fichiers h : c59a8|x_l3d_dy.h ]] * [[Mathc initiation/Fichiers h : c59a9|x_l3d_dz.h ]] * [[Mathc initiation/004z|x_prods.h ........... u = (-f_x i, -f_y j, 1 k) ]] * [[Mathc initiation/Fichiers h : c59ab|x_curl.h ............. Calculer le rotationel ]] * [[Mathc initiation/0050|x_stokxy.h ........... L'intégrale de Stoke ]] * [[Mathc initiation/0051|x_stokyx.h]] les fonctions f : * [[Mathc initiation/Fichiers h : c59fa|f.h]] Résolution avec : * [[Mathc initiation/0052|c0a1.c .............. L'intégrale de Stoke dxdy .... s = 113.081]] * [[Mathc initiation/a470|c0a2.c .............. Les intégrales curviligne ...... s = +113.097]] * [[Mathc initiation/0053|c0a3.c .............. L'intégrale de Stoke '''dydx''' .... s = 113.081]] * [[Mathc initiation/0054|c0b1.c .............. L'intégrale de Stoke dxdy .... s = -12.579]] * [[Mathc initiation/Fichiers c : c59cb2|c0b2.c .............. Les intégrales curviligne ...... s = -12.566]] * [[Mathc initiation/0058|c0b3.c .............. L'intégrale de Stoke '''dydx''' .... s = -12.579]] Regardons la fonction qui effectue le travail : * [[Mathc initiation/0056| Étudions la fonction '''stokes_dxdy();''']] {{AutoCat}} 5e90474ws664efqod5qzpjcarb0sa7l Mathc initiation/0059 0 83665 764738 761533 2026-04-23T22:51:55Z Xhungab 23827 764738 wikitext text/x-wiki __NOTOC__ [[Catégorie:Mathc initiation (livre)]] [[Mathc initiation/005i| Sommaire]] Le théorème de Stoke (version II) a) dS = [(f_x)^2+(f_y)^2+1]^1/2 dA dA = dxdy // || || (curl F).n dS = (-f_xi-f_yj+k) || b) n = ----------------------- // [(f_x)^2+(f_y)^2+1]^1/2 S n: dS: // // || || ( (-f_xi-f_yj+k) || (curl F).n dS = || (curl F).( ------------------------ [(f_x)^2+(f_y)^2+1]^1/2 dA || || ( [(f_x)^2+(f_y)^2+1]^1/2) // // S S Si vous simplifiez par [(f_x)^2+(f_y)^2+1]^1/2 vous allez obtenir la version I Le théorème de Stoke (version I) // // || || || (curl F).n dS = || (curl F).(-f_xi-f_yj+k) dA || || // // S S Le théorème de Stoke (version III) Dans cette version on a déterminé le vecteur u géométriquement. Il est introduit en début du fichier *.c par : v3d u = {1,2,3}; // // || || || (curl F).n dS = || (curl F).(-(u.i), -(u.j), 1) dA = || || // // S S s {{AutoCat}} srpin40trlf54nao8lno8e1y0x2xv5y Mathc initiation/005g 0 83672 764741 761522 2026-04-23T22:52:45Z Xhungab 23827 764741 wikitext text/x-wiki __NOTOC__ [[Catégorie:Mathc initiation (livre)]] : [[Mathc initiation/005i| Sommaire]] : {{Partie{{{type|}}}| Le théorème de Stoke (version III) }} En mathématiques, et plus particulièrement en géométrie différentielle, le théorème de Stokes est un résultat central sur l'intégration des formes différentielles, qui généralise le second théorème fondamental de l'analyse, ainsi que de nombreux théorèmes d'analyse vectorielle. [https://fr.khanacademy.org/math/multivariable-calculus/greens-theorem-and-stokes-theorem/stokes-theorem/v/stokes-theorem-intuition Khanacademy : stokes-theorem-intuition] ... [https://fr.khanacademy.org/math/multivariable-calculus/greens-theorem-and-stokes-theorem/stokes-proof/v/stokes-theorem-proof-part-1 Khanacademy : stokes-theorem-proof] Copier la bibliothèque dans votre répertoire de travail : * [[Mathc initiation/005a|x_afile.h ............ Déclaration des fichiers h]] * [[Mathc initiation/Fichiers h : c30a2|x_def.h .............. Déclaration des utilitaires]] * [[Mathc initiation/Fichiers c : c47ca|x_strcp.h ........... Déclaration des structures (points, vecteurs)]] * [[Mathc initiation/Fichiers h : c23a3|x_fx.h ................ Calculer les dérivées]] * [[Mathc initiation/Fichiers h : c26a4|x_fxyz.h]] * [[Mathc initiation/Fichiers h : c59a7|x_l3d_dx.h ......... L'intégrale curviligne 3d]] * [[Mathc initiation/Fichiers h : c59a8|x_l3d_dy.h ]] * [[Mathc initiation/Fichiers h : c59a9|x_l3d_dz.h ]] * [[Mathc initiation/Fichiers h : c59ab|x_curl.h ............. Calculer le rotationel ]] * [[Mathc initiation/005b|x_stokxy.h ........... L'intégrale de Stoke ]] * [[Mathc initiation/005c|x_stokyx.h]] les fonctions f : * [[Mathc initiation/005d|f.h]] Résolution avec : * [[Mathc initiation/005e|c0a1.c .............. L'intégrale de Stoke dxdy]] * [[Mathc initiation/005f|c0b1.c .............. L'intégrale de Stoke dydx]] {{AutoCat}} pc69y1f98a4drsw2vmrndq82lfyojpz Dictionnaire de philosophie/René Descartes 0 83728 764804 762063 2026-04-24T10:28:48Z ~2026-25114-99 123599 764804 wikitext text/x-wiki {{DicoPhilo|René Descartes}} La pensée de René Descartes constitue un tournant majeur dans l'histoire de la philosophie occidentale. Son ambition est de refonder l'édifice entier du savoir sur des bases inébranlables, en substituant à l'autorité de la tradition scolastique la souveraineté du sujet pensant guidé par la raison. == Vie et œuvres == René Descartes naît le 31 mars 1596 à La Haye, en Touraine, dans une famille de petite noblesse. Formé au collège jésuite de La Flèche entre 1606 et, vraisemblablement, 1614, il y reçoit un enseignement encyclopédique qui le confronte à la philosophie scolastique, aux mathématiques, à la physique et aux lettres. Après une licence en droit à l'université de Poitiers, il s'engage dans l'armée de Maurice de Nassau, puis dans celle du duc Maximilien de Bavière, voyageant à travers l'Europe. La nuit du 10 novembre 1619, en Allemagne, il rapporte avoir eu trois songes lui révélant sa vocation : fonder une science universelle sur des bases entièrement nouvelles. Installé en Hollande à partir de 1628 pour y jouir de la liberté intellectuelle, il consacre les deux décennies suivantes à l'élaboration de son système philosophique. Il rédige d'abord les ''Regulae ad directionem ingenii'' (inachevées, publiées ''post mortem''), puis ''Le Monde ou Traité de la lumière'', qu'il renonce à publier après la condamnation de Galilée en 1633. En 1637, il publie le ''Discours de la méthode'', suivi de trois essais scientifiques (''La Dioptrique'', ''Les Météores'', ''La Géométrie''). Les ''[[Méditations métaphysiques|Meditationes de prima philosophia]]'' paraissent en 1641, accompagnées de six séries d'objections et de réponses (une septième sera ajoutée à la seconde édition de 1642). Les ''Principia philosophiae'' de 1644 offrent un exposé systématique de l'ensemble de la philosophie cartésienne. Enfin, ''Les Passions de l'âme'' paraissent en 1649. Ayant rejoint la cour de la reine Christine de Suède à Stockholm, Descartes y meurt le 11 février 1650. Dans la célèbre lettre-préface à la traduction française des ''Principes'' (1647), il compare la philosophie à un arbre dont les racines sont la métaphysique, le tronc la physique et les branches les sciences particulières : la médecine, la mécanique et la morale. Cette image exprime la conviction cartésienne que la métaphysique fonde la physique, laquelle rend à son tour possibles les sciences appliquées. Comprendre Descartes exige donc de parcourir l'ensemble de cet arbre, depuis ses racines métaphysiques jusqu'à ses prolongements pratiques. == La méthode == Le projet cartésien s'enracine dans une insatisfaction profonde à l'égard du savoir hérité. Dès le ''Discours de la méthode'', Descartes fait le récit de sa formation intellectuelle pour en exposer les limites : hormis les mathématiques, aucune discipline ne lui a fourni de certitudes véritables. La philosophie des écoles, malgré des siècles de culture, n'a produit que des controverses interminables. Le constat est sans appel : il faut reprendre les choses par le commencement et se doter d'une méthode rigoureuse capable de conduire l'esprit à la vérité. Les ''Regulae ad directionem ingenii'', rédigées entre 1619 et 1628, constituent la première formulation de cette méthode. Descartes y identifie deux opérations fondamentales de l'esprit : l'intuition et la déduction. L'intuition est la saisie immédiate, par un esprit attentif, d'une proposition si simple et si claire qu'aucun doute ne peut s'y glisser. La déduction est le mouvement par lequel l'esprit progresse d'une intuition à une autre, chaque étape étant elle-même évidente. Toute connaissance certaine se ramène en définitive à une chaîne d'intuitions, et la méthode consiste à organiser le travail de la pensée de manière à ne jamais rompre cette chaîne. Le ''Discours'' résume cette méthode en quatre préceptes. Le premier, dit d'évidence, commande de ne recevoir aucune chose pour vraie qu'on ne la connaisse évidemment comme telle, c'est-à-dire de n'admettre que ce qui se présente si clairement et si distinctement à l'esprit qu'il est impossible d'en douter. Le deuxième, dit d'analyse, prescrit de diviser chaque difficulté en autant de parcelles qu'il se peut, afin de la résoudre par parties. Le troisième, dit de synthèse, recommande de conduire par ordre ses pensées, en commençant par les objets les plus simples pour s'élever graduellement jusqu'aux plus composés. Le quatrième, dit de dénombrement, exige des revues si complètes qu'on soit assuré de ne rien omettre. Ces quatre règles ne sont pas de simples recommandations pédagogiques : elles décrivent le fonctionnement naturel de l'intellect lorsqu'il est bien conduit, et les mathématiques en constituent le modèle par excellence. L'originalité de la méthode cartésienne réside dans sa portée universelle. Descartes ne l'envisage pas comme un instrument propre à telle ou telle discipline, mais comme le mode d'exercice de la raison elle-même. L'idée d'une ''mathesis universalis'', c'est-à-dire d'une science générale de l'ordre et de la mesure, traduit cette ambition : toute question susceptible d'être formulée en termes de rapports et de proportions peut être résolue selon les mêmes procédures. ''La Géométrie'' de 1637, qui pose les fondements de la géométrie analytique en traduisant les figures spatiales en équations algébriques, en fournit la démonstration éclatante. Cependant, Descartes reconnaîtra que la méthode, si puissante soit-elle dans le domaine des sciences, ne suffit pas à elle seule à garantir la vérité de nos connaissances sur le monde : cette garantie exige un fondement d'un autre ordre, celui de la métaphysique. == La physique et la science de la nature == La physique cartésienne se construit en rupture déclarée avec la physique d'inspiration aristotélicienne telle qu'elle était enseignée dans les universités depuis le Moyen Âge. Descartes expose sa conception de la nature dans ''Le Monde'' (1633), dans la cinquième partie du ''Discours'' et dans les ''Principes de la philosophie''. Le point de départ est une critique de la connaissance sensible. Contre la conviction ordinaire selon laquelle les qualités perçues – couleurs, sons, odeurs, saveurs, chaleur, froid – existent réellement dans les objets extérieurs, Descartes soutient que ces qualités ne sont que des modifications de notre esprit, produites par le mouvement de la matière sur nos organes sensoriels. De même que les mots du langage n'ont aucune ressemblance avec les choses qu'ils signifient, les sensations n'ont aucune ressemblance avec les propriétés réelles des corps qui les causent. Si les sens ne nous livrent pas l'essence des choses, seules les idées innées des mathématiques peuvent nous la révéler. La matière, dépouillée de toutes les qualités sensibles, se réduit à l'étendue en longueur, largeur et profondeur : la substance corporelle n'est rien d'autre que l'étendue. Cette identification de la matière et de l'étendue entraîne plusieurs conséquences importantes. Elle exclut d'abord le vide : puisque la matière est l'étendue elle-même, il ne saurait y avoir d'étendue sans matière, et l'espace vide est une contradiction dans les termes. Elle implique ensuite la divisibilité indéfinie de la matière : l'étendue étant divisible à l'infini, la matière l'est aussi, et il n'existe pas d'atomes. Elle réduit enfin tous les phénomènes naturels au mouvement local de parties de matière étendue : il n'y a dans la nature que des figures et des mouvements. Dans ''Le Monde'', Descartes procède par une fiction ingénieuse : il invite le lecteur à imaginer la genèse d'un monde nouveau à partir d'une matière indifférenciée. Dieu crée la matière, la divise en parties et leur communique des mouvements régis par trois lois fondamentales. La première loi pose que chaque partie de matière conserve son état de repos ou de mouvement tant qu'aucune cause extérieure ne vient le modifier : c'est le principe d'inertie, formulé ici pour la première fois de manière générale, en rupture avec le principe aristotélicien selon lequel tout corps en mouvement tend naturellement au repos. La deuxième loi établit que, dans le choc de deux corps, la quantité totale de mouvement se conserve, même si elle se répartit différemment entre eux. La troisième loi stipule que tout mouvement tend à se poursuivre en ligne droite. À partir de ces trois lois et des seuls chocs entre les parties de matière, Descartes prétend rendre compte de la formation des astres, des planètes, de la lumière et de l'ensemble des phénomènes naturels, par le biais d'un mécanisme universel de tourbillons de matière subtile. Descartes fonde ces lois sur l'immuabilité de Dieu : Dieu conserve dans le monde la même quantité de mouvement qu'il y a placée initialement, car il serait contraire à sa perfection d'en changer. L'explication des phénomènes naturels est donc entièrement ''a priori'' et mécaniste. Descartes récuse toute explication faisant appel aux causes finales : chercher à quoi sert un phénomène naturel, c'est prétendre connaître les desseins de Dieu, prétention qu'il juge aussi vaine qu'orgueilleuse. Il refuse également les formes substantielles et les qualités occultes de la physique scolastique, ne reconnaissant d'autres principes explicatifs que la matière étendue et le mouvement. L'expérimentation, dans ce cadre, ne sert pas à découvrir les principes de la physique, qui sont connus ''a priori'', mais à choisir entre plusieurs hypothèses mécaniques également compatibles avec ces principes. La physique cartésienne s'étend au domaine du vivant par une physiologie intégralement mécaniste. Dans la cinquième partie du ''Discours'' et dans le ''Traité de l'homme'' (publié ''post mortem''), Descartes décrit le corps humain comme une machine hydraulique d'une extrême complexité. La circulation du sang, la digestion, la respiration, le sommeil, les mouvements volontaires et involontaires s'expliquent par le seul jeu mécanique des organes, des nerfs, du sang et des esprits animaux (particules très fines du sang acheminées au cerveau par les artères). Nul besoin d'invoquer une âme végétative ou une âme sensible, comme le faisait la tradition aristotélicienne : le corps fonctionne comme un automate. Il s'ensuit que les animaux, dépourvus de raison et de langage, pourraient n'être que des machines, des automates dénués de toute conscience (thèse de l'animal-machine). L'âme, lorsqu'elle est présente chez l'homme, n'est pas principe de vie mais principe de pensée : elle apporte la conscience, la réflexion, le langage articulé. C'est donc une tout autre conception du rapport entre la vie et la pensée que Descartes oppose à celle des aristotéliciens. == La métaphysique : le projet fondateur == Si la physique cartésienne décrit la nature du monde au moyen d'idées claires et distinctes, encore faut-il établir que ces idées sont véridiques, c'est-à-dire qu'elles décrivent effectivement la réalité telle qu'elle est. La méthode, à elle seule, ne peut fournir cette garantie ultime. C'est à la métaphysique qu'il revient de fonder la physique en démontrant deux thèses capitales : d'une part, l'existence et la véracité de Dieu ; d'autre part, la distinction réelle de l'esprit et du corps. La première thèse est nécessaire pour garantir la vérité des idées claires et distinctes ; la seconde permet de justifier la connaissance indépendante de l'expérience sensible. Descartes expose sa métaphysique principalement dans la quatrième partie du ''Discours de la méthode'', dans les ''[[Méditations métaphysiques]]'' (1641) et dans la première partie des ''Principes''. C'est toutefois dans les ''Méditations'' que l'argumentation atteint son plus haut degré de rigueur et de profondeur. L'ouvrage se présente comme un exercice de pensée que le lecteur est invité à pratiquer avec l'auteur, une expérience intérieure de la raison progressant depuis l'incertitude jusqu'à la certitude la plus ferme. === Le doute méthodique === [[Méditations métaphysiques/Première méditation : Des choses que l'on peut révoquer en doute|La première ''Méditation'']] met en œuvre un doute systématique, volontaire et hyperbolique, dont la finalité n'est pas sceptique mais fondatrice : il s'agit de suspendre l'assentiment à toutes les opinions reçues pour ne retenir que ce qui résiste à l'épreuve du doute le plus radical. Descartes distingue plusieurs niveaux de doute, chacun plus profond que le précédent. Le premier niveau concerne la tromperie des sens. Nous savons par expérience que les sens nous abusent parfois : une tour carrée paraît ronde de loin, un bâton plongé dans l'eau semble brisé. Puisque les sens nous ont déjà trompés, la prudence commande de ne jamais s'y fier entièrement. Toutefois, ce doute ne touche que les perceptions de choses éloignées ou très petites ; il semble que certaines données sensibles, comme le fait que je suis ici, assis devant le feu, vêtu d'une robe de chambre, ne puissent être raisonnablement contestées. Le deuxième niveau est celui de l'argument du rêve. Il m'est arrivé souvent de rêver que j'étais ici même, assis près du feu, alors que j'étais dans mon lit. Il n'existe aucun critère absolument certain permettant de distinguer la veille du sommeil. Dès lors, l'existence même du monde extérieur et de mon propre corps devient incertaine. Cependant, même dans le rêve, les vérités mathématiques et les natures simples (étendue, figure, nombre) semblent subsister : que je dorme ou que je veille, deux et trois font cinq et le carré n'a que quatre côtés. Le troisième niveau, le plus extrême, est celui de l'hypothèse du Dieu trompeur, transformée ensuite en fiction du Malin génie. Si Dieu est tout-puissant, rien n'empêche qu'il ait pu me donner une nature telle que je me trompe même dans les vérités qui me paraissent les plus évidentes. Les vérités mathématiques elles-mêmes tombent sous le coup de ce doute. Pour donner à cette hypothèse toute sa force, Descartes la reformule sous la figure d'un malin génie, aussi puissant que rusé, qui emploierait toute son industrie à me tromper. Ce doute hyperbolique, en suspendant toute certitude, crée les conditions d'un recommencement absolu de la pensée. L'hypothèse du Dieu trompeur s'inscrit dans un contexte théologique précis. La philosophie scolastique avait longuement discuté la question de savoir si la toute-puissance divine était compatible avec la vérité de la connaissance humaine, et de nombreux théologiens avaient conclu que le savoir humain ne pouvait jamais être que provisoire et conjectural face à l'infinité divine. C'est contre cette résignation épistémologique que Descartes construit sa métaphysique. === Le ''cogito'' et la nature du sujet pensant === Au cœur du doute le plus radical surgit une certitude inébranlable. Alors même que je suppose qu'un être tout-puissant s'emploie à me tromper en toutes choses, il est impossible que je n'existe pas au moment même où je pense être trompé. Pour me tromper, il faut bien que je sois. Le « je pense, donc je suis » (''ego cogito, ergo sum'') est la première vérité que le doute ne peut entamer, le roc sur lequel s'édifiera tout l'édifice du savoir. Le statut logique exact du ''cogito'' a fait l'objet de débats durables parmi les commentateurs : est-ce un syllogisme (comme si l'on déduisait l'existence d'une prémisse majeure « tout ce qui pense existe »), ou bien une intuition immédiate par laquelle la pensée se saisit elle-même dans l'exercice même de son doute ? Descartes lui-même insiste sur le caractère non syllogistique de cette certitude. Quoi qu'il en soit de ce débat, sa vérité est établie par le fait que toute tentative de la nier la confirme : si je pense que je n'existe pas, j'existe ; si je suis trompé, j'existe. Du ''cogito'', Descartes tire aussitôt un enseignement sur la nature du moi. La certitude d'exister ne dépend que de la pensée : si je cessais de penser, rien ne m'assurerait plus de mon existence. En revanche, je puis concevoir que je n'ai pas de corps tout en restant certain d'exister comme être pensant. La pensée, prise dans toute l'étendue de ses modalités – douter, concevoir, affirmer, nier, vouloir, refuser, imaginer, sentir –, constitue donc l'attribut essentiel du moi. Le moi est une « chose qui pense » (''res cogitans''), une substance dont toute l'essence consiste dans la pensée. Cette détermination du moi par la seule pensée, indépendamment de toute référence au corps, marque une rupture profonde avec la tradition aristotélicienne, pour laquelle la connaissance de l'esprit dérivait de sa réflexion sur les objets corporels. Descartes illustre cette thèse par la célèbre analyse du morceau de cire, dans la [[Méditations métaphysiques/Méditation seconde : De la nature de l'esprit humain ; et qu'il est plus aisé à connaître que le corps|deuxième ''Méditation'']]. Un morceau de cire fraîchement tiré de la ruche possède une couleur, une odeur, une figure, une taille déterminées. Si on l'approche du feu, toutes ces qualités sensibles changent : la couleur disparaît, l'odeur s'évanouit, la figure se modifie. Pourtant, nous jugeons que c'est toujours le même morceau de cire. Ce jugement d'identité ne peut venir des sens, puisque toutes les qualités sensibles ont changé. Il résulte d'un acte de l'entendement, qui saisit la cire comme une substance étendue, flexible et muable – propriétés que l'imagination ne peut embrasser dans leur infinité, mais que l'esprit seul conçoit. Ainsi, même la connaissance des corps suppose l'intervention de l'entendement et de ses idées, et non la seule réception passive des données sensibles. Le doute et le ''cogito'' ont donc conjointement établi deux résultats : l'existence certaine du moi pensant et la primauté de la connaissance intellectuelle sur la connaissance sensible. Reste à savoir si cette certitude du moi peut s'étendre à d'autres réalités, et en premier lieu à Dieu. === Les preuves de l'existence de Dieu === Le ''cogito'' fournit un premier fondement, mais il ne suffit pas à garantir la vérité de toutes nos idées claires et distinctes, car celles-ci restent menacées par l'hypothèse du Dieu trompeur. Pour lever cette menace, il faut démontrer que Dieu existe et qu'il n'est pas trompeur. Descartes propose trois preuves de l'existence de Dieu dans les troisième et cinquième ''Méditations''. La première preuve, dite preuve par l'effet, part de l'idée de Dieu que l'esprit trouve en lui-même. Descartes distingue dans toute idée deux aspects : sa ''réalité formelle'', c'est-à-dire son être en tant que mode de la pensée, et sa ''réalité objective'', c'est-à-dire le contenu qu'elle représente. Or, la réalité objective d'une idée doit avoir une cause qui possède au moins autant de réalité formelle. L'idée de Dieu est l'idée d'une substance infinie, éternelle, immuable, toute-puissante, toute-connaissante et créatrice de toutes choses. La réalité objective de cette idée excède de manière incommensurable la réalité formelle du moi, substance finie et imparfaite. Le moi ne peut donc pas être la cause de l'idée de Dieu : seul un être effectivement infini et parfait peut avoir produit en nous cette idée. Donc Dieu existe. Cette preuve suppose que l'idée d'infini est positive et originaire, et non pas simplement dérivée de la négation du fini. Descartes s'oppose ici à la position de Thomas d'Aquin : loin que l'idée d'infini résulte de la négation des limites du fini, c'est au contraire l'idée du fini qui suppose celle de l'infini, car je ne pourrais pas me reconnaître fini et imparfait si je n'avais en moi l'idée d'un être infini et parfait par comparaison auquel je me juge déficient. La deuxième preuve, développée également dans la troisième ''Méditation'', cherche la cause de l'existence du moi en tant qu'il possède l'idée de Dieu. Je n'ai pas pu me créer moi-même, car si j'avais eu ce pouvoir, je me serais donné toutes les perfections dont j'ai l'idée. Aucune cause moins parfaite que Dieu ne peut être la cause ultime de mon existence, car il faudrait encore expliquer d'où lui viendrait l'idée d'infini. La régression à l'infini étant impossible, il faut nécessairement aboutir à un être qui a en soi le principe de son existence et qui possède toutes les perfections contenues dans l'idée de Dieu. La troisième preuve, exposée dans la cinquième ''Méditation'', est la preuve ontologique. Elle procède de la seule définition de Dieu comme être souverainement parfait. L'existence est une perfection. Il serait donc contradictoire de concevoir un être souverainement parfait auquel manquerait l'existence, tout comme il est contradictoire de concevoir un triangle dont les angles ne font pas cent quatre-vingts degrés. L'existence appartient donc nécessairement à l'essence de Dieu, et nier son existence reviendrait à se contredire. Kant qualifiera plus tard cet argument de « preuve ontologique », reprochant à Descartes de traiter l'existence comme un prédicat réel. === La véracité divine et le problème du cercle === L'existence de Dieu une fois établie, Descartes en déduit que Dieu, être souverainement parfait, ne saurait être trompeur, car la tromperie suppose un défaut incompatible avec la perfection divine. Si Dieu n'est pas trompeur, alors la lumière naturelle qu'il a mise en nous – c'est-à-dire notre faculté de connaître les choses clairement et distinctement – est fiable. Tout ce que nous concevons clairement et distinctement est vrai. La menace du malin génie est définitivement écartée, et la science humaine reçoit son fondement ultime dans la véracité divine. On a objecté à Descartes, dès la parution des ''Méditations'', que son raisonnement serait circulaire : il invoque Dieu pour garantir la vérité des idées claires et distinctes, mais il démontre l'existence de Dieu au moyen d'idées claires et distinctes. Ce « cercle cartésien » a suscité d'innombrables commentaires, et les interprètes ne s'accordent pas sur la portée exacte de la réponse de Descartes. Celui-ci distingue entre les intuitions actuelles, dont l'esprit ne peut douter au moment même où il les perçoit, et les résultats de démonstrations passées, dont on pourrait douter si l'on n'avait pas la garantie divine. La véracité de Dieu ne serait donc pas requise pour assurer les évidences présentes – celles-ci s'imposent d'elles-mêmes –, mais pour garantir que ce qui a été clairement et distinctement perçu reste vrai même lorsque l'esprit ne se le représente plus actuellement. La garantie divine porterait ainsi sur la mémoire de nos évidences et sur la fiabilité de nos raisonnements longs et complexes. == La théorie des idées == La métaphysique cartésienne repose sur une théorie des idées qui reprend certains thèmes de la tradition platonicienne, en particulier la thèse selon laquelle les connaissances les plus hautes ne proviennent pas des sens. Descartes n'adopte pas pour autant le cadre métaphysique de Platon – il ne postule ni un monde séparé des Formes, ni la réminiscence – mais il partage avec lui la conviction que l'expérience sensible ne fournit pas de connaissance véritable. Descartes distingue trois sortes d'idées selon leur origine. Les idées adventices semblent provenir de l'expérience extérieure : ce sont les sensations de couleur, de son, de chaleur, etc. Les idées factices sont fabriquées par l'imagination, comme l'idée de sirène ou de chimère. Les idées innées, enfin, se trouvent en nous indépendamment de toute expérience sensible : ce sont les idées de Dieu, de l'âme, de l'étendue, des figures géométriques, des vérités mathématiques et logiques. Il convient de préciser que l'innéisme cartésien n'implique pas que ces idées soient présentes dans l'esprit sous une forme toute faite : dans les ''Notae in programma'' (1648), Descartes précise que les idées innées ne sont pas des contenus explicites de l'esprit, mais des dispositions ou des aptitudes que l'esprit possède de produire ces idées par sa propre puissance. Les idées innées se distinguent des autres par plusieurs caractéristiques. Elles se présentent à l'esprit de manière volontaire et non sous la contrainte d'un objet extérieur. Leur contenu s'impose avec nécessité et ne peut être modifié par la volonté. L'esprit est passif devant leur contenu : les vérités mathématiques ne sont pas des inventions mais des découvertes, comme l'atteste le fait que les progrès en mathématiques se font pas à pas, chaque nouvelle vérité s'imposant à l'esprit avec une nécessité que celui-ci ne contrôle pas. Cette passivité de l'esprit devant le contenu des idées innées indique que ce contenu correspond à quelque chose d'extérieur à l'esprit : les essences immuables et éternelles des choses. Descartes reprend ainsi la définition classique de la vérité comme adéquation de la pensée et de la chose : les idées innées sont vraies parce qu'elles décrivent adéquatement les structures réelles du monde. La théorie des idées innées, associée à la véracité divine, constitue le fondement de l'anti-empirisme cartésien. La connaissance véritable du monde ne passe pas par les sens, mais par l'entendement pur et ses idées innées. Les idées adventices, quant à elles, ne nous informent pas sur la nature réelle des choses, mais elles remplissent une autre fonction : elles incitent irrésistiblement l'esprit à croire à l'existence d'un monde extérieur. Si les corps n'existaient pas, Dieu serait trompeur, puisqu'il nous a naturellement portés à croire à leur existence sans nous donner aucun moyen de corriger cette croyance. La véracité divine garantit donc l'existence du monde extérieur, mais c'est par les idées innées que nous en connaissons la nature. Il faut souligner le caractère remarquable de cet ordre : Descartes connaît la nature du monde (l'étendue géométrique) avant d'en avoir établi l'existence réelle. == La théorie de la libre création des vérités éternelles == Une dimension essentielle de la métaphysique cartésienne n'apparaît que dans la correspondance : la théorie de la libre création des vérités éternelles. Formulée dès la lettre à Mersenne du 15 avril 1630, cette doctrine affirme que les vérités éternelles – y compris les vérités mathématiques et les lois logiques – ont été librement instituées par Dieu. Dieu n'est pas soumis aux vérités éternelles comme à des contraintes extérieures ; c'est lui qui les a établies par un acte de volonté libre. Que deux et trois fassent cinq, que le tout soit plus grand que la partie, que les contradictoires ne puissent être vraies ensemble : tout cela ne résulte pas d'une nécessité que Dieu subirait, mais d'un décret divin. Dieu aurait pu faire qu'il en fût autrement. Cette thèse, sans véritable précédent, porte à son comble l'affirmation de la toute-puissance divine. Sa portée exacte et son articulation avec le reste de la métaphysique cartésienne font cependant l'objet de discussions persistantes parmi les commentateurs. La thèse est restée discrète dans les œuvres publiées, n'apparaissant que dans certaines réponses aux ''Objections'', et Descartes ne l'utilise pas explicitement dans la construction de ses preuves métaphysiques. == L'erreur et le libre arbitre == Si Dieu est vérace et a doté l'homme d'une faculté de connaître fiable, comment se fait-il que celui-ci se trompe ? La quatrième ''Méditation'' est consacrée à cette question. L'erreur résulte du concours de deux facultés : l'entendement, qui est fini, et la volonté, qui est infinie. L'entendement ne conçoit qu'un nombre limité d'idées claires et distinctes. La volonté, en revanche, s'étend à tout : elle peut affirmer ou nier au-delà de ce que l'entendement lui présente clairement. L'erreur survient lorsque la volonté se prononce sur des matières que l'entendement ne conçoit pas clairement et distinctement. Elle n'est donc pas imputable à Dieu, mais au mauvais usage que l'homme fait de sa liberté. Dieu a créé l'homme avec un entendement fini et une volonté infinie, et cette disproportion n'est pas un défaut mais le signe de la grandeur de l'homme : c'est par la volonté, qui est « la plus grande perfection de l'homme », que nous sommes faits à l'image de Dieu. La liberté humaine occupe ainsi une place centrale dans la métaphysique cartésienne. Descartes la conçoit comme le pouvoir de choisir entre des contraires, mais aussi et surtout comme la capacité de se déterminer par la seule lumière de l'entendement. La plus haute forme de liberté n'est pas l'indifférence – état dans lequel la volonté ne penche vers aucun côté –, mais la détermination éclairée par laquelle l'esprit adhère au vrai et au bien qu'il connaît clairement. Plus l'entendement voit clairement, plus la volonté s'y porte infailliblement, et plus la liberté est grande. Ce double visage de la liberté cartésienne – liberté d'indifférence et liberté éclairée – fera l'objet de discussions importantes dans la postérité du cartésianisme. == Le dualisme : distinction et union de l'âme et du corps == La sixième ''Méditation'' établit la distinction réelle de l'âme et du corps. Puisque l'esprit peut être conçu clairement et distinctement sans le corps, et le corps sans l'esprit, et puisque Dieu peut réaliser tout ce qui est clairement et distinctement conçu, l'âme et le corps sont deux substances réellement distinctes, capables d'exister séparément. L'âme est une substance pensante (''res cogitans''), dont l'essence est la pensée ; le corps est une substance étendue (''res extensa''), dont l'essence est l'étendue en trois dimensions. Ce dualisme des substances est la clé de voûte de toute la philosophie cartésienne : il fonde la possibilité des idées innées (l'âme, étant indépendante du corps, peut posséder des idées qui ne proviennent pas de l'expérience corporelle), justifie la physique mécaniste (les corps, n'étant qu'étendue, n'ont besoin pour être expliqués que de figures et de mouvements) et ouvre la possibilité de l'immortalité de l'âme. Cependant, le dualisme cartésien ne s'en tient pas à la seule distinction. L'expérience quotidienne atteste que l'âme et le corps, bien que réellement distincts, sont étroitement unis. La douleur, le plaisir, la faim, la soif ne sont pas de pures pensées détachées du corps, mais des sentiments confus résultant de l'union de l'âme et du corps. L'âme n'est pas dans le corps comme un pilote en son navire, qui percevrait les avaries du vaisseau de l'extérieur : elle lui est intimement mêlée, de sorte que les affections du corps se traduisent immédiatement en modifications de l'âme. Descartes parle à ce propos d'une « union substantielle » de l'âme et du corps, formant un composé unique. L'expression, empruntée au vocabulaire de la scolastique, est toutefois employée par Descartes dans un sens profondément transformé : il ne s'agit plus de l'union d'une forme et d'une matière au sens aristotélicien, mais d'un fait d'expérience irréductible à la seule distinction rationnelle des substances. Descartes cherche ainsi à se frayer une voie entre les modèles platonicien et aristotélicien. Platon avait raison de soutenir que l'âme et le corps sont deux réalités distinctes, mais il n'a pas suffisamment rendu compte de leur union intime. Aristote avait raison de concevoir leur union comme celle d'une forme et d'une matière constituant un seul tout, mais il n'avait pas, aux yeux de Descartes, suffisamment reconnu leur distinction substantielle. L'originalité de la position cartésienne est de maintenir ensemble ces deux thèses apparemment contradictoires. La distinction réelle est établie par la raison ; l'union est un donné de l'expérience intérieure, garanti par la véracité divine. Cette union a une finalité pratique : les sensations, bien qu'elles déforment la réalité du point de vue de la connaissance spéculative, informent correctement sur ce qui est utile ou nuisible à la conservation du composé âme-corps. L'homme se trompe non pas en ayant des sensations, mais lorsqu'il utilise ces informations pratiques à des fins de connaissance théorique. Sur la question du lieu de l'interaction entre l'âme et le corps, Descartes identifie la glande pinéale (ou ''conarium'') comme le siège principal de l'âme, non pas parce que l'âme y serait enfermée, mais parce que c'est la seule partie du cerveau qui ne soit pas double et qui puisse servir de point de convergence entre les mouvements des esprits animaux et les actes de la pensée. Cette hypothèse physiologique, vivement critiquée dès le vivant de Descartes, notamment par la princesse Élisabeth de Bohême, a le mérite de poser explicitement le problème de l'interaction entre deux substances hétérogènes, problème qui hantera toute la philosophie post-cartésienne. La question du dualisme ne concerne pas seulement la métaphysique : elle engage directement la morale. Car c'est de l'union de l'âme et du corps que naissent les passions, et c'est en apprenant à régler cette union que l'homme peut espérer bien conduire sa vie. == La morale == La morale, dans l'image de l'arbre des sciences, constitue l'une des branches les plus élevées. Descartes n'a pas eu le temps de rédiger un traité de morale systématique, mais sa pensée éthique se déploie dans le ''Discours de la méthode'', dans la correspondance avec la princesse Élisabeth de Bohême et avec Pierre Chanut, et dans ''Les Passions de l'âme''. Dans la troisième partie du ''Discours'', Descartes présente une « morale par provision », destinée à guider la conduite pendant que l'esprit est occupé à reconstruire l'édifice du savoir. Cette morale provisoire comporte trois maximes principales. La première prescrit d'obéir aux lois et aux coutumes de son pays, de conserver la religion dans laquelle on a été élevé et de suivre les opinions les plus modérées. La deuxième recommande la fermeté et la résolution dans les actions : une fois qu'on a adopté une opinion, il faut la suivre avec constance, même si elle est incertaine, plutôt que de demeurer irrésolu. La troisième maxime, d'inspiration stoïcienne, conseille de changer ses désirs plutôt que l'ordre du monde, c'est-à-dire de limiter ses aspirations à ce qui est véritablement en notre pouvoir – nos pensées seules le sont – et de considérer tout le reste comme indépendant de notre volonté. La correspondance avec Élisabeth, entre 1643 et 1649, permet à Descartes de préciser et d'approfondir sa pensée morale au-delà de la morale provisoire. Le souverain bien de l'homme est la béatitude, qui ne se confond pas avec le bonheur dépendant des circonstances extérieures, mais consiste en un contentement intérieur de l'esprit résultant du bon usage de la volonté. Trois conditions sont requises pour y parvenir : connaître ce qui est véritablement bien, c'est-à-dire ce que la raison nous enseigne ; avoir la ferme résolution de faire tout ce que la raison nous conseille ; limiter nos désirs à ce qui dépend de nous. Descartes ajoute à ces principes la méditation de certaines vérités fondamentales : l'existence d'un Dieu dont tout dépend, la distinction de l'âme et du corps qui nous assure de la possibilité de survivre à la mort, l'immensité de l'univers qui relativise nos attachements terrestres, et le sentiment d'appartenir à une communauté humaine dont le bien doit l'emporter sur notre bien particulier. == Les passions de l'âme == Le dernier ouvrage publié du vivant de Descartes, ''Les Passions de l'âme'' (1649), rédigé à la demande de la princesse Élisabeth, constitue à la fois un traité de physiologie et un traité de morale. Descartes y entreprend d'étudier les passions « en physicien », c'est-à-dire de les expliquer par leurs causes naturelles et mécaniques, sans visée rhétorique ni moraliste. Les passions sont des perceptions ou des sentiments que les mouvements des esprits animaux provoquent dans l'âme. Elles sont causées par le corps, mais ressenties par l'âme, et constituent l'un des effets les plus manifestes de l'union substantielle. Descartes identifie six passions primitives, dont toutes les autres sont des espèces ou des combinaisons : l'admiration, l'amour, la haine, le désir, la joie et la tristesse. L'admiration occupe une place à part, car elle est la passion qui nous dispose à apprendre et à connaître les choses nouvelles. Les autres passions se rapportent au bien et au mal, présent ou futur. Toutes les passions, considérées en elles-mêmes, sont bonnes et utiles : elles portent l'âme à vouloir les choses que la nature lui enseigne être utiles et à persévérer dans cette volonté. Une vie sans passions serait une vie appauvrie, dépourvue de saveur et de plaisir. Descartes se distingue ici nettement du stoïcisme, qui prônait l'éradication des passions. Néanmoins, les passions ont besoin d'être réglées, car leurs excès peuvent conduire à des actions nuisibles. Descartes estime cependant que la raison ne peut pas combattre directement les passions par la seule force de ses arguments : les passions procèdent de mouvements physiologiques que seuls des mouvements contraires peuvent contrebalancer. La stratégie consiste donc à susciter des passions contraires à celles que l'on veut maîtriser, en portant l'attention sur les pensées qui leur sont ordinairement associées. L'esprit peut aussi tirer parti du caractère arbitraire de la relation entre mouvements corporels et pensées de l'âme : par l'habitude et l'exercice, il est possible de modifier les associations naturelles et d'obtenir, par exemple, que la vue d'un danger suscite la hardiesse plutôt que la peur. Ce travail sur les passions, que Descartes compare au dressage d'un animal, fait de l'homme l'artisan de sa propre nature. La vertu cardinale de la morale cartésienne est la générosité, définie comme la ferme résolution de bien user de son libre arbitre, jointe à la conscience que rien ne nous appartient véritablement que cette libre disposition de nos volontés. L'homme généreux ne s'estime que pour son bon usage de la liberté, ne méprise personne, n'envie rien et affronte les événements avec résolution et tranquillité. La générosité est à la fois une passion et une vertu : c'est le sentiment de notre propre liberté érigé en principe de conduite. Elle conduit à l'estime de soi fondée sur le bon usage du libre arbitre, et à une bienveillance universelle, puisque celui qui reconnaît que la vraie grandeur réside dans le bon usage de la volonté ne peut manquer de la reconnaître aussi chez autrui. == L'héritage cartésien == L'influence de Descartes sur la philosophie moderne est considérable et multiforme. En fondant la philosophie sur le sujet pensant, il inaugure ce que l'on a pu appeler la « philosophie de la conscience », qui domine la pensée occidentale jusqu'à la fin du {{s-|XIX|e}}. Le dualisme de l'âme et du corps suscite d'emblée des difficultés considérables, en particulier la question de l'interaction entre deux substances hétérogènes, qui occupera Malebranche, Spinoza et Leibniz. Malebranche, refusant toute action du corps sur l'âme, développe l'occasionalisme : Dieu seul est cause efficiente, et les événements corporels ne sont que l'occasion des modifications de l'âme. Spinoza, récusant le dualisme lui-même, identifie la pensée et l'étendue comme deux attributs d'une substance unique, Dieu ou la Nature. Leibniz propose l'harmonie préétablie, selon laquelle l'âme et le corps suivent chacun leurs propres lois sans interaction réelle, leurs séries d'événements étant coordonnées de toute éternité par Dieu. Sur le plan scientifique, la physique cartésienne, bien qu'elle ait été largement supplantée par la physique newtonienne, a joué un rôle important dans l'établissement du mécanisme moderne et dans la formulation du principe d'inertie. La géométrie analytique, quant à elle, reste l'une des contributions majeures de Descartes à l'histoire des mathématiques. La méthode du doute, la primauté accordée à la certitude du ''cogito'', la théorie des idées innées et la distinction du sujet et de l'objet constituent des points de référence obligés de toute la philosophie ultérieure, de Kant à Husserl, que ce soit pour les reprendre, les transformer ou les contester. == Bibliographie == * Ferdinand Alquié, ''La Découverte métaphysique de l'homme chez Descartes'', Paris, PUF, 1950. * Martial Gueroult, ''Descartes selon l'ordre des raisons'', 2 vol., Paris, Aubier, 1953. * Jean-Marie Beyssade, ''La Philosophie première de Descartes'', Paris, Flammarion, 1979. * Geneviève Rodis-Lewis, ''Descartes. Biographie'', Paris, Calmann-Lévy, 1995. * Denis Kambouchner, ''L'Homme des passions'', 2 vol., Paris, Albin Michel, 1995. * Jean-François Pradeau (dir.), ''Histoire de la philosophie'', Paris, Seuil, 2009. {{DEFAULTSORT:Descartes}} [[Catégorie:Philosophe]] 19sonj17so4itd1mo0j6vp7m4wvsqsd Dictionnaire de philosophie/Karl Marx 0 83744 764802 762062 2026-04-24T10:28:23Z ~2026-25114-99 123599 764802 wikitext text/x-wiki {{DicoPhilo|Karl Marx}} == Formation intellectuelle et itinéraire == Karl Marx naît le 5 mai 1818 à Trèves, en Rhénanie, dans une famille de la bourgeoisie cultivée. Son père, Heinrich Marx, avocat, s'est converti du judaïsme au protestantisme vers 1817, moins par conviction religieuse que pour satisfaire aux contraintes civiques et juridiques que la législation prussienne faisait peser sur les Juifs, auxquels l'accès aux professions libérales et à la fonction publique était sévèrement restreint. Imprégné des Lumières françaises, lecteur de Voltaire et de Rousseau, Heinrich Marx transmet à son fils un rationalisme dont l'empreinte se retrouve dans la sensibilité du philosophe aux questions d'émancipation et de liberté civique. En 1835, Marx s'inscrit à l'université de Bonn pour y étudier le droit, avant de rejoindre l'année suivante l'université de Berlin. C'est là qu'il entre en contact avec la philosophie de Hegel, alors dominante dans l'Allemagne intellectuelle, et qu'il fréquente le cercle des Jeunes Hégéliens de Berlin — le ''Doktorklub'' de Bruno Bauer et Karl Friedrich Koppen — avant de nouer des liens avec Arnold Ruge et Moses Hess, qui participent au même mouvement de critique philosophique appliqué à la religion et à l'État prussien. La thèse de doctorat que Marx soumet en 1841 à l'université d'Iéna, consacrée à la ''Différence de la philosophie de la nature chez Démocrite et chez Épicure''<ref>K. Marx, Differenz der demokritischen und epikureischen Naturphilosophie, 1841 ; trad. fr. in Œuvres, éd. M. Rubel, Paris, Gallimard, « Bibliothèque de la Pléiade », t. III, 1982.</ref>, témoigne déjà d'un intérêt pour le matérialisme antique et pour la question de la liberté individuelle face au déterminisme. La carrière universitaire lui étant fermée par le climat politique, Marx se tourne vers le journalisme, collabore à la ''Rheinische Zeitung'' à partir de 1842 et en devient le rédacteur en chef le 15 octobre de la même année. C'est dans cette fonction qu'il se confronte pour la première fois à des questions économiques et sociales concrètes — les débats sur le vol de bois, la misère des vignerons mosellans — qui le convainquent de l'insuffisance d'une critique purement philosophique. L'interdiction du journal par la censure prussienne pousse Marx à l'exil. Il s'installe d'abord à Paris en 1843, où il fréquente les milieux socialistes français et les émigrés allemands, rencontre Friedrich Engels — qui deviendra son collaborateur de toute une vie — et rédige les ''Manuscrits économico-philosophiques de 1844'', texte resté inédit de son vivant mais devenu central pour la compréhension de sa pensée de jeunesse. Expulsé de France en 1845 à la demande du gouvernement prussien, Marx se réfugie à Bruxelles, où il écrit avec Engels ''L'Idéologie allemande'' (1845-1846, publiée posthumément) et le ''Manifeste du parti communiste'' (1848). Après les échecs des révolutions de 1848, il s'établit définitivement à Londres en 1849. C'est là, dans des conditions matérielles souvent précaires, qu'il consacre l'essentiel de son activité à l'élaboration de sa critique de l'économie politique, dont le premier volume du ''Capital'' paraît en 1867. Les deux volumes suivants seront publiés après sa mort par Engels. Marx meurt à Londres le 14 mars 1883. == La critique de Hegel et le renversement de la dialectique == Le rapport de Marx à Hegel relève à la fois de l'héritage et de la rupture. De Hegel, Marx retient la méthode dialectique — l'idée que la réalité se développe à travers des contradictions qui se surmontent — mais il en conteste le fondement idéaliste. Pour Hegel, le mouvement de l'histoire est celui de l'Esprit qui se réalise progressivement à travers les institutions, les cultures et les États ; le réel est en dernière instance l'expression d'une rationalité qui se déploie et se reconnaît elle-même. Marx considère que cette construction, aussi puissante soit-elle, renverse les rapports effectifs : ce n'est pas la conscience qui détermine l'être social, mais l'être social qui détermine la conscience. La première confrontation systématique se trouve dans la ''Critique du droit politique hégélien'' (1843)<ref>K. Marx, Zur Kritik der Hegelschen Rechtsphilosophie, 1843 ; trad. fr. in Œuvres, éd. cit., t. III.</ref>. Marx y montre que Hegel, en faisant de l'État la réalisation de l'Idée éthique, transforme les rapports réels entre la société civile et l'État en des moments logiques d'un développement conceptuel. Au lieu de partir de la famille et de la société civile comme réalités empiriques pour comprendre comment l'État s'en nourrit et s'y enracine, Hegel fait de l'État le sujet et de la société civile le prédicat. C'est ce que Marx appelle un renversement mystificateur : les rapports réels sont traités comme des manifestations de l'Idée, alors que c'est l'Idée qui devrait être comprise comme le reflet des rapports réels. À ce stade, la critique porte moins sur la dialectique elle-même que sur l'usage spéculatif qu'en fait Hegel dans le domaine politique. Ce n'est que trente ans plus tard, dans la postface à la seconde édition du ''Capital'' (1873)<ref name="n1">K. Marx, Postface à la seconde édition allemande du Capital, 1873, MEW, t. XXIII, p. 27 ; trad. fr. J.-P. Lefebvre, Paris, PUF, « Quadrige », 1993, p. 17.</ref>, que Marx formulera explicitement le projet de « remettre la dialectique sur ses pieds » en la retournant pour en extraire le noyau rationnel de son enveloppe mystique — donnant ainsi une portée méthodologique générale à ce qui avait d'abord été une critique politique de la philosophie de l'État. Ce renversement a des conséquences profondes. La philosophie ne peut plus être une contemplation du mouvement de l'Esprit ; elle doit devenir une analyse des conditions matérielles de l'existence humaine. La célèbre onzième thèse sur Feuerbach<ref>K. Marx, « Thesen über Feuerbach » (1845), n° 11, MEW, t. III, p. 7 ; trad. fr. in L'Idéologie allemande, Paris, Éditions sociales, 2012, p. 1.</ref> — « Les philosophes n'ont fait qu'interpréter le monde de diverses manières ; ce qui importe, c'est de le transformer » — condense cette exigence. La philosophie est ainsi appelée à se dépasser elle-même en devenant une pratique critique orientée vers la transformation des rapports sociaux. Ce passage de la spéculation à la praxis constitue l'un des gestes fondateurs de la pensée marxienne. == Feuerbach, la critique de la religion et le concept d'aliénation == La rupture avec l'idéalisme hégélien doit beaucoup à Ludwig Feuerbach, dont ''L'Essence du christianisme'' (1841) exerce sur le jeune Marx une influence considérable. Feuerbach soutient que la religion n'est pas la révélation d'un être transcendant, mais la projection par l'homme de ses propres qualités — raison, amour, volonté — dans un être imaginaire qu'il appelle Dieu. En adorant Dieu, l'homme adore en réalité sa propre essence, mais sous une forme aliénée : il s'appauvrit d'autant qu'il enrichit cet être fictif. La théologie doit donc être ramenée à l'anthropologie. Marx accueille cette critique avec enthousiasme, mais il en perçoit rapidement les limites. Si Feuerbach a raison de dissoudre l'essence religieuse dans l'essence humaine, il laisse sans réponse la question de savoir pourquoi l'homme a besoin de cette projection. Autrement dit, Feuerbach décrit le mécanisme de l'aliénation religieuse sans en expliquer les causes. Or, pour Marx, l'aliénation religieuse a ses racines dans l'aliénation sociale et économique. La religion est « le soupir de la créature opprimée, le cœur d'un monde sans cœur, l'esprit de conditions dépourvues d'esprit »<ref>K. Marx, « Zur Kritik der Hegelschen Rechtsphilosophie. Einleitung », in Deutsch-Französische Jahrbücher, 1844, MEW, t. I, p. 378 ; trad. fr. in Œuvres, éd. cit., t. III, p. 382.</ref>. Elle est à la fois l'expression d'une détresse réelle et la protestation contre cette détresse. La critique de la religion débouche donc nécessairement sur la critique des conditions sociales qui la rendent nécessaire. Les ''Thèses sur Feuerbach'' (1845), texte bref mais dense, précisent la nature de cette insuffisance. Feuerbach conçoit l'homme comme un être générique, abstrait, défini par des propriétés naturelles invariables. Il ignore que l'essence humaine n'est pas une abstraction résidant dans l'individu isolé, mais « l'ensemble des rapports sociaux »<ref>K. Marx, « Thesen über Feuerbach », n° 6, MEW, t. III, p. 6.</ref>. C'est cette conception relationnelle et historique de l'être humain que Marx oppose au naturalisme de Feuerbach. L'homme n'est pas une nature donnée une fois pour toutes ; il se constitue à travers son activité productive et ses rapports avec les autres dans des conditions historiques déterminées. Le concept d'aliénation est ainsi retravaillé : il ne désigne plus seulement la projection illusoire dans la religion, mais un ensemble de processus par lesquels l'activité humaine, ses produits et ses rapports sociaux prennent une forme étrangère et hostile à ceux qui les produisent. == La conception matérialiste de l'histoire == === Les fondements === La conception matérialiste de l'histoire, que Marx et Engels élaborent principalement dans ''L'Idéologie allemande'' et que Marx résume dans la célèbre préface à la ''Contribution à la critique de l'économie politique'' (1859)<ref>K. Marx, Zur Kritik der politischen Ökonomie. Vorwort, 1859, MEW, t. XIII, p. 8-9 ; trad. fr. in Œuvres, éd. cit., t. I, 1963, p. 272-275.</ref>, constitue le cadre général dans lequel s'inscrit l'ensemble de la pensée marxienne. Son principe peut se formuler ainsi : ce ne sont pas les idées, les représentations ou les formes de conscience qui déterminent l'existence sociale des hommes, mais, à l'inverse, leur existence sociale — c'est-à-dire la manière dont ils produisent et reproduisent leur vie matérielle — qui détermine leurs formes de conscience. Le point de départ est le constat que les hommes, pour vivre, doivent produire les moyens de leur subsistance. Cette production n'est pas un acte isolé mais un processus social qui engage des rapports déterminés entre les individus. À chaque stade du développement historique correspondent des forces productives — l'ensemble des moyens techniques, des savoirs et des capacités humaines mobilisés dans la production — et des rapports de production — les relations sociales dans lesquelles les hommes entrent pour produire, et qui déterminent notamment les formes de propriété. L'ensemble formé par les forces productives et les rapports de production constitue ce que Marx appelle le mode de production, qui est la base réelle sur laquelle s'élève une superstructure juridique et politique, à laquelle correspondent des formes de conscience sociale déterminées. Le dynamisme de l'histoire tient à la tension entre forces productives et rapports de production. À un certain moment du développement, les rapports de production, qui avaient favorisé le déploiement des forces productives, deviennent une entrave à leur développement ultérieur. S'ouvre alors, selon Marx, une époque de transformation sociale au cours de laquelle les formes juridiques, politiques, religieuses et philosophiques — en un mot, les formes idéologiques — à travers lesquelles les hommes prennent conscience de ce conflit et le mènent jusqu'au bout, se trouvent bouleversées. La succession des modes de production — Marx mentionne, dans la préface de 1859, les formes « asiatique, antique, féodale et bourgeoise moderne » — dessine ainsi les grandes époques de la formation sociale. Il convient toutefois de ne pas lire cette énumération comme une loi universelle de développement, applicable mécaniquement à toutes les sociétés : Marx lui-même la présente comme une esquisse portant sur l'Europe occidentale, et la catégorie de « mode de production asiatique » a fait l'objet de débats considérables, aussi bien du vivant de Marx que dans la tradition ultérieure. On reviendra plus bas sur les mises en garde que Marx formule explicitement à cet égard. === Mode de production et formation sociale === La notion de mode de production ne désigne pas simplement une technique ou un type d'économie, mais une totalité articulée de rapports sociaux. Le mode de production capitaliste, qui occupe l'essentiel de l'analyse de Marx, se caractérise par la séparation entre les propriétaires des moyens de production (la classe capitaliste) et les travailleurs qui, ne possédant que leur force de travail, sont contraints de la vendre pour subsister. Cette séparation, qui est le résultat d'un long processus historique que Marx décrit sous le nom d'« accumulation primitive », constitue la condition structurelle du rapport d'exploitation capitaliste. L'un des apports les plus remarquables de l'analyse marxienne est la reconstitution historique de ce processus de séparation, que Marx nomme l'« accumulation primitive » et à laquelle il consacre la dernière partie du livre I du ''Capital''. Il y montre que le capitalisme ne naît pas d'un processus pacifique d'épargne et d'échange, comme le veut le récit des économistes classiques, mais d'un mouvement violent de dépossession des producteurs directs — paysans chassés de leurs terres par les enclosures en Angleterre, dissolution des rapports féodaux, colonisation, traite négrière, législation sanguinaire contre le vagabondage. L'accumulation primitive n'est pas un épisode pré-capitaliste dont le système marchand serait ensuite le développement naturel ; elle est la condition historique de possibilité du capitalisme, et Marx souligne que ses méthodes sont tout sauf idylliques. Ce chapitre du ''Capital'' est aussi l'un de ceux où Marx déploie avec le plus de force ses talents d'historien et de polémiste, en documentant avec précision les législations, les expropriations et les résistances populaires qui accompagnent la naissance du salariat moderne. Il importe de noter que Marx ne conçoit pas le rapport entre la base économique et la superstructure de manière mécanique. Les formes juridiques, politiques et idéologiques ne sont pas le simple reflet passif des rapports de production ; elles possèdent une consistance propre, exercent en retour une influence sur la base économique et peuvent, dans certaines conditions, jouer un rôle actif dans les processus de transformation sociale. La correspondance d'Engels avec différents interlocuteurs dans les années 1890 insiste sur ce point : le matérialisme historique ne prétend pas que le facteur économique est le seul facteur déterminant, mais qu'il est déterminant « en dernière instance »<ref>F. Engels, lettre à J. Bloch du 21 septembre 1890, MEW, t. XXXVII, p. 463. La formule « en dernière instance » (in letzter Instanz) est d'Engels, non de Marx.</ref>. Les formes superstructurelles disposent d'une autonomie relative et leur développement obéit en partie à une logique interne propre. Cette précision est importante pour comprendre le statut de l'analyse marxienne. Il ne s'agit pas d'un déterminisme économique simpliste qui réduirait l'histoire à l'action d'un seul facteur, mais d'une tentative pour penser l'articulation complexe entre les différentes dimensions de la vie sociale — production, droit, politique, religion, art, philosophie — en rapportant cette articulation aux conditions matérielles de l'existence. C'est ce que Marx appelle la « méthode d'économie politique » dans l'introduction aux ''Grundrisse'' (1857-1858)<ref name="n2">K. Marx, Einleitung zur Kritik der politischen Ökonomie (1857), MEW, t. XIII, p. 631-642 ; trad. fr. in Grundrisse, Paris, Éditions sociales, 2011, t. I.</ref> : partir non de catégories abstraites, mais des rapports réels dans leur interdépendance concrète. == L'aliénation == Le concept d'aliénation traverse l'œuvre de Marx, depuis les écrits de jeunesse jusqu'au ''Capital'', même si la terminologie et l'usage qu'il en fait évoluent. Dans les ''Manuscrits de 1844'', Marx distingue quatre dimensions de l'aliénation du travail dans le mode de production capitaliste. La première concerne le rapport du travailleur au produit de son travail. Le produit apparaît comme un objet étranger, comme une puissance indépendante qui se dresse face à celui qui l'a créé. Plus le travailleur produit, plus il se trouve dépossédé, car le produit de son travail appartient à un autre et s'accumule comme capital qui renforce la domination exercée sur lui. La deuxième dimension touche l'activité productive elle-même : le travail n'est pas pour le travailleur l'expression de ses capacités et de sa personnalité, mais une activité subie, contrainte, dans laquelle il ne s'appartient pas. Le travailleur ne se sent pas chez lui dans son travail ; il ne se sent lui-même qu'en dehors du travail. Son travail n'est pas la satisfaction d'un besoin, mais seulement un moyen de satisfaire des besoins extérieurs à lui. La troisième dimension est l'aliénation de l'essence humaine ou « être générique ». Marx, reprenant de manière originale un concept de Feuerbach, considère que ce qui distingue l'activité humaine de l'activité animale est son caractère conscient, universel et libre : l'homme ne produit pas seulement sous l'emprise du besoin immédiat, mais peut produire selon les lois de la beauté, se rapporter à l'ensemble de la nature comme à son corps inorganique, et faire de son activité vitale un objet de sa volonté et de sa conscience. Le travail aliéné renverse ce rapport : l'activité vitale, la vie productive, n'est plus qu'un moyen d'assurer l'existence physique. L'homme est ainsi aliéné de ce qui constitue sa spécificité. La quatrième dimension découle des précédentes : l'aliénation de l'homme par rapport à l'homme. Dans le travail aliéné, le rapport de l'individu à l'autre est lui-même un rapport d'étrangeté et de domination ; le produit du travail ne lui appartient pas parce qu'il appartient à un autre homme qui ne travaille pas — le capitaliste. La question de savoir si le concept d'aliénation disparaît dans les œuvres de maturité ou s'il y persiste sous d'autres formes a fait l'objet de controverses interprétatives importantes. Louis Althusser a défendu la thèse d'une « coupure épistémologique » séparant un jeune Marx encore pris dans une problématique philosophique humaniste d'un Marx scientifique, auteur du ''Capital'', qui aurait abandonné le concept d'aliénation au profit de catégories strictement économiques. D'autres interprètes font valoir que le concept de fétichisme de la marchandise, central dans le ''Capital'', prolonge et approfondit l'analyse de l'aliénation sous une forme plus rigoureuse. Sans trancher ici ce débat, on peut observer que la préoccupation pour les processus par lesquels les rapports sociaux prennent une forme réifiée et se retournent contre les individus qui les constituent demeure présente d'un bout à l'autre de l'œuvre. == La critique de l'idéologie == On regroupe souvent sous le terme de « théorie marxienne de l'idéologie » un ensemble de formulations dont il faut d'emblée souligner le caractère dispersé. Marx n'a pas laissé de traité systématique de l'idéologie : les développements les plus étendus se trouvent dans ''L'Idéologie allemande'', mais des formulations distinctes apparaissent dans la préface de 1859, dans le ''Capital'' et dans les écrits politiques. Le statut exact de ces formulations — leur continuité, leur rapport au concept de fétichisme, le degré auquel elles constituent une « théorie » cohérente — fait l'objet de discussions savantes, notamment entre les lectures d'inspiration althussérienne, qui tendent à distinguer nettement idéologie et fétichisme, et celles qui, à la suite de Lukács, les articulent dans une théorie unitaire de la réification. Sous ces réserves, on peut dégager les lignes de force suivantes. Par idéologie, Marx entend un ensemble de représentations, d'idées et de valeurs qui, tout en se présentant comme universelles et autonomes, expriment en réalité les intérêts particuliers de la classe dominante et contribuent à les légitimer. L'idéologie n'est pas un simple mensonge ni une erreur individuelle ; elle est une forme de conscience socialement nécessaire, produite par les conditions mêmes de l'existence dans une société divisée en classes. La métaphore de la ''camera obscura'', que Marx emploie dans ''L'Idéologie allemande'', illustre ce renversement : de même que, dans la chambre noire, les objets apparaissent inversés, de même, dans l'idéologie, les rapports réels entre les hommes apparaissent à l'envers. Les idées semblent gouverner le monde alors qu'elles ne sont que l'expression intellectuelle des rapports matériels dominants. Les pensées dominantes d'une époque ne sont jamais que les pensées de la classe dominante, c'est-à-dire de la classe qui dispose des moyens de production matérielle et, par là, des moyens de production intellectuelle<ref name="n3">K. Marx et F. Engels, Die deutsche Ideologie (1845-1846), MEW, t. III, p. 33 ; trad. fr. Paris, Éditions sociales, 2012, p. 30.</ref>. La critique de l'idéologie ne conduit pas cependant à un pur relativisme. Marx ne soutient pas que toute pensée est une simple illusion de classe. La science — et notamment la critique de l'économie politique qu'il entreprend — prétend accéder à une connaissance objective des rapports sociaux, précisément en dévoilant les conditions matérielles que l'idéologie masque. Le dépassement de l'idéologie suppose à la fois un travail théorique de démystification et une transformation pratique des conditions sociales qui la produisent. C'est en ce sens que la critique de l'idéologie est indissociable de la critique de l'économie politique et de la perspective d'une transformation sociale : tant que subsistent les rapports de domination de classe, les formes idéologiques qui les expriment et les légitiment se reproduisent nécessairement. Il faut souligner que la critique de l'idéologie s'exerce d'abord et en priorité à l'encontre de l'économie politique elle-même. Marx reconnaît à la tradition de Smith et de Ricardo le mérite d'avoir identifié le travail comme source de la richesse, mais il leur reproche de naturaliser les catégories du capitalisme — propriété privée, salaire, profit, rente — en les traitant comme des données éternelles au lieu de les comprendre comme des formes historiques déterminées. L'économie politique « classique » décrit le capitalisme, mais en le présentant comme l'horizon indépassable de toute société humaine, elle assume une fonction idéologique. La critique de Marx consiste à montrer que ces catégories, loin d'être naturelles, sont les produits de rapports sociaux historiquement situés et, à ce titre, transitoires. == La critique de l'économie politique == === La marchandise et la valeur === Le ''Capital'' s'ouvre par l'analyse de la marchandise, que Marx présente comme la « cellule » de la société bourgeoise. Ce choix n'est pas arbitraire : la marchandise est la forme élémentaire dans laquelle se manifeste la richesse dans les sociétés où domine le mode de production capitaliste. Toute marchandise possède un double caractère : elle est à la fois valeur d'usage — elle satisfait un besoin humain déterminé — et valeur d'échange — elle est échangeable contre d'autres marchandises dans des proportions déterminées. La question que Marx pose est celle du fondement de la valeur d'échange : qu'est-ce qui fait que des marchandises qualitativement différentes — une table, du linge, du blé — sont commensurables et peuvent s'échanger dans des proportions définies ? Si l'on fait abstraction des qualités sensibles qui font de chaque marchandise une valeur d'usage particulière, il ne leur reste plus, selon Marx, qu'une propriété commune : celle d'être des produits du travail humain. Mais pas du travail concret — celui du menuisier, du tisserand, du cultivateur — qui produit des valeurs d'usage déterminées. Il faut faire abstraction du caractère concret du travail pour ne retenir que ce que tous ces travaux ont en commun : la dépense de force humaine de travail en général, ce que Marx appelle le travail abstrait. La valeur d'une marchandise est alors déterminée par la quantité de travail socialement nécessaire à sa production, c'est-à-dire le temps de travail requis dans les conditions moyennes de production, avec le degré moyen d'habileté et d'intensité du travail en vigueur dans une société donnée. Ce double caractère de la marchandise — valeur d'usage et valeur — reflète le double caractère du travail qui la produit : travail concret, utile, créateur de valeurs d'usage, et travail abstrait, créateur de valeur. Marx considère cette distinction comme l'un des apports les plus importants de son analyse. Elle permet de comprendre que la valeur n'est pas une propriété naturelle des choses, mais un rapport social qui prend la forme d'une propriété des choses. Dans une société marchande, les rapports entre les producteurs ne se manifestent pas directement comme rapports sociaux entre personnes, mais comme rapports entre les produits de leur travail. C'est cette inversion que Marx nomme le « fétichisme de la marchandise ». === Le fétichisme de la marchandise === L'analyse du fétichisme de la marchandise, développée dans le premier chapitre du ''Capital''<ref>K. Marx, Das Kapital, livre I, chap. 1, section 4 : « Der Fetischcharakter der Ware und sein Geheimnis », MEW, t. XXIII, p. 85-98 ; trad. fr. Lefebvre, p. 81-95.</ref>, constitue l'un des passages les plus commentés de l'œuvre. Marx y montre que dans une économie marchande, les rapports sociaux entre les producteurs prennent nécessairement la forme de rapports entre les produits du travail. Les marchandises semblent posséder une valeur intrinsèque, comme si celle-ci était une propriété naturelle des objets, alors qu'elle n'est que l'expression d'un rapport social déterminé. Il y a fétichisme au sens où les hommes attribuent aux choses — et singulièrement à l'argent — des propriétés qui ne leur appartiennent pas en propre mais qui ne sont que le reflet des rapports entre les producteurs. Ce processus n'est pas une illusion qu'il suffirait de dissiper par la pensée ; il est enraciné dans la structure même de la production marchande. Tant que les travaux privés des producteurs indépendants ne se valident socialement qu'à travers l'échange de leurs produits sur le marché, les rapports sociaux revêtent nécessairement cette forme réifiée. Le fétichisme de la marchandise n'est donc ni une erreur de perception ni une mystification délibérée, mais une apparence objectivement produite par les rapports sociaux eux-mêmes. On mesure ici la distance qui sépare l'analyse marxienne d'une simple « dénonciation » de l'idéologie : il ne s'agit pas de critiquer de fausses croyances, mais de comprendre pourquoi les rapports sociaux prennent nécessairement une forme qui en dissimule la nature. Le fétichisme atteint son expression la plus achevée dans la forme monétaire. L'argent, qui n'est d'abord que l'équivalent général facilitant les échanges, semble posséder par nature le pouvoir de commander le travail et d'acquérir toute marchandise. Marx démonte cette apparence en montrant que l'argent n'est que la cristallisation du travail social abstrait, la forme dans laquelle la société reconnaît et valide les travaux privés. Mais cette forme acquiert une autonomie qui occulte sa genèse : l'argent devient un objet de désir en lui-même, et la logique de l'accumulation monétaire — acheter pour vendre plus cher, transformer l'argent en plus d'argent — constitue le ressort du capitalisme. === La force de travail et la plus-value === L'une des questions centrales que pose Marx est celle de l'origine du profit. Dans le cadre de sa théorie de la valeur-travail, si les marchandises s'échangent conformément au temps de travail socialement nécessaire à leur production, comment le capitaliste peut-il tirer un surplus de l'échange ? La réponse, selon l'analyse du livre I du ''Capital'', passe par la découverte d'une marchandise particulière dont la consommation est elle-même source de valeur : la force de travail. La force de travail est, pour Marx, la capacité de travail que le travailleur vend au capitaliste sur le marché du travail. Comme toute marchandise, elle a une valeur, déterminée par le temps de travail nécessaire à sa production et à sa reproduction — c'est-à-dire par la valeur des moyens de subsistance (nourriture, logement, vêtement, éducation) nécessaires à l'entretien du travailleur et de sa famille. Mais la force de travail a cette propriété singulière que son usage — le travail effectif — crée plus de valeur que sa propre valeur. Le capitaliste achète la force de travail à sa valeur, mais il fait travailler l'ouvrier au-delà du temps nécessaire pour reproduire cette valeur. La différence entre la valeur produite par le travailleur et la valeur de sa force de travail constitue ce que Marx appelle la plus-value, qui est, dans son analyse, la source du profit capitaliste. Marx distingue deux formes de plus-value. La plus-value absolue résulte de l'allongement de la journée de travail<ref>K. Marx, Das Kapital, livre I, chap. 8 : « Der Arbeitstag », MEW, t. XXIII, p. 245-320.</ref> au-delà du temps de travail nécessaire : plus le capitaliste prolonge la durée du travail, plus il extrait de surtravail. Cette forme trouve sa limite dans les résistances physiques et sociales des travailleurs, d'où les luttes historiques pour la limitation de la journée de travail, auxquelles Marx consacre un chapitre du ''Capital'' richement documenté. La plus-value relative résulte de la diminution du temps de travail nécessaire par l'augmentation de la productivité du travail dans les branches qui produisent les moyens de subsistance. L'introduction de machines et de procédés plus efficaces permet de réduire la valeur de la force de travail sans abaisser le niveau de vie réel, augmentant ainsi la part de surtravail. C'est cette forme qui prédomine à mesure que le capitalisme se développe et que la production s'industrialise. === L'accumulation du capital et ses contradictions === La plus-value n'est pas simplement consommée par le capitaliste ; elle est en grande partie réinvestie dans la production sous forme de capital additionnel. C'est le processus d'accumulation du capital, que Marx analyse comme le ressort fondamental du développement capitaliste. La concurrence entre capitalistes les contraint à réinvestir continuellement leurs profits pour accroître la productivité du travail, sous peine d'être éliminés par des concurrents plus efficaces. Ce processus engendre une concentration et une centralisation croissantes du capital : les entreprises les plus grandes absorbent ou éliminent les plus petites, et le capital se concentre entre un nombre toujours plus restreint de mains. Marx analyse la composition du capital en distinguant le capital constant — la valeur des moyens de production (machines, matières premières, bâtiments) — et le capital variable — la valeur de la force de travail achetée. Seul le capital variable, c'est-à-dire le travail vivant, crée de la valeur nouvelle et donc de la plus-value. Or, le développement capitaliste se caractérise par une hausse tendancielle de la composition organique du capital, c'est-à-dire du rapport entre capital constant et capital variable : à mesure que la production se mécanise, la part du capital investi en machines et en matières premières augmente par rapport à celle investie en force de travail. Si le taux de plus-value reste constant, cette évolution entraîne une tendance à la baisse du taux de profit — le rapport entre la plus-value et le capital total investi<ref>K. Marx, Das Kapital, livre III, 3e section, MEW, t. XXV, p. 221-277 ; trad. fr. Éditions sociales, 1977, t. VI.</ref>. Marx présente cette loi de la baisse tendancielle du taux de profit comme « la loi la plus importante de l'économie politique moderne ». Elle ne décrit pas une chute mécanique et continue, mais une tendance contrecarrée par un ensemble de facteurs — intensification de l'exploitation, abaissement du prix des éléments du capital constant, commerce extérieur, expansion vers de nouveaux marchés — qui ralentissent ou compensent temporairement la baisse. Il faut préciser que cette loi ne constitue pas la seule explication marxienne des crises capitalistes : Marx avance aussi des analyses portant sur les crises de surproduction, les disproportions entre secteurs et les contradictions liées à la réalisation de la valeur sur le marché. C'est l'articulation de ces différentes lignes d'analyse, plutôt qu'un facteur unique, qui rend compte, dans la théorie marxienne, du caractère cyclique et des crises récurrentes du capitalisme. Il faut ajouter que le passage des valeurs aux prix de production, tel que Marx le propose dans le livre III du ''Capital'', soulève un problème théorique considérable — connu sous le nom de « problème de la transformation » — dont la résolution a fait l'objet de débats prolongés parmi les économistes, aussi bien partisans que critiques de la théorie de la valeur-travail. Les crises, pour Marx, ne sont pas des accidents extérieurs mais des moments nécessaires du processus d'accumulation, par lesquels le système restaure temporairement les conditions de sa propre reproduction — par la destruction de capital, la dévalorisation des marchandises et la reconstitution d'une « armée industrielle de réserve » de travailleurs sans emploi qui fait pression à la baisse sur les salaires. L'analyse des crises est prolongée par celle de ce que Marx appelle la « loi générale de l'accumulation capitaliste ». À mesure que le capital s'accumule, la classe ouvrière subit un double mouvement : d'un côté, la demande de travail croît avec l'accumulation ; de l'autre, la mécanisation rend une partie des travailleurs surnuméraires, constituant cette armée industrielle de réserve dont la taille fluctue au rythme des cycles économiques. La tendance de long terme est à la polarisation de la société entre une richesse accumulée à un pôle et une masse croissante de travailleurs dont l'existence dépend entièrement des aléas du marché. Marx voit dans cette polarisation non pas une fatalité, mais le produit d'un rapport social déterminé, historiquement constitué et, en principe, transformable. == Les classes sociales et la lutte des classes == L'affirmation selon laquelle « l'histoire de toute société jusqu'à nos jours n'a été que l'histoire de luttes de classes »<ref name="n4">K. Marx et F. Engels, Manifest der Kommunistischen Partei, 1848, MEW, t. IV, p. 462 ; trad. fr. in Œuvres, éd. cit., t. I, p. 161.</ref>, qui ouvre le ''Manifeste du parti communiste'', condense l'une des thèses les plus connues de Marx. La lutte des classes n'est pas pour lui un phénomène accidentel ou secondaire, mais le principe moteur de la transformation historique. Dans chaque mode de production, une classe dominante — qui possède ou contrôle les moyens de production — s'oppose à une ou plusieurs classes dominées dont le travail est la source de la richesse sociale. Marx ne donne nulle part une théorie systématique et achevée des classes sociales — le chapitre du livre III du ''Capital'' qui devait y être consacré s'interrompt après quelques paragraphes. Néanmoins, les éléments dispersés dans l'ensemble de l'œuvre permettent de reconstituer sa conception. Une classe se définit d'abord par sa position dans les rapports de production : propriétaire ou non-propriétaire des moyens de production. Mais cette détermination objective ne suffit pas. Dans la ''Misère de la philosophie'' (1847), Marx décrit le passage du prolétariat d'une masse dispersée, partageant objectivement les mêmes conditions, à une classe organisée et consciente de ses intérêts communs — un passage que les commentateurs ont formulé dans les termes hégéliens de « classe en soi » et de « classe pour soi ». Cette transformation n'est pas automatique ; elle dépend d'un ensemble de conditions historiques, culturelles et politiques. L'analyse que Marx donne des classes dans la société capitaliste est plus complexe que le schéma binaire capitalistes/prolétaires. Il identifie de nombreuses catégories intermédiaires — petite bourgeoisie, paysannerie, Lumpenproletariat — et accorde une attention soutenue aux fractions de classe et à leurs rapports. Les écrits politiques et historiques, en particulier ''Le 18 Brumaire de Louis Bonaparte'' (1852)<ref>K. Marx, Der achtzehnte Brumaire des Louis Bonaparte, 1852, MEW, t. VIII ; trad. fr. in Œuvres, éd. cit., t. IV, 1994.</ref> et ''Les Luttes de classes en France'' (1850), offrent des analyses remarquablement nuancées de la dynamique des alliances et des conflits entre les différentes couches sociales. Marx y montre que la classe paysanne, parcellaire et dispersée, incapable de s'organiser politiquement par elle-même, finit par déléguer sa représentation à un pouvoir autoritaire — en l'occurrence Louis-Napoléon Bonaparte. Il y montre aussi que la bourgeoisie elle-même est traversée de tensions entre ses différentes fractions — bourgeoisie foncière, bourgeoisie industrielle, bourgeoisie financière — dont les intérêts ne convergent pas toujours. La lutte des classes dans le capitalisme oppose fondamentalement la bourgeoisie, qui détient les moyens de production, et le prolétariat, qui n'a d'autre ressource que la vente de sa force de travail. Mais cette opposition ne se réduit pas à un conflit d'intérêts économiques ; elle structure l'ensemble de la vie sociale, politique et culturelle. La lutte pour la limitation de la journée de travail, que Marx analyse en détail dans le ''Capital'', illustre cette dimension : elle met en jeu non seulement des intérêts matériels immédiats, mais des conceptions antagonistes du droit, de la liberté et de la dignité humaine. Le capitaliste invoque son droit de propriétaire à disposer de la marchandise qu'il a achetée — la force de travail — comme il l'entend ; le travailleur invoque son droit à ne pas être usé jusqu'à la mort. C'est la lutte sociale qui tranche entre ces droits également fondés du point de vue de la logique marchande. == L'État et la question de la révolution == La conception marxienne de l'État s'inscrit dans le prolongement de l'analyse des classes sociales. L'État n'est pas pour Marx une institution neutre, arbitre impartial des conflits sociaux, mais un instrument de domination de classe. Dans le ''Manifeste'', il est défini comme un « comité qui gère les affaires communes de la classe bourgeoise tout entière ». Cette formule, souvent jugée trop schématique, doit être mise en regard des analyses plus nuancées que Marx développe dans ses écrits historiques. Le ''18 Brumaire'' montre en effet que l'État peut acquérir, dans certaines conjonctures, une autonomie relative par rapport aux classes en présence. Le bonapartisme représente une situation où l'État, profitant de l'équilibre des forces entre les classes, semble s'élever au-dessus de la société et gouverner en son nom propre. Mais cette autonomie reste en dernière instance subordonnée aux intérêts de la propriété : l'État bonapartiste protège la structure sociale existante tout en paraissant se situer au-dessus des conflits de classe. Le pouvoir étatique, avec son appareil bureaucratique, militaire et policier, est un héritage que chaque classe dominante s'empresse de perfectionner au lieu de le briser — c'est cette observation qui conduit Marx à distinguer entre la simple conquête de l'appareil d'État et sa destruction. La Commune de Paris (1871) représente pour Marx le premier exemple d'un pouvoir politique de type nouveau. Dans ''La Guerre civile en France''<ref>K. Marx, Der Bürgerkrieg in Frankreich, 1871, MEW, t. XVII, p. 336-362 ; trad. fr. in Œuvres, éd. cit., t. IV.</ref>, il analyse les mesures prises par la Commune — élection et révocabilité des fonctionnaires, suppression de l'armée permanente remplacée par le peuple en armes, rémunération des responsables publics au niveau du salaire ouvrier — comme les formes politiques enfin trouvées de l'émancipation du travail. Marx tend à penser la Commune comme une forme politique de transition qui rompt avec l'État bourgeois traditionnel : il y voit le type d'un pouvoir ouvrier qui commence à défaire la machine étatique séparée. Il faut toutefois noter que cette lecture est formulée dans le feu de l'événement, et que son statut — description de ce que la Commune a effectivement été ou projection de ce qu'elle aurait pu devenir — reste discuté. La question de la transition entre le capitalisme et le communisme se pose alors dans les termes suivants. Marx distingue, dans la ''Critique du programme de Gotha'' (1875)<ref>K. Marx, Kritik des Gothaer Programms, 1875, MEW, t. XIX, p. 13-32 ; trad. fr. in Œuvres, éd. cit., t. I, p. 1413-1434.</ref>, deux phases de la société communiste. La première phase, souvent appelée ultérieurement « socialisme », porte encore les marques de l'ancienne société dont elle est issue : la répartition y obéit au principe « à chacun selon son travail », ce qui maintient une forme d'inégalité puisque les individus ont des capacités inégales. La seconde phase, le communisme proprement dit, suppose un développement des forces productives tel que la société puisse inscrire sur ses drapeaux le principe « de chacun selon ses capacités, à chacun selon ses besoins ». Entre la société capitaliste et la société communiste se place une période de transformation au cours de laquelle l'État revêt la forme d'une « dictature révolutionnaire du prolétariat ». Il convient de noter que cette formule, devenue centrale dans le marxisme postérieur — en particulier chez Lénine —, n'apparaît que de manière occasionnelle dans les textes de Marx lui-même, qui n'en précise guère le contenu institutionnel, au-delà de la référence à la Commune de Paris. La pratique politique de Marx est indissociable de sa réflexion théorique. Son engagement dans l'Association internationale des travailleurs (la Première Internationale, fondée en 1864) illustre sa conviction que l'émancipation des travailleurs doit être l'œuvre des travailleurs eux-mêmes, et que la lutte de classe dépasse les frontières nationales. Au sein de l'Internationale, Marx s'efforce de concilier des tendances très diverses — syndicalistes anglais, mutualistes proudhoniens, républicains mazziniens, anarchistes bakouniniens — tout en défendant la nécessité d'une organisation politique autonome de la classe ouvrière. Les polémiques avec Proudhon — auquel ''La Misère de la philosophie'' (1847) oppose une critique en règle —, puis avec Bakounine, qui reproche à Marx son centralisme et son autoritarisme, éclairent les tensions constitutives du mouvement socialiste entre la libération sociale et l'organisation politique, entre la spontanéité des masses et la direction du mouvement. Ces débats conduiront à la scission de l'Internationale au congrès de La Haye en 1872, puis à sa dissolution formelle en 1876 ; ils restent instructifs sur les difficultés pratiques que pose la traduction d'une théorie de l'émancipation en stratégie politique effective. La ''Critique du programme de Gotha'' est aussi l'occasion pour Marx de préciser sa conception du rapport entre le parti ouvrier, la transformation sociale et les revendications concrètes. Il y conteste un programme qu'il juge trop influencé par les thèses de Ferdinand Lassalle — notamment l'idée d'un « État libre » et d'une « répartition équitable du produit du travail » — en montrant que ces formules restent prisonnières de catégories bourgeoises. La distribution du produit social, rappelle Marx, ne peut être pensée indépendamment des rapports de production qui la déterminent ; parler de « répartition équitable » sans remettre en cause les rapports capitalistes, c'est rester à la surface des choses. Cet écrit, d'une grande densité argumentative, illustre la manière dont Marx articule constamment l'analyse théorique et l'intervention politique concrète. == La méthode : dialectique et critique == La question de la méthode est omniprésente dans l'œuvre de Marx, sans qu'il en ait jamais livré un exposé séparé et complet. Les indications les plus précieuses se trouvent dans la postface à la seconde édition du ''Capital'' (1873)<ref name="n1" />, dans l'introduction aux ''Grundrisse'' (1857) et dans les remarques méthodologiques dispersées dans l'ensemble de l'œuvre. Trois traits principaux caractérisent la démarche marxienne. Le premier est l'usage de la dialectique. Marx reprend à Hegel l'idée que la réalité ne peut être saisie que dans son mouvement, à travers ses contradictions et ses transformations, et non comme un ensemble de faits fixes et séparés. Mais la dialectique marxienne se distingue de la dialectique hégélienne en ce qu'elle prend pour objet non le mouvement de l'Idée mais le mouvement réel des rapports sociaux. Chez Hegel, explique Marx dans la postface au ''Capital'', la dialectique « marche sur la tête » ; il faut « la retourner pour découvrir le noyau rationnel sous l'enveloppe mystique ». Car pour Marx, l'idéal n'est rien d'autre que le matériel transposé et traduit dans la tête de l'homme. Ce renversement ne signifie pas un simple remplacement de l'Esprit par la Matière : la dialectique matérialiste vise à saisir le mouvement effectif de la réalité sociale, avec ses contradictions, ses ruptures et ses transformations. Le deuxième trait est le mouvement de l'abstrait au concret. Dans l'introduction aux ''Grundrisse'', Marx décrit la méthode de l'économie politique comme un parcours qui va des déterminations abstraites les plus simples — la marchandise, la valeur, le travail — vers la reconstitution d'une « totalité concrète », c'est-à-dire la compréhension du mode de production capitaliste dans l'ensemble de ses déterminations et de ses articulations. Le concret n'est pas le point de départ immédiat de la connaissance, mais son résultat : il est « concret parce qu'il est la synthèse de multiples déterminations, donc unité de la diversité »<ref name="n2" />. Cette méthode n'a rien à voir avec une déduction logique à partir de principes ; elle suppose au contraire un va-et-vient constant entre l'analyse des formes abstraites et l'étude des processus historiques réels. Le troisième trait est la dimension critique. La critique de l'économie politique n'est pas une simple correction des erreurs des économistes classiques, ni la substitution d'un système théorique à un autre. Elle consiste à montrer que les catégories de l'économie politique — valeur, capital, salaire, profit — ne sont pas des formes naturelles et éternelles de la vie économique, mais des formes sociales historiquement déterminées qui expriment des rapports sociaux spécifiques. La critique est donc à la fois une analyse de la réalité capitaliste et un dévoilement du caractère historique et transitoire de cette réalité. En montrant que le capitalisme est un mode de production parmi d'autres, qu'il est né dans des conditions historiques déterminées et qu'il porte en lui des contradictions qui rendent possible son dépassement, la critique ne se contente pas de décrire le monde : elle ouvre la perspective de sa transformation. Il faut ajouter que la méthode de Marx n'est pas seulement une méthode d'exposition mais aussi une méthode d'investigation. Marx insiste sur la distinction entre les deux dans la postface au ''Capital'' : l'investigation doit s'emparer de la matière dans le détail, en analyser les formes de développement et découvrir le lien interne de ces formes ; ce n'est qu'une fois ce travail accompli que le mouvement réel peut être présenté de manière adéquate. Le plan du ''Capital'' — de la marchandise à l'argent, de l'argent au capital, du capital à la plus-value, de la plus-value aux formes concrètes du profit, de l'intérêt et de la rente — ne retrace donc pas la genèse chronologique du capitalisme mais reconstruit sa logique interne, en partant des formes les plus abstraites pour en déployer progressivement la complexité. Cette démarche confère à l'œuvre sa rigueur architecturale, mais aussi sa difficulté : le lecteur doit accepter de traverser un long parcours de déterminations abstraites avant que le système ne se révèle dans sa totalité articulée. == Marx et la nature : travail, technique, écologie == Un aspect de la pensée de Marx, longtemps négligé mais qui retient aujourd'hui une attention croissante, concerne son analyse du rapport entre le travail humain et la nature. Dans le livre I du ''Capital'', Marx définit le travail comme un processus entre l'homme et la nature, dans lequel l'homme « règle et contrôle par sa propre action son métabolisme avec la nature »<ref>K. Marx, Das Kapital, livre I, chap. 5, section 1, MEW, t. XXIII, p. 192 ; trad. fr. Lefebvre, p. 199-200.</ref>. Cette notion de métabolisme (''Stoffwechsel'') désigne l'échange matériel constant par lequel les sociétés humaines prélèvent des ressources naturelles, les transforment et rejettent des déchets dans l'environnement. Le travail humain est ainsi inscrit dans un rapport d'interdépendance avec les cycles naturels, et la terre est présentée, à côté du travail, comme l'une des deux sources de toute richesse. Marx observe que le capitalisme, en concentrant la population dans les villes et en intensifiant l'exploitation agricole, perturbe ce métabolisme entre l'homme et la terre. Il s'appuie sur les travaux du chimiste Justus von Liebig concernant l'épuisement des sols pour montrer que l'agriculture capitaliste ne se contente pas d'exploiter le travailleur : elle épuise aussi la fertilité naturelle de la terre, en rompant le cycle des nutriments par la séparation entre la production agricole et la consommation urbaine<ref>K. Marx, Das Kapital, livre I, chap. 13, section 10, et livre III, chap. 47, MEW, t. XXIII, p. 528 et t. XXV, p. 821.</ref>. Marx décrit à ce propos une fracture irréparable (''unheilbarer Riß'') dans le métabolisme entre la société et la nature. Il est important de distinguer ce que Marx écrit effectivement — des passages dispersés dans le ''Capital'' et les ''Grundrisse'', portant sur l'épuisement des sols, le machinisme et la subordination de la nature à la logique du profit — de la reconstruction systématique qu'en proposent des travaux contemporains, notamment ceux de John Bellamy Foster (''Marx's Ecology'', 2000), Kohei Saito (''Karl Marx's Ecosocialism'', 2017) et Paul Burkett (''Marx and Nature'', 1999). Ces auteurs ont forgé le concept de « rupture métabolique » pour donner une cohérence écologique d'ensemble à des remarques que Marx n'avait pas lui-même rassemblées en une théorie unifiée de la nature. Le débat reste ouvert sur la portée qu'il convient de leur accorder. Cette dimension de l'œuvre ne se réduit pas à quelques remarques isolées. Elle traverse l'analyse du machinisme, de la grande industrie et de l'agriculture capitaliste dans le ''Capital'', et elle se retrouve dans les réflexions sur la technique et ses effets ambivalents. Marx reconnaît dans le développement des forces productives une puissance d'émancipation potentielle — la machine pourrait libérer l'homme du labeur pénible et réduire le temps de travail nécessaire — mais il montre que, sous les rapports capitalistes, la machine se retourne contre le travailleur : elle l'assujettit à son rythme, le déqualifie, le rend remplaçable et allonge paradoxalement sa journée de travail en dévalorisant sa force de travail. La question de la technique chez Marx n'est donc ni celle d'un optimisme technologique sans réserve, ni celle d'une condamnation de la machine en tant que telle ; c'est la question de la forme sociale dans laquelle la technique est mise en œuvre, et des fins auxquelles elle est subordonnée. == Philosophie de l'histoire et question du progrès == La pensée de Marx a souvent été réduite à un schéma évolutionniste linéaire, selon lequel l'humanité passerait nécessairement de la communauté primitive au communisme en traversant les stades de l'esclavage antique, du féodalisme et du capitalisme. Cette lecture téléologique s'autorise de certaines formulations, notamment de la préface de 1859, mais elle méconnaît la complexité et les hésitations de la réflexion marxienne sur l'histoire. Plusieurs textes tardifs conduisent à corriger une interprétation trop téléologique de l'œuvre. Dans une lettre de 1877 à la rédaction des ''Otétchestvennyïé Zapiski''<ref>K. Marx, lettre à la rédaction des Otétchestvennyïé Zapiski, novembre 1877, MEW, t. XIX, p. 111 ; trad. fr. in Œuvres, éd. cit., t. II, p. 1553-1555.</ref>, Marx refuse explicitement que l'on transforme son esquisse de l'origine du capitalisme en Europe occidentale en une « théorie historico-philosophique de la marche générale, fatalement imposée à tous les peuples ». L'étude des modes de production extra-européens, notamment du mode de production asiatique, témoigne de la conscience que le développement historique ne se réduit pas à une trajectoire unique. Les notes ethnologiques de la fin de sa vie, consacrées notamment aux travaux de Lewis Morgan sur les sociétés amérindiennes, montrent un intérêt soutenu pour les formes sociales non-européennes et les voies alternatives de développement. La question de savoir si ces textes suffisent à dégager Marx de toute philosophie de l'histoire, ou s'il subsiste dans l'œuvre une tension non résolue entre l'analyse historique concrète et les schémas de développement hérités de Hegel, reste discutée dans la littérature savante. La question du progrès se pose chez Marx de manière ambivalente. D'un côté, le développement des forces productives sous le capitalisme est présenté comme une condition nécessaire de l'émancipation future : c'est parce que le capitalisme a développé à un degré sans précédent la capacité productive de l'humanité que devient possible, en principe, une société dans laquelle la satisfaction des besoins de tous serait assurée. De l'autre, ce développement s'accompagne de formes d'exploitation et de destruction — destruction de la nature, épuisement des travailleurs, déracinement des communautés traditionnelles — qui en font un processus profondément contradictoire. Marx ne célèbre pas le progrès capitaliste ; il en analyse les conditions, les formes et les limites, en montrant que le développement des forces productives et le développement humain ne coïncident pas nécessairement dans le cadre des rapports capitalistes. == Communisme et émancipation humaine == Le communisme tel que Marx l'envisage ne se réduit pas à un programme politique ou à un modèle d'organisation économique. Il est conçu comme le mouvement réel d'abolition de l'état de choses existant — c'est-à-dire comme un processus historique et non comme un idéal abstrait à réaliser. Dans les ''Manuscrits de 1844'', Marx le définit comme « l'appropriation réelle de l'essence humaine par l'homme et pour l'homme », comme « le retour complet de l'homme à lui-même en tant qu'être social ». Loin de se limiter à un changement de propriété des moyens de production, le communisme vise une transformation de l'ensemble des rapports humains — rapport au travail, à la nature, aux autres et à soi-même. Marx refuse de dessiner en détail les contours de la société future, estimant que c'est là une tâche qui incombe à la pratique historique elle-même et non à la spéculation philosophique. Les quelques indications qu'il donne portent moins sur les institutions que sur les principes. Dans une société communiste, la division sociale du travail serait dépassée, non pas au sens où chacun ferait la même chose, mais au sens où l'assignation des individus à une activité exclusive et contrainte — ouvrier, paysan, intellectuel — serait abolie au profit d'un développement pluriel des capacités. La célèbre formulation de ''L'Idéologie allemande'', selon laquelle chacun pourrait « chasser le matin, pêcher l'après-midi, faire de l'élevage le soir, faire de la critique après le repas »<ref name="n3" />, exprime moins un programme littéral qu'une aspiration à la libre disposition de son temps et de ses activités. L'abolition de la propriété privée des moyens de production — qui ne se confond pas avec l'abolition de la propriété personnelle — vise à supprimer les conditions structurelles de l'exploitation et de la domination de classe. La production serait désormais organisée de manière consciente et collective, au lieu d'être abandonnée à l'anarchie du marché et à la logique aveugle de l'accumulation. Marx envisage le communisme comme un « libre développement de chacun » qui est la « condition du libre développement de tous », selon la formule du ''Manifeste''<ref name="n4" />. L'individu n'y est pas dissous dans la collectivité, mais c'est au contraire la collectivité qui constitue le cadre dans lequel l'individualité peut s'épanouir librement, une fois libérée des contraintes de la nécessité matérielle et de la domination sociale. Il convient de distinguer cette conception de celle que les régimes se réclamant du marxisme ont mise en œuvre au XXe siècle. Bien des éléments de la pensée de Marx — son attachement à la liberté individuelle, sa méfiance à l'égard de l'État, sa critique de la bureaucratie — contrastent avec les pratiques autoritaires des États qui se sont réclamés de son héritage. Le rapport entre la pensée de Marx et le marxisme ultérieur — dans ses multiples variantes : léninisme, social-démocratie, maoïsme, marxisme occidental — constitue une question historique et philosophique à part entière, qu'il importe de ne pas confondre avec l'interprétation de l'œuvre elle-même. Si l'on peut, à des fins de synthèse, reconstituer dans la pensée de Marx une conception de la liberté irréductible tant au libéralisme classique qu'au collectivisme autoritaire, il faut préciser que cette reconstruction ne correspond pas à un exposé systématique que Marx aurait lui-même proposé. La liberté, telle qu'on peut la dégager de ses textes, ne se réduit pas à l'absence de contrainte extérieure ni à la jouissance de droits formels — droit de propriété, liberté de contracter, égalité devant la loi — qui, dans le cadre du capitalisme, coexistent avec une servitude réelle du travailleur. Elle suppose que les individus maîtrisent collectivement les conditions de leur existence, au lieu de subir les contraintes d'un ordre social qui s'impose à eux comme une puissance étrangère. La réduction du temps de travail constitue à cet égard un enjeu central : c'est dans le « règne de la liberté », qui commence au-delà du domaine de la nécessité matérielle, que l'épanouissement des facultés humaines peut se déployer comme une fin en soi<ref>K. Marx, Das Kapital, livre III, chap. 48, MEW, t. XXV, p. 828 ; trad. fr. Éditions sociales, 1977, t. VIII, p. 198-199.</ref>. Mais ce règne de la liberté ne peut s'épanouir que sur la base d'un règne de la nécessité organisé rationnellement, c'est-à-dire d'une production collective qui minimise le temps de travail contraint tout en assurant la satisfaction des besoins. == Portée et postérité philosophique == L'influence de Marx sur la pensée contemporaine dépasse de loin les frontières du mouvement politique qui se réclame de lui. La sociologie, l'histoire, l'anthropologie, la critique littéraire, la géographie humaine et la philosophie politique ont été profondément marquées par ses catégories et ses méthodes d'analyse. Des penseurs aussi différents que Max Weber, qui dialogue constamment avec le matérialisme historique même pour s'en écarter, Émile Durkheim, dont la réflexion sur la division du travail social entretient un rapport étroit avec les analyses marxiennes, ou Thorstein Veblen, qui reprend à sa manière la critique de l'économie orthodoxe, témoignent de la diffusion des questionnements ouverts par Marx bien au-delà du cercle de ses partisans. Dans le champ philosophique, la pensée de Marx a donné lieu à des reprises et à des réélaborations variées. Le marxisme occidental, représenté notamment par Georg Lukács, Antonio Gramsci, l'École de Francfort (Horkheimer, Adorno, Marcuse), a approfondi l'analyse de l'idéologie, de la réification et de la culture, en intégrant des éléments empruntés à la phénoménologie, à la psychanalyse et à la sociologie de la connaissance. Lukács, dans ''Histoire et conscience de classe'' (1923), a développé le concept de réification — la transformation des rapports sociaux en rapports entre choses — comme clé de lecture de la société capitaliste, en reliant le fétichisme de la marchandise à la rationalisation bureaucratique analysée par Weber. Gramsci a élargi la notion de domination de classe en introduisant le concept d'hégémonie, qui désigne la capacité de la classe dominante à obtenir le consentement des classes dominées par l'intégration culturelle et idéologique, et non par la seule coercition. L'école structuraliste, avec Louis Althusser, a proposé dans les années 1960 une relecture du ''Capital'' qui insiste sur la scientificité de l'entreprise marxienne et sur la rupture avec l'humanisme philosophique des œuvres de jeunesse. Althusser distingue entre l'idéologie et la science, et soutient que le ''Capital'' opère un changement de terrain théorique — une « coupure épistémologique » — par rapport aux textes antérieurs à 1845. Cette lecture a suscité des débats importants, notamment avec les partisans d'une interprétation humaniste qui voient dans le concept d'aliénation le fil conducteur de l'ensemble de l'œuvre. D'autres courants — l'opéraïsme italien, le marxisme analytique anglo-saxon, les approches de la reproduction sociale — ont renouvelé de diverses manières la réflexion sur les catégories marxiennes en les confrontant aux transformations du capitalisme contemporain. La pensée de Marx demeure ainsi un objet de travail philosophique vivant, irréductible tant à l'hagiographie qu'au rejet sommaire. Ses analyses de la marchandise, du fétichisme, de l'idéologie et de l'accumulation du capital continuent de nourrir la réflexion sur les formes contemporaines du capitalisme, sur les mécanismes de la domination sociale et sur les conditions d'une émancipation possible. La question de savoir dans quelle mesure les catégories forgées par Marx pour analyser le capitalisme du XIXe siècle restent opératoires pour comprendre les transformations du XXIe siècle — financiarisation, mondialisation, numérique, écologie — reste l'un des enjeux de la discussion philosophique et politique actuelle. == Notes et références == {{Reflist}} == Indications bibliographiques == === Œuvres de Marx === K. Marx, ''Œuvres'', éd. M. Rubel, Paris, Gallimard, « Bibliothèque de la Pléiade », 4 vol., 1963-1994. — Édition française de référence, richement annotée. K. Marx et F. Engels, ''Marx-Engels-Werke'' (MEW), Berlin, Dietz Verlag, 44 vol., 1956-2018. — Édition allemande complète de référence. K. Marx et F. Engels, ''Marx-Engels-Gesamtausgabe'' (MEGA²), Berlin, Akademie Verlag puis De Gruyter, 1975-. — Édition historico-critique en cours, qui renouvelle l'accès aux manuscrits et aux variantes. K. Marx, ''Le Capital. Critique de l'économie politique'', trad. J.-P. Lefebvre, Paris, PUF, « Quadrige », livre I : 1993 ; livres II et III : Éditions sociales, 1977-1978. — Traduction française du livre I la plus utilisée dans le monde universitaire francophone. K. Marx, ''Manuscrits économico-philosophiques de 1844'', trad. F. Fischbach, Paris, Vrin, 2007. K. Marx, ''Grundrisse'', trad. sous la dir. de J.-P. Lefebvre, Paris, Éditions sociales, 2011. === Études et commentaires === L. Althusser, ''Pour Marx'', Paris, Maspero, 1965. — Lecture structuraliste défendant la thèse de la « coupure épistémologique » entre le jeune Marx et le Marx du ''Capital''. L. Althusser et É. Balibar, ''Lire le Capital'', Paris, Maspero, 1965 ; rééd. PUF, « Quadrige », 1996. É. Balibar, ''La Philosophie de Marx'', Paris, La Découverte, « Repères », 1993 ; rééd. augmentée 2014. — Introduction synthétique et rigoureuse. J. Bidet, ''Que faire du « Capital » ?'', Paris, PUF, 2000. — Relecture critique de l'architecture du ''Capital''. P. Burkett, ''Marx and Nature: A Red and Green Perspective'', New York, St. Martin's Press, 1999. J. B. Foster, ''Marx's Ecology: Materialism and Nature'', New York, Monthly Review Press, 2000. — Ouvrage fondateur de la lecture écologique de Marx. A. Gramsci, ''Cahiers de prison'', trad. sous la dir. de R. Paris, Paris, Gallimard, 5 vol., 1978-1996. — Élaboration du concept d'hégémonie. M. Heinrich, ''Die Wissenschaft vom Wert'', Münster, Westfälisches Dampfboot, 1999 ; trad. angl. ''An Introduction to the Three Volumes of Karl Marx's Capital'', New York, Monthly Review Press, 2012. — Présentation rigoureuse de la « Neue Marx-Lektüre ». D. Harvey, ''A Companion to Marx's Capital'', Londres, Verso, 2010. — Guide de lecture détaillé du livre I. G. Lukács, ''Histoire et conscience de classe'' (1923), trad. K. Axelos et J. Bois, Paris, Minuit, 1960. — Développement du concept de réification. K. Saito, ''Karl Marx's Ecosocialism: Capital, Nature, and the Unfinished Critique of Political Economy'', New York, Monthly Review Press, 2017. — Approfondissement de la lecture écologique à partir des manuscrits tardifs. G. Stedman Jones, ''Karl Marx: Greatness and Illusion'', Londres, Allen Lane, 2016. — Biographie intellectuelle récente, attentive aux contextes historiques. {{DEFAULTSORT:Marx}} [[Catégorie:Philosophe]] flm60h27l8tva261gga4zmpgkp8e14p Dictionnaire de philosophie/Moritz Schlick 0 83747 764803 762060 2026-04-24T10:28:35Z ~2026-25114-99 123599 764803 wikitext text/x-wiki {{DicoPhilo|Moritz Schlick}} == Formation et origines (1882–1911) == Friedrich Albert Moritz Schlick naît le 14 avril 1882 à Berlin dans une famille de la bourgeoisie cultivée. Après des études secondaires au Luisenstädtisches Realgymnasium, il s'inscrit à l'automne 1900 à la Königliche Friedrich-Wilhelms-Universität de Berlin pour y étudier la physique, les mathématiques, la chimie et la philosophie. L'intérêt pour les questions philosophiques s'était manifesté dès les années de lycée, à travers la lecture de Descartes, Schopenhauer, Nietzsche et Kant, tandis que l'enseignement scientifique orientait ses « tendances naturphilosophiques » naissantes vers une pensée plus disciplinée, en particulier par la découverte d'Ernst Mach. Néanmoins, Schlick ne songe pas un instant à faire de la philosophie l'objet principal de ses études universitaires : il considère que les questions philosophiques ultimes — celles qui touchent à l'homme et au sens de l'existence — doivent être travaillées de manière autonome, tandis que l'étude rigoureuse de la physique fournira les fondements sûrs dont toute réflexion sur la connaissance a besoin. C'est donc un besoin philosophique qui le conduit à la physique, et non l'inverse : il cherche dans la science exacte cette certitude « soustraite à la querelle des opinions » qu'il ne trouve pas dans les cours de philosophie auxquels il assiste sans profit. Son maître en physique est Max Planck, dont les cours magistraux, déployés sur six semestres d'introduction aux différentes branches de la physique théorique, exercent sur lui une influence décisive. Schlick étudie également à Heidelberg et à Lausanne avant de revenir à Berlin pour y rédiger, sous la direction de Planck, une dissertation intitulée ''Über die Reflexion des Lichtes in einer inhomogenen Schicht'', soutenue le 20 mai 1904 avec la mention magna cum laude. Ce travail, qui porte sur un problème spécial d'optique — la réflexion de la lumière dans une couche dont l'indice de réfraction varie continûment —, appartient encore entièrement à la physique théorique. Planck en approuve la rigueur formelle et en recommande l'acceptation. Mais déjà, ce qui intéresse Schlick dans la physique n'est pas le détail empirique : c'est l'architecture logique des théories, les « dernières formules dans lesquelles les événements peuvent être exprimés ». Tout le reste n'est, selon ses propres termes, que « porte et vestibule ». Après la promotion, Schlick tente brièvement de poursuivre dans la recherche expérimentale, d'abord à Göttingen auprès de Woldemar Voigt, puis à nouveau à Heidelberg. Il reconnaît cependant que ce type de travail ne correspond pas à sa nature et que ses véritables capacités se situent du côté de la réflexion théorique et philosophique. La transition vers la philosophie s'opère progressivement entre 1905 et 1908. Schlick s'installe à Zurich à la fin de l'année 1907 — il a épousé Blanche Guy Hardy aux États-Unis à l'automne — et y entreprend des études de psychologie auprès de Gustav Störring. Sa première œuvre philosophique, la ''Lebensweisheit. Versuch einer Glückseligkeitslehre'', avait été rédigée pour l'essentiel dès avant la fin de 1907, à Heidelberg, et paraît cette même année portant la date de 1908. Cette transition ne représente cependant pas une rupture : Schlick conserve la conviction que la philosophie doit être fondée sur la connaissance scientifique exacte, et que les questions philosophiques authentiques sont ultimement des questions empiriques susceptibles d'un traitement rigoureux. La tentative de Schlick de s'habiliter à Zurich en philosophie échoue en 1909. La Faculté de philosophie, dont Störring est alors le doyen, émet un avis défavorable, et le Conseil de l'éducation du canton rejette le dossier au motif qu'il n'existe pas de besoin pour les matières demandées et que Schlick, ancien élève d'un Realgymnasium, ne peut attester de la connaissance du grec requise pour l'histoire de la philosophie grecque. La ''Lebensweisheit'', seul ouvrage d'ampleur publié, est en outre jugée trop populaire pour servir de base à une habilitation académique. Mais cette période zurichoise (1907-1910) est déterminante pour sa formation intellectuelle : c'est là, dans le séminaire de psychologie de Störring et à travers ses lectures de Mach, Helmholtz, Poincaré, qu'il élabore les premiers éléments de sa conception de la connaissance comme désignation par des signes. Ses travaux sur la nature du temps et de l'espace — conservés dans des manuscrits datant de cette période — anticipent déjà les thèses de l{{'}}''Allgemeine Erkenntnislehre'' : le temps confère au réel son caractère d'actualité, l'espace objectif est un schéma d'ordonnancement distinct de l'espace perçu. En 1911, Schlick se habilite avec succès à l'Université de Rostock, où il prononce le 29 juin sa leçon inaugurale (''Antrittsvorlesung''), intitulée « Die Aufgabe der Philosophie in der Gegenwart ». Il y définit la philosophie comme l'activité qui réalise l'unité des sciences : elle ne se tient pas à côté des sciences particulières comme une discipline coordonnée, mais les englobe en un certain sens, en tant qu'elle vise la « perfection harmonieuse de la vie intellectuelle, dans la mesure où elle est atteignable par des moyens intellectuels ». Dès le semestre d'hiver 1911-1912, il donne sa première série de cours sous le titre « Grundzüge der Erkenntnislehre und Logik ». La structure de ces cours — divisés en trois parties : « essence de la connaissance », « problèmes de la pensée » et « problèmes de la réalité » — préfigure exactement la tripartition de l{{'}}''Allgemeine Erkenntnislehre'', dont ils constituent la plus importante des ''Vorarbeiten''. == Premiers travaux philosophiques == La ''Lebensweisheit'' présente une doctrine du bonheur fondée sur une explication causale du comportement humain. L'ouvrage, placé sous une épigraphe tirée du Zarathustra de Nietzsche — « en ce temps-là la vie m'était plus chère que toute ma sagesse » — et profondément marqué par la Fröhliche Wissenschaft, conçoit l'éthique non comme une discipline normative établissant des impératifs a priori, mais comme une science empirique qui étudie les conditions du bonheur humain. Schlick avance que seuls peuvent être buts de l'action les états dont la représentation est associée au plaisir — ce qui ne revient pas à faire du plaisir lui-même le but visé, car le plaisir en tant que tel ne peut être représenté mais seulement éprouvé. L'action n'atteint pas sa forme la plus accomplie lorsqu'elle se fixe des buts lointains, mais lorsqu'elle devient sa propre fin, c'est-à-dire en jeu. Cette idée, selon laquelle l'activité ludique — l'activité qui est à elle-même son propre but — représente la forme la plus haute de l'existence, forme le noyau de la Lebensweisheit. Schlick l'applique à la science elle-même, qu'il décrit comme un « jeu de l'esprit » (''Spiel des Geistes'') : la science atteint sa plénitude lorsqu'elle cesse d'être un labeur contraint et devient une activité librement exercée pour elle-même. Ce thème de la science comme activité ludique, inspiré à la fois de Schiller et de Nietzsche, trouvera un prolongement dans la théorie esthétique de Schlick et réapparaîtra dans ses dernières réflexions sur le sens de la vie. Dans son article de 1909 sur le « problème fondamental de l'esthétique » (''Das Grundproblem der Ästhetik in entwicklungsgeschichtlicher Beleuchtung''), Schlick applique au beau le même type d'explication : l'esthétique est pour lui une discipline psychogénétique qui retrace l'émergence du sens du beau à partir du plaisir associé à la perception d'objets utiles, selon les lois de la biologie et de la psychologie. Le plaisir esthétique dérive de ce plaisir lié à l'utilité, plaisir qui s'est progressivement autonomisé au cours de l'évolution pour devenir le plaisir désintéressé que nous éprouvons devant le beau. L'esthétique, comme l'éthique, repose donc entièrement sur des bases empiriques et ne relève pas d'une « philosophie pure » distincte des sciences particulières. L'article procède directement de l'approfondissement, dans le séminaire de Störring, d'un thème majeur de la ''Lebensweisheit'' : l'idée que l'action humaine la plus sensée est celle qui est devenue jeu, c'est-à-dire ''Selbstzweck''. L'application de cette idée à l'esthétique conduit Schlick à la théorie du jeu artistique (''Spieltheorie der Kunst'') dans la tradition de Schiller, qu'il reformule en termes évolutionnistes. Ces premiers travaux révèlent déjà une orientation directrice : les disciplines traditionnellement considérées comme philosophiques — éthique, esthétique — sont pour Schlick des cas particuliers de l'investigation scientifique de la nature humaine. Elles soulèvent les mêmes questions épistémologiques que la physique et appellent le même type de réponse. C'est cette prise de conscience qui conduit Schlick de l'éthique et de l'esthétique vers l'épistémologie : si toute connaissance valide est de nature empirique, il faut déterminer en quoi consiste exactement la connaissance, quelles sont ses conditions et ses limites. Schlick lui-même reconnaît rétrospectivement que l'intérêt pour les questions ultimes de la connaissance de la nature s'est développé parallèlement au souci de clarifier les problèmes de l'existence. == Premiers écrits épistémologiques (1910–1913) == Le premier écrit proprement épistémologique de Schlick est l'article de 1910 « Das Wesen der Wahrheit nach der modernen Logik », dans lequel il pose la question de la nature de la vérité et examine les différentes réponses qui lui ont été données. Schlick y critique les théories pragmatistes de la vérité, selon lesquelles le critère de la vérité réside dans l'utilité pratique des jugements. Contre William James et les pragmatistes, il fait valoir que l'utilité est un concept trop vague et fluctuant pour servir de définition de la vérité, et que la proposition « vrai est ce qui est utile » est une moins bonne expression du rapport entre vérité et utilité que la proposition inverse « utile est ce qui est vrai ». Il critique également les conceptions néo-kantiennes de Windelband et Rickert, qui font de la vérité une valeur (''Wert'') et du jugement vrai un jugement conforme à un « devoir » (''Sollen''). Schlick leur oppose l'idée que la vérité est une relation objective entre des jugements et des faits, relation qu'il commence à caractériser en termes de « coordination univoque » (''eindeutige Zuordnung''). C'est dans cet article que se trouve en germe la conception de la vérité qui sera développée dans l{{'}}''Allgemeine Erkenntnislehre''. En 1910 également paraît « Die Grenze der naturwissenschaftlichen und philosophischen Begriffsbildung », où Schlick examine le rapport entre la formation des concepts dans les sciences naturelles et en philosophie. Il y soutient que la philosophie ne se situe pas à côté des sciences particulières comme une discipline coordonnée, mais qu'elle les surplombe en un certain sens, qu'elle les englobe. La philosophie s'occupe des principes les plus généraux que les sciences particulières présupposent sans les thématiser. Ce rapport entre philosophie et sciences, que Schlick précisera dans l{{'}}''Allgemeine Erkenntnislehre'', est celui d'une théorie générale de la connaissance aux théories spéciales de la connaissance — philosophie de la nature, philosophie des mathématiques, philosophie de l'histoire — qui forment une couche intermédiaire entre les sciences et la réflexion épistémologique ultime. L'article de 1913 « Gibt es intuitive Erkenntnis ? » représente un autre jalon important. Schlick y refuse l'idée, défendue par Bergson et par d'autres, qu'il existe un mode de connaissance intuitif, distinct de la connaissance conceptuelle et supérieur à elle. La connaissance intuitive prétend saisir son objet directement, sans la médiation des concepts et des signes. Schlick fait valoir au contraire que toute connaissance passe nécessairement par la conceptualisation, en d'autres termes par la désignation au moyen de signes. L'intuition, au sens de Bergson, n'est pas une forme de connaissance, mais un vécu (''Erlebnis'') qui, en tant que tel, ne livre aucune vérité communicable. Connaître et vivre sont deux choses distinctes : on peut vivre une douleur sans la connaître au sens strict, et la connaissance d'une douleur — par exemple sa classification physiologique — n'implique pas qu'on la vive. Cette distinction entre ''Erkennen'' (connaître) et ''Erleben'' (vivre) est capitale dans la pensée de Schlick et traverse toute l{{'}}''Allgemeine Erkenntnislehre''. Dès 1912, Schlick rédige en outre une ébauche de naturphilosophie dans laquelle il pose les bases de sa conception de la physique. L'objectif ultime de la physique, écrit-il, est de formuler des lois qui expriment la dépendance de l'état régnant en un point de l'univers par rapport aux états des points immédiatement voisins — soit des lois sous forme d'équations différentielles, sans recours à l'action à distance. Cette exigence de détermination locale et continue du réel par des grandeurs quantitatives se retrouvera dans les analyses de la connaissance physique au sein de l{{'}}''Allgemeine Erkenntnislehre'' et dans ''Raum und Zeit in der gegenwärtigen Physik''. C'est aussi dans ces notes de 1912 que Schlick formule pour la première fois l'exigence de la validité universelle du principe de relativité pour l'ensemble du savoir physique, exigence que la rencontre avec la théorie d'Einstein viendra confirmer et préciser. == L{{'}}''Allgemeine Erkenntnislehre'' == C'est à Rostock, où il s'est habilité en 1911 et enseigne désormais comme ''Privatdozent'', que Schlick entreprend la rédaction de son œuvre maîtresse, l{{'}}''Allgemeine Erkenntnislehre'', dont la première partie est achevée vers la fin de 1913 ou le début de 1914 et l'essentiel du texte rédigé entre le milieu de 1913 et l'automne 1915. L'ouvrage paraît en 1918 chez Julius Springer et connaît une seconde édition, révisée et augmentée, en 1925. Schlick le présente comme une théorie générale de la connaissance, soit une investigation portant sur les principes suprêmes et ultimes de toute connaissance, par distinction avec les théories spéciales — philosophie de la nature, des mathématiques, de l'histoire — qui s'arrêtent à un niveau de généralité moindre. L'exposition se veut accessible au lecteur non spécialiste de philosophie, les quelques passages de discussion technique pouvant être sautés sans dommage. L'ouvrage se divise en trois parties : « L'essence de la connaissance » (''Das Wesen der Erkenntnis''), « Problèmes de la pensée » (''Denkprobleme'') et « Problèmes de la réalité » (''Wirklichkeitsprobleme''). === L'essence de la connaissance === La première partie s'ouvre par une réflexion sur le sens et la tâche de la théorie de la connaissance. Schlick observe que la connaissance est une activité que nous exerçons constamment sans en comprendre la nature, tout comme nous remuons nos membres sans connaître les processus nerveux et musculaires qui rendent ce mouvement possible. La théorie de la connaissance n'a pas pour objet de rendre la connaissance possible — celle-ci s'exerce très bien sans elle — mais de comprendre en quoi elle consiste. Elle n'est pas une psychologie de l'acte de connaître, qui décrirait les processus mentaux impliqués, mais une analyse de ce qui fait d'un acte mental un acte de connaissance, autrement dit des conditions de sa validité. Son domaine est celui de la signification et de la vérité, non celui des événements psychiques. Pour déterminer l'essence de la connaissance, Schlick part de l'examen de ce que nous appelons déjà « connaître » dans la vie quotidienne et dans la science. Dans la vie ordinaire, connaître un objet, c'est le reconnaître, c'est-à-dire le retrouver comme identique à quelque chose de déjà connu : reconnaître une plante comme un tilleul, un visage comme celui d'un ami. La connaissance quotidienne réside ainsi dans un acte de ''Wiedererkennen'' — de re-connaissance — par lequel le nouveau est subsumé sous le connu. La connaissance scientifique ne fait que prolonger et systématiser ce processus : elle aussi consiste à retrouver le connu dans l'inconnu, mais elle le fait avec une rigueur et une systématicité incomparablement plus grandes. Le physicien qui identifie la lumière comme un phénomène ondulatoire « reconnaît » la lumière comme un cas particulier des phénomènes vibratoires, et par là réduit l'inconnu au connu. L'instrument de cette re-connaissance est le concept (''Begriff''). Schlick distingue soigneusement les concepts des représentations mentales (''Vorstellungen''). Les représentations — images visuelles, souvenirs sensoriels, associations — sont des événements psychiques individuels, variables d'un sujet à l'autre et d'un moment à l'autre. Elles ne peuvent pas servir d'instruments de connaissance, car elles sont essentiellement indéterminées et fluctuantes : la représentation que je me fais d'un arbre n'est jamais tout à fait la même et ne correspond jamais exactement à l'arbre réel. Les concepts, au contraire, sont des signes (''Zeichen'') qui désignent des objets de manière univoque. Un concept n'est pas une image qui « copie » la réalité ; c'est une « chose de pensée » (''Gedankending'') dont la seule fonction est de permettre la désignation exacte des objets à des fins de connaissance. Entre un concept et l'objet qu'il désigne, il n'y a pas de ressemblance, pas plus qu'entre le nom « César » et la personne historique de César : il y a une relation de désignation, soit une coordination (''Zuordnung''). La question se pose alors de savoir comment les concepts acquièrent leur signification, en somme comment ils se trouvent mis en relation avec les objets qu'ils désignent. Schlick distingue deux modes de définition : la définition concrète et la définition abstraite ou implicite. La définition concrète consiste à montrer l'objet désigné — par exemple, montrer du doigt une couleur et dire « ceci est jaune ». Elle permet de relier un concept à une intuition (''Anschauung''), à un contenu d'expérience. Mais elle ne suffit pas à fonder un système de connaissance, car les contenus d'expérience sont subjectifs et incommunicables en tant que tels. La définition implicite, que Schlick emprunte à la pratique des mathématiciens et en particulier à David Hilbert, constitue l'autre voie. Un concept est défini implicitement lorsqu'il est caractérisé uniquement par les relations qu'il entretient avec d'autres concepts au sein d'un système d'axiomes. Ainsi, les termes « point », « droite » et « plan » de la géométrie de Hilbert ne reçoivent pas de définition directe par ostension ou par description ; leur signification est entièrement déterminée par les axiomes qui énoncent les relations entre eux. Un concept défini implicitement n'a donc pas de contenu intuitif : il est une pure place dans un réseau de relations. Cette conception de la définition implicite a des conséquences considérables pour la théorie de la connaissance telle que Schlick la développe dans l{{'}}''Allgemeine Erkenntnislehre''. Elle montre que les concepts scientifiques n'ont pas besoin de « ressembler » à ce qu'ils désignent, ni de contenir un résidu d'intuition sensible. Ils peuvent être entièrement abstraits, pourvu que les relations entre eux soient univoquement déterminées. La connaissance ne procède pas par la reproduction d'images du réel, mais par la construction de systèmes de signes dont la structure formelle correspond à la structure des faits. Cette idée se rattache à la Zeichentheorie de Helmholtz — la théorie selon laquelle nos sensations sont des signes des choses, et non des copies — mais Schlick la radicalise en l'étendant des sensations aux concepts scientifiques eux-mêmes. Il convient toutefois de noter que le concept de définition implicite, central dans l{{'}}''Allgemeine Erkenntnislehre'', disparaît des écrits de Schlick après l'article « Erleben, Erkennen, Metaphysik » (1926), qui en représente la dernière défense publiée. Cette évolution, probablement liée à l'influence de Carnap et de Wittgenstein, n'affecte cependant pas la thèse générale selon laquelle la connaissance est connaissance de structures formelles. La connaissance proprement dite ne réside cependant pas dans les concepts isolés, mais dans les jugements (''Urteile''). Un jugement met en relation deux ou plusieurs concepts et, ce faisant, exprime un fait — autrement dit l'existence d'une relation entre les objets désignés par ces concepts. Le jugement « la lumière est un phénomène ondulatoire » coordonne les concepts de « lumière » et de « phénomène ondulatoire » de telle sorte qu'ils désignent le même objet : la lumière est reconnue comme vibration, et cette reconnaissance constitue un acte de connaissance. Le jugement est donc l'unité fondamentale de la connaissance : seuls les jugements sont vrais ou faux, seuls les jugements contiennent de la connaissance. Les concepts isolés ne sont ni vrais ni faux ; ils ne sont que des instruments de désignation. La vérité d'un jugement consiste, selon Schlick, dans la coordination univoque (''eindeutige Zuordnung'') de ce jugement aux faits. Un jugement est vrai lorsqu'à chacun de ses éléments correspond, de manière univoque, un élément du fait qu'il désigne, et que la structure relationnelle du jugement reproduit la structure relationnelle du fait. La vérité n'est donc ni l'utilité pratique (contre le pragmatisme), ni la conformité à un « devoir » de penser (contre le néo-kantisme), ni la cohérence interne d'un système de jugements (contre la théorie de la cohérence). Elle est une relation de coordination entre deux systèmes — le système des jugements et le système des faits —, relation qui doit être univoque pour que la connaissance soit accomplie. Schlick précise que cette univocité (''Eindeutigkeit'') de la coordination est le critère suprême de la vérité et, par extension, de toute connaissance : toutes les sciences travaillent à construire le grand réseau de jugements dans lequel le système des faits doit être « pris au filet », et la première et suprême condition pour que ce travail ait un sens est que chaque maillon du réseau de jugements soit univoquement coordonné à un maillon du réseau de faits. Le rôle de l'égalité (''Gleichheit'') mérite une mention particulière dans ce contexte. Schlick accorde à la relation d'égalité une place privilégiée parmi toutes les relations : c'est elle qui rend possible la re-connaissance (''Wiedererkennen'') et, par suite, l'univocité de la coordination. Sans la possibilité de reconnaître que deux choses — ou deux aspects de la même chose — sont identiques ou semblables, aucune désignation univoque ne serait possible. Toute relation, pour être identifiée comme telle, doit être reconnue comme « la même » que des relations antérieurement rencontrées. L'égalité est ainsi la condition première de toute connaissance, celle sans laquelle rien de « cognitif » n'existerait. Cette thèse est précisée dans la seconde édition à la suite d'un échange avec le psychologue Wolfgang Köhler, qui avait objecté que la reconnaissance d'égalités n'est qu'un cas particulier de la constatation de relations. Schlick lui répond que l'égalité occupe bel et bien une position privilégiée, car constater n'importe quelle relation revient toujours à la reconnaître comme identique à des relations antérieures. Dans la seconde édition de l'ouvrage, Schlick ajoute un paragraphe important (§ 11) consacré à la distinction entre définitions, conventions et jugements d'expérience. Cette distinction, qui intervient après l'élucidation de la nature de la vérité, précise la structure logique des systèmes scientifiques de jugements. Les définitions sont des jugements analytiques qui fixent le sens des concepts par stipulation ; leur vérité est affaire de convention, non de correspondance avec les faits. Les conventions, au sens de Poincaré, sont des jugements qui ne sont ni de pures définitions ni de purs jugements empiriques : ils contiennent un élément de choix, une libre création de l'esprit, mais ce choix est guidé par l'expérience et contraint par l'exigence de simplicité. Les jugements d'expérience (''Erfahrungsurteile'') sont les seuls jugements synthétiques ; leur vérité dépend de la correspondance avec les faits et doit être vérifiée empiriquement. Schlick nie catégoriquement l'existence d'une troisième classe de jugements — les jugements synthétiques a priori de Kant — et soutient que tous les jugements qui ne sont pas analytiques sont empiriques, en d'autres termes soumis au contrôle de l'expérience. Cette distinction tripartite entre définitions analytiques, conventions et jugements d'expérience synthétiques forme la charpente logique de la théorie schlickienne de la science. La première partie se clôt par l'examen de ce que la connaissance n'est pas. Schlick critique d'abord l'idée que connaître consisterait à « voir » intuitivement l'essence des choses, que ce soit par une intuition intellectuelle (Schelling, Husserl) ou par une intuition vitale (Bergson). La connaissance ne relève pas de l'intuition, mais d' une coordination conceptuelle. Il critique ensuite l'idée que la connaissance ait pour but de reproduire le réel dans la conscience, d'en fournir une « copie » mentale. Le but de la connaissance ne réside pas dans l'Abbildung (la représentation-copie), mais la Zuordnung (la coordination-désignation). Enfin, il examine la valeur de la connaissance et juge que celle-ci ne réside pas dans un bénéfice pratique, mais dans l'économie de pensée qu'elle permet : plus un système de connaissance réduit le nombre de concepts fondamentaux nécessaires pour désigner l'ensemble des faits, plus le degré de connaissance est élevé. Ce principe d'économie de pensée (''Denkökonomie''), que Schlick emprunte à Avenarius et à Mach, joue un rôle régulateur dans toute sa conception de la science. L'idéal de la science est de parvenir à un système de jugements aussi unifié et parcimonieux que possible, dans lequel un minimum de concepts fondamentaux et de lois fondamentales suffit à désigner de manière univoque la totalité des faits. Le progrès scientifique se mesure à la réduction du nombre de principes indépendants : chaque unification de domaines autrefois séparés — par exemple, l'unification de l'optique et de l'électromagnétisme par Maxwell — constitue une avancée de la connaissance. Ce n'est pas l'accumulation de faits qui fait progresser la science, mais leur subsomption sous des principes de plus en plus généraux et de moins en moins nombreux. Schlick ne confond cependant pas l'économie de pensée avec une simple commodité pratique : l'économie est la forme que prend la vérité dans un système accompli de jugements, la marque d'une coordination réussie entre les signes et les faits. === Problèmes de la pensée === La deuxième partie de l{{'}}''Allgemeine Erkenntnislehre'', intitulée « Problèmes de la pensée », quitte le terrain de l'analyse de la connaissance en général pour examiner les conditions de l'enchaînement des connaissances dans la pensée effective. Les problèmes traités sont ceux du raisonnement, de la logique, de la psychologie, de l'évidence et de la vérification. Schlick commence par analyser la structure du raisonnement déductif rigoureux. Tout raisonnement revient à substituer les uns aux autres les signes qui désignent les mêmes objets : si A = B et B = C, alors A = C. Le raisonnement ne produit pas de contenu objectivement nouveau ; il déploie les conséquences de ce qui était déjà contenu dans les prémisses. C'est le processus de l'analyse, dont les lois relèvent de la logique formelle. La déduction est donc un mouvement analytique : elle clarifie et explicite, mais n'ajoute rien au contenu des prémisses. L'examen sceptique du raisonnement conduit Schlick à affirmer la validité indépassable des principes logiques. Le principe de non-contradiction et les autres principes de la logique ne sont pas des hypothèses que l'on pourrait accepter ou rejeter : ils sont constitutifs de la pensée elle-même. Toute assertion, toute négation les présuppose. Le scepticisme logique — le doute portant sur la validité de la logique elle-même — est autoréfutant, car il recourt nécessairement à la logique pour s'énoncer. Cela ne signifie pas que nous ne commettions jamais d'erreurs dans l'application des règles logiques ; mais ces erreurs sont des accidents psychologiques, toujours corrigibles en principe, et elles n'entament pas la validité des principes eux-mêmes. La question de l'unité de la conscience (Einheit des Bewußtseins) est abordée ensuite. Le raisonnement lie des jugements successifs ; ces jugements appartiennent à une même conscience. L'unité de cette conscience semble être une condition nécessaire du raisonnement, car il faut que les prémisses et la conclusion soient présentes à un même sujet pour que la déduction s'effectue. Schlick reconnaît cette condition, mais en limite la portée : l'unité de la conscience est une condition psychologique de l'exercice de la pensée, non un fondement logique de sa validité. La validité d'une inférence ne dépend pas de l'unité du sujet qui l'effectue ; elle dépend des relations logiques entre les propositions. L'appel à l'unité de la conscience — que l'on trouve notamment dans la doctrine kantienne de l'aperception transcendantale — ne peut donc pas fonder la logique ni garantir la vérité. Cette conclusion prépare la distinction, capitale dans la pensée de Schlick, entre le psychologique et le logique. Les lois logiques ne sont pas des lois naturelles décrivant le fonctionnement de l'esprit humain : elles sont des normes (Normen) de la pensée correcte. Le fait que les êtres humains pensent de telle ou telle manière — qu'ils fassent des associations, qu'ils raisonnent parfois mal, qu'ils soient sujets à des biais — relève de la psychologie empirique et n'a aucune pertinence pour la validité logique. Le psychologisme — la doctrine qui réduit les lois logiques à des lois psychologiques — est donc une erreur de principe. Schlick rejoint ici la critique du psychologisme formulée par Frege et par Husserl dans les Prolégomènes à la logique pure, mais sa position propre est plus nuancée : il admet que toutes nos constatations présupposent des conditions psychologiques et refuse de jouer au « cache-cache avec soi-même » en niant ce fait, tout en maintenant fermement que les règles logiques ne sont pas des régularités psychologiques. L'examen de l'évidence (''Evidenz'') poursuit cette critique de la confusion entre le psychologique et le logique. L'évidence est un sentiment de certitude qui accompagne certaines pensées, un état psychologique que nous éprouvons lorsque quelque chose nous paraît « clair et distinct ». Mais un sentiment, aussi fort soit-il, ne peut pas servir de critère de vérité, car il reste un fait psychologique subjectif. L'histoire des sciences montre que des propositions tenues pour évidentes se sont révélées fausses. L'évidence peut accompagner la vérité, mais elle ne la constitue pas. De même, la perception interne (innere Wahrnehmung) — l'observation que le sujet fait de ses propres états mentaux — n'a pas le privilège épistémologique que Descartes et d'autres lui ont attribué. La perception de nos propres pensées et sentiments est faillible au même titre que la perception des objets extérieurs. La deuxième partie se conclut par l'examen de la vérification (''Verifikation''), processus par lequel les jugements empiriques sont confrontés à l'expérience pour déterminer leur vérité. La vérification est le mode propre de justification des jugements synthétiques, mais elle n'atteint jamais une certitude absolue : elle reste toujours faillible et révisable. === Problèmes de la réalité === La troisième partie, « Problèmes de la réalité », marque un tournant dans l'ouvrage. Les deux premières parties avaient examiné la forme de la connaissance — en quoi elle consiste et comment elle s'enchaîne dans la pensée — sans se prononcer sur son contenu, c'est-à-dire sur la nature des objets connus. La troisième partie passe de la forme au contenu : elle se tourne vers le système des faits, vers les objets désignés par les jugements, et pose la question de la réalité. Schlick la subdivise en trois sections : la « position du réel » (die Setzung des Wirklichen), qui demande ce qu'est le réel et comment on le distingue de l'irréel ; la « connaissance du réel » (die Erkenntnis des Wirklichen), qui examine ce que nous pouvons savoir du monde réel et sous quelle forme ; la « validité de la connaissance du réel » (die Gültigkeit der Wirklichkeitserkenntnis), qui interroge les conditions de possibilité et les limites de notre connaissance de la réalité. La « position du réel » s'ouvre par une distinction entre l'attitude naïve — le réalisme spontané de la vie quotidienne, qui prend les objets perçus pour des réalités indépendantes — et les attitudes philosophiques qui la mettent en question : l'idéalisme, le phénoménalisme, le positivisme. Schlick avance que le critère de la réalité est la temporalité : est réel ce qui s'insère dans le temps, ce qui advient, dure et passe. Cette thèse, qu'il avait déjà formulée dans ses notes de Zurich, exclut du domaine du réel les entités atemporelles — les idées platoniciennes, les valeurs absolues — mais inclut les entités physiques non immédiatement perçues, pourvu qu'elles soient temporellement déterminées. Elle fournit un critère formel de réalité qui ne préjuge pas du contenu de ce qui est réel. La question de la « chose en soi » (''Ding an sich'') et de la pensée d'immanence (''Immanenzgedanke'') est ensuite abordée avec une ampleur qui en fait l'une des discussions centrales du livre. La pensée d'immanence, défendue par des philosophes comme Avenarius, Schuppe et, sous une forme différente, Mach, soutient que nous ne connaissons que nos propres représentations, que le monde perçu est immanent à la conscience et qu'il est illégitime de postuler l'existence de choses « en soi » au-delà de l'expérience. Schlick soumet cette position à une critique détaillée. Il distingue deux problèmes : celui des objets non perçus (qu'advient-il de la table quand personne ne la regarde ?) et celui des objets perçus par plusieurs sujets (comment expliquer que différents individus perçoivent le « même » objet ?). Le premier problème conduit à examiner la tentative de réduire les objets non perçus à de simples « possibilités de sensation » (Möglichkeiten der Empfindung), dans l'esprit de Mill et de Mach. Schlick montre que cette réduction échoue : les possibilités ne sont pas des réalités, elles ne peuvent pas figurer comme termes dans des relations causales, et la science a besoin de postuler l'existence de réalités indépendantes de la perception pour maintenir la continuité de l'enchaînement causal. Le second problème confirme ce résultat : la concordance des perceptions de différents sujets ne peut être expliquée que par l'existence d'un monde objectif commun qui en est la cause. Schlick aboutit ainsi à un réalisme critique : il y a bien des choses « en soi », des réalités indépendantes de la conscience, mais nous ne les connaissons pas telles qu'elles sont « en elles-mêmes » — nous ne connaissons que leur structure, c'est-à-dire les relations qui existent entre elles. La section consacrée à « la connaissance du réel » développe les conséquences de ce réalisme structurel. Schlick examine d'abord la distinction entre « essence » (''Wesen'') et « apparence » (''Erscheinung''). Cette distinction n'est pas ontologique, comme si les apparences étaient moins réelles que les essences ; elle est fonctionnelle : l'apparence est ce qui varie selon les conditions de l'observation, l'essence est ce qui reste constant sous ces variations. La science vise précisément à dégager, sous la variabilité des apparences, les structures invariantes qui constituent le réel. Schlick procède ensuite à l'examen systématique de la subjectivité du temps, de l'espace et des qualités sensibles. Le temps vécu — le temps tel que nous en faisons l'expérience, avec son flux irréversible et la singularité du « maintenant » — est subjectif ; il dépend de notre constitution psychologique. Mais la structure temporelle objective — l'ordre de succession des événements, les relations de « avant » et « après » — existe indépendamment du sujet et peut être désignée par des concepts scientifiques. De même, l'espace perçu — avec ses qualités visuelles, tactiles, son horizon et sa profondeur — est subjectif ; il varie d'un sens à l'autre et d'un individu à l'autre. Mais l'espace objectif est un système d'ordonnancement abstrait, un schéma conceptuel qui permet la localisation univoque des événements. Les qualités sensibles — couleurs, sons, odeurs — sont intégralement subjectives : ce sont des « signes » que notre système nerveux produit en réponse aux stimulations physiques, et rien dans ces signes ne ressemble à ce qu'ils désignent. L'objectivité de notre connaissance du monde ne réside donc pas dans les qualités — qui sont subjectives et incommunicables — mais dans les relations quantitatives entre les grandeurs physiques. Cette analyse conduit Schlick à l'une des thèses les plus importantes de l'ouvrage : la distinction entre connaissance quantitative et connaissance qualitative. La connaissance qualitative — celle qui saisit les qualités sensibles, les couleurs et les sons — ne dépasse pas la sphère du subjectif. La connaissance quantitative — celle qui établit des relations mesurables entre des grandeurs — est la seule qui atteigne l'objectivité, la seule qui mérite pleinement le nom de connaissance scientifique. Toute mesure repose sur la méthode des coïncidences ''raumzeitlicher Koinzidenzen'' : mesurer, c'est observer la coïncidence spatio-temporelle de deux points — l'extrémité d'un objet et la graduation d'un instrument. Cette méthode, qui sera développée dans ''Raum und Zeit in der gegenwärtigen Physik'', forme la base de toute détermination physique du réel. Le traitement du problème psychophysique — le rapport entre le physique et le psychique — occupe une place considérable dans la troisième partie. Schlick défend l'idée que le physique et le psychique ne sont pas deux « substances » distinctes (dualisme), ni que l'un se réduit à l'autre (matérialisme ou idéalisme), mais qu'ils représentent deux modes de désignation d'une même réalité sous-jacente. Les concepts de la physique et les concepts de la psychologie peuvent être coordonnés aux mêmes processus réels, mais ils les désignent sous des descriptions différentes. Ce qui est décrit physiquement comme un processus cérébral est décrit psychologiquement comme une perception ou une pensée. Le « parallélisme » psychophysique n'est pas une relation causale entre deux séries d'événements distincts — physiques d'un côté, psychiques de l'autre — mais l'expression du fait qu'une même réalité admet deux systèmes de désignation. Schlick défend le principe de la « causalité physique fermée » (''geschlossene Naturkausalität''), selon lequel tout événement physique a une cause physique suffisante et le monde physique forme un système clos de déterminations causales. Les qualités psychiques — couleurs, sons, émotions — n'apparaissent pas dans les lois physiques et ne jouent aucun rôle causal dans le monde physique. Elles sont les « signes » subjectifs par lesquels la réalité se manifeste à la conscience. La position de Schlick s'apparente ainsi à un monisme neutre : il n'y a qu'une seule réalité, dont le physique et le psychique sont deux expressions. La dernière section de la troisième partie, consacrée à la « validité de la connaissance du réel », se confronte directement à la philosophie de Kant. Schlick y examine les trois prétentions kantiennes : l'existence de formes pures de l'intuition (''Anschauungsformen''), l'existence de formes pures de la pensée (''Denkformen'') et l'existence de catégories a priori. Il nie d'abord qu'il existe une intuition pure, c'est-à-dire une forme de l'espace ou du temps qui serait donnée a priori indépendamment de toute expérience et qui constituerait une condition de possibilité de l'expérience. L'espace de la géométrie n'est pas un donné intuitif mais une construction conceptuelle ; il est tout aussi facile de « penser » des relations géométriques non euclidiennes que des relations euclidiennes, car il ne s'agit dans les deux cas que de l'ajout de concepts par lesquels les données intuitives sont interprétées. Il nie ensuite qu'il existe des formes pures de la pensée qui soient à la fois synthétiques et a priori. Les formes logiques sont bien a priori — elles sont présupposées par toute pensée — mais elles sont analytiques : elles ne produisent pas de contenu nouveau sur le monde. Les jugements synthétiques, en revanche, sont tous a posteriori : leur vérité ne peut être déterminée que par l'expérience. La catégorie de causalité fait l'objet d'un examen particulier : Schlick la considère comme un principe heuristique de la recherche scientifique, une présupposition méthodologique selon laquelle tout événement naturel obéit à des lois, mais il refuse de la considérer comme un jugement synthétique a priori au sens kantien. Le principe de causalité n'est pas une vérité nécessaire sur le monde ; il exprime la conviction, fondée sur l'expérience passée et toujours révisable, que la nature est régulière et que des lois universellement valides peuvent y être découvertes. L'ouvrage se termine par un examen de la connaissance inductive, que Schlick traite avec une concision qu'il reconnaît lui-même insuffisante, le sujet exigeant « un livre à part ». L'induction — le passage de l'observation de cas particuliers à des lois générales — ne peut jamais atteindre une certitude logique, car la conclusion dépasse toujours les prémisses. Schlick esquisse une conception probabiliste de l'induction : les hypothèses scientifiques ne sont jamais définitivement vérifiées, mais leur probabilité augmente à mesure que l'expérience les confirme. La science avance ainsi par un processus de coordination toujours plus fine entre les jugements et les faits, sans atteindre jamais une vérité définitive et complète. == Philosophie de la relativité == Parallèlement à la rédaction de l{{'}}''Allgemeine Erkenntnislehre'', puis dans son prolongement, Schlick s'engage dans une réflexion approfondie sur les fondements philosophiques de la physique contemporaine, et en particulier de la théorie de la relativité d'Einstein. Cette réflexion, amorcée dès 1912 dans ses notes de naturphilosophie et dans son échange épistolaire avec Max von Laue, aboutit en 1917 à la publication de l'article « ''Raum und Zeit in der gegenwärtigen Physik'' » dans la revue ''Die Naturwissenschaften'', article qui sera ensuite développé en un livre à part entière, publié en quatre éditions successives (1917, 1919, 1920, 1922). Einstein lui-même lit et approuve le manuscrit avant publication, y voyant une interprétation philosophique juste de sa théorie. L'engagement de Schlick avec la théorie de la relativité ne doit pas être compris comme un simple exercice d'interprétation rétrospective. Dès son article de 1915 « Die philosophische Bedeutung des Relativitätsprinzips », il voit dans la relativité une confirmation de ses thèses épistémologiques les plus essentielles : l'abandon de l'espace et du temps absolus de Newton confirme que l'espace et le temps ne sont pas des réalités indépendantes mais des schémas d'ordonnancement ; la covariance générale confirme que seules les coïncidences spatio-temporelles — les Koinzidenzen — ont une réalité physique ; le rôle des conventions dans le choix de la géométrie confirme la distinction entre définitions et jugements d'expérience. La théorie de la relativité fournit ainsi à Schlick un cas privilégié d'application de sa théorie générale de la connaissance, et réciproquement, sa théorie de la connaissance fournit à la relativité un cadre philosophique adéquat. Einstein reconnaît cette adéquation mutuelle et la correspondance entre les deux hommes, qui s'étend de 1915 à la mort de Schlick, demeure un document majeur de l'histoire de la philosophie des sciences. L'intention de Schlick dans ''Raum und Zeit'' n'est pas d'exposer la théorie de la relativité en tant que telle, mais d'en dégager les implications épistémologiques, à savoir montrer que cette théorie confirme et radicalise les conclusions atteintes dans l{{'}}''Allgemeine Erkenntnislehre'' concernant la nature de l'espace, du temps et de la connaissance physique. La thèse centrale est que la relativité achève le processus par lequel l'espace et le temps ont perdu « le dernier reste de réalité physique indépendante ». L'espace et le temps ne sont pas des réalités absolues, des cadres préexistants dans lesquels les événements physiques se déroulent ; ils sont des schémas d'ordonnancement abstraits, des systèmes de relations dont la structure est déterminée en partie par convention et en partie par l'expérience. Schlick développe à cette occasion la « méthode des coïncidences » (''Koinzidenzmethode'') comme fondement de toute mesure physique. Toute mesure revient en dernière analyse à observer la coïncidence spatio-temporelle de deux événements — par exemple, la coïncidence d'un repère sur un instrument avec un point d'un objet mesuré. Einstein avait montré que la physique peut être entièrement formulée comme un ensemble de lois régissant ces coïncidences, et que tout ce qui ne se réduit pas à de telles coïncidences est dépourvu de réalité physique. Schlick saisit immédiatement la portée épistémologique de cette idée : la méthode des coïncidences est l'application physique de la méthode générale de la coordination univoque. De même que la connaissance en général revient à coordonner des signes aux faits, la connaissance physique consiste à coordonner des grandeurs mesurables aux événements par le moyen des coïncidences ponctuelles. Schlick prend position dans le débat entre le positivisme strict de Mach, qui ne reconnaît de réalité qu'aux « éléments » directement vécus (couleurs, sons, pressions), et le réalisme critique qu'il défend. Il admet avec Mach que la base de toute physique est l'observation de coïncidences sensibles. Mais il objecte à Mach, que les grandeurs physiques qui apparaissent dans les équations différentielles de la physique — champs électriques, intensités magnétiques — ne sont pas de simples « fictions économiques » : elles peuvent désigner des réalités non immédiatement vécues, pourvu qu'on puisse leur assigner une détermination spatio-temporelle, en d'autres termes un « où » et un « quand » déterminés selon les règles de la recherche. Ce critère de la détermination spatio-temporelle — et non la simple mesurabilité en général — est ce qui distingue, pour Schlick, les objets réels des entités fictives. Le concept d'électron ou d'atome n'est pas nécessairement un simple outil de calcul ; il peut désigner un complexe réel d'éléments objectifs, de même que le concept du « moi » désigne un complexe réel d'éléments intuitifs. Concernant la géométrie, Schlick adopte une position conventionnaliste inspirée de Poincaré. L'expérience ne nous contraint pas à utiliser une géométrie déterminée — euclidienne ou non euclidienne — pour décrire l'espace physique ; elle nous indique seulement laquelle nous devons choisir si nous voulons parvenir aux formulations les plus simples des lois de la nature. Le « choix » d'une géométrie relève donc en partie de la convention, mais c'est une convention guidée par l'expérience et soumise au critère de simplicité. Il n'a pas de sens de parler de la géométrie « de l'espace » indépendamment de la physique, car la structure géométrique n'est déterminée qu'en relation avec le comportement des corps physiques. Cette position se situe entre l'apriorisme kantien, qui fait de la géométrie euclidienne une vérité nécessaire de l'intuition pure, et l'empirisme radical, qui fait de la géométrie un simple résumé d'observations. Schlick la dirige aussi contre les tentatives de sauver une forme d'a priori constitutif à la lumière de la relativité. Ernst Cassirer, dans ''Zur Einsteinschen Relativitätstheorie'' (1921), cherche à conserver l'idée kantienne selon laquelle certains principes formels — la fonction d'objectivation, la loi de causalité — constituent les conditions de possibilité de l'expérience, même s'ils ne dictent pas à l'avance le contenu des lois de la nature. Hans Reichenbach, dans sa ''Relativitätstheorie und Erkenntnis Apriori'' (1920), tente de redéfinir l'a priori kantien en le dépouillant de son caractère de nécessité et d'universalité absolues : les « principes constitutifs » ne seraient plus des vérités immuables, mais des présuppositions révisables qui structurent l'expérience à un stade donné de la science. La position de Reichenbach en 1920 est subtile et ne se réduit pas à un néo-kantisme ordinaire ; elle constitue plutôt une tentative originale de conciliation entre kantisme et empirisme, que Reichenbach lui-même abandonnera d'ailleurs rapidement au profit d'un conventionnalisme plus radical. Schlick rejette ces deux tentatives : pour lui, dès lors qu'un principe est révisable et dépendant de l'expérience, il n'est plus a priori en aucun sens philosophiquement intéressant du terme, et l'usage du mot « a priori » pour désigner des présuppositions empiriques révisables ne fait qu'entretenir une confusion terminologique. == La période viennoise == En 1922, après un bref passage par l'Université de Kiel, Schlick est appelé à occuper la chaire de philosophie des sciences inductives à l'Université de Vienne, chaire qui avait été celle d'Ernst Mach puis de Ludwig Boltzmann. Autour de lui se constitue progressivement un groupe de discussion — le futur « Cercle de Vienne » (Wiener Kreis) — qui réunit des philosophes, des mathématiciens et des scientifiques : Rudolf Carnap, Otto Neurath, Friedrich Waismann, Hans Hahn, Herbert Feigl, parmi d'autres. Ce groupe entreprend l'élaboration d'une philosophie scientifique rigoureuse, opposée à la métaphysique traditionnelle et fondée sur l'analyse logique du langage. Schlick en est le foyer intellectuel et organisationnel, quoiqu'il ne s'identifie pas à toutes les positions collectivement attribuées au Cercle. === Wittgenstein et le principe de vérification === L'événement intellectuel décisif de la période viennoise est la rencontre avec la pensée de Ludwig Wittgenstein, et en particulier avec le ''Tractatus Logico-Philosophicus'' (1921). Le Cercle de Vienne lit le ''Tractatus'' en détail au cours de séances régulières en 1926-1927, et Schlick entretient à partir de 1927 une relation personnelle avec Wittgenstein. L'influence du ''Tractatus'' sur Schlick est considérable, bien que sélective. Schlick retient principalement deux idées : d'une part, que les propositions de la logique et des mathématiques sont des tautologies, c'est-à-dire des propositions vides de contenu factuel, qui ne disent rien sur le monde mais explicitent les règles de notre système de signes ; d'autre part, que la signification d'une proposition est déterminée par les conditions de sa vérification, autrement dit par les observations qui la confirmeraient ou l'infirmeraient. Cette seconde idée, transformée en un critère général de signification, devient le « principe de vérification » (''Verifikationsprinzip''), qui forme la pierre angulaire de l'empirisme logique tel que le Cercle de Vienne le conçoit. L'idée que les vérités logiques et mathématiques sont des tautologies s'articule de manière naturelle avec les analyses de l'''Allgemeine Erkenntnislehre''. Dans l'ouvrage de 1918/1925, Schlick avait déjà soutenu que le raisonnement déductif est analytique — qu'il ne produit pas de contenu nouveau — et que les définitions sont des jugements dont la vérité est affaire de stipulation. La thèse wittgensteinienne des tautologies précise et radicalise cette position : les vérités de la logique ne sont pas des « lois de la pensée » au sens psychologique, ni des « lois de l'être » au sens métaphysique ; elles sont des expressions de la structure de notre langage qui ne disent rien sur le monde. Cette reformulation linguistique de la distinction analytique/synthétique permet à Schlick d'affiner sa critique du synthétique a priori kantien : si les vérités logiques et mathématiques sont des tautologies, il ne reste aucun espace pour des vérités qui seraient à la fois synthétiques — autrement dit porteuses d'un contenu factuel — et a priori — autrement dit indépendantes de l'expérience. Le principe de vérification énonce qu'une proposition n'a de sens (''Sinn'') que si l'on peut indiquer, au moins en principe, les observations qui permettraient de la vérifier ou de la réfuter. Les propositions qui ne satisfont pas cette condition — qui ne sont reliées à aucune expérience possible — sont dépourvues de signification cognitive, même si elles ont l'apparence grammaticale de propositions bien formées. Ce critère permet de tracer une ligne de démarcation entre les propositions scientifiques, qui sont vérifiables et donc douées de sens, et les propositions métaphysiques, qui ne le sont pas. Schlick formule cette position notamment dans l'article « Positivismus und Realismus » (1932), où il déclare que la question métaphysique traditionnelle de l'existence du monde extérieur est un pseudo-problème (''Scheinproblem'') : elle ne peut être ni vérifiée ni réfutée par aucune expérience possible, et elle est donc dépourvue de contenu cognitif. Cette position ne revient pas à nier l'existence du monde extérieur, mais à déclarer que la question de son existence, telle que la métaphysique la pose, n'a pas de sens déterminé. === Réalisme, positivisme et ''Scheinproblem'' === L'adoption du principe de vérification modifie sensiblement la position épistémologique de Schlick par rapport à celle de l{{'}}''Allgemeine Erkenntnislehre''. Dans l'ouvrage de 1918/1925, la question du réalisme recevait une réponse positive : Schlick y défendait un réalisme critique, affirmant l'existence de choses en soi au-delà de l'expérience et critiquant l'immanentisme. Dans « Positivismus und Realismus », il abandonne ce réalisme sous sa forme antérieure. Il ne verse pas dans l'idéalisme, mais il affirme désormais que l'opposition traditionnelle entre réalisme et positivisme est un pseudo-problème : correctement comprises, les deux positions ne sont pas des doctrines métaphysiques rivales. Dire qu'un objet physique existe indépendamment de la perception, c'est dire que certaines séquences régulières d'expériences sont possibles, et cette affirmation est parfaitement vérifiable. Ce qui est rejeté n'est pas l'existence du monde extérieur, mais l'idée qu'affirmer ou nier cette existence constituerait une thèse métaphysique substantielle. Le réalisme, vidé de ses prétentions métaphysiques, se réduit à une hypothèse empirique — la plus simple et la plus féconde pour rendre compte de la régularité de l'expérience — et cesse d'être une position philosophique rivale du positivisme. === Le débat des énoncés protocolaires === La période viennoise est aussi celle du « débat des énoncés protocolaires » (''Protokollsatzdebatte''), qui oppose Schlick à Neurath et Carnap sur la question du fondement de la connaissance empirique. Pour Neurath, les énoncés de base de la science (Protokollsätze) sont des énoncés intersubjectifs, formulés dans un langage physiciste, et ils n'ont pas de statut épistémologique privilégié : ils peuvent être révisés comme n'importe quel autre énoncé du système scientifique. Carnap adopte une position voisine. Schlick, en revanche, soutient dans « Über das Fundament der Erkenntnis » (1934) qu'il existe des énoncés d'observation jouissant d'une certitude absolue, qu'il appelle « constatations » (''Konstatierungen''). Ces constatations sont toujours de la forme « ici, maintenant, tel et tel » (hier jetzt so und so) — par exemple : « ici coïncident maintenant deux points noirs », « ici maintenant du jaune jouxte du bleu ». Elles comportent des mots déictiques (ici, maintenant) dont le sens ne peut être donné que par un geste d'ostension accompagnant l'énoncé ; c'est pourquoi une constatation authentique ne peut pas être mise par écrit : dès que les mots « ici » et « maintenant » sont consignés sur le papier, ils perdent leur sens. Une constatation écrite se transforme immédiatement en un énoncé protocolaire, c'est-à-dire en une hypothèse faillible. Il est essentiel de bien distinguer ces trois niveaux, car toute la position de Schlick repose sur leur différence. La constatation (''Konstatierung'') est un événement ponctuel, un acte présent de comparaison entre un jugement et un fait, qui ne survit pas à l'instant où il s'accomplit. L'énoncé protocolaire (''Protokollsatz''), au sens de Neurath et Carnap, est un énoncé inscrit, du type « M. S. a perçu du bleu le 15 avril 1934 à tel endroit » : il contient le nom d'un observateur, une date, un lieu, et il est par nature une hypothèse, car rien ne garantit qu'il reproduit fidèlement la constatation qui l'a occasionné. L'hypothèse scientifique, enfin, est un énoncé général déduit du système de la science. Pour Schlick, les énoncés protocolaires ne sont qu'un cas particulier d'hypothèses ; les constatations, en revanche, sont les seuls énoncés synthétiques qui ne sont pas des hypothèses, mais elles ne peuvent servir de fondation logique précisément parce qu'elles sont évanescentes. La spécificité des constatations réside en ce que, chez elles, le processus par lequel on saisit le sens et celui par lequel on établit la vérité coïncident — exactement comme pour les jugements analytiques, à cette différence près que les constatations ont un contenu factuel. Schlick insiste sur le fait que ces constatations ne sont pas les « fondements » de la science au sens où le reste de la science serait déduit d'elles : la science ne repose pas sur elles, mais conduit à elles. Elles sont un terme absolu (''absolutes Ende'') : le point d'arrivée du processus de vérification, le moment où la coordination entre les signes et les faits est effectivement accomplie. Elles jouent un rôle de vérification, non de fondation logique. Cette position de Schlick dans le débat des ''Protokollsätze'' est cohérente avec la conception développée dans l{{'}}''Allgemeine Erkenntnislehre'' : la connaissance est une coordination de jugements à des faits, et cette coordination doit, à un certain moment, entrer en contact avec l'expérience. Les ''Konstatierungen'' sont précisément ces moments de contact, et c'est en les atteignant que la science accomplit sa mission. Mais la position de 1934 se distingue de celle de 1918/1925 par l'accent mis sur le langage : la question n'est plus seulement celle de la coordination entre pensée et réalité, mais celle de la coordination entre énoncés linguistiques et observations. Ce déplacement reflète l'influence de Wittgenstein et du « tournant linguistique » qui caractérise l'empirisme logique viennois. === La philosophie comme activité === Un autre développement important de la période viennoise concerne la conception de la philosophie elle-même. Dans une série d'articles et de conférences — notamment « Die Wende der Philosophie » (1930), « The Future of Philosophy » (1931 et 1932) et « Philosophie und Naturwissenschaft » (1934) — Schlick avance que la philosophie n'est pas un système de propositions mais une activité (''Tätigkeit''), celle par laquelle le sens des énoncés est établi ou mis au jour. La philosophie clarifie les propositions, les sciences les vérifient. L'idée que la tâche de la philosophie vise à clarifier le sens des résultats scientifiques (''Sinnklärung'') n'est pas entièrement nouvelle chez Schlick : elle se trouve déjà, comme le signale l'appareil éditorial de la Gesamtausgabe, dans le dernier paragraphe de l{{'}}''Allgemeine Erkenntnislehre'' et même dans la première leçon de Rostock en 1911. Ce qui est nouveau, et vient clairement de Wittgenstein, c'est la thèse radicale que la philosophie n'est pas du tout une science, qu'elle ne produit pas de propositions vraies, que ses résultats ne sont pas des « propositions philosophiques » mais des actes d'élucidation. Cette thèse apparaît pour la première fois dans l'article « Erkenntnistheorie und moderne Physik », dont la rédaction remonte à 1925 mais qui ne paraît qu'en 1929 dans ''Scientia''. Mais c'est surtout dans « Die Wende der Philosophie » (1930), puis dans les deux versions de « The Future of Philosophy » (1931 et 1932), que cette conception prend sa forme publique, programmatique et tranchée. Les problèmes traditionnels de la métaphysique — l'existence du monde extérieur, la nature de l'âme, la liberté du vouloir — ne sont pas des problèmes insolubles mais des problèmes mal posés, en d'autres termes formulés qui ne satisfont pas les conditions de la signification. La métaphysique échoue non parce que la raison humaine n'est pas à la hauteur de la tâche, mais parce que cette tâche n'existe pas. Correctement analysés, les pseudo-problèmes de la métaphysique se dissolvent ou se transforment en questions empiriques traitables par les sciences. Cette conception de la philosophie comme activité clarificatrice, et non comme corps de doctrine, sera l'un des héritages les plus durables du Cercle de Vienne. === Philosophie de la nature et mécanique quantique === Schlick poursuit aussi à Vienne son travail de philosophie de la nature, auquel il consacre des cours réguliers tout au long de la période viennoise — en 1927, en 1932-1933 et encore en 1936. Dans ces cours, il applique le principe de vérification aux développements de la physique contemporaine, en particulier à la mécanique quantique. Il argue par exemple que si la position et la vitesse d'un électron ne peuvent être simultanément déterminées par aucune expérience — comme le montre la relation d'indétermination de Heisenberg —, il n'a pas de sens de dire que l'électron possède simultanément ces deux propriétés. La philosophie de la nature est pour lui une « interprétation du sens » (''Sinndeutung'') des propositions de la science naturelle : elle ne produit pas de connaissance nouvelle de la nature, mais clarifie la signification de la connaissance que la science a produite. Schlick s'engage aussi dans une réflexion sur les conséquences du nouvel indéterminisme physique pour le problème de la liberté de la volonté, faisant valoir que l'acausalité quantique ne fournit aucun fondement à la liberté au sens éthique du terme, car un comportement régi par le pur hasard ne serait ni libre ni responsable. === Forme et contenu === Dans ses derniers écrits épistémologiques, Schlick approfondit la distinction entre forme et contenu (''Form und Inhalt'') qui traverse toute sa pensée. L'ouvrage « ''Form and Content: An Introduction to Philosophical Thinking'' » (rédigé en anglais à partir de conférences données à Londres, publié posthume dans les ''Gesammelte Aufsätze 1926-1936'') développe systématiquement la thèse selon laquelle la connaissance ne saisit que la structure formelle de la réalité — les relations entre les choses — et non leur contenu qualitatif intrinsèque. Les qualités vécues — la rougeur du rouge, le caractère douloureux de la douleur — sont des contenus d'expérience (''Erlebnisse'') qui ne peuvent être ni communiqués ni connus au sens strict. Ce qui peut être communiqué et connu, ce sont les relations structurelles : la position d'une couleur dans l'espace chromatique, l'intensité relative d'une douleur, les lois qui relient un stimulus à une sensation. La science, en tant que système de propositions communicables et vérifiables, est nécessairement une connaissance de structures, une désignation des relations entre les choses, et non une reproduction de leurs qualités. Il faut cependant noter que Schlick lui-même prit ses distances avec cet ouvrage en janvier 1935, écrivant à Louis Rougier que « toute la conception du livre ne correspondait plus à [s]es vues actuelles » et qu'il ne le ferait probablement pas imprimer. L'ouvrage ne parut que dans l'édition posthume de 1938. Cette thèse de la nature structurelle de la connaissance est en continuité directe avec les analyses de l{{'}}''Allgemeine Erkenntnislehre'' sur la distinction entre connaissance quantitative et connaissance qualitative, et avec la Zeichentheorie héritée de Helmholtz. Mais elle acquiert dans les derniers écrits une formulation plus nette, influencée à la fois par le ''Tractatus'' de Wittgenstein et par les discussions au sein du Cercle de Vienne. Schlick avance que la distinction forme/contenu recouvre deux types d'indicible qu'il a tendance à juxtaposer sans toujours les distinguer clairement. Le premier type, emprunté au ''Tractatus'' de Wittgenstein, est l'indicible de la forme logique : la forme que les propositions partagent avec la réalité pour pouvoir la représenter ne peut pas elle-même être dite dans des propositions, mais seulement montrée (''gezeigt''). Le second type, qui est la contribution propre de Schlick, est l'indicible du contenu qualitatif : les qualités vécues ne peuvent être communiquées par aucune proposition, mais seulement éprouvées (''erlebt''). La science dit les structures ; l'art, la poésie et l'expérience personnelle donnent accès aux contenus. Les deux sont nécessaires à une existence humaine complète, mais ils ne relèvent pas du même registre. == Éthique == L'éthique occupe une place significative dans l'œuvre de Schlick, depuis la ''Lebensweisheit'' de sa jeunesse jusqu'aux ''Fragen der Ethik'', publiées en 1930. Ce dernier ouvrage, qui représente sa contribution la plus systématique à la philosophie morale, reprend et approfondit les intuitions de la ''Lebensweisheit'' dans le cadre conceptuel élaboré au fil des ans. La thèse fondamentale des ''Fragen der Ethik'' est que l'éthique n'est pas une discipline normative au sens où elle prescrirait des devoirs absolus, mais une discipline descriptive et explicative qui étudie les faits du comportement moral humain et en recherche les causes. La question centrale de l'éthique n'est pas « que devons-nous faire ? » mais « pourquoi les hommes agissent-ils comme ils agissent, et pourquoi certaines actions sont-elles approuvées et d'autres désapprouvées ? ». Cette formulation du problème éthique comme problème d'explication, et non de prescription, doit beaucoup à Schopenhauer, à qui Schlick consacre des cours répétés tout au long de sa carrière et dont il juge l'approche « beaucoup plus féconde que celle de la morale kantienne, qui nous donne de nouvelles énigmes au lieu de résoudre les anciennes ». Schlick identifie la motivation comme le concept directeur de l'éthique. Tout comportement humain est motivé, en d'autres termes causalement déterminé par des désirs, des penchants, des dispositions psychologiques. Le déterminisme — la thèse selon laquelle tout événement, y compris tout acte humain, a des causes suffisantes — n'est pas incompatible avec la responsabilité morale, contrairement à ce que soutiennent les libertariens. La responsabilité n'exige pas que l'agent ait pu agir autrement en un sens absolu (liberté d'indifférence) ; elle exige seulement que l'action ait été déterminée par le caractère de l'agent, par ses dispositions et ses motifs, et non par une contrainte extérieure. Nous tenons un homme pour responsable lorsque son action exprime sa volonté propre, et cette attribution de responsabilité a elle-même une fonction causale : elle agit comme un motif supplémentaire dans la délibération future de l'agent. Schlick défend ainsi un compatibilisme : le déterminisme et la responsabilité morale sont non seulement compatibles, mais la seconde présuppose le premier. Les ''Fragen der Ethik'' poursuivent la ''Lebensweisheit'' en fondant l'éthique sur une psychologie du plaisir. Ce qui est moralement approuvé est, en dernière analyse, ce qui procure du bonheur ; ce qui est moralement désapprouvé est ce qui cause de la souffrance. Les règles morales ne sont pas des commandements tombés du ciel ni des impératifs catégoriques de la raison pure ; elles sont des généralisations de l'expérience humaine concernant les conditions du bien-vivre en société. Elles sont révisables à la lumière de l'expérience, tout comme les hypothèses scientifiques. L'éthique est ainsi ramenée au statut d'une science empirique parmi d'autres, conformément à la conviction constante de Schlick que les questions philosophiques sont ultimement des questions empiriques. Il faut noter que les ''Fragen der Ethik'' se confrontent explicitement à Kant et à la tradition de l'éthique déontologique. Schlick rejette le formalisme kantien — l'idée que l'impératif catégorique fournit un critère purement formel de l'action juste, indépendant de tout contenu empirique. Pour Schlick, un tel formalisme est vide : la forme logique « agis de telle sorte que la maxime de ton action puisse devenir une loi universelle » ne suffit pas à déterminer ce qu'il faut faire dans une situation concrète sans faire appel aux conséquences empiriques de l'action. De même, la notion kantienne de « devoir » (''Pflicht'') comme fondement ultime de la morale est critiquée : le devoir n'est pas un fait moral irréductible, mais l'expression de la pression sociale exercée sur l'individu par les normes de son groupe. L'éthique scientifique doit expliquer l'origine et la fonction de ces normes, non les poser comme des absolus. Schlick conçoit le progrès moral comme un passage de l'éthique du devoir (''Ethik der Pflicht'') à une éthique de la bonté (''Ethik der Güte''), dans laquelle l'action bonne n'est plus accomplie par contrainte ou par obéissance à une loi, mais procède spontanément du caractère de l'agent — processus qu'il illustre, dans ses cours sur Nietzsche, par la figure du Surhumain. Schlick reconnaît cependant que le sentiment du devoir est un fait psychologique puissant et que sa compréhension causale ne diminue en rien sa force motivatrice. == Trajectoire d'ensemble == L'œuvre de Schlick, interrompue par son assassinat le 22 juin 1936 sur les marches de l'Université de Vienne, ne forme pas un système achevé et uniforme. Elle présente plutôt une trajectoire intellectuelle marquée par des déplacements, des rectifications et des ruptures partielles. Le Schlick de l{{'}}''Allgemeine Erkenntnislehre'', qui défend un réalisme critique, construit une théorie de la connaissance en termes de coordination univoque et recourt au concept de définition implicite, n'est pas identique au Schlick tardif de « Die Wende der Philosophie » et du débat des ''Protokollsätze'', qui soutient que la philosophie n'est pas une science et ne produit pas de propositions. Schlick lui-même, d'ailleurs, exprimait une certaine insatisfaction devant la seconde édition de l{{'}}''Allgemeine Erkenntnislehre'', qu'il jugeait insuffisamment poussée dans la direction nouvelle que lui indiquait la lecture de Wittgenstein. La doctrine des ''Konstatierungen'', elle-même, ne s'intègre pas sans tension au reste du système : ces « points de contact » entre connaissance et réalité sont par définition éphémères, non inscriptibles, et ne peuvent servir de fondation logique — ce qui entre en tension avec l'ambition systématique des premiers écrits. Il y a cependant des continuités réelles, et elles ne sont pas négligeables. Le refus de la métaphysique intuitive, l'intérêt pour la physique comme terrain d'épreuve de l'épistémologie, le souci de précision conceptuelle et l'idée que la connaissance est affaire de structure formelle et non de contenu qualitatif — ces orientations traversent toute l'œuvre, de Rostock à Vienne, même si elles changent de formulation et de justification. La théorie de la connaissance comme coordination univoque (''Zuordnung'') de signes à des faits, formulée dès 1910 et systématisée dans l{{'}}''Allgemeine Erkenntnislehre'', reste un soubassement de la réflexion de Schlick, y compris dans sa phase viennoise, même si le vocabulaire se transforme sous l'influence de Wittgenstein. Le rapport au réalisme est plus complexe et ne se laisse pas résumer par une simple continuité. Le Schlick de l{{'}}''Allgemeine Erkenntnislehre'' défend clairement un réalisme critique : il affirme l'existence de choses en soi, critique l'immanentisme et soutient que la science connaît la structure du réel. Mais à partir de « Erleben, Erkennen, Metaphysik » (1926) et surtout de « Positivismus und Realismus » (1932), Schlick ne reprend plus à son compte le réalisme tel qu'il l'avait formulé. Il ne bascule pas dans un idéalisme, mais il reformule le débat de telle manière que l'alternative classique entre réalisme et positivisme perd, selon lui, son sens philosophique traditionnel : la question de savoir si le monde extérieur « existe vraiment » est déclarée pseudo-problème, non parce que la réponse serait négative, mais parce que la question, telle que la métaphysique la pose, ne satisfait pas les conditions de la signification. C'est un déplacement sémantique radical, et non un simple ajustement de surface. Le refus du synthétique a priori est peut-être la thèse la plus constante de toutes : toute connaissance du monde est empirique et révisable, et les structures logiques et mathématiques sont analytiques. Mais — et c'est un point capital — ce qui change entre les deux périodes est le statut de la philosophie elle-même. Pour le Schlick de l{{'}}''Allgemeine Erkenntnislehre'', la philosophie est encore, au moins en partie, une science qui énonce des vérités sur les conditions de la connaissance. Pour le Schlick tardif, la philosophie n'énonce rien : elle réside exclusivement dans l'activité par laquelle le sens des propositions scientifiques est clarifié. Le contenu de tout ce qui peut être dit avec sens appartient aux sciences empiriques ; la philosophie, quant à elle, n'a pas de contenu propre, mais seulement une fonction. C'est ce déplacement qui représente la véritable « Wende » de la pensée de Schlick. === Schlick et Kant === La relation de Schlick à Kant est particulièrement complexe et éclairante. Il emprunte à Kant la question transcendantale — comment la connaissance du réel est-elle possible ? — mais il refuse systématiquement la réponse kantienne. Il n'y a pas de formes a priori de l'intuition, pas de catégories constitutives de l'expérience, pas de jugements synthétiques a priori. L'espace et le temps ne sont pas des conditions subjectives de la sensibilité, mais des systèmes d'ordonnancement dont la structure est déterminée par la convention et l'expérience. La causalité n'est pas une catégorie a priori, mais un principe heuristique empiriquement motivé. La réponse de Schlick à la question transcendantale est que la connaissance du réel est possible parce que les faits se laissent coordonner de manière univoque par des systèmes de jugements, et que cette coordination n'exige ni formes a priori ni catégories métaphysiques. === Schlick et Mach === La relation à Mach est tout aussi significative. Schlick partage avec Mach l'orientation empiriste, l'idéal d'économie de pensée, la critique de la métaphysique. Mais il se sépare de Mach sur un point capital : le statut des entités théoriques de la physique. Pour Mach, les atomes, les électrons, les champs ne sont que des fictions économiques, des abrégés commodes pour résumer des régularités dans les données sensibles. Pour Schlick, ces entités peuvent désigner des réalités objectives, non immédiatement perceptibles mais dotées d'une détermination spatio-temporelle. Le critère de réalité n'est pas la perception directe mais la possibilité d'assigner à un objet un lieu et un temps déterminés. Cette divergence se manifeste dès l{{'}}''Allgemeine Erkenntnislehre'' dans la critique de la pensée d'immanence. Dans la période viennoise, la divergence avec Mach prend une forme différente : Schlick ne défend plus un réalisme critique au sens traditionnel, mais il ne verse pas non plus dans le phénoménalisme machien. Il affirme que l'opposition même entre réalisme et positivisme, correctement analysée, se dissout comme pseudo-problème — position qui reste incompatible avec le réductionnisme de Mach, mais qui ne reconduit plus le réalisme métaphysique de l{{'}}''Allgemeine Erkenntnislehre''. === Influence et postérité === L'influence de Schlick sur la philosophie du vingtième siècle s'exerce à travers plusieurs canaux : sa philosophie de la relativité, qui fut la première interprétation philosophique systématique de la théorie d'Einstein et reçut l'approbation d'Einstein lui-même ; sa fondation et son animation du Cercle de Vienne, qui constitua le noyau institutionnel de l'empirisme logique ; sa formulation du principe de vérification, qui devint la thèse la plus discutée et la plus controversée du positivisme logique ; et sa théorie structurelle de la connaissance, qui anticipe des développements ultérieurs en philosophie des sciences. L{{'}}''Allgemeine Erkenntnislehre'' elle-même, bien que rapidement dépassée par les développements de la logique formelle et de la philosophie du langage, reste un document central de l'histoire de l'épistémologie au vingtième siècle, par la clarté et la rigueur avec lesquelles elle formule les problèmes fondamentaux de la connaissance et propose une réponse systématique fondée sur la notion de coordination univoque. Le style philosophique de Schlick se caractérise par une clarté délibérée et un souci constant de s'adresser au-delà du cercle des spécialistes. Il revendique une exposition « aussi simple que possible, bâtie lentement », et considère que l'obscurité en philosophie n'est pas le signe de la profondeur mais celui de la confusion. Cette exigence de clarté n'est pas un trait accidentel de son tempérament ; elle est la conséquence directe de sa conception de la philosophie comme activité de clarification. Si la philosophie a pour tâche de déterminer le sens des propositions et de dissoudre les pseudo-problèmes, elle ne peut s'acquitter de cette tâche qu'en étant elle-même parfaitement claire. La philosophie obscure est une philosophie qui a échoué dans sa mission. C'est cette conviction qui distingue Schlick de la tradition métaphysique allemande et le rapproche de la tradition empiriste anglo-saxonne, avec laquelle il entretient un dialogue constant — de Hume et Mill à Russell et Wittgenstein. Sa pensée forme ainsi un pont entre deux traditions intellectuelles que le vingtième siècle a souvent opposées, et c'est peut-être dans cette position de médiateur que réside l'une de ses contributions les plus durables à la philosophie contemporaine. {{DEFAULTSORT:Schlick}} [[Catégorie:Philosophe]] gepacgzvowbnqs436x2w11tep6glvem Dictionnaire de philosophie/Chamfort 0 83799 764800 763710 2026-04-24T10:27:50Z ~2026-25114-99 123599 /* Études */ 764800 wikitext text/x-wiki {{DicoPhilo|Chamfort}} == Repères biographiques : naissance, formation, entrée dans les lettres == === Une naissance substituée === L'homme que la postérité connaît sous le nom de Chamfort est baptisé le 22 juin 1740, en l'église Saint-Genès de Clermont, sous le nom de Sébastien-Roch Nicolas, la date traditionnelle de 1741, que donne la notice d'Auguis, a été corrigée par Claude Arnaud à partir des registres paroissiaux. C'est le nom, et ce sont les prénoms, d'un enfant qui vient de mourir en bas âge chez l'épicier Nicolas ; c'est aussi le nom d'un enfant qui n'est pas celui du couple Nicolas. Sa mère biologique est Jacqueline de Cisternes de Vinzelles, dite la dame de Montrodeix, issue d'une très ancienne famille d'Auvergne de noblesse chevaleresque ; épouse depuis 1719 du procureur général Jean-François Dauphin de Leyval, seigneur de Montrodeix, mère déjà de deux filles, elle est enceinte à quarante-quatre ans des œuvres de Pierre Nicolas, chanoine semi-prébendé de la cathédrale de Clermont, parent de l'épicier. Pour étouffer le scandale, un échange de berceaux est arrangé avec la famille Nicolas, dont l'enfant légitime vient de s'éteindre. Le nouveau-né prend l'identité du mort, et l'épouse de l'épicier, Thérèse Creuzet, quarante-quatre ans elle aussi, devient sa mère officielle. Cet épisode, dont les éléments ont été établis par le baron d'Espinchal dans des mémoires inédits et confirmés par les recherches de Claude Arnaud, ne relève pas du simple pittoresque biographique. Claude Arnaud, qui a consacré à cette question les pages les plus neuves de sa biographie, voit dans la découverte de ses origines un événement dont les répercussions sur l'œuvre sont considérables. Vers l'âge de sept ou huit ans, l'enfant apprend de Thérèse Nicolas qu'il descend d'une lignée chevaleresque par sa mère, qu'il aurait pu être un Dauphin de Leyval ou un Vinzelles, et qu'il a été privé de son rang au nom des convenances. Cette découverte laisse sur lui une blessure dont on peut penser que l'œuvre ultérieure porte la trace, tantôt comme ressentiment avoué, tantôt comme méfiance générale à l'égard des institutions sociales, même s'il faut se garder de tout ramener à cette seule clé. L'adoption fait qu'il n'est plus tout à fait de la noblesse, sans être non plus vraiment du peuple : la condition de bâtard, au sens plein, inscrit en lui une double appartenance qui ne se résoudra jamais. Sa mère biologique et son père, le chanoine, semblent être demeurés à distance ; de Pierre Nicolas, mort en 1783, il ne parlera jamais publiquement. C'est à Thérèse Nicolas seule qu'il rendra les devoirs d'un fils, avec une piété filiale qui ne s'est jamais démentie et dont témoignent ses lettres. === Le collège des Grassins === Conduit à Paris à l'été 1750 muni d'une demi-bourse, Sébastien Nicolas entre au collège des Grassins, sur la montagne Sainte-Geneviève, l'un des établissements les plus exigeants de l'université (Arnaud, Chamfort, p. 23-25). Il s'y distingue d'abord par son caractère difficile, puis, à partir de la troisième, par des succès continus ; en rhétorique, il est présenté aux cinq épreuves du Concours général, remporte quatre prix, il manque le thème latin, puis, sur l'insistance du collège, remet ses quatre titres en jeu et l'emporte cette fois sur toute la ligne : son nom est inscrit en lettres d'or sur les cinq tableaux d'honneur du réfectoire. Ce succès, dont le souvenir ne le quittera plus quand il sera question de concours, de couronnes et d'académies, est inséparable d'une seconde formation, plus irréversible encore : la lecture des Anciens. Homère, Plutarque, Lucien, les Stoïciens nourrissent sa géographie mentale et dessinent un idéal de dignité que la société contemporaine ne cessera, à ses yeux, de démentir. Avant d'être renvoyé pour s'être opposé à son professeur de grec Lebeau, il songe un instant à partir pour l'Amérique avec son condisciple Letourneur : « Avant de faire le tour du monde, si nous faisions le tour de nous-mêmes ? » aurait-il dit à Cherbourg. Le trait est rapporté par Sélis, et le mot résume déjà une disposition dont les Maximes feront la méthode. Il quitte alors le collège et refuse l'état ecclésiastique auquel l'habit d'abbé, porté comme la plupart des enfants pauvres formés par l'Église, semblait le destiner. « Je ne serai jamais prêtre, dit-il au principal ; j'aime trop le repos, la philosophie, les femmes, l'honneur, la vraie gloire ; et trop peu les querelles, l'hypocrisie, les honneurs et l'argent. » La déclaration est prémonitoire ; elle trace à l'avance, non sans ironie, les limites que sa vie aura voulu tenir. === Les succès académiques et mondains === Jeté sans fortune dans la vie littéraire, Chamfort, nom qu'il se donne au début des années 1760, vit d'abord de travaux alimentaires et de sermons composés pour des prédicateurs. La reconnaissance vient par les voies convenues : la comédie avec ''La Jeune Indienne'' (1764), puis ''Le Marchand de Smyrne'' (1770) ; l'éloquence académique avec l'Épître d'un père à son fils sur la naissance d'un petit-fils, couronnée par l'Académie française, l'Éloge de Molière qui remporte en 1769 le prix de l'Académie, et l'Éloge de La Fontaine qui, en 1774, lui vaut celui de l'Académie de Marseille face à La Harpe ; la tragédie enfin avec ''Mustapha et Zéangir'', jouée à Fontainebleau en 1776 et qui lui vaut une pension sur les menus-plaisirs ainsi que la place de secrétaire des commandements du prince de Condé, emploi dont il se déchargera bientôt, ne supportant aucune forme de dépendance. Durant ces années, il partage sa vie entre les soins que réclame une santé ruinée par des maladies vénériennes contractées jeune, Spa, Contrexéville, Barèges, et la fréquentation des salons, en particulier celui de Madame Helvétius à Auteuil, qui demeurera pour lui une seconde famille. La réception à l'Académie française, en avril 1781, n'est obtenue qu'à la troisième tentative, après deux candidatures malheureuses et plusieurs désistements, notamment en faveur de Chabanon, qui avait menacé de se suicider en cas d'échec. Chamfort occupe le fauteuil laissé vacant par La Curne de Sainte-Palaye ; il y prononce un discours qui fit date autant par son brio que par ses ambiguïtés. Il y louait, comme il se devait, le roi, la reine, le prince de Condé et la compagnie qui le recevait, mais s'attaquait aussi, non sans audace, à la chevalerie que son prédécesseur avait étudiée, comme à une caste ayant pour vice principal la morgue et le refus de s'allier à d'autres classes, allusion à peine voilée à la noblesse de son temps. Cette dualité entre reconnaissance officielle et critique voilée, qui deviendra la marque de ses années révolutionnaires, est déjà entière dans ce discours. === Mirabeau, Sieyès, les années de la Révolution === La rencontre décisive de la vie intellectuelle de Chamfort est celle de Mirabeau, autour de 1783-1784. Pendant près de sept ans, selon le témoignage de Ginguené (Notice, p. xliii sq.) et l'analyse qu'en donne Arnaud (Chamfort, chap. 11-17), les deux hommes se voient presque chaque jour. Chamfort sert au comte plébéien de conseiller, de conscience, de correcteur et parfois de plume : il rédige ou co-rédige des passages importants des ''Considérations sur l'ordre de Cincinnatus'', des discours et des articles que Mirabeau publie ou prononce sous son propre nom, y compris le Discours contre les Académies que le tribun devait lire à l'Assemblée nationale en 1791 ; une fois Mirabeau mort, Chamfort publiera ce texte sous son propre nom. Cette collaboration souterraine, attestée par la correspondance et rappelée par Ginguené puis par Arnaud, place Chamfort dans une position originale : il est, selon le mot d'Arnaud, « l'éminence grise » de la première Révolution, l'homme qui parle par la bouche des autres et qui fait passer ses formules dans les discours et les journaux, refusant de paraître lui-même à la tribune comme il avait refusé de paraître au théâtre. Un collaborateur de Mirabeau, Dumont, résume : « Pendant que d'autres voulaient attaquer le colosse avec un bélier, Chamfort cherchait à le cribler de traits satiriques. » Sa seconde amitié politique, celle de Sieyès, est de la même veine. Chamfort appelait Sieyès son « puritain » ; l'abbé lui rendait un respect qu'il accordait à peu de ses contemporains. Selon une anecdote rapportée par le comte de Lauraguais et publiée en 1802, mais contestée dès cette date par Suard (voir Arnaud, p. 186-187, et les notes afférentes),, Chamfort aurait lui-même soufflé à Sieyès, au début de 1789, le titre et la formule qui firent le succès du pamphlet le plus célèbre de la Révolution : « Qu'est-ce que le tiers état ? Tout. Qu'a-t-il ? Rien. » Sieyès y ajoutera : « Que veut-il ? Quelque chose. » Vraie ou romancée, l'anecdote dit quelque chose d'exact : dans l'hiver précédant les États généraux, Chamfort était au carrefour des réseaux qui préparèrent la transformation du tiers en Assemblée nationale, et ses formules traversaient les brochures comme les conversations. Il est probable qu'il ait eu, par ces voies obliques, un rôle dans la formulation des mots qui firent la Révolution, sans jamais apparaître en première ligne. Devenu collaborateur du ''Mercure de France'' après 1789, Chamfort entreprend avec Pierre-Louis Ginguené, dont l'amitié sera le dernier appui de sa vie, et avec d'autres la publication des ''Tableaux historiques de la Révolution française'', accompagnés des gravures célèbres de Prieur. Il en fournit le texte des treize premières livraisons, composées chacune de deux tableaux, avant que la maladie et les persécutions ne l'obligent à abandonner l'entreprise, continuée par Ginguené. Nommé par Roland en 1792 à l'un des postes de direction de la Bibliothèque nationale, il y demeure jusqu'à son arrestation. Rallié d'abord aux Girondins, il dirigea brièvement la ''Gazette de France'' sous la même période,, méfiant à l'égard de Robespierre et des Jacobins dès 1791, il se trouve à partir de 1793 dans la position intenable de celui qui a servi la Révolution avec ferveur et qui la voit se retourner contre lui. === L'arrestation, le suicide, la mort === Dénoncé en juillet 1793 par un employé subalterne de la Bibliothèque, Tobiesen Duby, qui convoitait sa place et trouva dans ses propos imprudents les prétextes qu'il cherchait, Chamfort avait critiqué Marat publiquement, puis salué Charlotte Corday comme l'auteur d'une « œuvre sublime »,, Chamfort est arrêté à l'aube du 2 septembre 1793, date anniversaire des massacres de l'année précédente (le dossier du Comité de sûreté générale, Archives nationales F7 4638, est analysé par Arnaud, p. 283-292). Il passe deux jours à la prison des Madelonnettes, dans des conditions que son état de santé rendait presque intolérables : entassement, vermine, privation de soins, menace permanente du pire. Libéré sur ordre du Comité de sûreté générale, il doit vivre sous la surveillance d'un gendarme et jure qu'il ne se laissera jamais reconduire en prison. Lorsque, quelque temps plus tard, on vient lui signifier qu'il doit retourner en détention, au Luxembourg et non aux Madelonnettes, comme il l'apprendra trop tard,, il se retire dans un cabinet voisin, se tire une balle dans la tête qui lui fracasse la cloison nasale et l'œil droit sans pénétrer jusqu'au cerveau, puis, l'arme ayant failli, s'entaille à plusieurs reprises le cou, la poitrine, les cuisses et les mollets au rasoir. Les chirurgiens dénombreront vingt-deux plaies, dont plusieurs profondes, et laisseront la balle en place, jugeant son extraction mortelle. Contre toute attente, il ne meurt pas. Pendant plus de six mois, il survit dans un état de mutilation que Ginguené décrit avec effroi, recevant ses proches, commentant la politique, dictant des notes à son biographe, travaillant même, avec Ginguené et Jean-Baptiste Say, au projet d'une revue philosophique, ''La Décade philosophique'', qui verra le jour après sa mort. Il finit par succomber, non à ses blessures premières, mais à une erreur du chirurgien Brasdor, qui referma ses plaies sans leur ménager d'ouverture : l'humeur se répandit dans le corps et provoqua une violente inflammation de la vessie. Desault, un des plus grands chirurgiens vivants, fut appelé en renfort, mais se trompa de remède et décida trop tard une opération. Chamfort meurt le dimanche 13 avril 1794, veillé par ses derniers amis ; son enterrement, dans l'atmosphère de la Terreur, ne réunit qu'un très petit cortège, Ginguené, Sieyès, Colchen et Van Praet en tête. Son corps, jeté quelque temps plus tard parmi les restes anonymes des cimetières parisiens d'Ancien Régime, finira, selon toute vraisemblance, dans les catacombes. Ses manuscrits, pillés au moment des scellés, ne seront que partiellement retrouvés par Ginguené, qui en tirera en 1795 la première édition de ses Œuvres, celle qui a donné à Chamfort son existence posthume comme moraliste. == L'œuvre et ses formes == L'œuvre de Chamfort, telle qu'elle nous est parvenue, est volontairement hétérogène et ne se laisse pas aisément ranger sous une étiquette unique. Les cinq volumes que rassemble l'édition Auguis en 1824-1825 juxtaposent en effet des registres que la tradition littéraire avait jusque-là tenus séparés : la dissertation académique, l'éloge, la comédie et la tragédie, les contes en vers et les épîtres, les notes sur La Fontaine et sur Racine, les articles critiques destinés au Mercure, les ''Tableaux historiques de la Révolution'', et surtout les deux ensembles qui ont assuré sa survie, les ''Maximes et pensées'' et les ''Caractères et anecdotes'',, tous deux recueillis après sa mort par Ginguené sur les feuillets épars que l'auteur tenait par-devers lui et ne montrait à personne. Il est essentiel, pour bien lire Chamfort, de savoir qu'il n'a jamais voulu publier ces feuillets. Un long préambule, rédigé aux alentours de 1790 et que Ginguené a placé en tête des Maximes, énumère les raisons de ce silence : dégoût du public, peur de mourir sans avoir vécu, certitude que tous les hommes célèbres qu'il a connus ont été malheureux, refus enfin de vouloir plaire encore à qui ne lui ressemble pas. Cette dispersion formelle n'est pas un accident. Chamfort lui-même, dans le premier chapitre de ses ''Maximes générales'', avertit que la maxime n'est qu'un abrégé qui vaut par la finesse des observations dont elle procède, et que l'esprit médiocre transforme en règle absolue ce qui, chez son auteur, n'était qu'un relevé singulier. « Les maximes, les axiômes sont, ainsi que les abrégés, l'ouvrage des gens d'esprit qui ont travaillé, ce semble, à l'usage des esprits médiocres ou paresseux. » L'homme supérieur, poursuit-il, « saisit tout d'un coup les ressemblances, les différences qui font que la maxime est plus ou moins applicable à tel ou tel cas, ou ne l'est pas du tout ». Le fragment n'est donc pas, chez Chamfort, l'expression d'une vérité synthétique, mais l'enregistrement ponctuel d'un regard : il note ce qu'il voit, comme le naturaliste qui découvre, au-delà de ses classes et de ses divisions, « l'insuffisance des divisions et des classes ». Le caractère aphoristique de son œuvre majeure n'est ainsi pas une concession à la brièveté, mais la traduction formelle d'une méthode d'observation qui se défie des systèmes. On aurait tort, cependant, d'isoler les Maximes du reste de l'œuvre. Les Éloges de Molière et de La Fontaine, la ''Dissertation sur l'imitation de la nature'', le ''Discours de réception'' à l'Académie, le petit traité ''Des Académies'' qu'il composa pour Mirabeau, et jusqu'aux ébauches d'une histoire du théâtre ancien et moderne conservées dans le tome IV de l'édition Auguis, forment, avec les fragments moraux, un ensemble cohérent. On y reconnaît partout la même main : celle d'un écrivain pour qui la littérature n'est pas séparable de la peinture des mœurs, et pour qui l'histoire des formes, la fable, la comédie, l'éloge, est aussi une histoire sociale. Les ''Tableaux historiques de la Révolution'', de leur côté, prolongent pour le présent ce que les ''Caractères et anecdotes'' avaient entrepris pour la cour de Louis XV : fixer, dans une série de scènes détachées, le visage changeant d'une société qui se défait. === Les comédies et la tragédie === La carrière théâtrale de Chamfort est brève, cinq pièces entre 1764 et 1776, et inégale, mais elle engage des questions qui resteront au cœur de sa pensée. ''La Jeune Indienne'' (1764), comédie en un acte et en vers inspirée d'un épisode du ''Spectator'' anglais, met en scène une jeune fille élevée dans l'état de nature qui découvre les convenances de la société européenne et s'en étonne ; le thème, proche du Huron de Voltaire et de l'Ingénu, est celui du regard étranger porté sur les mœurs civilisées, et l'on reconnaît déjà, dans cette confrontation entre naïveté naturelle et artifice social, une intuition que les Maximes développeront dans un tout autre registre. ''Le Marchand de Smyrne'' (1770), comédie en un acte en prose, transpose la critique sociale sur un autre plan : un marchand d'esclaves y met en vente des captifs européens, parmi lesquels un gentilhomme et un chevalier, que personne ne veut acheter et qui finissent « donnés pour rien », satire de la noblesse que Chamfort rappellera lui-même, dans sa défense de 1793, comme preuve de ses convictions républicaines. La tragédie de ''Mustapha et Zéangir'', représentée à Fontainebleau en 1776 devant la cour, est l'œuvre la plus ambitieuse et, au jugement d'Arnaud, la plus révélatrice de ses limites d'écrivain dramatique. Le sujet est emprunté à l'histoire ottomane, les amours et la mort de deux frères, fils de Soliman le Magnifique, et le traitement s'inscrit dans la filiation racinienne : plusieurs scènes, selon Auguis, témoignent de la profondeur avec laquelle Chamfort avait étudié la manière de Racine, et « jusqu'où il en aurait peut-être porté l'imitation ». La pièce obtint un succès de circonstance, mais elle fut plus froidement reçue par le parterre parisien. Le demi-échec marqua Chamfort. Il n'écrira plus pour le théâtre, et ce silence volontaire est à la fois un trait de caractère, il ne voulait s'exposer qu'à coup sûr, et le signe d'un déplacement vers un mode d'écriture plus personnel, plus fragmentaire, moins dépendant du jugement collectif : les Maximes. Arnaud formule ce tournant en termes forts : Chamfort « mourut ainsi, sous la Terreur, en écrivain moyen, et presque oublié. De cette vie anthume il ne reste rien, sinon sa longue agonie, perceptible dans les maximes et pensées du second Chamfort. » === La critique littéraire : les Éloges, les Notes sur La Fontaine, le Commentaire sur Racine === Les travaux de critique littéraire de Chamfort ont été longtemps éclipsés par les Maximes, mais ils représentent une part substantielle de l'œuvre et méritent d'être considérés pour eux-mêmes. L'Éloge de Molière (1769), couronné par l'Académie française, est un morceau d'éloquence où la célébration du poète comique se double d'une réflexion sur les rapports entre la comédie et la connaissance morale. Chamfort y défend l'idée que Molière est un philosophe autant qu'un dramaturge, et que la peinture des mœurs, lorsqu'elle atteint une certaine profondeur, vaut un traité de morale. L'Éloge de La Fontaine (1774), couronné par l'Académie de Marseille aux dépens de La Harpe, prolonge cette lecture en y ajoutant une attention au détail poétique : Chamfort y analyse la fable non seulement comme un véhicule d'enseignement, mais comme une forme originale d'écriture dont la naïveté apparente est le fruit d'un art savant. Les Notes sur les Fables de La Fontaine qui accompagnent cet Éloge dans l'édition Auguis offrent un commentaire suivi, livre par livre, où Chamfort alterne observations stylistiques et réflexions morales, elles sont l'un des premiers commentaires détaillés consacrés aux Fables. Le tome V de l'édition Auguis contient, sous le titre d'''Essai d'un commentaire sur Racine'', des notes sur Esther et Athalie qui témoignent d'un égal souci d'analyser le travail du poète dans le détail de son texte. La ''Dissertation sur l'imitation de la nature'', enfin, est un essai théorique où Chamfort aborde la question du naturel dans l'art dramatique : il y distingue l'imitation servile de la nature, qui ne produit que des copies, de l'imitation créatrice, qui saisit les « traits saillants » d'un caractère et les compose en un type intelligible. La question du naturel, qui traverse toute sa réflexion, des personnages de la comédie aux maximes sur la « composition factice » de la société,, trouve ici sa formulation théorique la plus explicite. == La tradition moraliste et ses déplacements == === Chamfort au terme d'une lignée === Chamfort appartient à la lignée des moralistes français qui, de Montaigne à Vauvenargues, ont préféré la forme brève à la construction systématique et ont fait de l'observation des conduites humaines la matière même de la philosophie morale. Il reconnaît ses prédécesseurs sans détour : « Il y a deux classes de moralistes et de politiques : ceux qui n'ont vu la nature humaine que du côté odieux ou ridicule, et c'est le plus grand nombre ; Lucien, Montaigne, La Bruyère, La Rochefoucauld, Swift, Mandeville, Helvétius, etc. ; ceux qui ne l'ont vue que du beau côté et dans ses perfections ; tels sont Shaftesbury et quelques autres. Les premiers ne connaissent pas le palais dont ils n'ont vu que les latrines ; les seconds sont des enthousiastes qui détournent leurs yeux loin de ce qui les offense, et qui n'en existe pas moins. Est in medio verum. » Cette déclaration a l'apparence d'un éclectisme modéré, mais la suite de l'œuvre révèle un parti autrement plus décidé : si la vérité est au milieu, Chamfort campe ordinairement sur le versant sévère, celui qui voit dans la société l'ennemie de la nature et dans les usages consacrés autant d'artifices dont la raison, quand elle se ressaisit, reconnaît l'inanité. Le rapport à La Rochefoucauld est à cet égard le plus significatif. Chamfort lui doit le goût de l'antithèse courte, de la chute paradoxale, de la démystification de l'amour-propre. Mais il déplace le centre d'analyse : chez l'auteur des Maximes, l'amour-propre est une disposition universelle, une loi de la nature humaine abstraite de tout cadre social ; chez Chamfort, il est toujours situé, inscrit dans un ordre de rangs, d'emplois et de préjugés dont il faut aussi dresser l'inventaire. La critique se fait institutionnelle autant que psychologique. « La plupart des nobles rappellent leurs ancêtres, à peu près comme un Cicerone d'Italie rappelle Cicéron » : la pointe ne porte plus seulement sur un trait de caractère, mais sur un état social qui confond la gloire héritée et la gloire due. C'est en ce point que Chamfort prend congé du moraliste classique : il inscrit les maximes dans une sociologie, et il rend à la critique de l'ordre social ce que le pessimisme janséniste avait rendu à la critique de l'amour-propre. === L'ascendant des Lumières === À la tradition moraliste s'ajoute la pression des Lumières. Chamfort a lu Helvétius et fréquenté son cercle, il s'est nourri de Rousseau et de Diderot, il connaît les physiocrates et l'Encyclopédie. De l'utilitarisme helvétien, il retient l'idée que l'intérêt bien compris gouverne les actions humaines plus sûrement qu'aucune vertu, et que la morale sociale est largement le produit des institutions ; de Rousseau, il adopte l'intuition maîtresse selon laquelle l'état social a corrompu un état antérieur plus authentique, et que la plupart des douleurs humaines sont filles de la civilisation. Mais Chamfort n'est ni systématique comme Helvétius ni enthousiaste comme Rousseau. Il ne propose aucune refondation théorique, aucun nouveau contrat social, aucune pédagogie. Son matérialisme est un scepticisme ; son rousseauisme, une nostalgie. À la fois héritier des moralistes du Grand Siècle et contemporain des philosophes, il occupe une position qu'on peut qualifier de liminaire : celle d'un moraliste des Lumières, plus proche, par la méthode, de La Bruyère que de d'Holbach, plus proche, par le jugement politique, de Condorcet que de La Rochefoucauld. == Anthropologie : nature, passions, raison == === La nature et la « composition factice » === La pensée anthropologique de Chamfort repose sur une distinction structurante entre la nature et la société, mais cette distinction ne prend jamais, chez lui, la forme d'un mythe des origines à la manière rousseauiste. Il ne remonte pas à un homme primitif dont il reconstituerait la figure ; il se borne à faire apparaître, par contraste, ce que la socialisation a défait. « La société n'est pas, comme on le croit d'ordinaire, le développement de la nature, mais bien sa décomposition et sa refonte entière. C'est un second édifice, bâti avec des décombres du premier. » L'image vaut d'être méditée : elle récuse le modèle progressiste d'une civilisation qui perfectionnerait la nature, mais sans verser dans l'utopie du retour ; elle suggère que la société travaille toujours avec les matériaux de ce qu'elle défait, et que l'on trouve, çà et là, les débris d'une architecture antérieure, comme ces expressions naïves d'un sentiment vrai qui, par surprise, nous émeuvent dans la conversation des grands et qui sont, dit-il, « un hommage à la nature ». Si la civilisation est une décomposition, c'est qu'elle substitue à la spontanéité des affections primitives un système d'usages, de rangs et d'intérêts qui ne souffre plus d'y paraître. « En général, si la société n'était pas une composition factice, tout sentiment simple et vrai ne produirait pas le grand effet qu'il produit : il plairait sans étonner ; mais il étonne et il plaît. Notre surprise est la satire de la société, et notre plaisir est un hommage à la nature. » Le ressort de la critique n'est donc pas la dénonciation d'une déchéance abstraite ; c'est l'observation que la société du XVIIIe siècle a rendu extraordinaire ce qui devrait être ordinaire, et qu'elle fait de la sincérité une rareté muséale. === Passions et raison === Le rapport entre passions et raison, qui avait occupé toute la philosophie morale du XVIIe siècle, se voit chez Chamfort notablement redistribué. Aux moralistes augustiniens qui avaient fait des passions la source de la corruption humaine, il oppose une thèse exactement inverse : « L'homme, dans l'état actuel de la société, me paraît plus corrompu par sa raison que par ses passions. Ses passions (j'entends ici celles qui appartiennent à l'homme primitif) ont conservé, dans l'ordre social, le peu de nature qu'on y retrouve encore. » C'est la raison, c'est-à-dire le calcul, l'artifice, l'intérêt social, qui a altéré l'homme ; les passions, en ce qu'elles ont d'immédiat et de naturel, en sauvent encore quelque chose. La formule n'est pas dirigée contre la raison en général, Chamfort reste un homme des Lumières, mais contre la raison sociale, c'est-à-dire contre l'instrument par lequel l'homme en société apprend à dissimuler, à ménager et à se vendre. De cette valorisation des passions naturelles découle une manière de stoïcisme tempéré. Chamfort ne recommande ni le triomphe sur les passions ni leur libre cours ; il recommande leur authenticité. Un sentiment vrai, éprouvé à temps, vaut, dit-il, mieux que toutes les réflexions savantes : « Le moraliste qui voudrait faire taire ses passions est comme le chimiste qui voudrait éteindre son feu. » La dignité du caractère, qui donnera son titre à l'un des chapitres des Maximes, est moins une maîtrise qu'une fidélité : le refus obstiné de laisser la société défaire en soi ce qui reste de sensibilité spontanée. == Critique de la société == === Les niches et les rangs === L'image la plus achevée qu'ait laissée Chamfort de la hiérarchie sociale est celle de l'édifice aux niches. Il faut la citer presque entièrement, car elle résume, mieux qu'aucune formule générale, sa manière d'analyser l'ordre monarchique : « On peut considérer l'édifice métaphysique de la société comme un édifice matériel qui serait composé de différentes niches ou compartiments, d'une grandeur plus ou moins considérable. Les places avec leurs prérogatives, leurs droits, etc., forment ces divers compartiments, ces différentes niches. Elles sont durables, et les hommes passent. Ceux qui les occupent sont tantôt grands, tantôt petits ; et aucun ou presque aucun n'est fait pour sa place. Là, c'est un géant courbé ou accroupi dans sa niche ; là, c'est un nain sous une arcade : rarement la niche est faite pour la statue. » L'analogie architecturale permet un renversement du principe aristocratique : les places, loin d'être l'expression des mérites qu'elles couronnent, les précèdent et les commandent ; les hommes s'y insèrent avec les disproportions qu'impose la naissance ou la faveur ; et ce qui choque, c'est moins telle inégalité particulière que la règle générale qui veut que l'instrument ne convienne jamais à son étui. Cette image n'est pas isolée. Elle prolonge une série d'observations où Chamfort met en évidence le caractère purement conventionnel de la considération sociale. « Un sot, fier de quelque cordon, me paraît au-dessous de cet homme ridicule qui, dans ses plaisirs, se faisait mettre des plumes de paon au derrière par ses maîtresses. » La comparaison est volontairement grossière ; elle a pour fonction de rappeler que les ornements sociaux, rubans, croix, charges, décorations, ne valent pas davantage, philosophiquement, que les parures que chacun s'octroie en privé, et que l'adhésion à ces signes trahit chez qui les porte une infériorité morale que nul plumage ne rachète. === Les grands, les riches, les gens du monde === Le chapitre III des ''Maximes générales'', entièrement consacré « à la société, aux grands, aux riches et aux gens du monde », développe une critique qui, sous l'apparence de notes détachées, constitue une véritable sociologie de la cour et des salons. Chamfort y parle en témoin. Il a vu l'aristocratie de très près, elle l'a fêté autant qu'elle l'a humilié,, il a servi comme secrétaire du prince de Condé et comme secrétaire du Cabinet de Madame Élisabeth, sœur de Louis XVI ; il a subi la protection affectueuse du comte de Vaudreuil, favori du comte d'Artois, dont il fut proche sans jamais se laisser acquérir ; il a connu intimement Julie Careau, Marthe Buffon, Henriette de Nehra. Son regard est d'autant plus aigu que la société qu'il peint est celle où il fut à la fois admis et étranger. « La société, écrit-il, est composée de deux grandes classes : ceux qui ont plus de dînés que d'appétit, et ceux qui ont plus d'appétit que de dînés. » La formule, d'abord plaisante, est en fait une synthèse économique : l'inégalité y est ramenée à un déséquilibre de besoins et de moyens qui dérobe à ceux qui pâtissent le nécessaire et à ceux qui possèdent la capacité même d'en jouir. À cette critique économique s'ajoute une critique morale. « On ne peut vivre dans la société, après l'âge des passions. Elle n'est tolérable que dans l'époque où l'on se sert de son estomac pour s'amuser, et de sa personne pour tuer le temps. » Ce que Chamfort dénonce n'est pas seulement le luxe ; c'est l'ennui organisé, le remplissage des heures par des plaisirs convenus, la soumission à des liturgies de politesse que rien n'anime plus. Les « gens du monde », remarque-t-il encore, « ne sont pas plutôt attroupés qu'ils se croient en société », mot qui distingue la véritable société, commerce de pensées et d'affections, de cette agrégation où chacun surveille chacun sans jamais rien échanger. === La noblesse héréditaire et les préjugés === De toutes les institutions qu'il critique, la noblesse héréditaire est celle contre laquelle Chamfort déploie la plus grande énergie. Il y voit non seulement un abus particulier, mais le symptôme de l'impuissance propre à la pensée morale à l'égard des préjugés établis : « Veut-on avoir la preuve de la parfaite inutilité de tous les livres de morale, de sermons, etc. ? Il n'y a qu'à jeter les yeux sur le préjugé de la noblesse héréditaire. Y a-t-il un travers contre lequel les philosophes, les orateurs, les poètes aient lancé plus de traits satiriques, qui ait plus exercé les esprits de toute espèce, qui ait fait naître plus de sarcasmes ? cela a-t-il fait tomber les présentations, la fantaisie de monter dans les carrosses ? » La remarque, frappante par sa conclusion désabusée, engage une thèse générale sur l'impuissance du discours moral face aux institutions : la critique raisonnée ne détruit pas ce que le rang, la fortune et l'habitude soutiennent. Elle explique, par avance, pourquoi Chamfort accueillera avec ferveur la Révolution : seule une transformation politique, et non un supplément d'arguments, peut détruire ce que les arguments n'ont pu défaire. Le rapport entre opinions et institutions se laisse ainsi penser, chez Chamfort, selon une circularité qui n'est pas loin d'anticiper la critique idéologique : les opinions reçues naissent des institutions qui les soutiennent, et les institutions durent parce qu'elles sont reconduites par les opinions qu'elles ont engendrées. Chamfort cite à ce propos l'exemple de l'éducation, pour montrer que celle-ci ne peut être réformée séparément des réformes politiques et religieuses dont elle dépend. « L'éducation n'ayant d'autre objet que de conformer la raison de l'enfance à la raison publique relativement à ces trois objets [législation, religion, opinion publique], quelle instruction donner, tant que ces trois objets se combattent ? » On reconnaît là l'intuition qui sera celle, au siècle suivant, d'un certain républicanisme français : la formation des citoyens exige la transformation simultanée de toutes les sphères où se constitue leur raison. === La question de la bâtardise et du ressentiment === Il importe, pour comprendre une part de la critique sociale chez Chamfort, de la rapporter à son expérience propre, tout en se gardant d'en faire la clé unique de l'œuvre. Claude Arnaud a proposé, dans la biographie qu'il lui a consacrée, une lecture d'ensemble articulée autour de la bâtardise et du ressentiment : « Ayant deux identités, celle, aristocratique, de sa mère ; celle, populaire, de sa famille adoptive,, il prend un surnom littéraire à l'âge de vingt ans : Chamfort. Pourtant il restera toujours aussi double que ce faux patronyme, qui commence en douceur et finit en revendication. » Cette lecture, qui a l'avantage de rendre intelligible la trajectoire dans son ensemble, du courtisan couronné au républicain pourchassé,, a le mérite de prendre au sérieux ce que Chamfort lui-même ne cessait de taire mais qui informe, de manière diffuse, la sévérité de son regard sur les distinctions de rang. Elle a toutefois été nuancée par d'autres approches : Jean Dagen, dans son édition des Maximes (GF, 1968), s'attache davantage à la filiation proprement littéraire et philosophique de la pensée de Chamfort, à son rapport aux moralistes classiques, à la logique interne de la forme fragmentaire, et refuse de tout dériver d'une clé biographique. Georges Poulet, dans ''La Distance intérieure'' (1952), lit Chamfort sous l'angle de l'expérience temporelle, la conscience d'un décalage entre le moi et le monde, sans privilégier le ressentiment. Sainte-Beuve, dans les ''Causeries du lundi'', voyait quant à lui un cas « des plus curieux et des plus nets d'ulcération de l'esprit », formule que Nietzsche reprendra pour la retourner. Il y a donc plusieurs lectures possibles du rapport entre la vie et l'œuvre, et la plus prudente est sans doute celle qui reconnaît dans la bâtardise un facteur important sans en faire le principe explicatif exclusif. Nietzsche a été le premier à lire Chamfort sous l'angle du ressentiment, dans un passage du Gai Savoir qui reste l'un des textes de réception les plus pénétrants qui lui aient été consacrés. Il y voyait, dans le « trop explicable ressentiment » de Chamfort, à la fois la condition de sa lucidité et la raison pour laquelle ce moraliste avait fini par se jeter dans la Révolution plutôt que de demeurer, comme Nietzsche l'eût voulu, dans une supériorité philosophique désintéressée. Dans ''La Généalogie de la morale'' (1887), Nietzsche ira plus loin en intégrant Chamfort à sa théorie du ressentiment comme acte créateur de valeurs. Là où Nietzsche diagnostique une limite, Chamfort n'a pas été « philosophe d'un degré de plus »,, on peut aussi bien voir un geste dont la cohérence est propre à Chamfort : refuser de séparer l'exercice du jugement moral de l'engagement politique. Ce débat entre lecture psychobiographique et lecture proprement philosophique reste, aujourd'hui encore, ouvert. == Amour-propre, lettres, académies == Chamfort hérite de La Rochefoucauld la conviction que l'amour-propre est le ressort caché de la plupart des conduites humaines, mais il en déplace l'analyse du terrain strictement psychologique vers le terrain social. L'amour-propre, tel qu'il le peint, n'est pas seulement la complaisance en soi-même : c'est la dépendance où chacun se trouve du regard d'autrui, et particulièrement du regard de ceux que la société a placés au-dessus. De là cette observation aiguë : « J'ai trois sortes d'amis : mes amis qui me détestent, mes amis qui me craignent, et mes amis qui ne se soucient pas du tout de moi. » La remarque, qui a la brièveté d'un mot, contient une doctrine : l'amitié, dans le monde, n'est pas un commerce des cœurs, mais un arrangement des intérêts et des vanités. Le chapitre qui, dans les Maximes, est consacré « aux savants et aux gens de lettres » prolonge cette analyse dans le champ particulier qui était celui de Chamfort lui-même. Il y décrit la vie littéraire comme une seconde cour, avec ses clientèles, ses jalousies et ses bassesses, et n'épargne pas ses propres pairs. Mais c'est le petit traité ''Des Académies'', que Mirabeau devait lire à l'Assemblée en 1791 sous le titre de Rapport sur les Académies, et que Chamfort publia sous son propre nom après la mort du tribun, qui donne à cette critique sa dimension politique. Chamfort y analyse les académies comme des institutions de l'Ancien Régime, héritées d'un temps où la protection royale régissait la vie des lettres, et il y reconnaît un double vice : elles soumettent la pensée au goût du pouvoir, et elles la hiérarchisent selon les rangs plutôt que selon les talents. La suppression des académies, qu'il appelle de ses vœux, n'est donc pas un geste iconoclaste, mais la conséquence logique d'une doctrine de la liberté intellectuelle : là où la pensée reçoit ses récompenses du pouvoir, elle cesse d'être libre. La publication de ce texte valut à Chamfort la rupture de plusieurs de ses anciens amis, et notamment de l'abbé Morellet, qui lui répondit par une brochure, ''De l'Académie française, ou réponse à l'écrit de M. de Chamfort'' (1791), rappelant avec ironie que Chamfort avait mis vingt ans à entrer à l'Académie qu'il demandait à présent de détruire, qu'il y avait prononcé en 1781 un discours où il en louait l'institution et ses protecteurs, et qu'il y avait été assidu pendant dix ans. « Courage de circonstance », concluait le vieil académicien. Le reproche n'est pas entièrement faux : la critique des académies n'est pas seulement un pamphlet contre un ordre révolu, c'est aussi une pièce dans un procès qu'il se fait à lui-même, le dernier acte d'une rupture par laquelle Chamfort renonce solennellement aux honneurs qu'il avait conquis et consent, à perte, à tout ce que la Révolution exigeait de ceux qui avaient appartenu à l'Ancien Régime. On peut lire dans cette rupture, comme l'a fait Arnaud, l'acte par lequel Chamfort solde définitivement ses comptes avec l'Ancien Régime. Morellet, en répliquant, montrait l'autre face du geste : la part d'inconséquence ou de mauvaise conscience qu'il pouvait aussi contenir. Il reste que Chamfort a renoncé sans retour aux bénéfices d'un monde dont il avait été, pendant vingt ans, l'un des ornements. == Politique : royauté, liberté, République == === La critique de la royauté === La pensée politique de Chamfort, aussi longtemps qu'elle s'est exercée sous la monarchie, a pris la forme d'une critique de l'ordre ancien plutôt que d'une théorie positive de la liberté. Il voit dans le régime de Louis XV et de Louis XVI la perpétuation d'un système où les places, les pensions et les charges forment un réseau de dépendances qui corrompt jusqu'à ceux qui s'en défendent. « Quand les sots sortent de place, soit qu'ils aient été ministres ou premiers commis, ils conservent une morgue ou une importance ridicule » : la remarque vise moins les individus que l'institution qui leur a donné cette morgue. De même, sa critique du serment « foi de gentilhomme », « Louis XV a fait banqueroute en détail trois ou quatre fois, et on n'en jure pas moins foi de gentilhomme », n'est pas un trait contre un roi particulier, mais un exemple de la manière dont les formules consacrées résistent à l'évidence des faits. Cette critique se redouble d'une observation plus profonde sur le mécanisme de la domination. Chamfort note que la servitude la plus humiliante n'est pas celle qu'on subit par contrainte, mais celle qu'on consent par intérêt ou par habitude. « J'ai vu des hommes trahir leur conscience, pour complaire à un homme qui a un mortier ou une simare : étonnez-vous ensuite de ceux qui l'échangent pour le mortier, ou pour la simare même. Tous également vils, et les premiers absurdes plus que les autres. » L'analyse du pouvoir se confond ici avec l'analyse de l'amour-propre : c'est parce que chacun cherche sa considération dans le regard des puissants que le pouvoir dure, et ce qui le soutient n'est pas la force mais la complaisance. === L'éminence grise de la première Révolution === Lorsque la Révolution survient, Chamfort en accueille les principes avec un enthousiasme qui tranche sur le scepticisme de ses années antérieures. Son rôle, dans la préparation et les premiers temps du mouvement, est bien plus actif qu'on ne l'a longtemps supposé, comme l'ont montré les travaux de John Renwick (« Chamfort patriote en coulisse », 1980) puis la biographie d'Arnaud. Il est l'un des hommes du club des Trente, il inspire ou relit des discours de Mirabeau, il participe à la Société de 1789 aux côtés de Sieyès, Condorcet, Bailly, Talleyrand et La Fayette, il fréquente les réunions où se discute le sort de la monarchie. Il est aux côtés des députés pour le Serment du Jeu de Paume et, selon un témoignage rapporté par Arnaud, il aurait inspiré à Mirabeau la phrase célèbre : « Nous sommes ici par la volonté du peuple et nous n'en sortirons que par la force des baïonnettes. » Chroniqueur de la Révolution dans les ''Tableaux historiques'', rédacteur au Mercure, auteur de mots qui circulent de bouche en bouche, « Guerre aux châteaux, paix aux chaumières » ; la noblesse « intermédiaire entre le roi et le peuple, comme le chien de chasse est un intermédiaire entre le chasseur et les lièvres »,, Chamfort se tient, selon sa formule propre, dans l'ombre : « Pendant que d'autres voulaient attaquer le colosse avec un bélier, Chamfort cherchait à le cribler de traits satiriques. » Son républicanisme n'est pas une doctrine abstraite ; c'est la conclusion cohérente de toutes ses critiques antérieures. Si les institutions de l'Ancien Régime corrompaient nécessairement ceux qui y entraient, et si les arguments moraux ne pouvaient les faire tomber, il fallait bien que leur chute vînt de l'action politique, et la République offrait la forme où la dignité du caractère pourrait enfin s'exercer sans se compromettre. Chamfort y a cru littéralement. Il a donné ses pensions, sacrifié son revenu, rédigé pour presque rien dans le Mercure, accepté d'être proposé pour plusieurs postes sans jamais les briguer, et s'est installé dans le rôle, ingrat mais cohérent, de celui qui sert la Révolution en la pensant. === Le républicanisme mélancolique === Mais ce républicanisme n'a pas été sans réserves, et c'est dans le huitième chapitre des Maximes, « De l'esclavage et de la liberté en France, avant et depuis la Révolution », que s'inscrivent ces réserves. Chamfort y observe que les hommes ne passent pas, d'une société à l'autre, sans emporter avec eux les habitudes qu'ils y ont contractées. « Je ne croirai pas à la révolution, disait-il en 1792, tant que je verrai ces carrosses et ces cabriolets écraser les passants » : la formule met en évidence le décalage entre les institutions nouvelles et les mœurs qui les précèdent. De même, sa célèbre traduction ironique de la devise « Fraternité ou la mort », « Sois mon frère ou je te tue », n'est pas un sarcasme conservateur, c'est l'observation qu'une vertu imposée par la menace cesse d'être une vertu, et qu'une fraternité qui se maintient par la terreur détruit le principe même dont elle s'autorise. Il faut bien mesurer la portée de ces réserves. Elles ne viennent pas d'un modéré qui regrette la monarchie ; elles viennent d'un républicain qui s'inquiète de voir la République se dégrader en ce qu'elle dénonçait. L'homme qui s'est rangé aux côtés des Girondins en 1792, qui a salué Charlotte Corday comme la « sainte » d'une cause perdue, qui a refusé, aux pires moments de la Terreur, de se dissocier publiquement de ses amis déjà frappés, n'est pas un opposant d'occasion. C'est un homme qui a tenu, jusqu'à l'épreuve, la position d'un républicain intransigeant, et dont la lucidité a fini par mesurer le prix. Chamfort appartient à cette lignée de républicains désenchantés, tel Condorcet, son contemporain, qui accueillirent la chute de l'Ancien Régime comme une nécessité morale et qui ne cessèrent de craindre que la Révolution, dans sa précipitation, ne produisît de nouveaux despotes à la place des anciens. Sa mort, qui fut la conséquence de cette position et non d'un hasard, donna à sa vie la signification d'un choix. === Les Tableaux historiques === Le projet des ''Tableaux historiques de la Révolution française'', pour lequel il fournit treize livraisons composées chacune de deux tableaux et ornées des gravures de Prieur, avant que la tâche ne soit poursuivie par Ginguené, mérite d'être rattaché à sa pensée politique d'ensemble. Chaque « tableau » est une scène : le Serment du Jeu de Paume, la Prise de la Bastille, la Nuit du 14 au 15 juillet, le Roi à l'hôtel de ville de Paris. La méthode est celle de ses ''Caractères et anecdotes'' transportée sur le théâtre révolutionnaire : décrire les événements comme des instants significatifs, en dégager la valeur morale, en repérer les ambiguïtés. On y voit que Chamfort n'était pas un chroniqueur au sens strict ; il concevait l'histoire comme une suite de scènes où se révélait le caractère d'un peuple, et le récit historique comme la prolongation naturelle de la peinture morale. == Morale de la retraite et dignité du caractère == La critique incessante de la société dans laquelle il vit n'aboutit pas, chez Chamfort, à un programme positif ; elle aboutit à une morale de la retraite. Le chapitre IV des ''Maximes générales'', « Du goût pour la retraite, et de la dignité du caractère », en porte le titre explicite. La retraite, chez Chamfort, n'a rien de la clôture religieuse ni de la méditation stoïcienne dans sa forme classique : elle est le refuge d'un esprit que la fréquentation du monde a fatigué, et la condition de possibilité d'un jugement qui ne se soit pas altéré au contact des intérêts. Il faut donc se retirer, non par haine du monde, mais par fidélité à soi-même. C'est le parti qu'il avait voulu prendre dès 1784, lorsqu'il quittait Paris pour la Provence, et qu'il reprit à plusieurs reprises par la suite. Cette morale de la retraite a un corrélat positif, qui est la dignité du caractère. L'expression est à prendre dans un sens très concret : il s'agit, pour celui qui ne peut plus rien changer à l'ordre social, de maintenir en soi une cohérence entre ce qu'il pense et ce qu'il fait, un refus constant des petites lâchetés qu'exige la vie commune. « Un homme du peuple, un mendiant, peut se laisser mépriser, sans donner l'idée d'un homme vil, si le mépris ne paraît s'adresser qu'à son extérieur : mais ce même mendiant, qui laisserait insulter sa conscience, fût-ce par le premier souverain de l'Europe, devient alors aussi vil par sa personne que par son état. » La dignité, telle qu'elle est pensée ici, n'est pas une position sociale ni même une vertu héroïque : c'est le refus, accessible à tous, de laisser insulter sa conscience. Elle fournit le critère par lequel se distinguent les « honnêtes gens » des « fripons » : « Il faut convenir qu'il est impossible de vivre dans le monde sans jouer de temps en temps la comédie. Ce qui distingue l'honnête homme du fripon, c'est de ne la jouer que dans les cas forcés, et pour échapper au péril ; au lieu que l'autre va au-devant des occasions. » On a souvent vu dans Chamfort un misanthrope. Le mot est trop court. La misanthropie suppose une haine générale de l'espèce ; Chamfort, à l'observer, ressentait plutôt une tristesse particulière à l'égard d'une société qu'il voyait incapable de se réformer. Lorsque la Révolution lui offrit un objet d'engagement, il s'y engagea sans réserve ; lorsque cet engagement lui-même devint douteux, il revint à la retraite, et c'est dans cet aller-retour que se lit l'unité d'une vie morale. Son geste final, refuser de rentrer vivant dans une prison, n'est pas une désespérance philosophique ; il est le prolongement extrême de cette dignité du caractère qu'il avait définie. « Je suis un homme libre, dit-il encore aux personnes présentes, jamais on ne me fera rentrer vivant dans une prison. » La déclaration, qu'il relut et signa au procès-verbal, peut être lue comme l'épitaphe de toute une vie morale : le refus, jusque dans la chair, que la société atteigne ce qui, en l'homme, ne lui appartient pas. == La maxime comme forme philosophique == La question de la forme ne saurait être disjointe, chez Chamfort, de la question de la pensée. Il a réfléchi lui-même à ce que c'est qu'une maxime, et l'on a vu qu'il la considérait avec quelque méfiance, comme l'outil commode des esprits paresseux. Pourquoi, alors, a-t-il choisi cette forme ? Parce qu'elle permet, mieux qu'aucune autre, de restituer ce qu'il appelle les « mille observations fines dont l'amour-propre n'ose faire confidence à personne ». La maxime, chez lui, n'est pas la règle d'une morale ; elle est la trace écrite d'une observation particulière, susceptible de corrections et de démentis, qui ne prétend valoir que par sa vérité occasionnelle. Ses maximes, souligne-t-il dès le premier fragment de ses Produits, n'ont pas valeur universelle : elles doivent être lues et interprétées, comme l'a montré Claude Arnaud, en fonction du trajet qui a mené à leur naissance, avertissement méthodologique capital, et qui commande toute l'herméneutique de son œuvre. Cette conception a une conséquence formelle que l'on reconnaît aisément dans les ''Maximes et pensées'' : les fragments y sont souvent introduits par un « j'ai vu », par un « M*** me disait », par un « quelqu'un disait », qui rappellent que la pensée procède toujours d'un cas. La vérité morale, telle que Chamfort l'entend, n'a pas la généralité abstraite d'un principe ; elle s'attache à un contexte, à une scène, à une personne, et ne s'étend au-delà que par la ressemblance que le lecteur consent à reconnaître. Les ''Caractères et anecdotes'' en sont, plus encore que les Maximes, la mise en œuvre exemplaire. Chaque anecdote est un petit récit qui ne dispense aucune leçon formulée, mais qui laisse au lecteur le soin de tirer, ou non, la vérité qu'elle contient. Il en résulte une poétique de la pensée que l'on peut comparer à celle des Essais de Montaigne ou des Caractères de La Bruyère, mais qui s'en distingue par une ironie plus aiguisée et par une économie de moyens plus stricte. Là où Montaigne développe et où La Bruyère dresse le portrait, Chamfort frappe et passe. Son style, qui fut l'un des plus loués du XVIIIe siècle français, n'est pas un ornement ajouté à la pensée : il en est la condition. La brièveté, le paradoxe, la chute inattendue ne sont pas des effets de surface, mais la manière dont la pensée saisit son objet, en sachant qu'elle ne le tient qu'un instant. Sainte-Beuve a pu dire de Chamfort qu'il écrivait « comme on grave » ; la formule est juste, à condition d'ajouter que l'outil était aussi un instrument de connaissance. == Postérité et lectures == La postérité de Chamfort est sinueuse. Publié posthumément par Ginguené en l'an III (1795) sous le titre de ''Produits de la civilisation perfectionnée'', son corpus fragmentaire fut lu avec gravité par les hommes du Directoire et du premier Empire, qui y cherchaient un jugement sur le monde disparu. Les républicains y reconnurent un des leurs ; les royalistes, un esprit trop libre pour être rangé dans un camp. Le fidèle Ginguené poursuivit son travail d'éditeur malgré la disparition de la majeure partie des manuscrits, pillés au moment des scellés. Les premières Œuvres complètes, celles d'Auguis en 1824, ont joué un rôle important dans cette réception : elles rassemblaient pour la première fois l'ensemble des textes, des éloges académiques aux ''Tableaux historiques'', et elles ont fixé l'image d'un moraliste complet. Stendhal, qui reconnaissait en Chamfort l'un de ses maîtres de prose, en a absorbé le style et les préoccupations au point qu'on a pu lire les ''Caractères et anecdotes'' comme une préfiguration de la manière stendhalienne, même économie de moyens, même goût pour le détail révélateur, même ironie retenue. Chateaubriand, qui avait connu Chamfort personnellement et l'avait fréquenté dans les derniers mois de l'Ancien Régime, lui a consacré dans son ''Essai sur les révolutions'' un portrait resté célèbre, dont la phrase, « son œil bleu, souvent froid et couvert dans le repos, lançait l'éclair quand il venait à s'animer », fixa pour longtemps l'image physique de l'homme. Balzac, selon Pierre Citron, fut un lecteur attentif des Maximes. Sainte-Beuve, dans les ''Causeries du lundi'', en fit le sujet de l'une de ses études les plus longues, y voyant un cas « des plus curieux et des plus nets d'ulcération de l'esprit ». C'est Nietzsche qui lui a rendu l'hommage à la fois le plus appuyé et le plus complexe, principalement dans ''Le Gai Savoir'' (1881-1882), dans la préface de ''Humain, trop humain'' (1886) et dans ses ''Fragments posthumes''. Il voyait en Chamfort « le plus malicieux de tous les moralistes » et « un La Rochefoucauld du XVIIIe siècle, mais plus noble et plus philosophe » ; il saluait dans ses Maximes une œuvre possédant « à l'extrême une force de poisson-torpille ». Nietzsche reconnaissait surtout dans Chamfort le portrait d'une intelligence double, tiraillée entre la lucidité de l'observateur et la ferveur du partisan, et à qui son « trop explicable ressentiment » avait fait manquer sa pleine philosophie. « À supposer que Chamfort fût alors demeuré plus philosophe d'un degré, écrit-il dans ''Le Gai Savoir'', la Révolution eût perdu de son tragique mordant et eût été privée de son aiguillon le plus acéré : elle passerait pour un événement beaucoup plus stupide et n'exercerait pas une telle séduction sur les esprits. » Le propos, plus tard nuancé dans ''La Généalogie de la morale'' où Nietzsche intégrera Chamfort à sa théorie du ressentiment, signale à la fois une parenté reconnue, Nietzsche se reconnaissait dans ce moraliste « riche en profondeurs et en arrière-fonds de l'âme, sombre, douloureux, ardent », et une distance : Chamfort, à la différence de Nietzsche, n'a pas voulu se retirer dans la seule supériorité intellectuelle, il a préféré payer de sa personne, et c'est cela même qui lui donne, aux yeux du philosophe allemand, sa singularité tragique. Au XXe siècle, Chamfort a été lu principalement dans la tradition française du moralisme, à côté de La Rochefoucauld, La Bruyère et Vauvenargues ; Albert Camus, dans une préface souvent citée de 1944, l'a présenté comme un écrivain de la lucidité et du refus, et a contribué à le réintroduire dans le canon moderne. Cioran, à sa suite, l'a placé parmi ses moralistes d'élection. Les éditions modernes, celle de Jean Dagen dans la collection GF (1968), celle de Claude Roy, plus récemment l'anthologie ''La Pensée console de tout'' présentée par Claude Arnaud (2014), ont progressivement rendu disponibles les parties de l'œuvre qui avaient été négligées, notamment les écrits politiques et les ''Tableaux historiques''. La biographie qu'a consacrée à Chamfort le même Claude Arnaud en 1988 a, de son côté, renouvelé en profondeur la connaissance de sa vie : en éclairant la question de la naissance, les réseaux de l'amitié avec Mirabeau et Sieyès, le rôle politique de « l'éminence grise » de la première Révolution, elle a permis de lire enfin Chamfort comme un auteur dont la cohérence n'est pas d'abord celle d'un recueil, mais celle d'une trajectoire. == Conclusion == Chamfort occupe, dans l'histoire de la philosophie morale française, une position qui ne se laisse rattacher à aucune école. Héritier des moralistes classiques par la forme qu'il donne à ses pensées, contemporain des Lumières par la confiance qu'il accorde à l'observation et par sa critique des préjugés, républicain de la première heure par conviction plus que par doctrine, il articule ces héritages avec une indépendance qui lui est propre. Sa critique sociale, l'une des plus incisives de sa génération, ne débouche ni sur un système ni sur une utopie : elle s'appuie sur la conviction simple qu'il existe, par-dessous les compositions factices de la vie commune, une nature humaine qu'il est possible de respecter si l'on ne consent pas à s'en laisser dépouiller. Cette position l'expose à une forme de tragique propre, que les interprètes ont diversement qualifiée, « ulcération de l'esprit » selon Sainte-Beuve, « ressentiment » selon Nietzsche, « bâtardise » selon Arnaud, et dont aucune formule ne rend compte à elle seule. Né en marge, il a porté sa vie entière la marque d'une double appartenance sociale, et c'est sans doute de là que venait une part de sa lucidité et de son besoin de ne jamais s'établir. Mais d'autres facteurs, la maladie chronique, les échecs littéraires, la fréquentation prolongée d'un monde dont il voyait les artifices, ont contribué à façonner un regard que l'on aurait tort de dériver d'une seule cause. La société, chez lui, n'a jamais été simplement l'objet d'une critique extérieure : elle a été, en même temps, le lieu de son humiliation et de son triomphe, un ordre qu'il observait de l'intérieur parce qu'il n'y avait eu sa place que par effraction, mais aussi par talent, par séduction et par un effort de volonté dont il ne faut pas sous-estimer l'étendue. Si la société corrompt par ses institutions ce que la nature avait donné de meilleur, et si la réforme politique elle-même ne peut s'accomplir qu'au prix de nouvelles violences, que reste-t-il à l'homme qui pense ? La réponse de Chamfort, celle qu'il a donnée par ses Maximes et qu'il a scellée par sa mort, est que reste du moins le devoir de ne pas mentir à soi-même. C'est peu ; c'est aussi beaucoup, puisque c'est le seul terrain sur lequel la dignité du caractère, sa seule morale, demeure intégralement entre les mains de celui qui la pratique. On comprend alors que ses Maximes, en dépit de leur apparente dispersion, forment une œuvre : elles sont l'inventaire des occasions dans lesquelles cette dignité s'exerce ou se manque, et c'est à cet inventaire que Chamfort a consacré les observations de toute une vie. Ce qui survit ainsi de lui n'est pas une doctrine, mais une figure et un ton. Figure d'un écrivain qui a refusé toutes les complaisances, et dont les contemporains disaient déjà qu'il écrivait « comme on grave » ; ton d'une ironie qui n'est jamais pure cruauté, parce qu'elle s'applique d'abord à celui qui la formule. Ce ton, la postérité l'a reconnu chez Stendhal, chez Nietzsche, chez tous ceux qui, sans fonder d'école, ont fait de la brièveté et de l'acuité les instruments d'une pensée morale sans illusions. Chamfort est de cette famille : celle des moralistes qui, pour avoir regardé le monde d'assez près, ont conclu qu'il valait mieux le dire que l'expliquer, et dont l'œuvre, précisément parce qu'elle ne se clôt sur aucun système, continue d'être utile à quiconque veut, à son tour, ne pas mentir. == Indications bibliographiques == === Sources primaires === * {{ouvrage|auteur1=Chamfort|responsabilité1=aut.|directeur1=Pierre-Louis Ginguené|titre=Œuvres de Chamfort|lieu=Paris|éditeur=Imprimerie des Sciences et Arts|année=an III (1795)|tome=4 vol.}} : Première publication posthume, à laquelle on doit l'essentiel de ce que nous lisons aujourd'hui sous le nom de ''Maximes et pensées'' et de ''Caractères et anecdotes''. Ginguené y a accompli un travail d'établissement dans des conditions difficiles, une grande partie des manuscrits ayant été volée au moment de la pose des scellés. * {{ouvrage|auteur1=Chamfort|responsabilité1=aut.|directeur1=P. R. Auguis|titre=Œuvres complètes de Chamfort|sous-titre=recueillies et publiées avec une notice historique sur la vie et les écrits de l'auteur|lieu=Paris|éditeur=Chaumerot jeune|année=1824-1825|tome=5 vol.}} : Édition rassemblant les œuvres littéraires, critiques, politiques et morales. C'est à elle que renvoient la plupart des citations du présent travail. La notice d'Auguis, fondée sur les papiers de Ginguené, est le premier récit continu de la vie de Chamfort. Réédition en fac-similé par Slatkine. * {{ouvrage|auteur1=Chamfort|directeur1=Jean Dagen|titre=Maximes et pensées, caractères et anecdotes|lieu=Paris|éditeur=Garnier-Flammarion|année=1968|id=Dagen 1968}} (rééd. 2013). : Édition de référence en format courant, avec introduction, notes et établissement philologique solide des fragments ; c'est celle qui s'est imposée dans l'usage universitaire. * {{ouvrage|auteur1=Chamfort|titre=Maximes et pensées, caractères et anecdotes|préface=[[Albert Camus]]|lieu=Monaco|éditeur=Éditions du Rocher|année=1944}} : Préface historiquement importante qui a contribué à la redécouverte de Chamfort au {{s|XX}} et qui ouvre toute la réception contemporaine. * {{ouvrage|auteur1=Chamfort|directeur1=Claude Arnaud|titre=La Pensée console de tout|lieu=Paris|éditeur=[[Éditions Flammarion|Flammarion]]|collection=GF|année=2014}} : Anthologie récente accompagnée d'un appareil critique à jour et d'une présentation qui prolonge le travail biographique de 1988. === Études === * {{ouvrage|auteur1=Claude Arnaud|titre=Chamfort. Biographie|sous-titre=suivie de soixante-dix maximes, anecdotes, mots et dialogues inédits ou jamais réédités|lieu=Paris|éditeur=[[Robert Laffont]]|collection=Les hommes et l'histoire|année=1988|id=Arnaud 1988}} (rééd. [[Éditions Gallimard|Gallimard]], coll. « Tel », 2003). : Biographie de référence, fondée sur un dépouillement systématique des sources d'archives. Arnaud a établi, en s'appuyant sur les mémoires inédits du baron d'Espinchal, les circonstances de la naissance de Chamfort ; il a reconstitué son rôle politique aux côtés de Mirabeau et de Sieyès ; il a proposé une lecture d'ensemble articulée autour de la bâtardise et du ressentiment. L'ouvrage publie en annexe soixante-dix fragments inédits et une enquête sur le vol des manuscrits. * {{ouvrage|auteur1=John Renwick|directeur1=oui|titre=Chamfort and the French Revolution|lieu=Oxford|éditeur=Voltaire Foundation|collection=Studies on Voltaire and the Eighteenth Century|année=1990}} : Voir aussi, du même auteur : « Chamfort patriote en coulisse », ''Studies on Voltaire and the 18th century'', vol. 183, 1980, {{p.|165}} sq. Travaux qui, sur la base de documents d'archives inédits, ont établi le rôle politique de Chamfort au cours des années 1789-1793 et publié plusieurs lettres inconnues. * {{article|auteur1=Jean Dagen|titre=Chamfort moraliste|périodique=Revue d'histoire littéraire de la France|année=1968}} : Étude préparatoire à l'édition GF qui établit les principales filiations (Montaigne, La Rochefoucauld, La Bruyère, Vauvenargues) et propose une lecture interne de la forme fragmentaire. * {{ouvrage|auteur1=Louis Van Delft|titre=Le Moraliste classique. Essai de définition et de typologie|lieu=Genève|éditeur=[[Librairie Droz|Droz]]|année=1982}} : Ouvrage de référence sur la tradition moraliste française, dans lequel la place de Chamfort est discutée à la lumière de ses prédécesseurs et de ses successeurs directs. * {{ouvrage|auteur1=Maurice Pellisson|titre=Chamfort, étude sur sa vie, son caractère, ses écrits|lieu=Paris|année=1895}} (rééd. Slatkine, 1970). : Première étude érudite importante consacrée à Chamfort après Sainte-Beuve ; elle demeure utile malgré les corrections qu'a imposées la biographie d'Arnaud. * {{ouvrage|auteur1=Émile Doucet|titre=Chamfort et son temps|lieu=Paris|éditeur=Fasquelle|année=1944}} (rééd. Volcans, 1974). * {{ouvrage|auteur1=Julien Teppe|titre=Chamfort, sa vie, son œuvre, sa pensée|préface=[[Jean Rostand]]|lieu=Paris|éditeur=Pierre Clairac|année=1950}} : Deux monographies de la première moitié du {{s|XX}} qui ont accompagné la redécouverte du moraliste. * [[Friedrich Nietzsche]], ''[[Le Gai Savoir]]'' (1882), préface à ''[[Humain, trop humain]]'' (1886), et ''Fragments posthumes'' (1881). : Lieux essentiels de la réception allemande, où Nietzsche fait de Chamfort son moraliste d'élection et reconnaît en lui un frère en pensée. * {{article|auteur1=[[Charles-Augustin Sainte-Beuve|Sainte-Beuve]]|titre=Chamfort|périodique=[[Causeries du lundi]]|volume=t. IV|année=1852}} : Étude parmi les plus pénétrantes du {{s|XIX}}, qui voit dans Chamfort « un des plus curieux et des plus nets cas d'ulcération de l'esprit » et qui a longtemps déterminé la lecture du moraliste. * {{article|auteur1=Pierre Citron|titre=Balzac, lecteur de Chamfort|périodique=L'Année balzacienne|année=1969}} : Article qui montre, à partir de relevés précis, la présence diffuse mais réelle de Chamfort dans l'œuvre de Balzac. * {{chapitre|auteur1=[[Georges Poulet]]|titre chapitre=Chamfort|titre ouvrage=La Distance intérieure|lieu=Paris|éditeur=[[Éditions Plon|Plon]]|année=1952}} : Lecture phénoménologique qui, sous l'angle de la distance et du retrait, dégage l'expérience temporelle propre au moraliste. * {{ouvrage|auteur1=[[Marc Fumaroli]]|titre=La République des lettres|lieu=Paris|éditeur=[[Éditions Gallimard|Gallimard]]|année=2015}} : Ouvrage qui replace la trajectoire de Chamfort dans une histoire plus large des institutions lettrées de l'Ancien Régime. [[Catégorie:Philosophe]] t3vkjrcfkq3ntm5nvcnimmyl409ol8g Pour lire Platon/Guide des dialogues/Apologie de Socrate 0 83824 764671 764071 2026-04-23T18:02:11Z PandaMystique 119061 764671 wikitext text/x-wiki == Introduction générale == {{wikisource|Apologie de Socrate (Platon)|Apologie de Socrate}} === L’événement et sa portée === [[Fichier:Socrate du Louvre.jpg|vignette|droite|upright=0.9|Portrait de Socrate, marbre, copie romaine d’époque impériale reproduisant un original grec attribué à Lysippe (IV{{e}} siècle av. J.-C.). Musée du Louvre, Paris (inv. Ma 59).]] En l’an 399 avant notre ère, à Athènes, un vieil homme de soixante-dix ans comparaît devant un tribunal populaire composé de cinq cent un jurés citoyens. Il s’appelle Socrate, fils de Sophronisque ; il est accusé d’impiété et de corruption de la jeunesse. Au terme d’une longue journée d’audience, à une faible majorité d’abord (telle que, selon ses propres mots en 36a, un basculement de trente voix aurait suffi à l’acquittement), puis à une majorité plus large pour la peine capitale, il sera condamné à mort. Quelques semaines plus tard, il boira la ciguë dans sa cellule, devant ses disciples, après le retour du bateau sacré de Délos. Cet événement, déjà traumatisant en son temps, est devenu le mythe fondateur de la philosophie occidentale : le moment où la cité met à mort celui qui prétendait y exercer, par la parole et l’examen, un magistère moral. L’''Apologie de Socrate'' est l’œuvre par laquelle Platon, alors âgé d’une quarantaine d’années, rend compte de ce procès. On situe sa composition probablement entre 390 et 385, soit une dizaine d’années après les faits. Le texte se présente sans cadre fictionnel : nul narrateur, nul personnage introductif, nulle scène préalable comme il en existe dans la plupart des autres dialogues. Nous sommes plongés d’emblée dans la parole de Socrate, au moment où il se lève pour prendre la parole. Contrairement à la plupart des dialogues platoniciens, Platon s’efface presque entièrement : comme le remarque B. Piettre dans son commentaire, dans aucune autre œuvre on n’a l’impression d’entendre avec une telle proximité la parole de Socrate, « comme s’il nous était donné d’assister au procès »<ref name="piettre">Bernard Piettre, ''Platon, Apologie de Socrate'', traduction, présentation et notes de Bernard et Renée Piettre, Paris, Le Livre de Poche (Librairie générale française), coll. « Libretti », 1997, p. 21.</ref>. Cela ne signifie pas que l’''Apologie'' soit une transcription sténographique du plaidoyer : Platon recompose, réorganise, stylise. Mais il s’efforce, probablement plus que dans tout autre dialogue, de restituer la voix, le ton, l’argumentation du maître. Il convient de rappeler que l’''Apologie'' de Platon n’est pas la seule défense posthume de Socrate. Xénophon a également rédigé une ''Apologie'' qui nous est parvenue, brève et d’un ton psychologique différent, ainsi que des ''Mémorables'' qui reprennent certains motifs<ref>Xénophon, ''Apologie de Socrate'' et ''Mémorables'', trad. P. Chambry, Paris, Garnier-Flammarion, 1967.</ref>. D’autres disciples, comme Eschine de Sphettos, ont composé des écrits socratiques aujourd’hui perdus<ref>Voir Gabriele Giannantoni, ''Socratis et Socraticorum Reliquiae'', 4 vol., Naples, Bibliopolis, 1990.</ref>. Un pamphlet hostile, l’''Accusation de Socrate'' du sophiste Polycrate (probablement composé vers 393-392), circulait et aurait donné à Platon l’occasion de répondre indirectement. La concurrence entre ces « socratiques » explique vraisemblablement en partie le soin que Platon met à camper son propre Socrate, qui finira par s’imposer comme la figure canonique dans la postérité. L’enjeu de l’''Apologie'' dépasse de loin la réhabilitation posthume d’un homme. À travers le procès de Socrate, Platon met en accusation la cité qui l’a condamné ; il ouvre l’espace d’une autre politique, où la philosophie, cet « amour du savoir » (''philosophía''), apparaît comme la véritable vocation civique. Condamner le philosophe, c’est pour Athènes se condamner elle-même, préférer l’ignorance au savoir, la facilité au courage, la flatterie à la vérité. L’''Apologie'' constitue ainsi, selon une formule souvent reprise, l’un des textes fondateurs de la philosophie comme discours distinct, à la fois opposé à la sophistique et à la rhétorique, et radicalement engagé dans l’existence concrète. Elle offre en outre une cristallisation exemplaire de ce qu’on appellera le « dialogue socratique », genre dont Platon fera sa forme propre de pensée. === Contexte historique du procès === Pour comprendre la portée du texte, il faut garder à l’esprit la situation d’Athènes en 399. La cité sort épuisée de la longue guerre du Péloponnèse (431-404), qui l’a opposée à Sparte et s’est soldée par la défaite totale d’Athènes. Après la capitulation, Lysandre, le général spartiate, a imposé un gouvernement oligarchique (les Trente) qui a régné par la terreur pendant environ huit mois (404-403) : exécutions sommaires, confiscations, exil des démocrates. La démocratie a été rétablie par un soulèvement armé parti de Phylè en 403, et, dans le cadre des accords de réconciliation conclus sous l’archontat d’Euclide, une amnistie générale a été proclamée pour ne pas ajouter la guerre civile aux malheurs déjà subis : il était désormais interdit, sous peine de sanctions, de se référer aux événements antérieurs à la restauration<ref>Sur cette amnistie, qu’il ne faut pas confondre avec le décret de Patroclide de 405 (qui portait sur la réhabilitation des ''atimoi''), voir Aristote, ''Constitution d’Athènes'', 39-40 ; et Xénophon, ''Helléniques'', II, 4, 38-43.</ref>. Mais les plaies étaient béantes, et le ressentiment courait souterrainement. Or Socrate avait été, dans les années antérieures, un familier de personnages qui incarnaient précisément ces désastres. Alcibiade, son disciple brillant et fantasque, avait entraîné Athènes dans la désastreuse expédition de Sicile (415-413), scandalisé la cité par l’affaire de la mutilation des hermès, puis fini par trahir sa patrie en passant chez Sparte. Critias, autre de ses proches, avait été l’un des chefs, peut-être le principal, du gouvernement des Trente, artisan de la terreur oligarchique. Charmide, oncle de Platon et lui aussi de l’entourage socratique, avait également appartenu à ce régime. Même si Socrate lui-même avait refusé de collaborer avec les Trente (comme il le rappellera dans son plaidoyer à propos de l’arrestation de Léon de Salamine), sa réputation se trouvait entachée. Le procès de 399, bien que portant officiellement sur des griefs religieux, fonctionne donc aussi comme un règlement de comptes politique, que l’amnistie interdisait pourtant de mener ouvertement. Eschine le rhéteur, quelques décennies plus tard, dira sans détour que les Athéniens avaient condamné Socrate « parce qu’il avait été le maître de Critias »<ref>Eschine, ''Contre Timarque'', I, 173.</ref>. L’accusation est portée par trois hommes. Mélétos, poète tragique obscur, plutôt jeune, est le plaignant officiel, celui qui a déposé la plainte auprès de l’archonte-roi, magistrat compétent pour les affaires religieuses. Mais Socrate sait parfaitement que le véritable moteur de la procédure est Anytos, riche tanneur, homme politique influent de la démocratie restaurée, qui avait participé à la chute des Trente. Son fils, raconte Xénophon, fréquentait Socrate et préférait ses entretiens à la tannerie paternelle : hostilité d’autant plus vive<ref>Xénophon, ''Apologie de Socrate'', 29-31.</ref>. Dans le ''Ménon'' de Platon (90b-95a), Anytos apparaît comme le type même du démocrate borné, ennemi viscéral des sophistes et plus largement des intellectuels<ref>Platon, ''Ménon'', 89e-95a.</ref>. Le troisième accusateur, Lycon, est un orateur dont la fonction semble avoir été de soutenir la plaidoirie par une péroraison énergique. La plainte, dont Diogène Laërce nous a conservé le texte<ref>Diogène Laërce, ''Vies et doctrines des philosophes illustres'', II, 40.</ref>, s’énonce approximativement ainsi : <blockquote>Socrate est coupable de ne pas reconnaître les dieux que reconnaît la cité et d’introduire des divinités nouvelles ; il est aussi coupable de corrompre la jeunesse. Peine requise : la mort.</blockquote> Trois griefs, donc : la négation des dieux traditionnels, l’introduction de nouvelles divinités, la corruption de la jeunesse. Ces trois chefs d’accusation sont intimement liés dans la logique de l’accusation : en introduisant de nouvelles divinités et en niant les anciennes, Socrate aurait, par son enseignement, détourné les jeunes gens du respect dû à la religion civique, donc corrompu la cité dans ses fondements. L’accusation d’impiété (''asébeia'') était une accusation politique au sens fort, puisque la religion à Athènes n’était pas une affaire privée mais la substance même du lien civique. === Le cadre procédural === Le procès se tient à l’Héliée, le tribunal populaire, probablement dans un local situé sur l’agora. Les jurés (501 ce jour-là, nombre impair pour éviter les égalités) ont été tirés au sort le matin même parmi les six mille citoyens qui s’étaient inscrits comme héliastes pour l’année. Une journée entière est consacrée à la procédure, dont le temps est rigoureusement partagé entre l’accusation et la défense au moyen de la clepsydre, horloge à eau. Chaque partie parle pour son propre compte : ni procureur ni avocat. Il est permis de faire corroborer son discours par un orateur plus habile (ce dont Mélétos semble avoir bénéficié avec Lycon), ou de lire un discours écrit par un logographe. La tradition rapporte que Lysias, le plus grand logographe de l’époque, aurait composé pour Socrate un plaidoyer de défense ; Socrate en aurait reconnu la beauté avant de le rejeter comme ne lui convenant pas<ref>Diogène Laërce, ''Vies et doctrines des philosophes illustres'', II, 40 ; Cicéron, ''De l’orateur'', I, 231.</ref>. La procédure, pour un procès où la loi n’a pas fixé la peine à l’avance (on parle alors d’''agôn timētós'', « procès à peine à estimer »), comporte deux votes. Après les plaidoiries, un premier vote tranche sur la culpabilité. En cas de condamnation, l’accusateur propose une peine (ici, la mort), l’accusé doit proposer une contre-peine, et un second vote choisit entre les deux sans possibilité de moyen terme. Cette contrainte procédurale pèse lourd sur la suite : les jurés, contraints de choisir entre deux propositions extrêmes, se trouvent ainsi, par la stratégie de Socrate refusant de proposer une peine crédible, poussés à voter la mort. C’est la procédure en deux temps qui explique la structure tripartite de l’''Apologie'' telle que Platon nous l’a transmise : d’abord le grand plaidoyer de défense (17a-35d), puis le discours de contre-proposition pénale après le verdict de culpabilité (35e-38b), enfin une dernière allocution prononcée après la condamnation à mort, adressée tour à tour aux jurés qui ont voté contre lui et à ceux qui l’ont soutenu (38c-42a). Il faut ajouter que les procès athéniens étaient des spectacles autant que des procédures. Le public y assiste ; les plaideurs usent de toute la rhétorique, des larmes de la famille éplorée, des supplications ostentatoires, des témoignages de moralité. Aristophane, dans ''Les Guêpes'' (422), ridiculise la passion que les Athéniens portaient à ces mises en scène judiciaires et le plaisir qu’ils prenaient à voir les accusés se répandre en lamentations. Socrate, comme on le verra, refuse délibérément toute cette théâtralité, ce qui nourrira, après la condamnation, son reproche aux juges : ils l’ont condamné non pas parce qu’il était coupable, mais parce qu’il n’a pas consenti à s’abaisser. === Structure et dispositif du texte === Lu rétrospectivement à travers la grille qu’Aristote constituera dans sa ''Rhétorique'', le plaidoyer de Socrate laisse reconnaître, dans son grand plan, les parties canoniques du discours judiciaire : on y distingue successivement l’exorde (17a-18a), la diabolè ou dénigrement des accusations antérieures (18a-19d), la narration ou ''diḗgēsis'' qui expose les faits (19d-24b), la réfutation proprement dite (24b-28a), l’amplification et la péroraison (28a-35d). Il ne s’agit pas de prétendre que Platon a composé son texte en suivant consciemment un schéma déjà codifié (la ''Rhétorique'' aristotélicienne est postérieure), mais de remarquer que le plaidoyer épouse, dans ses grandes articulations, les usages rhétoriques de son temps. Cette conformité apparente est constamment subvertie par l’ironie socratique : Socrate adopte la forme du discours judiciaire tout en la démentant dans son contenu, en refusant ses procédés et en retournant ses attentes. Le texte fonctionne ainsi comme une parodie philosophique de la rhétorique : il emprunte à l’art oratoire sa structure, mais seulement pour en exhiber la vanité quand il s’agit de la vérité. == Lecture suivie du texte == === Le premier discours : la défense (17a-35d) === ==== L’exorde (17a-18a) : la vérité contre l’éloquence ==== Dès les premiers mots, Socrate pose le ton. La formule d’ouverture, <blockquote>Quel effet, Athéniens, ont produit sur vous mes accusateurs, je l’ignore,</blockquote> est un exorde classique (en grec ''prooímion'') dont la fonction rhétorique, comme le rappelle Aristote dans la ''Rhétorique'' (III, 14), est de capter la bienveillance et l’attention de l’auditoire<ref>Aristote, ''Rhétorique'', III, 14, 1415a22-b30.</ref>. Socrate s’en sert pour établir immédiatement l’antithèse qui structurera tout son plaidoyer : ses accusateurs ont parlé avec persuasion, mais n’ont dit « rien de vrai ou presque » ; lui, en revanche, dira « toute la vérité ». Cette opposition entre l’éloquence ornée des adversaires et la parole nue de Socrate est un retournement ironique savamment préparé. Les accusateurs ont mis les juges en garde contre le « redoutable discoureur » qu’est Socrate. Mais, rétorque-t-il, s’ils entendent par là celui qui dit la vérité, il concède qu’il est un orateur, bien que pas à leur manière. Ses discours ne seront pas, dit-il, « des discours élégamment tournés, comme les leurs, ni même des discours qu’embellissent des expressions et des termes choisis », mais « des choses dites à l’improviste dans les termes qui [lui] viendront à l’esprit ». L’opposition technique est nette : d’un côté le discours apprêté, fondé sur la sélection du vocabulaire (''onómata''), la tournure des phrases (''rhḗmata'') et les arrangements (''kósmos'') ; de l’autre une parole qui se veut « au hasard », sans préparation. Socrate demande aux juges de lui pardonner cette façon de parler : il a soixante-dix ans, comparaît pour la première fois de sa vie devant un tribunal, et il est donc « étranger à la langue en usage ici ». Comme un véritable étranger qui parlerait dans son dialecte, il entend s’exprimer comme il le fait ordinairement, sur l’agora ou devant les comptoirs des changeurs. Le juge, conclut-il, doit juger sur le fond : <blockquote>si mes allégations sont justes ou non. Telle est en effet la vertu du juge, tandis que celle de l’orateur est de dire la vérité. (18a)</blockquote> Cette prise de position initiale est philosophiquement lourde de conséquences. Elle reformule, dès l’ouverture, l’opposition fondamentale entre la rhétorique (l’art de persuader, tel que le pratiquent sophistes et logographes) et la philosophie (recherche de la vérité par l’examen rationnel). Les sophistes, comme Gorgias ou Protagoras, avaient fait de la rhétorique un instrument neutre, capable de « faire de l’argument le plus faible l’argument le plus fort » (formule qui reviendra explicitement contre Socrate au chef d’accusation). Socrate, à l’inverse, affirme que la parole a pour fonction première de manifester le vrai, et que celui qui se défend en justice ne doit pas jouer des passions ou des artifices, mais soumettre les arguments à l’examen rationnel des jurés. On notera pourtant que cette protestation de simplicité est elle-même une figure rhétorique sophistiquée : celui qui clame qu’il ne sait pas parler parle déjà, et fort bien. Il s’agit de la ''dissimulatio artis'' que Cicéron théorisera plus tard : l’art supérieur consiste à cacher l’art. L’ironie socratique commence dès l’exorde. Socrate se présente comme un ''idiṓtēs'' (un simple particulier, un « idiot » au sens grec), étranger aux codes du tribunal, mais cette posture est elle-même un dispositif stratégique qui retourne contre l’accusation la suspicion de sophistique : ce n’est pas Socrate qui manipule les mots, ce sont les accusateurs. L’inversion est complète. ==== Les « anciens accusateurs » : la calomnie de longue durée (18a-19d) ==== Socrate introduit ensuite une distinction cruciale : il doit répondre à deux catégories d’accusations, celles de ses « accusateurs récents » (Mélétos, Anytos, Lycon), mais aussi, et d’abord, celles de ses « anciens accusateurs », qui ont « depuis de nombreuses années » répandu une image fausse de lui. Ceux-là, il les redoute davantage que les nouveaux, pour trois raisons convergentes. Ils sont d’abord nombreux : il ne s’agit pas de trois personnes identifiables, mais d’une rumeur collective. Leurs accusations sont ensuite anciennes : elles ont eu le temps de s’enraciner dans les esprits. Enfin, ils ont agi auprès des juges « dès l’enfance », à un âge où « vous aviez le moins de défiance », de sorte que les jurés en ont reçu l’empreinte avant même d’avoir l’âge de l’examiner. Quelle est la teneur de cette calomnie ancienne ? Socrate la résume en une formule qui reviendra plusieurs fois dans le texte comme un chef d’accusation fantasmatique : <blockquote>Il existe un certain Socrate, un savant, un « penseur » qui s’intéresse aux choses qui se trouvent en l’air, qui mène des recherches sur tout ce qui se trouve sous la terre et qui de l’argument le plus faible fait l’argument le plus fort. (18b)</blockquote> Cette caricature hétéroclite condense en réalité trois traits initialement distincts. Il y a d’abord la figure du physicien à la manière des présocratiques (Anaxagore, Diogène d’Apollonie), qui spéculait sur les phénomènes célestes (''tà metéōra'') et souterrains. Il y a ensuite celle de l’athée, puisque interroger la nature par la raison revenait, dans la perception populaire, à nier les dieux traditionnels (Anaxagore avait été poursuivi pour cette raison vers 433, Protagoras également, et tous deux avaient été contraints à l’exil). Il y a enfin celle du sophiste, expert en retournements dialectiques, capable de faire triompher n’importe quelle cause par le seul art des mots. L’incohérence de ce portrait (un philosophe de la nature qui serait en même temps un manipulateur rhétorique) ne l’empêche pas d’être efficace dans l’opinion. C’est le propre de la rumeur (''diabolḗ'') : elle n’a pas à être cohérente pour être puissante. Socrate le reconnaît avec lucidité : la force de cette calomnie vient précisément de ce qu’elle ne repose sur aucune source identifiable, qu’on ne peut donc ni l’interroger, ni la réfuter. Combattre ces accusateurs anonymes, dit Socrate, « c’est comme se battre contre des ombres » (18d). C’est une difficulté propre au philosophe dans la cité : face à l’opinion établie, il n’a pas d’adversaire identifiable, donc pas de prise dialectique. Socrate désigne cependant une source probable : les comédies, et en particulier ''Les Nuées'' d’Aristophane, représentées en 423 (vingt-quatre ans avant le procès)<ref>Aristophane, ''Les Nuées'', représentées pour la première fois aux Grandes Dionysies de 423 av. J.-C.</ref>. Il la mentionne d’abord anonymement, parlant d’un <blockquote>Socrate qui se balançait, en prétendant qu’il se déplaçait dans les airs et en débitant plein d’autres bêtises concernant des sujets sur lesquels je ne suis un expert ni peu ni prou. (19c)</blockquote> Le nom d’Aristophane sera explicitement prononcé peu après. Dans cette pièce, le poète comique met en scène un Socrate juché dans une corbeille suspendue, pour mieux « mêler sa pensée subtile à l’air », invoquant les Nuées comme divinités substitutives à celles de la religion populaire, enseignant à un paysan, Strepsiade, puis à son fils Phidippide, comment faire triompher le « Raisonnement injuste » et ruiner par cet apprentissage la piété filiale. La pièce s’achève d’ailleurs sur l’incendie du « Pensoir » socratique. L’enjeu pour Socrate n’est pas seulement de corriger une image fausse : c’est de montrer que les accusations de Mélétos se rabattent exactement sur cette caricature (négation des dieux, introduction de nouveautés religieuses, corruption de la jeunesse), de sorte que contre-attaquer la comédie, c’est déjà dissoudre la plainte. Il y a là un geste tranché : Socrate refuse explicitement d’être confondu, d’une part avec les physiciens qui spéculent sur la nature, d’autre part avec les sophistes qui enseignent la rhétorique contre rémunération. Il énumère d’ailleurs plusieurs sophistes célèbres (Gorgias de Léontinoi, Prodicos de Céos, Hippias d’Élis) et rappelle l’anecdote d’Événos de Paros, qui faisait payer cinq mines pour son enseignement (20b) : somme considérable, correspondant à environ un an et demi de salaire d’un ouvrier qualifié<ref name="brisson">Platon, ''Apologie de Socrate. Criton'', trad. et notes de Luc Brisson, Paris, GF-Flammarion, 2016, p. 131, note 54.</ref>. Socrate, lui, n’a rien à vendre, et c’est précisément cela, comme on le verra, qui témoigne de la pureté de sa démarche. Notons enfin une dimension cruciale de ce moment : Socrate récuse par avance toute identification à la figure du philosophe-penseur retiré du monde, que Platon décrit dans le ''Théétète'' (174a) par l’anecdote célèbre de Thalès tombant dans le puits en contemplant les étoiles<ref>Platon, ''Théétète'', 174a.</ref>. La défense de Socrate sera celle d’un homme de l’agora, immergé dans la cité, engagé dans l’examen de ses concitoyens. Le philosophe socratique n’est pas un contemplatif éloigné des affaires humaines : c’est au contraire le plus urbain des hommes, celui qui ne quitte jamais la ville (comme il le dit dans le ''Phèdre''), celui dont l’activité est fondamentalement politique, même s’il n’exerce aucune charge politique. ==== La narration : l’oracle de Delphes et la naissance de la mission (19d-24b) ==== Pour expliquer l’origine de la calomnie, Socrate engage ce qui correspond à la narration (''diḗgēsis'') du discours rhétorique. Il entreprend de raconter comment il est devenu ce personnage que certains considèrent comme un savant. Cette narration, qui occupe une part importante du discours, contient deux éléments structurants : le récit de l’oracle de Delphes et l’exposé de l’enquête qui s’ensuivit. ===== Le savoir humain et le savoir plus qu’humain (20d-21a) ===== Avant de raconter l’oracle, Socrate pose une distinction fondamentale qui constitue peut-être la pièce conceptuelle centrale de tout le plaidoyer. On dit qu’il est « savant » (''sophós'') : soit. Mais savant en quoi ? Il existe, dit-il, un savoir qui excède la mesure humaine, celui auquel prétendent, moyennant rétribution, les sophistes. Ce savoir-là, Socrate ne le possède pas ; et il aurait « des chances d’être un savant » seulement dans un sens plus modeste, celui d’un savoir qui « se rapporte à l’être humain », une sagesse humaine (''anthrōpínē sophía''). La distinction est capitale. Elle sépare deux ordres de connaissance : celui qui porte sur les choses divines (la nature, le cosmos, les causes premières), que Socrate refuse de revendiquer ; et celui qui porte sur l’humain, sur ce qu’il convient de faire pour vivre bien. Cette distinction préfigure le partage que toute l’histoire de la philosophie reprendra entre philosophie théorique et philosophie pratique, et elle annonce également la réorientation socratique qu’évoque Cicéron dans un passage célèbre : <blockquote>Socrate a fait descendre la philosophie du ciel sur la terre, l’a introduite dans les villes et même dans les maisons, et l’a obligée à s’enquérir de la vie, des mœurs, des choses bonnes et mauvaises. (''Tusculanes'', V, 10-11)<ref>Cicéron, ''Tusculanes'', V, 4, 10-11 : « Socrates autem primus philosophiam devocavit e caelo et in urbibus conlocavit et in domus etiam introduxit... »</ref></blockquote> La philosophie cesse d’être cosmologie pour devenir éthique. Pour prouver l’existence et la nature de ce savoir proprement humain, Socrate invoque un témoin insolite mais sans appel : « le dieu de Delphes », c’est-à-dire Apollon pythien. Le recours à la parole oraculaire, dans un tribunal populaire attaché à la religion civique, est rhétoriquement habile : il retourne l’accusation d’impiété en présentant Socrate comme un serviteur du dieu. ===== L’oracle et l’enquête (21a-23c) ===== [[Fichier:John Collier - Priestess of Delphi.jpg|vignette|gauche|upright=0.85|John Collier, ''La Prêtresse de Delphes'', 1891, huile sur toile, Art Gallery of South Australia. La Pythie est figurée assise sur son trépied, dans les vapeurs qui montent de la faille, laurier en main. C’est à cette prêtresse que Chéréphon aurait posé sa question, selon le récit que fait Socrate en ''Apologie'' 20e-21a.]] C’est le moment où surgit, dans le texte, le récit de l’oracle. Chéréphon, ami d’enfance de Socrate (un homme à la passion impétueuse, démocrate, exilé sous les Trente et revenu avec la démocratie, que la comédie moquait pour sa maigreur ascétique et sa « mine d’endive »<ref>Aristophane, ''Les Guêpes'', v. 1408 ; ''Les Oiseaux'', v. 1296 ; ''Les Nuées'', v. 104.</ref>), était allé un jour à Delphes et avait eu l’audace de demander à la Pythie s’il existait quelqu’un de plus sage que Socrate. La Pythie, prêtresse d’Apollon, parle au nom du dieu et rend des oracles aux consultants : elle répondit que « personne n’était plus sage ». Chéréphon est mort entre-temps (probablement vers 403), mais son frère, présent à l’audience, pourra en témoigner. Cette réponse divine met Socrate dans un profond embarras. Il a « conscience de n’être savant ni peu ni prou ». Mais le dieu, par définition, ne peut mentir : « la loi divine l’interdit » (21b). Comment résoudre l’énigme (''aínigma'') ? Socrate décide alors d’entreprendre une vérification. Il va chercher quelqu’un de plus sage que lui, afin de pouvoir revenir à Delphes et dire : <blockquote>ce dieu m’avait désigné comme le plus sage, mais voici qui l’est davantage.</blockquote> Cette démarche, loin d’être un geste d’orgueil, est présentée comme un service rendu au dieu : c’est pour vérifier l’oracle, non pour le contredire, que Socrate entreprend son enquête. Il accorde à l’oracle une autorité suffisante pour l’interroger méthodiquement, selon un principe qui rappelle la maxime exégétique attribuée à Héraclite : « le maître dont l’oracle est à Delphes ne dit ni ne cache rien : il fait signe »<ref>Héraclite d’Éphèse, fragment DK 22 B 93 (Diels-Kranz) = fragment 14 Conche. Voir Marcel Conche, ''Héraclite. Fragments'', Paris, PUF, coll. « Épiméthée », 1986, p. 168-170.</ref>. L’enquête est méthodiquement menée dans trois directions, que Socrate parcourt successivement. Il va d’abord trouver un homme politique (dont il tait le nom, conformément à la pudeur judiciaire) qui passait pour sage. Après l’avoir interrogé, il constate que cet homme se croit sage mais ne l’est pas. Socrate tire alors la leçon capitale qui donnera son contenu à la sagesse humaine : <blockquote>Il y a des chances que je sois moi-même plus sage que cet homme. Car aucun de nous, il est vraisemblable, ne sait rien qui en vaille la peine ; mais lui pense savoir alors qu’il ne sait pas, tandis que moi, tout comme je ne sais pas, je ne pense pas non plus savoir. (21d)</blockquote> Tel est le célèbre savoir du non-savoir socratique : non pas une ignorance totale, mais la conscience lucide et réfléchie de sa propre ignorance, qui vaut davantage que l’illusion de la science. Socrate répète l’opération avec d’autres hommes politiques, et chaque fois la déception est la même, mais pire : plus l’homme est réputé, plus son ignorance est grande ; plus il est humble, plus il est proche du vrai. Socrate passe ensuite aux poètes : auteurs de tragédies, poètes dithyrambiques et autres. Il leur demande ce que signifient leurs propres œuvres, espérant d’eux un savoir, puisqu’ils produisent de la beauté. Il découvre alors qu’ils composent « non par savoir, mais par une sorte de disposition naturelle et par inspiration, comme les devins et les oracles » (22c). Les poètes disent beaucoup de belles choses sans savoir ce qu’elles veulent dire ; et, comme les hommes politiques, ils se croient, à cause de leur art, savants dans d’autres domaines où ils ne le sont pas. L’inspiration poétique est ainsi reconnue comme réelle, mais dissociée du savoir : le poète est possédé par une muse, non par une compétence. Cette analyse, qui reviendra dans le ''Ion'', est une pièce importante de la pensée platonicienne sur l’art<ref>Platon, ''Ion'', 533d-535a.</ref>. Socrate examine enfin les artisans (''cheirotéchnai''). Ici, la situation est plus nuancée. Les artisans possèdent une compétence réelle, une ''tékhnē'', que Socrate ne songe pas à leur dénier. Mais cette compétence les induit à se croire savants « dans les choses les plus importantes », c’est-à-dire dans les questions morales et politiques, alors qu’ils ne le sont pas. L’artisan, parce qu’il sait faire une paire de chaussures, se croit en droit d’avoir un avis éclairé sur la justice. C’est là un point crucial : Socrate reconnaît la légitimité de la ''tékhnē'' dans son domaine propre, mais refuse l’extrapolation de la compétence technique à la sagesse pratique. La conclusion de l’enquête est double. D’une part, Socrate conclut à la véracité de l’oracle : sa sagesse consiste en ce qu’il ne croit pas savoir ce qu’il ne sait pas. D’autre part, il comprend que l’oracle ne le désignait pas ''lui'' spécifiquement comme sage, mais se servait de son nom à titre d’exemple : <blockquote>il y a des chances, Messieurs, pour qu’en réalité le sage, ce soit le dieu, et que dans ce fameux oracle il veuille dire que la sagesse humaine a bien peu de valeur, et même aucune ; et il est clair qu’en désignant Socrate il s’est servi de mon nom pour me prendre en exemple, comme s’il disait : « Le plus sage d’entre vous, hommes, est celui qui, comme Socrate, a reconnu qu’en réalité sa sagesse ne vaut rien. » (23a-b)</blockquote> Socrate devient ainsi, non pas un savant, mais un exemple (''parádeigma'') par lequel le dieu invite tous les hommes à reconnaître la misère de leur prétention au savoir. La sagesse n’est pas une propriété de l’individu, mais une relation à l’ignorance ; elle est, on pourrait dire, éminemment ''maïeutique'' : elle fait accoucher les autres de la conscience de leur propre ignorance. ===== La mission et la naissance de la haine (23b-24b) ===== De cette interprétation de l’oracle naît la mission socratique. Socrate continue, « en service pour le dieu » (''hypēresía toû theoû''), à enquêter sur quiconque prétend être sage, pour manifester chaque fois qu’il ne l’est pas. Cette activité l’a réduit à « une grande pauvreté », car elle lui a occupé toute sa vie au détriment de ses affaires. Mais elle a aussi suscité contre lui des haines innombrables, chaque fois qu’il a démasqué l’ignorance d’un puissant ou d’un réputé. On touche ici à un mécanisme psychologique qu’il faut bien mesurer : nul n’aime être convaincu d’ignorance, surtout publiquement ; celui qui le fait, même par amour du vrai, se constitue des ennemis à proportion de sa rigueur. Les jeunes gens de bonne famille, ceux qui disposent de loisir (''scholḗ''), prennent plaisir à le voir faire ; ils tentent à leur tour d’imiter sa démarche, et ainsi « se font eux-mêmes haïr par ceux qu’ils examinent » (23c), qui ne s’en prennent pas à ces jeunes, mais à Socrate. Ce dernier devient ainsi le responsable imaginaire d’une activité critique qui déborde largement sa personne. C’est le ressort profond de l’accusation de « corruption de la jeunesse » : ce n’est pas que Socrate ait enseigné le mal, c’est que ses méthodes, imitées par ses disciples, font éclater un scandale qu’on veut lui imputer. C’est ainsi que s’est formée la réputation selon laquelle Socrate serait un « corrupteur de la jeunesse », et qu’on a repris contre lui la vieille caricature : un homme qui fait des recherches sur le ciel et la terre et qui fait triompher la mauvaise cause. Socrate tire la conclusion rhétorique : « c’est en disant la vérité que je me fais des ennemis », ce qui est, dit-il, la preuve qu’il dit vrai. Les causes de l’accusation sont là : non dans une faute réelle, mais dans le ressentiment de ceux qu’il a démasqués. Cette section du plaidoyer est philosophiquement fondamentale. Elle met en place plusieurs concepts clefs du socratisme tel que Platon l’entend. D’abord, la philosophie comme examen (''exétasis'') et non comme doctrine : on ne peut enseigner la philosophie de Socrate parce qu’elle n’est pas un corps de propositions à transmettre, mais une pratique à exercer. Ensuite, le savoir de l’ignorance comme seule forme accessible du savoir humain. Enfin, la philosophie comme mission divine, ce qui lui confère une légitimité supérieure à celle des institutions politiques. Cette dimension religieuse de la philosophie est essentielle à l’économie du texte : si la mission est divine, elle ne peut être interrompue par un décret humain, fût-il celui d’un tribunal souverain. On notera également que cette démarche, en démasquant l’ignorance des prétendus savants, introduit une différence entre savoir et non-savoir sur des sujets où la démocratie athénienne supposait, pour délibérer, qu’il n’y en avait pas. Comme l’explique un passage du ''Protagoras'' (319c-d), rédigé par Platon peu après l’''Apologie'', les assemblées démocratiques distinguent les sujets techniques (sur lesquels seuls les compétents s’expriment) et les sujets politiques (sur lesquels tous les citoyens, cordonniers, potiers, tanneurs ou menuisiers, peuvent donner leur avis)<ref>Platon, ''Protagoras'', 319b-d.</ref>. La délibération collective suppose que sur les sujets politiques, il n’existe pas de différence de compétence entre les citoyens. Or l’enquête socratique a précisément pour effet de réintroduire cette différence sur les objets où l’institution démocratique la refoulait. Se prétendre ignorant soi-même et révéler l’ignorance d’autrui sur les questions de justice ou de vertu, c’est mettre en cause le principe majoritaire là où il s’applique. On comprend en quoi, malgré les apparences, la posture socratique est subversive pour la démocratie athénienne : elle frappe à sa racine épistémologique. ==== La réfutation de Mélétos (24b-28a) ==== Socrate en vient maintenant aux accusations officielles. Il relit la plainte, <blockquote>Socrate est coupable de corrompre la jeunesse et de reconnaître non pas les dieux que la cité reconnaît, mais, au lieu de ceux-là, des divinités nouvelles,</blockquote> et entreprend de l’examiner point par point. Il utilise alors la prérogative que la loi athénienne accorde à l’accusé : interroger directement son accusateur, qui est tenu de répondre. Ce qui suit est l’un des grands morceaux dialectiques de l’''Apologie''. Socrate, en un véritable elenchos (cette réfutation par interrogation qui est sa marque de fabrique), démonte successivement les trois volets de l’accusation. On notera d’emblée un jeu de mots grec qui court tout l’interrogatoire : le nom de Mélétos (''Mélētos'') évoque le verbe ''mélei'' (« se soucier de »). Socrate va reprocher à Mélétos, précisément, de ne pas se soucier (mélein) des choses dont il prétend se soucier. L’ironie est ciselée jusque dans l’onomastique. La méthode que Socrate utilise ici est l’''elenchos'' : il part des thèses de l’interlocuteur, l’amène par des questions à en reconnaître les conséquences, puis lui fait constater la contradiction entre ces conséquences et d’autres thèses qu’il tient également pour vraies. Cette procédure, qui ne démontre rien positivement mais montre qu’une thèse est intenable, est le moteur de la dialectique socratique dans les dialogues de jeunesse de Platon<ref>Sur la méthode de l'elenchus, voir Gregory Vlastos, « The Socratic Elenchus », ''Oxford Studies in Ancient Philosophy'', I, 1983, p. 27-58.</ref>. ===== Qui rend les jeunes meilleurs ? (24b-25c) ===== Premier volet : la corruption de la jeunesse. Socrate commence par un piège dialectique. Si Mélétos accuse quelqu’un de corrompre les jeunes, c’est qu’il a à l’esprit ce qui les rend meilleurs. Qu’il le dise donc. Mélétos, pris au dépourvu, balbutie : « Les lois ». Mais ce n’est pas une personne, objecte Socrate. Il insiste : quel ''homme'' rend les jeunes meilleurs ? Mélétos répond alors, au gré d’une improvisation visiblement embarrassée : les juges, puis les membres du Conseil, puis ceux de l’Assemblée, bref tous les Athéniens ; tous, sauf Socrate. Socrate retourne alors l’argument par une analogie mémorable, celle des chevaux. Suppose-t-on que tous les hommes rendent les chevaux meilleurs, et qu’un seul les corromprait ? Non, c’est évidemment le contraire : pour les chevaux comme pour tous les animaux, seuls quelques spécialistes savent les rendre meilleurs, et la plupart des gens, s’ils s’en occupent, les nuisent. Il en va de même pour les jeunes gens. L’éducation est un art, et l’art suppose la compétence, qui n’est pas le partage du plus grand nombre. En prétendant que tous éduquent bien sauf Socrate, Mélétos révèle qu’il n’a jamais sérieusement réfléchi à ce dont il parle : il s’est désintéressé de la question. Ce qui est précisément ce que le jeu de mots sur son nom entendait suggérer. Ce premier argument est intéressant par ce qu’il laisse apercevoir de la position de Socrate à l’égard de la démocratie. L’éducation, comme le souligne le commentaire de C. Chrétien, est « une affaire politique tant la formation de l’homme paraît indissociable de celle du citoyen »<ref name="chretien">Claude Chrétien, ''Platon, Apologie de Socrate'', Paris, Hatier, coll. « Profil philosophie », 1993, p. 44.</ref>. Or, pour Mélétos et pour les Athéniens en général, la cité est à elle-même sa propre pédagogie : chaque citoyen forme, par son exemple et sa participation à la vie publique, les futurs citoyens. Socrate, à l’inverse, soutient que l’éducation, comme les autres arts, relève d’une compétence particulière, ce qui va à l’encontre du présupposé démocratique d’une compétence civique également partagée. En paraissant ne se battre que sur un détail logique, Socrate met en cause, à travers cet argument, l’un des présupposés majeurs de la démocratie athénienne : celui selon lequel chaque citoyen serait naturellement compétent pour la délibération politique. ===== Corrompre volontairement ? (25c-26a) ===== Deuxième pièce du dispositif : Socrate demande à Mélétos s’il le pense corrompre les jeunes volontairement ou involontairement. Mélétos, rageusement, répond : volontairement. Mais Socrate montre alors que cette réponse est intenable. Car si les méchants font du mal à leur entourage, et les bons du bien, alors corrompre volontairement les gens qui nous entourent (avec lesquels on vit) c’est s’exposer soi-même à en pâtir. Personne, pas même le plus ignorant, ne choisit délibérément de se nuire à soi-même. Donc, ou bien Socrate ne corrompt pas, ou bien, s’il corrompt, c’est involontairement. Et dans ce cas, la loi ne prévoit pas un procès mais une remontrance privée : si on m’avait averti, j’aurais cessé. En me traînant devant le tribunal, Mélétos prouve que son but n’est pas de me corriger mais de me punir ; ce qui est contradictoire avec l’idée d’une faute involontaire. Cet argument repose sur l’un des principes les plus fermes du socratisme, le célèbre paradoxe socratique selon lequel nul ne fait le mal volontairement (''oudeís hekṓn hamartánei''). Socrate y croit sincèrement : si l’on savait vraiment ce qu’est le bien, on ne pourrait pas ne pas le vouloir. Le vice n’est pas une perversion de la volonté, c’est une forme d’ignorance. Cette thèse, qui paraîtra contre-intuitive à toute la tradition postérieure (notamment chrétienne, qui mettra l’accent sur la malice du mal), sera développée dans plusieurs dialogues platoniciens, notamment le ''Gorgias'' et le ''Protagoras''<ref>Platon, ''Gorgias'', 466a-468e, 509c-e ; ''Protagoras'', 352b-358d.</ref>. Aristote, dans l’''Éthique à Nicomaque'', la critiquera comme négligeant la réalité de la ''akrasía'' (faiblesse de la volonté)<ref>Aristote, ''Éthique à Nicomaque'', VII, 2-3, 1145b21-1147b19.</ref>. Mais l’argument sert ici surtout à piéger Mélétos dans une contradiction : soit tu m’accuses d’une faute involontaire, et le procès est illégitime (car la procédure judiciaire vise des fautes intentionnelles) ; soit tu m’accuses d’une faute volontaire, mais celle-ci est psychologiquement impossible (personne ne choisit de se nuire à soi-même). Dans les deux cas, la plainte s’effondre. Remarquons la précision juridique : Socrate joue habilement sur la distinction athénienne entre fautes volontaires (justiciables) et involontaires (pour lesquelles la remontrance privée, la ''nouthesía'', était la procédure appropriée). ===== L’athéisme et les divinités démoniques (26a-28a) ===== Troisième volet : la question religieuse, qui est le cœur même de l’accusation. Socrate demande à Mélétos de préciser son propos : l’accuse-t-il de reconnaître des dieux différents de ceux de la cité, ou de ne reconnaître aucun dieu du tout ? La distinction est cruciale, car la plainte elle-même est ambiguë (elle évoque des « divinités nouvelles », ce qui présuppose que Socrate croit à des divinités, mais lui reproche aussi de ne pas reconnaître celles de la cité). Mélétos, avec une maladresse que Socrate exploite pleinement, s’emporte et répond : « aucun dieu du tout ». Il ajoute même, piégé par sa propre fureur, que Socrate prétend, à la manière d’Anaxagore, que « le soleil est une pierre et la lune une terre ». Socrate saisit l’occasion avec une précision chirurgicale. D’abord, il ridiculise la confusion : Anaxagore, en effet, a soutenu cette thèse physicienne, mais les livres d’Anaxagore, qui se trouvent au marché (à l’orchestre, endroit de l’agora où l’on vendait les livres), coûtent « tout au plus une drachme » ; pourquoi donc accuser Socrate d’avoir inventé ce qu’on peut lire partout et qui n’est pas de lui ? Le trait est doublement dévastateur : il innocente Socrate et il prouve l’incompétence de Mélétos, qui ne distingue pas Socrate d’Anaxagore. Ensuite, il tend son piège principal. La plainte officielle dit que Socrate introduit de nouvelles divinités (''daimónia''). Or Mélétos vient d’affirmer que Socrate n’admet aucun dieu du tout. Ces deux affirmations sont contradictoires : on ne peut pas à la fois reconnaître des divinités et ne reconnaître aucun dieu. Mélétos se contredit lui-même, et sous serment, car la plainte avait été déposée sous serment réciproque (''antōmosía''). Socrate poursuit par un syllogisme subtil. Peut-on reconnaître des « phénomènes démoniques » (''daimónia prágmata'') sans reconnaître l’existence de démons ? De même que l’on ne peut reconnaître des phénomènes hippiques sans reconnaître les chevaux, ni des phénomènes musicaux sans reconnaître les musiciens, on ne peut reconnaître des phénomènes démoniques sans reconnaître les démons. Or les démons, selon la religion grecque traditionnelle, sont soit des dieux soit des enfants de dieux. Donc, si Socrate reconnaît des démons, il reconnaît aussi des dieux, ou à tout le moins des êtres divins. La plainte se contredit elle-même : Mélétos affirme à la fois que Socrate ne reconnaît aucun dieu et qu’il reconnaît des démons, donc des dieux. Ce raisonnement est brillant sur le plan dialectique, mais il a suscité la perplexité des commentateurs. Il repose sur une définition traditionnelle des démons comme « enfants des dieux », qui n’est pas toujours stabilisée dans la culture grecque (chez Hésiode, les démons sont plutôt des hommes de l’âge d’or devenus esprits), et laisse entière la question de savoir ce que sont les « nouvelles divinités » dont Socrate était réellement accusé. La plupart des commentateurs modernes estiment que l’accusation visait précisément le fameux ''daimónion'' socratique, la voix intérieure divine dont Socrate parlera plus loin, qui serait apparue aux Athéniens comme une divinité privée, nouvelle, donc impie car non reconnue par la cité. Socrate, en déplaçant le débat sur la question abstraite de l’existence ou non des démons, élude habilement cette difficulté. C’est un procédé dialectique, non un argument de fond. Il faut aussi percevoir le geste de fond. Comme le note Claude Chrétien, Socrate, par ce raisonnement, rattache sa croyance en des « phénomènes démoniques » à une croyance minimale mais ferme en la divinité, sur un mode qui relève d’une théologie négative : il ne dit rien de positif sur les dieux, mais affirme seulement que quelque chose, dans l’expérience humaine, manifeste leur existence<ref name="chretien-30">Claude Chrétien, ''Platon, Apologie de Socrate'', ''op. cit.'', p. 30-31.</ref>. Cela est cohérent avec son agnosticisme sur les mythes (dans le ''Phèdre'', Socrate refuse de spéculer sur les aventures de Borée enlevant Orithye<ref>Platon, ''Phèdre'', 229b-230a.</ref>) et avec sa dévotion pratique à la religion de la cité (on le voit obéir aux rites dans plusieurs dialogues). Socrate est pieux en acte, agnostique en théorie : il reconnaît une transcendance divine, sans prétendre en décrire la nature. Cette position, fine et paradoxale, est à l’origine d’une tension féconde dans toute la théologie philosophique ultérieure. À la fin de cette section, Socrate conclut qu’il a suffisamment montré que l’accusation de Mélétos est sans consistance. Mais, ajoute-t-il avec lucidité, il sait bien que ce n’est pas elle qui le fera condamner : c’est la vieille calomnie, la haine accumulée au fil des années. D’autres hommes justes, avant lui, ont subi le même sort, et beaucoup en subiront après. ==== La mission divine et le modèle héroïque (28a-30c) ==== Ayant réfuté l’accusation formelle, Socrate entreprend alors ce que Piettre appelle une « amplification »<ref>Piettre, ''op. cit.'', p. 45-46 (sur le découpage du plaidoyer en réfutation et amplification).</ref> : il justifie tout son mode de vie. L’''Apologie'' bascule ici d’un plaidoyer juridique vers une proclamation philosophique. La question n’est plus « suis-je coupable de ces griefs précis ? » mais : « comment justifier un mode d’existence qui expose à la mort ? » C’est à partir de ce moment que l’''Apologie'' cesse d’être un simple plaidoyer pour devenir un manifeste. ===== Le modèle d’Achille (28b-d) ===== Socrate anticipe une objection qu’un auditoire athénien pouvait sincèrement formuler : n’as-tu pas honte, Socrate, d’avoir mené une existence qui t’expose à mourir ? Il y répond par l’évocation des héros homériques, et notamment d’Achille, la figure de référence de la vertu guerrière grecque. Quand Thétis, sa mère, lui annonça qu’il mourrait s’il vengeait Patrocle en tuant Hector, Achille répondit, selon l’''Iliade'' : <blockquote>que je meure immédiatement, pour peu que je punisse le coupable, plutôt que de rester ici, à être la risée de tous, assis sur mes vaisseaux, poids inutile de la terre.<ref>Homère, ''Iliade'', XVIII, v. 96-104 ; cité par Socrate en ''Apologie'', 28c-d.</ref></blockquote> Achille a donc méprisé la mort pour préserver l’honneur. Celui qui occupe une place, explique Socrate, doit y rester, au péril de sa vie, « sans tenir compte d’autre chose que du déshonneur ». Cet argument est une adaptation du modèle homérique, et non une simple reprise. Socrate transforme l’héroïsme aristocratique d’Achille (celui d’un demi-dieu, fils d’une déesse) en une vertu démocratique accessible à tout soldat de la phalange hoplitique : il s’agit de tenir son poste, quelle que soit sa place, qu’on l’ait choisie ou qu’on y ait été affecté par son chef (28d). La vertu n’est plus aristocratique, elle est civique ; elle n’est plus la propriété d’une élite, elle est accessible à quiconque occupe sa place avec fermeté. Cette démocratisation de la vertu héroïque prépare la transposition qui va suivre. Socrate rappelle d’ailleurs son propre passé militaire. Il a combattu à Potidée (432-429), à Amphipolis (424), et surtout à Délion (424), où son courage fut loué par Alcibiade dans le ''Banquet'' (219e et suivants<ref>Platon, ''Banquet'', 219e-221c.</ref>) et par le général Lachès dans le dialogue du même nom (''Lachès'', 181a-b<ref>Platon, ''Lachès'', 181a-b.</ref>). Comme ces soldats qui ne quittent pas leur poste, Socrate ne peut quitter celui qui lui a été assigné, par le dieu. ===== Le poste assigné par le dieu (28d-29a) ===== Socrate pose alors la transposition capitale : <blockquote>le poste qu’on m’a assigné, moi, est celui du philosophe, qui doit vivre en philosophant, en s’examinant soi-même et en examinant les autres. Je ne peux le quitter par crainte de la mort, pas plus qu’un soldat ne peut quitter le sien.</blockquote> La philosophie est ainsi présentée comme une assignation divine, équivalente à l’ordre d’un chef de guerre, et plus impérieuse encore, puisque l’ordre vient du dieu. Cette analogie entre vie philosophique et vie militaire, qui fera carrière dans toute la tradition stoïcienne (Sénèque, Épictète, Marc Aurèle en useront abondamment<ref>Voir notamment Épictète, ''Entretiens'', I, 9, 24 ; III, 24, 31-36 ; Marc Aurèle, ''Pensées'', III, 5 ; VII, 45.</ref>), fonde la philosophie comme service, comme ''officium'', comme devoir qu’on ne peut déserter sans se déshonorer. ===== L’ignorance de la mort (29a-b) ===== Vient alors l’un des passages les plus célèbres du texte, où Socrate renverse la psychologie commune du courage : <blockquote>Craindre la mort, Athéniens, ce n’est rien d’autre que se donner pour savant sans l’être ; c’est donner l’impression qu’on sait ce qu’on ne sait pas. (29a)</blockquote> Car personne ne sait ce qu’est la mort, ni si elle n’est pas pour l’homme le plus grand des biens ; mais on la redoute comme si l’on savait qu’elle est le plus grand des maux. C’est là, dit Socrate, la forme la plus répréhensible d’ignorance : croire savoir ce qu’on ne sait pas, donc la même erreur que celle des faux sages qu’il a démasqués dans son enquête. Le raisonnement est d’une rigueur remarquable. Il articule le savoir du non-savoir à l’éthique du courage. Socrate, lui, sait qu’il ne sait rien de la mort ; il ne la craint donc pas. Mais il sait en revanche, et c’est la seule « exception » à son ignorance proclamée, que <blockquote>commettre une injustice et désobéir à un meilleur que soi, dieu ou homme, cela je sais que c’est mauvais et honteux. (29b)</blockquote> On voit ici le dispositif éthique qui va commander toute la suite : entre un mal certain (l’injustice et la lâcheté) et un mal supposé mais incertain (la mort), le sage choisit sans hésiter d’éviter le premier. Le courage philosophique n’est donc pas un mépris enthousiaste de la mort, comme celui d’Achille, c’est une lucidité sur ce qui est réellement à craindre. Ce renversement de la psychologie héroïque en lucidité rationnelle est l’un des gestes fondateurs de la philosophie morale. ===== L’hypothèse de l’acquittement conditionnel (29c-30c) ===== Socrate imagine alors une situation extrême, une sorte d’expérience de pensée. Supposons que les juges lui offrent de l’acquitter à la condition qu’il cesse de philosopher. Alors Socrate répondrait : <blockquote>Athéniens, je vous suis reconnaissant et je vous aime, mais j’obéirai au dieu plutôt qu’à vous ; et tant qu’il me restera un souffle de vie, tant que j’en serai capable, je ne cesserai, soyez-en sûrs, de philosopher, de vous exhorter et de m’expliquer avec tel ou tel d’entre vous. (29d)</blockquote> Il continuerait à dire à chacun la formule qui résume toute sa mission : <blockquote>Ô excellent homme, toi qui es d’Athènes, la cité la plus grande et la plus réputée pour son savoir et sa puissance, tu n’as pas honte de t’occuper de ta fortune et des moyens de t’enrichir le plus possible, de ta réputation, des honneurs, alors que de ton intelligence, de la vérité, de ton âme et des moyens de la perfectionner, tu ne t’en occupes et ne t’en soucies aucunement ? (29d-e)</blockquote> On est ici au cœur du message socratique, tel que Platon l’a consigné. Le renversement qu’opère ce passage est philosophiquement majeur. D’une part, Socrate affirme que son obéissance au dieu l’emporte sur son obéissance à la cité : c’est, en puissance, toute la doctrine de la désobéissance civile au nom d’une norme transcendante. D’autre part, il renverse la hiérarchie des biens : la vertu ne vient pas de l’argent, mais l’argent et tous les autres biens viennent de la vertu ; il faut donc se soucier prioritairement de son âme (''psuchḗ''), et non de ses biens matériels ou de sa réputation. L’''Apologie'' est ainsi, dans la philosophie occidentale, l’un des textes où s’origine ce thème du souci de soi (''epiméleia heautoû'') compris comme soin de l’âme et examen permanent de soi-même, thème qui parcourra toute la tradition ultérieure, des écoles hellénistiques aux spirituels chrétiens, jusqu’aux lectures contemporaines de Michel Foucault qui en fera un objet majeur de ses derniers cours au Collège de France<ref name="foucault-hs">Michel Foucault, ''L’Herméneutique du sujet. Cours au Collège de France, 1981-1982'', éd. F. Gros, Paris, Gallimard/Seuil, coll. « Hautes Études », 2001, en particulier les leçons des 6, 13 et 20 janvier 1982.</ref>. Socrate ajoute alors l’une de ses déclarations les plus provocatrices : <blockquote>Là-dessus, Athéniens, croyez-en ou non Anytos, acquittez-moi ou ne m’acquittez pas, toujours est-il que je ne changerai pas de conduite, même si je devais souffrir mille morts. (30c)</blockquote> La défense ne demande plus un acquittement ; elle proclame la pérennité de la mission, quel que soit le verdict. Socrate, à ce moment précis du plaidoyer, cesse d’être un accusé pour devenir un apôtre. Cette attitude explique, rétrospectivement, l’incompréhension et l’irritation des jurés : il ne se défend pas, il les défie. ==== Socrate, « taon de la cité » (30c-31c) ==== Socrate affirme alors, avec une audace étonnante, <blockquote>si vous me condamnez à mort, ce n’est pas à moi, mais à vous-mêmes, que vous ferez le plus de tort. (30c)</blockquote> Car ni Mélétos ni Anytos ne peuvent le léser véritablement : ils peuvent le tuer, l’exiler, le priver de ses droits civiques (''atimía''), choses que certains tiendraient pour de grands malheurs, mais lui ne les tient pas pour tels. Le vrai mal est celui que font à leur âme ceux qui entreprennent de tuer injustement. Cette thèse, que Platon développera dans le ''Gorgias'' (469c) sous la forme « il vaut mieux subir l’injustice que la commettre »<ref>Platon, ''Gorgias'', 469c : « [...] je choisirais de subir plutôt que de commettre l’injustice. » Voir aussi 474b-479e.</ref>, est probablement la plus radicale de toutes les thèses morales de l’antiquité. Surgit alors la célèbre image du taon. Socrate est <blockquote>un homme attaché à la cité par le dieu, comme le serait un taon au flanc d’un cheval de grande taille et de bonne race, mais qui se montrerait un peu mou en raison même de sa taille et qui aurait besoin d’être réveillé par l’insecte. (30e)</blockquote> Athènes est ce cheval noble mais assoupi ; Socrate est l’insecte qui le pique, le réveille, le harcèle. Le dieu lui a donné cette mission, qui explique qu’il passe son temps à aborder chacun, « comme un père ou un frère plus âgé », pour le persuader d’avoir souci de la vertu. Cette image mérite qu’on s’y arrête, tant elle est dense. Elle articule trois éléments. D’abord, la noblesse du cheval : Athènes n’est pas critiquée absolument, mais reconnue pour ce qu’elle est, la plus belle cité du monde grec, de « grande taille et de bonne race ». Ensuite, son engourdissement : cette grandeur même la rend molle, somnolente, incapable de s’ébrouer spontanément. Enfin, la nécessité du taon : seule une figure dérangeante, insupportable, inutile en apparence, peut réveiller la cité. Le taon n’est pas à sa place dans le cheval ; il est un corps étranger, irritant ; mais précisément, c’est de cette position dérangeante que vient son utilité. La philosophie est pensée ici comme critique nécessaire, comme dissidence féconde, comme décalage qui maintient la cité vivante. On voit se dessiner une dialectique subtile. Socrate est indissociablement dedans et dehors : citoyen d’Athènes, engagé dans sa cité, respectueux de ses lois au point d’accepter la mort plutôt que de fuir (comme l’exprimera le ''Criton''<ref>Platon, ''Criton'', 50a-54d.</ref>), et en même temps étranger à ses conformismes, à ses illusions, à ses complaisances. Sans le taon, le cheval dormirait ; mais le cheval peut aussi, d’un mouvement irrité, écraser le taon. C’est exactement ce qui se passe au procès. L’image contient en elle-même une prophétie : tuer le taon, c’est se priver de la piqûre bienfaisante, condamner la cité au sommeil. D’où la prédiction de Socrate : <blockquote>en suite de quoi, vous passeriez votre vie à dormir, à moins que le dieu, ayant souci de vous, ne vous envoie quelqu’un d’autre. (31a)</blockquote> La suite de l’histoire, à tout le moins celle de la pensée, dira que cet autre sera Platon lui-même, puis Aristote, et la longue lignée des philosophes que l’''Apologie'' aura rendus possibles. Socrate apporte ensuite une preuve empirique de son désintéressement : sa pauvreté. Si son activité avait un but intéressé, si elle rapportait un salaire, on pourrait douter de la pureté de ses motivations. Mais ses accusateurs, malgré leur acharnement, n’ont pu produire aucun témoin attestant qu’il ait jamais exigé ou reçu un salaire. Sa misère est la meilleure preuve qu’il dit vrai. Cette insistance sur la gratuité de son enseignement est une pique adressée aux sophistes, qui se faisaient richement rémunérer, et un trait supplémentaire qui distingue la philosophie socratique de la ''téchnē'' marchande des sophistes. Socrate n’est pas un prestataire de services ; il est un serviteur du dieu. ==== Le démon et la prudence politique (31c-32e) ==== Une objection se présente naturellement : si Socrate est ce grand conseiller des particuliers, pourquoi ne monte-t-il pas à la tribune pour conseiller la cité elle-même dans ses assemblées ? La réponse est le fameux passage sur le ''daimónion'' socratique. ===== Qu’est-ce que le démon de Socrate ? (31c-d) ===== Socrate confie aux juges ce qu’il a déjà dit « maintes fois en maints endroits » : <blockquote>il m’advient quelque chose de divin et de démonique (''theîón ti kai daimónion''), une voix intérieure qui, depuis [mon] enfance, [...] chaque fois qu’elle m’advient, me détourne toujours de ce que je me propose de faire, mais jamais ne m’y encourage. (31c-d)</blockquote> Cette voix a trois caractéristiques remarquables. Elle est toujours dissuasive : « jamais elle ne m’y encourage ». Elle est personnelle : elle ne s’adresse qu’à Socrate. Elle est présente depuis l’enfance, donc constitutive de son rapport au monde. Elle s’est précisément opposée à son entrée en politique. Il s’agit donc, littéralement, de cette « divinité nouvelle » que l’accusation lui impute, ce que Mélétos, ironise Socrate, a d’ailleurs « consigné dans son acte d’accusation » (31d). Cette ironie est cinglante : l’accusation a pris pour un crime ce que Socrate revendique comme une grâce. La nature du ''daimónion'' a fait l’objet de multiples interprétations, dès l’Antiquité et jusqu’aux temps modernes. Plutarque a consacré un traité entier à la question (''Du démon de Socrate'') dans lequel il discute plusieurs hypothèses<ref>Plutarque, ''Du démon de Socrate'' (''De genio Socratis''), dans ''Œuvres morales'', t. VIII, trad. J. Hani, Paris, Les Belles Lettres, 1980.</ref>. À l’époque moderne, on l’a interprété tour à tour comme une hallucination d’un névrosé<ref>F. Lélut, ''Du démon de Socrate, spécimen d’une application de la science psychologique à celle de l’histoire'', Paris, Trinquart, 1836.</ref>, comme une manifestation de l’inconscient<ref>Arthur Koestler, ''Le Démon de Socrate'', Paris, Calmann-Lévy, 1970.</ref>, comme la voix de la conscience morale<ref>G. W. F. Hegel, ''Leçons sur l’histoire de la philosophie'', tome II (sur Socrate), trad. G. Marmasse, Paris, Vrin, 2007, p. 316-321.</ref>, comme une inspiration divine authentique<ref>Henri Bergson, ''Les Deux sources de la morale et de la religion'' (1932), dans ''Œuvres'', éd. du Centenaire, Paris, PUF, 1959, p. 1027.</ref>, comme une intuition irrationnelle<ref>E. R. Dodds, ''Les Grecs et l’irrationnel'' (1951), trad. M. Gibson, Paris, Flammarion, coll. « Champs », 1977, chap. VII.</ref>. Nietzsche y voyait, de manière originale, la monstruosité d’un homme chez qui l’instinct, contrairement à l’ordinaire, ne crée pas mais critique, et chez qui la conscience rationnelle est au contraire créatrice : Socrate comme « homme théorique », rupture dans l’histoire de l’esprit grec dionysiaque<ref name="nietzsche-nt">Friedrich Nietzsche, ''La Naissance de la tragédie'' (1872), § 13-15, trad. P. Lacoue-Labarthe, dans ''Œuvres philosophiques complètes'', t. I, Paris, Gallimard, 1977.</ref>. Il faut probablement admettre, avec Claude Chrétien, que le démon est irréductible à une simple astuce défensive ou à un symbole : Socrate y croyait réellement, au point de risquer sa vie en le suivant<ref>Chrétien, ''op. cit.'', p. 28-29.</ref>. Son caractère purement négatif (il inhibe, ne prescrit jamais) en fait un signe du divin dans la vie humaine, mais un signe essentiellement limitatif : la divinité indique seulement ce qu’il ne faut pas faire, et laisse à l’homme la responsabilité de chercher, par l’examen rationnel, ce qu’il doit faire. Cette structure, où le divin ne donne pas la vérité mais seulement la limite, est cohérente avec la théologie négative que Socrate manifeste dans tout le plaidoyer : nous ne savons pas positivement ce que sont les dieux, mais nous recevons d’eux des signes qui nous empêchent de nous égarer. ===== Pourquoi pas la politique ? (31d-32a) ===== Socrate explique que si le démon l’a détourné de la politique, c’est pour préserver sa vie : « s’il y avait longtemps que j’avais entrepris de faire de la politique, il y a longtemps que je serais mort ». Et il formule alors une sentence vertigineuse, qui est peut-être la plus critique de l’''Apologie'' à l’égard de la démocratie athénienne : <blockquote>il n’y a personne au monde qui puisse garder la vie sauve s’il s’oppose loyalement à vous ou à toute autre collectivité, et s’il cherche à empêcher qu’il ne se produise dans la cité de nombreuses injustices et illégalités. Mais nécessairement tout vrai champion de la justice, s’il veut garder la vie sauve ne serait-ce qu’un peu de temps, doit vivre en simple particulier (''idiōteúein'') mais non en homme public. (32a)</blockquote> Cette sentence est d’une portée immense. Elle signifie que la politique telle qu’elle se pratique à Athènes est incompatible avec la justice. Le juste, s’il veut vivre, doit rester à l’écart des affaires publiques ; et s’il y entre, il doit s’attendre à mourir. C’est la cassure socratique avec la tradition civique grecque, qui voyait dans la participation politique (''politeía'') la plus haute réalisation de l’homme libre. L’homme proprement libre, pour les Grecs classiques, c’est le citoyen qui prend part aux assemblées ; l’''idiṓtēs'' qui se retire dans la sphère privée est, sinon méprisé, du moins considéré comme incomplet. Socrate renverse cette hiérarchie : la vraie vie politique, pour le juste, passe par le retrait de la politique officielle et par une politique privée, celle de la discussion personne à personne, de l’enseignement moral qui opère non par les discours publics mais par l’examen intime de chacun. C’est, comme le suggère la présentation de la collection Flammarion, « l’espace d’une autre politique »<ref name="brisson-mace-presentation">Arnaud Macé, « Présentation », dans Platon, ''Apologie de Socrate'', traduction par Luc Brisson, Paris, GF-Flammarion, 2017.</ref>. Cette thèse, qu’on peut lire comme une désertion civique, est aussi une critique profonde des conditions de la délibération démocratique. Elle rejoint ce que Platon développera dans la ''République'' : la cité idéale est celle où la philosophie serait au pouvoir, non celle où elle est écrasée par le plus grand nombre. Mais l’''Apologie'' n’est pas encore la ''République'' : Socrate n’y propose pas une contre-cité, il y constate seulement qu’aucune cité existante ne permet au juste de participer à ses affaires sans se renier. ===== Les deux épisodes probants (32a-e) ===== Socrate prouve cette thèse par deux épisodes biographiques, choisis avec soin. Le premier se situe sous la démocratie, en 406, l’année du procès des généraux des Arginuses. Les Athéniens avaient remporté une victoire navale importante contre Sparte près des îles Arginuses (au large de Lesbos), mais les généraux victorieux avaient été empêchés par une tempête de ramasser les cadavres et les naufragés athéniens, violation grave des usages religieux. Rentrés à Athènes, ils furent mis en cause ; mais au lieu de leur garantir un procès individuel comme l’exigeait le droit athénien, l’Assemblée, emportée par la colère populaire, voulut les juger en bloc. Socrate siégeait ce jour-là au Conseil, comme prytane pour sa tribu, l’Antiochide. Il fut le seul des cinquante prytanes à refuser de mettre aux voix cette motion collective, malgré les menaces et les cris de la foule<ref>Le récit détaillé du procès des généraux des Arginuses se trouve chez Xénophon, ''Helléniques'', I, 7, 1-35.</ref>. Les orateurs voulaient le faire arrêter sur-le-champ, les citoyens eux-mêmes l’y encourageaient ; il tint bon. Les généraux furent néanmoins jugés et six d’entre eux exécutés. Peu après, Athènes regretta sa décision, mais Socrate avait risqué sa vie pour la légalité, sans succès immédiat. Le second épisode se situe sous l’oligarchie, en 404. Les Trente l’avaient convoqué avec quatre autres citoyens à la Tholos (la rotonde, siège des prytanes occupée par le régime) et lui avaient ordonné d’aller chercher à Salamine un riche citoyen démocrate, Léon, pour qu’il soit exécuté et que ses biens soient confisqués. C’était une manœuvre classique des Trente : compromettre un maximum de citoyens dans leurs crimes pour les rendre solidaires du régime<ref>Xénophon, ''Helléniques'', II, 3, 39 ; Platon, ''Lettre VII'', 324d-325a.</ref>. Socrate, lui, refusa l’ordre. Il rentra simplement chez lui pendant que les quatre autres allaient chercher Léon, qui fut assassiné. Socrate, rappelle-t-il, aurait probablement payé cela de sa vie si les Trente n’avaient pas été renversés peu après (c’était le cas : le régime tomba en 403). Ces deux épisodes sont politiquement remarquables, et Platon les a sans doute choisis avec une intention nette. Ils montrent Socrate s’opposant également aux excès de la démocratie (procès des Arginuses) et à ceux de l’oligarchie (affaire de Léon), par fidélité à une justice supérieure au régime en place. Il n’est ni un démocrate de conviction ni un oligarque : il est un homme qui, dans l’un et l’autre cas, risque sa vie pour ne pas commettre d’injustice. Cette double symétrie est cruciale : elle répond par avance à tous ceux qui, dans la démocratie restaurée de 399, voudraient voir en Socrate un sympathisant des Trente, en raison de ses liens avec Critias et Charmide. Platon montre au contraire que Socrate a résisté aux Trente au péril de sa vie. Mais il montre également qu’il a résisté à la démocratie elle-même, quand celle-ci violait le droit. La neutralité politique de Socrate, ou plutôt cet au-delà du partisanisme, constitue une part de sa radicalité, qui déconcerte tous ceux qui voudraient l’enrôler dans un camp. ==== Socrate et ses « disciples » (33a-34b) ==== Socrate en vient alors au dernier volet du premier discours : la question des jeunes gens qu’on l’accuse d’avoir corrompus. Il récuse d’abord le terme même de « disciple » : <blockquote>je n’ai jamais, moi, été le maître (''didáskalos'') de personne. (33a)</blockquote> Cette affirmation est importante. Elle distingue radicalement Socrate des sophistes, qui se présentaient comme maîtres (''didáskaloi'') et vendaient un enseignement structuré ; Socrate n’a jamais promis un enseignement, jamais fait payer, jamais suivi un programme ; il a parlé à quiconque voulait l’écouter, jeunes et vieux, riches et pauvres, sans distinction, et n’est pas responsable de ce que chacun devient au sortir de la conversation. Cette posture est philosophiquement significative : elle implique que la philosophie n’est pas transmissible comme une technique, mais seulement comme une pratique qu’on ne peut qu’imiter. Socrate produit alors un argument a contrario d’une grande force. Si réellement il avait corrompu les jeunes, pourquoi n’est-ce pas ''eux-mêmes'', devenus adultes, qui viendraient témoigner contre lui ? Ou du moins leurs proches, parents, frères, qui auraient à se plaindre de cette corruption et en seraient les premiers concernés ? Or, bien au contraire, nombre de ses familiers, ou de leurs proches, sont présents à l’audience pour le soutenir. Socrate en énumère plusieurs, nommément, dans un passage qui vaut témoignage historique : Criton et son fils Critobule, Lysanias de Sphettos père d’Eschine (l’auteur de dialogues socratiques), Antiphon père d’Épigène, Nicostratos frère de Théodotos, ainsi qu’Adimante (le frère aîné de Platon) et plusieurs autres<ref>''Apologie'', 33d-34a. Sur ces personnages, voir Debra Nails, ''The People of Plato: A Prosopography of Plato and Other Socratics'', Indianapolis, Hackett, 2002.</ref>. <blockquote>Je pourrais citer pour vous beaucoup d’autres hommes, parmi lesquels il aurait fallu que Mélétos produise, au cours de son discours, quelque témoin. (34a)</blockquote> Cette preuve par les témoins absents est d’une grande force logique : l’accusateur a été incapable de trouver, parmi tous ceux qui auraient dû être les premières victimes, un seul pour le blâmer. C’est ce qu’on appelle un argument ''a silentio'' : le silence des supposées victimes prouve l’innocence de l’accusé. On notera que Platon se fait ici historien. En nommant les disciples présents, il fixe un moment dans le temps et offre à la postérité un témoignage vérifiable. Il se nomme lui-même plus loin (34a, puis 38b), parmi les amis prêts à se porter caution pour l’amende. Cette présence documentaire est rare dans les dialogues platoniciens, où Platon s’efface généralement : ici, il est témoin du procès de son maître. ==== Le refus du pathos (34b-35d) ==== Socrate aborde alors la péroraison de son premier discours. Il sait parfaitement ce qu’on attend d’un accusé athénien au moment crucial : larmes, supplications, présentation de la femme et des enfants éplorés, appel à la pitié, théâtralité de la détresse. Cette mise en scène, connue de tous par les comédies d’Aristophane et par l’habitude des tribunaux, était devenue quasi rituelle<ref>Sur la caricature du tribunal populaire, voir Aristophane, ''Les Guêpes'', v. 548-630.</ref>. Socrate a trois fils, dont l’un est déjà adolescent (''meirákion'') et les deux autres encore petits ; il pourrait les faire paraître, lui qui ne refuse pas d’être un homme. Mais il ne le fera pas. Pourquoi ? Deux motifs convergent. D’abord, par souci de l’honneur : un homme de sa réputation, réelle ou supposée, ne peut s’abaisser à ces scènes sans se ridiculiser et sans « ridiculiser la cité ». Les citoyens étrangers qui l’observent et qui connaissent la réputation d’Athènes s’étonneraient de voir les plus éminents de ses hommes se comporter de manière indigne. Socrate invoque la ''dóxa'' (l’opinion) d’Athènes aux yeux du monde grec pour justifier son refus de jouer le jeu. Mais surtout, et c’est le second motif, plus profond : il ne serait pas juste de supplier le juge. Et ici, Socrate formule une analyse capitale de la fonction judiciaire : <blockquote>le juge ne siège pas pour réduire la justice en faveur (''charízesthai''), mais pour décider de ce qui est juste ; et il a fait serment non de favoriser qui lui plaît, mais de rendre la justice selon les lois. (35c)</blockquote> Le juge athénien prêtait en effet un serment (l’''héliastikós hórkos'') par lequel il s’engageait à juger selon les lois<ref>Sur le serment héliastique, voir Démosthène, ''Contre Timocrate'', 149-151 ; Adriaan Lanni, ''Law and Justice in the Courts of Classical Athens'', Cambridge, Cambridge University Press, 2006, p. 75-76.</ref>. Supplier les juges, c’est leur demander de se parjurer, donc d’introduire le parjure dans la cité, donc de commettre une impiété effective contre les dieux garants du serment. Le geste de Socrate est ici d’une cohérence parfaite et quasi mathématique. Lui qui est accusé d’impiété ne peut, à l’instant critique, demander aux juges de commettre une vraie impiété (le parjure) pour le sauver. Ce serait confirmer dans les faits, activement, l’accusation qu’il réfute en paroles. Il préfère la mort à cet abaissement, qui serait en outre, non plus imaginairement mais réellement, une atteinte aux dieux de la cité. Par ce refus, Socrate démontre par les actes ce qu’il prétendait par les mots : il est le véritable pieux, et ce sont les accusateurs qui, en voulant la mort d’un juste, pratiquent la vraie impiété. Le premier discours s’achève là. Les juges votent. Socrate est déclaré coupable. La majorité est étroite : si trente voix s’étaient portées sur l’autre bord, Socrate aurait été acquitté. Pour un jury de 501, cela suggère un vote d’environ 280 contre 221 (chiffres que Diogène Laërce confirme dans son récit<ref>Diogène Laërce, ''Vies et doctrines des philosophes illustres'', II, 41.</ref>, mais qui ne sont pas dans Platon). Ce qui est frappant, c’est la relative faiblesse de la condamnation : l’acquittement était à portée. === Le second discours : la contre-peine (35e-38b) === La procédure athénienne exige maintenant que Socrate propose une peine alternative à celle réclamée par l’accusation. Mélétos a proposé la mort. L’usage voulait qu’on proposât une peine sensiblement moins sévère (un lourd exil, une amende importante) pour donner au jury une alternative crédible. Le calcul tacite, dans les procès à peine à estimer, consistait à offrir une sanction à peine en deçà de celle demandée par l’accusateur, de manière à ne pas trop décevoir les attentes du jury tout en se ménageant un sort moins dur. Socrate va adopter une tout autre stratégie. ==== La contingence du vote (35e-36b) ==== Socrate remarque d’abord, avec une ironie qui frise la provocation, qu’il est étonné non pas d’avoir été condamné, mais de l’avoir été à une si faible majorité. Il s’attendait à une condamnation bien plus nette. Si trente voix de plus avaient basculé, il aurait été acquitté. Il observe aussi que, ce qui le perd véritablement, ce n’est pas Mélétos : car sans l’appui d’Anytos et de Lycon, le seul Mélétos, compte tenu des voix obtenues, aurait dû payer mille drachmes d’amende pour n’avoir pas recueilli le cinquième des suffrages (règle destinée à décourager les plaintes frivoles). En divisant malicieusement les voix reçues entre ses trois accusateurs, Socrate montre que Mélétos seul n’aurait pas obtenu sa condamnation. Cette remarque, en apparence technique, est profondément déstabilisante : elle montre que la procédure qui vient de condamner Socrate est elle-même contingente, dépendante du nombre d’accusateurs autant que du fond du dossier. Elle suggère, au passage, que la véritable force du parti de l’accusation réside dans Anytos, l’homme politique, non dans Mélétos, le plaignant nominal. Le procès apparaît donc comme un montage politique, sous un habillage religieux. ==== La proposition du Prytanée (36b-37a) ==== Quelle contre-peine proposer ? Socrate prend la question au sérieux, mais dans un sens retourné : quelle peine mérité-je ? Il rappelle toute sa vie : avoir négligé les affaires, l’argent, les magistratures, les assemblées et les honneurs, pour se consacrer au service privé de la vertu, <blockquote>en essayant de convaincre chacun d’entre vous de ne pas se préoccuper de ses affaires personnelles avant de se préoccuper, pour lui-même, de la façon de devenir le meilleur et le plus sensé possible. (36c)</blockquote> Que mérite un homme ainsi ? Un bon traitement, dit-il, et non une peine. Il propose donc non pas un châtiment, mais une récompense : être nourri aux frais de l’État au Prytanée. Le Prytanée était à Athènes l’édifice public où la cité nourrissait, aux frais de l’État, les prytanes en exercice, les hôtes officiels et les citoyens illustres, notamment les vainqueurs des jeux Olympiques et les bienfaiteurs de la patrie. C’était la plus haute distinction civique, l’équivalent d’une reconnaissance par l’État comme héros ou sauveur de la cité. Socrate explique qu’il la mérite plus que quiconque : <blockquote>si celui-ci [le vainqueur olympique] vous procure l’apparence du bonheur, je vous en offre, moi, la réalité ; lui n’a aucun besoin d’être nourri, mais moi, j’en ai besoin. (36d-e)</blockquote> L’argument est double : Socrate est un bienfaiteur réel (plus que le champion olympique, dont la gloire n’est que sportive) et il est pauvre (donc il a besoin de ce soutien alimentaire, alors que le champion en a moins besoin). Cette proposition est manifestement provocatrice, et aucun commentateur n’en doute. Socrate ne joue plus le jeu de la procédure ; il retourne le tribunal. Pour un jury qui vient de le condamner, proposer d’être traité en héros civique est une humiliation délibérée. Comment comprendre cette audace ? Plusieurs explications se combinent. D’abord, une cohérence logique : Socrate est convaincu de n’avoir commis aucune injustice, il refuse donc de s’infliger une peine comme s’il était coupable. Ensuite, une fidélité à sa parole : s’il proposait une peine qu’il estime injuste, il trahirait son principe d’agir toujours selon le juste. Enfin, peut-être, une acceptation anticipée de la mort : il est âgé (soixante-dix ans, espérance de vie largement dépassée dans l’Antiquité), il a accompli sa mission, il ne craint pas la sentence, il n’a donc aucune raison de ruser. À quoi s’ajoute, plus subtilement, un calcul dramatique : proposer une vraie contre-peine reviendrait à reconnaître la compétence du tribunal ; en proposant une récompense, Socrate refuse symboliquement la sentence avant même qu’elle ne tombe. ==== L’examen des autres peines et la proposition d’amende (37a-38b) ==== Socrate continue son raisonnement avec une rigueur didactique : puisque je sais que je ne me cause volontairement de tort à personne, je ne vais pas, loin de là, m’en causer à moi-même en proposant une peine qui serait un tort. Il passe alors en revue, méthodiquement, les peines possibles. La prison ? Ce serait passer sa vie soumis au pouvoir des Onze, les magistrats qui administraient les prisons et les exécutions, donc à une sujétion indigne. Une amende lourde assortie de contrainte par corps jusqu’au paiement ? Cela revient au même : il n’a pas d’argent. L’exil ? C’est ici que Socrate développe sa réponse la plus fine. Il serait absurde, dit-il, de choisir l’exil : si ses propres concitoyens ne supportent plus ses entretiens, au point d’en vouloir « se débarrasser », pourquoi les étrangers les supporteraient-ils davantage ? Il serait chassé de ville en ville. Et ne pourrait-il vivre en se taisant ? Non, et ici vient l’un des sommets du texte : cesser de philosopher équivaudrait à désobéir au dieu. <blockquote>Il n’y a pas pour un homme de plus grand bien que de s’entretenir chaque jour de la vertu et des autres sujets dont vous m’entendez discuter, en examinant moi-même les autres ; car une vie sans examen n’est pas digne d’être vécue par un homme. (38a)</blockquote> Cette dernière formule, en grec ''ho anéxetastos bíos ou biōtós anthrṓpōi'', est probablement la plus célèbre de toute l’''Apologie'' et peut-être de toute la philosophie antique. Elle condense le programme de la philosophie socratique : la vie doit être soumise à un examen (''exétasis'') constant, à une interrogation rationnelle sur ses buts et ses valeurs. Sans cet examen, on ne vit pas une vie proprement humaine. Formule vertigineuse, qui fait de la philosophie non pas un luxe intellectuel mais la condition même d’une existence digne de ce nom. Elle suggère qu’il existe un seuil d’humanité : en deçà de l’examen, l’homme n’est pas pleinement homme. La philosophie cesse alors d’être une option ajoutée à la vie pour devenir l’élément qui en fait une vie humaine. Finalement, Socrate concède une mine d’argent (somme modique, correspondant à peu près à trois mois de salaire d’un ouvrier qualifié), mais accepte, sur la pression de ses amis (Platon, Criton, Critobule, Apollodore), de proposer trente mines, avec leur caution personnelle. C’est une somme importante, équivalent à plusieurs années de salaire, mais manifestement insuffisante face à la proposition de mort, et la manière dont elle est introduite (sous la pression des amis, comme en dernier recours) ne masque pas que Socrate lui-même n’y adhère pas vraiment. Le jury vote une seconde fois. Cette fois, la majorité est beaucoup plus nette : selon les chiffres traditionnels rapportés par Diogène Laërce, quatre-vingts jurés supplémentaires ont voté contre Socrate par rapport au premier vote, manifestement indignés par son attitude. Socrate est condamné à mort. === Le troisième discours : après la condamnation (38c-42a) === Ce troisième discours n’est pas prévu par la procédure. Une fois la sentence prononcée, les magistrats passent aux formalités d’enregistrement, notamment la notification aux Onze, chefs des geôliers et du bourreau. Socrate prend pourtant la parole une dernière fois, sans doute pendant que les Onze procèdent à ces écritures, pour s’adresser à la partie de l’assistance qui est restée sur place. Il s’agit d’une péroraison spontanée, hors procédure, probablement élargie et composée par Platon pour les besoins du texte, même si l’événement lui-même est plausible. Ce discours se divise en deux moments : d’abord aux jurés qui ont voté sa mort, puis à ceux qui ont voté son acquittement. ==== Aux jurés de la condamnation (38c-39d) ==== Socrate commence par un constat ironique : les Athéniens ont gagné peu de temps, il est âgé, il serait mort bientôt de toute façon. Mais en échange, ils vont gagner <blockquote>le renom, auprès des gens avides de diffamer notre cité, d’avoir fait mourir un sage en la personne de Socrate. (38c)</blockquote> La réputation d’Athènes souffrira de ce procès bien au-delà de ce qu’elle a cru gagner. La prédiction est parfaitement exacte : la condamnation de Socrate a, dans la mémoire collective de l’Occident, sérieusement terni l’image de la démocratie athénienne. Surtout, Socrate retourne contre les juges l’argument central de son plaidoyer : s’il n’a pas réussi à les persuader, ce n’est pas faute d’arguments, mais parce qu’il n’a pas voulu employer les tactiques indignes (larmes, supplications) qu’ils attendaient. <blockquote>Je préfère de beaucoup mourir après m’être défendu comme je l’ai fait plutôt que vivre après un plaidoyer à leur façon. (38e)</blockquote> Et il prononce cette antithèse fameuse : <blockquote>la difficulté n’est pas d’échapper à la mort, elle est bien plus d’échapper à la lâcheté (''ponēría''), car elle court plus vite que la mort. En l’occurrence, moi qui suis lent et vieux, j’ai été rattrapé par la plus lente des deux [la mort], cependant que mes accusateurs, qui sont lestes et rapides, ont été rattrapés par la plus rapide, qui est la méchanceté. (39a-b)</blockquote> L’image est saisissante : chaque homme est poursuivi par son destin, mais les uns sont rattrapés par la mort physique et les autres par la mort morale, infiniment plus grave. Socrate se permet alors une prophétie (''manteúomai''). Il est, dit-il, <blockquote>au point où les hommes prophétisent le mieux, quand ils sont à la veille de mourir, (39c)</blockquote> allusion à la croyance grecque selon laquelle les mourants acquièrent un don de divination (voir le motif du chant du cygne dans le ''Phédon'' 84e<ref>Platon, ''Phédon'', 84e-85b : « les cygnes [...], lorsqu’ils sentent qu’ils vont mourir, chantent ce jour-là plus fort et plus beau qu’ils n’ont jamais chanté, dans la joie d’aller trouver le dieu dont ils sont les serviteurs ».</ref>). Sa prophétie est terrible : <blockquote>un châtiment vous viendra aussitôt après ma mort, bien plus pénible que celui par lequel vous m’aurez tué. [...] Le nombre croîtra de ceux qui vous demanderont des comptes, que je retenais jusqu’ici, sans que vous vous en aperceviez ; et ils seront d’autant plus pénibles qu’ils sont plus jeunes. (39c-d)</blockquote> Tuer n’est pas la façon de se délivrer du blâme ; la seule manière honorable est « de se préparer soi-même à être le meilleur possible ». Cette prophétie peut sembler s’accomplir, dans une certaine mesure, par l’histoire qui suivra : les « petits socratiques » (Antisthène le cynique, Aristippe, Euclide de Mégare), puis Platon et son Académie, puis Aristote et le Lycée, poursuivront inlassablement l’interrogation commencée par Socrate. Le procès et la mort de Socrate peuvent ainsi être lus comme le moment à partir duquel la philosophie s’affirme, dans la tradition platonicienne, comme une vocation publique à part entière. ==== Aux jurés de l’acquittement (39e-42a) ==== Socrate se tourne alors vers ceux qui ont voté pour lui, qu’il appelle désormais, seuls, ''juges'' véritables. Cette distinction terminologique est capitale : avant le verdict, tous étaient indistinctement ''andres'' (« messieurs ») ou ''Athēnaîoi'' (« Athéniens ») ; maintenant, seuls ceux qui ont voté juste méritent, selon Socrate, le titre de juges (''dikastaí''). Le tribunal vient d’être scindé en deux catégories asymétriques. Il leur confie deux pensées, l’une sur le signe divin, l’autre sur la mort. ===== Le silence du démon (39e-40c) ===== Son démon, dit-il, avait coutume de l’arrêter chaque fois qu’il s’apprêtait à faire quelque chose de mauvais, même dans des circonstances mineures. Or, aujourd’hui, depuis le matin, tout au long de cette journée qui l’a mené à la condamnation à mort, le démon ne s’est pas manifesté une seule fois. Il ne l’a pas arrêté au moment où il quittait son domicile, ni lorsqu’il montait au tribunal, ni à aucun moment de son plaidoyer. Cette absence est elle-même un signe : <blockquote>il y a des chances pour que ce qui m’est arrivé soit un bien ; et c’est nous qui faisons des suppositions incorrectes quand nous considérons la mort comme un mal. (40b-c)</blockquote> Le silence du démon est la preuve, pour Socrate, que la voie qu’il suit est la bonne. L’argument est philosophiquement subtil. Socrate ne dit pas qu’il sait que la mort est un bien ; il dit qu’il a une raison de penser que ce qui lui arrive est un bien, puisque le dieu (par la voix du démon) ne l’a pas arrêté. C’est un argument ''e silentio'' transposé sur le plan religieux : le silence divin, pour qui est habitué à être averti, vaut approbation. Cette structure de raisonnement est fragile, mais elle est cohérente avec la théologie socratique : la divinité se manifeste par ses interventions plutôt que par ses paroles positives ; son silence, quand il y a habitude d’intervention, est significatif. ===== Les deux hypothèses sur la mort (40c-41d) ===== [[Fichier:David - The Death of Socrates.jpg|vignette|centre|upright=2.0|Jacques-Louis David, ''La Mort de Socrate'', 1787, huile sur toile, 129,5 × 196,2 cm, New York, Metropolitan Museum of Art. Le tableau illustre à proprement parler la scène finale du ''Phédon'' plutôt que l’''Apologie'' ; il est cependant devenu l’image emblématique, dans la culture occidentale moderne, du philosophe maintenant, jusque dans la mort, la cohérence entre sa parole et sa vie.]] Socrate propose alors une méditation sur la mort, l’une des plus belles pages de la philosophie antique, construite comme une alternative raisonnée. De deux choses l’une : ou bien la mort est l’absence de toute sensation, ou bien elle est une migration (''metoíkēsis'') de l’âme de ce lieu vers un autre lieu. Dans la première hypothèse (la mort comme absence de sensation), la mort ressemble au sommeil sans rêve. Or qui n’aimerait pas, si on lui demandait de choisir entre une nuit de sommeil profond sans rêves et toutes les autres nuits et jours de sa vie, reconnaître que cette nuit est plus précieuse que la plupart ? Même le Grand Roi de Perse (homme réputé le plus heureux du monde aux yeux des Grecs) trouverait peu de jours comparables à une telle nuit. Si la mort est cela, alors la totalité du temps après la mort se réduit à « une seule nuit », et cette nuit est un gain. L’argument est intéressant par sa structure : il part d’une expérience commune (le sommeil sans rêve) pour désamorcer la peur métaphysique de la mort. Si l’on aime le sommeil quand il nous prend, pourquoi craindre la mort si elle lui ressemble ? La stratégie argumentative, qui rappelle celle d’Épicure et de Lucrèce plus tard (« la mort n’est rien pour nous »<ref>Épicure, ''Lettre à Ménécée'', § 125 : « la mort n’est rien pour nous, puisque tant que nous sommes, la mort n’est pas, et quand la mort est là, nous ne sommes plus ». Lucrèce, ''De la nature des choses'', III, v. 830 et suiv.</ref>), désarticule la crainte en la confrontant à ce que nous expérimentons quotidiennement. Dans la seconde hypothèse (la mort comme migration), la mort est un voyage vers l’au-delà, où l’on retrouve tous les morts. Que pourrait-on imaginer de plus heureux ? On serait délivré des juges qui prétendent juger ici-bas, pour rencontrer les vrais juges dont on dit qu’ils y rendent la justice : Minos, Rhadamante, Éaque (les trois juges infernaux traditionnels), plus Triptolème (qui les remplace parfois dans l’iconographie attique). On rencontrerait Orphée, Musée, Hésiode, Homère, les grandes figures poétiques et religieuses du passé. On pourrait y continuer, sans cette fois risquer la mort, l’activité d’examen qui fut la sienne sur terre : interroger Palamède, Ajax (tous deux morts par jugements injustes, comme lui-même : la comparaison est évidemment à son avantage), le chef de l’armée grecque à Troie (Agamemnon), Ulysse, Sisyphe, « et tant d’autres hommes et femmes qu’on pourrait nommer ». <blockquote>Discuter avec ceux de là-bas, vivre en leur société, les soumettre à examen, ne serait-ce pas le comble du bonheur ? Aussi bien, les gens de là-bas ne mettent à mort personne pour ce motif. (41c)</blockquote> Ce passage est riche de tonalités. Il y a une évidente dimension humoristique : Socrate imagine l’au-delà comme la continuation indéfinie de son activité terrestre, l’examen dialectique, mais cette fois sans risque, puisqu’on n’y meurt plus. L’Hadès devient une agora élargie à tous les temps. Il y a également une dimension consolatoire : la comparaison avec Palamède et Ajax, héros victimes de procès injustes, ennoblit le sort de Socrate. Il y a enfin une dimension ironique vis-à-vis des jurés athéniens : les vrais juges ne sont pas à Athènes, mais dans l’Hadès, et Socrate se réjouit d’aller les retrouver. Ce passage a suscité des interprétations contrastées. Certains commentateurs y voient une réelle espérance platonicienne en l’immortalité de l’âme, telle qu’elle sera développée dans le ''Phédon''<ref>Platon, ''Phédon'', 80a-84b, 105c-107a.</ref>. D’autres, comme Chrétien, soulignent que le Socrate de l’''Apologie'' reste fondamentalement agnostique : il présente deux hypothèses, il ne tranche pas entre elles, et l’imagination y a au moins autant de part que la raison<ref>Chrétien, ''op. cit.'', p. 32-36.</ref>. Socrate lui-même conclut prudemment : <blockquote>aucun mal ne peut toucher un homme de bien ni pendant sa vie ni après sa mort, et les dieux ne se désintéressent pas de son sort. (41d)</blockquote> Cette conclusion n’affirme pas dogmatiquement une survie, mais énonce une foi pratique : quoi qu’il arrive, le juste n’a rien à craindre. L’''Apologie'', contrairement au ''Phédon'', ne construit pas de doctrine positive sur l’immortalité ; elle se tient au seuil d’une telle doctrine, dans un agnosticisme serein. ===== Le testament (41e-42a) ===== Socrate clôt son discours par un testament pour ses fils. Il ne demande qu’une chose aux Athéniens : <blockquote>Quand mes fils seront grands, punissez-les, citoyens, en les tourmentant comme je vous tourmentais, pour peu qu’ils vous paraissent se soucier d’argent ou de n’importe quoi d’autre plus que de la vertu. Et, s’ils croient être quelque chose, alors qu’ils ne sont rien, adressez-leur le reproche que je vous adressais. (41e)</blockquote> La mission philosophique se transmet ainsi, comme un héritage inversé : Socrate demande à ses bourreaux de devenir eux-mêmes, envers ses enfants, ce qu’il était pour eux, des tourmenteurs par la vertu. C’est le pardon actif d’un homme qui refuse de laisser sa mort briser la chaîne de l’examen. Puis vient la dernière phrase, l’une des plus célèbres de la littérature philosophique : <blockquote>Mais voici déjà l’heure de partir, moi pour mourir et vous pour vivre. De mon sort ou du vôtre lequel est le meilleur ? La réponse reste incertaine pour tout le monde, sauf pour la divinité. (''plḕn hē tôi theôi'', 42a)</blockquote> Cette clôture ouvre, sur l’indécidable, l’agnosticisme ultime. Socrate ne sait pas, nul ne sait, lequel est le plus heureux, de lui qui va mourir ou de ses juges qui vont continuer à vivre. Seule la divinité le sait. L’''Apologie'' se ferme ainsi sur le mot même de la sagesse socratique : la reconnaissance de l’ignorance humaine, couplée à la confiance paisible en un ordre divin qui excède notre mesure. Rien n’est affirmé dogmatiquement ; tout se termine sur une interrogation qui n’attend pas de réponse humaine. C’est une fin d’une sobriété admirable, qui évite à la fois la plainte et la consolation artificielle. Elle est, par son rythme et par son contenu, digne d’une page d’évangile ou d’un chapitre des ''Pensées'' de Marc-Aurèle. == Concepts et thèmes majeurs == Une lecture suivie ne serait pas complète sans reprendre quelques-uns des grands thèmes qui courent à travers le texte et en font la portée philosophique durable. === La sagesse humaine : le savoir du non-savoir === Le concept central de l’''Apologie'' est celui de la sagesse humaine (''anthrōpínē sophía'', 20d). Socrate n’est pas savant au sens fort du terme, c’est-à-dire à la manière des sophistes qui prétendaient posséder un savoir sur les choses divines, sur la nature, sur la politique, sur la vertu. Mais il possède une sagesse proprement humaine, qui consiste à reconnaître que l’on ne sait pas. Ce savoir du non-savoir n’est pas un scepticisme, encore moins un renoncement. C’est une position éthique : celui qui sait qu’il ne sait pas est disposé à chercher, à interroger, à examiner ; celui qui croit savoir est fermé à toute remise en question et, par là même, incapable de tout progrès. Cette sagesse n’est pourtant pas purement négative. Socrate affirme savoir au moins deux choses : qu’il est mauvais et laid de commettre l’injustice et de désobéir à un meilleur que soi (29b), et que la vie non examinée n’est pas digne d’être vécue (38a). De ces deux « savoirs » découle tout le comportement de Socrate au procès : il ne peut trahir la justice pour sauver sa vie, et il ne peut cesser d’examiner les autres sans trahir sa vocation propre. On voit donc que le savoir du non-savoir n’est pas une position d’ignorance totale, mais une structure articulée : une ignorance avouée sur les grandes questions métaphysiques, et une certitude pratique sur les exigences éthiques. Cette combinaison fait du socratisme une forme de philosophie pratique : elle rend possible l’action juste sans requérir une science achevée. Il faut enfin noter que le savoir du non-savoir est peut-être la thèse la plus féconde du socratisme pour l’histoire de la pensée. Toute la philosophie postérieure, chaque fois qu’elle commence par un doute méthodique (Descartes), par une critique des prétentions de la raison (Kant), par une déconstruction des évidences (phénoménologie), s’inscrit dans le sillage de l’interrogation socratique. La philosophie moderne est, en ce sens, socratique par son geste inaugural, même quand elle ne l’est plus par ses conclusions. === La méthode de l’elenchus === L’''Apologie'' donne à voir, en acte, la méthode philosophique propre de Socrate : l’elenchus ou réfutation par interrogation. Elle apparaît à plusieurs reprises, mais surtout dans l’interrogatoire de Mélétos (24b-28a). Son fonctionnement est simple dans son principe : Socrate ne pose pas directement ses propres thèses ; il prend pour point de départ celles de son interlocuteur, puis, par une série de questions dont chacune requiert une réponse qui semble évidente, il conduit l’interlocuteur à reconnaître des conséquences incompatibles avec d’autres thèses qu’il tient également pour vraies. La contradiction ainsi mise au jour n’est pas celle de Socrate, mais celle de l’interlocuteur avec lui-même. Cette méthode a plusieurs vertus philosophiques. D’abord, elle respecte la liberté de l’interlocuteur : Socrate ne lui impose rien, il l’amène seulement à voir ce qu’il pensait déjà. Ensuite, elle produit un savoir négatif sûr : la réfutation, à défaut de démontrer le vrai, prouve au moins que la thèse examinée est fausse. Enfin, elle a un effet moral : elle introduit chez l’interlocuteur l’expérience de l’''aporía'' (la perplexité), qui est le point de départ possible d’une recherche véritable. L’interlocuteur, déchargé de son illusion de savoir, peut commencer à apprendre. L’elenchus est ainsi, au sens propre, maïeutique : il fait accoucher les esprits. Mais l’elenchus a aussi ses limites, que l’''Apologie'' laisse apercevoir. Il peut blesser l’amour-propre ; il peut créer des haines durables ; il peut donner à ceux qui en sont la cible l’impression d’être humiliés publiquement. Socrate lui-même en témoigne : son enquête a suscité contre lui des rancœurs innombrables. La philosophie a un coût social, que l’elenchus rend visible avec une particulière acuité. === Le souci de l’âme === Le message moral central de l’''Apologie'' tient dans une exhortation : il faut se soucier prioritairement de son âme (''psuchḗ'') et de son amélioration, non de son corps, de sa richesse ou de sa réputation (29d-30b). <blockquote>La vertu ne naît pas de l’argent, mais c’est de la vertu que naissent et l’argent et tout le reste des biens utiles aux hommes, aussi bien privés que publics. (30b)</blockquote> Ce renversement est l’une des sources principales de la morale occidentale : la hiérarchie des biens est réordonnée autour du bien intérieur, et la richesse extérieure n’a de valeur qu’en tant qu’elle découle d’une vertu préalable. Ce souci de soi (''epiméleia heautoû'') n’est pas égoïste. Il est au contraire la condition du souci des autres : on ne peut aider autrui à améliorer son âme sans avoir travaillé à la sienne. Socrate harcèle ses concitoyens parce qu’il veut qu’ils prennent soin de leur âme, non parce qu’il veut sauver la sienne à ses dépens. Et c’est précisément parce qu’il se soucie de la cité qu’il la secoue : le taon pique le cheval pour le réveiller, non pour le faire souffrir. Cette thèse a eu une postérité immense. Elle est reprise, transformée, intériorisée par les écoles hellénistiques (stoïciens, épicuriens), qui en font le cœur de la sagesse pratique. Elle passe ensuite dans le christianisme, où le « soin de l’âme » devient le salut personnel. À l’époque moderne, Michel Foucault lui a consacré une part importante de ses derniers cours, voyant dans le souci de soi une alternative à l’éthique cartésienne fondée sur la seule connaissance de soi<ref>Michel Foucault, ''Le Souci de soi'' (''Histoire de la sexualité'', t. III), Paris, Gallimard, 1984 ; et ''L’Herméneutique du sujet'', ''op. cit.''</ref>. L’''Apologie'' est à l’origine de cette longue tradition, même si le souci de soi socratique reste, par certains aspects, très différent des élaborations postérieures : il est moins un travail sur soi qu’un examen de soi par la discussion avec autrui. === La philosophie comme mission divine === L’''Apologie'' fonde la légitimité de la philosophie sur une mission divine. Socrate ne philosophe pas par goût ou par choix : il le fait parce que le dieu lui en a fait l’ordre, à travers l’oracle de Delphes et à travers le démon. Cette dimension religieuse est essentielle pour comprendre la posture socratique. S’il pouvait cesser d’interroger, il le ferait peut-être (c’est une activité ingrate et dangereuse) ; mais il ne le peut pas, car ce serait désobéir au dieu. La philosophie est ainsi un service sacré, équivalent à celui des prêtres ou des devins, mais accompli par d’autres moyens : non par le rite, mais par l’examen rationnel. Cette dimension place Socrate dans une position paradoxale par rapport aux accusations d’impiété. L’homme que l’on accuse de ne pas croire aux dieux est en réalité celui qui les sert le plus fidèlement, au point de mourir pour leur obéir. Platon retourne ainsi l’accusation : les véritables impies sont ceux qui, en condamnant Socrate, refusent le présent que le dieu leur a fait. Le procès apparaît alors sous un jour inversé : non plus un acte de piété de la cité contre un impie, mais un acte d’impiété de la cité contre un serviteur du dieu. Ce thème a eu un écho particulier dans la tradition chrétienne, qui a parfois vu en Socrate une figure prophétique du Christ : un juste mis à mort par une communauté religieuse qui croyait servir ses dieux en le tuant. Justin martyr, au IIᵉ siècle, comparera explicitement Socrate et Jésus, voyant dans le philosophe athénien une préfiguration providentielle de la Passion<ref>Justin de Naplouse, ''Première Apologie'', 46, 1-4 : les chrétiens considèrent comme chrétiens avant la lettre « ceux qui ont vécu avec le Logos [...] parmi les Grecs, Socrate, Héraclite et ceux qui leur furent semblables ». Voir aussi ''Seconde Apologie'', 10, 5-6.</ref>. La philosophie chrétienne primitive a ainsi trouvé dans l’''Apologie'' une matrice pour penser la martyrologie. === La justice supérieure === Le thème de la justice traverse tout le texte. Socrate se présente comme un homme profondément respectueux des lois : il a risqué sa vie pour le respect de la procédure sous la démocratie (affaire des Arginuses) ; il mourra par fidélité aux lois dans le ''Criton'' plutôt que de s’évader. Mais son obéissance aux lois n’est pas inconditionnelle : il y a une justice supérieure, fondée sur des valeurs, qui commande dans certains cas la désobéissance civile, comme lorsqu’il refuse d’exécuter les ordres des Trente concernant Léon de Salamine. Cette justice supérieure n’est pas un simple idéal abstrait ; elle est, pour Socrate, ce qui fait le prix (''axía'') de la vie humaine. Elle a une origine divine et s’impose à la conscience au-delà des conventions sociales. On voit déjà poindre ici, avant les développements platoniciens de la ''République'', l’idée d’une justice en soi, distincte de la justice légale, et qui fonde celle-ci sans s’y réduire. Antigone, chez Sophocle, invoquait déjà les « lois non écrites » des dieux contre les décrets humains<ref>Sophocle, ''Antigone'', v. 450-460.</ref> ; Socrate, à sa manière, s’inscrit dans cette tradition. Mais il la rationalise : ce n’est plus le simple respect d’une tradition familiale ou religieuse, c’est la fidélité à un ordre supérieur accessible par l’examen rationnel. La philosophie devient ainsi le lieu où se manifeste cette justice transcendante, dont les lois humaines ne sont qu’une approximation imparfaite. Cette double fidélité (aux lois de la cité et à une justice supérieure) est source d’une tension qui traversera toute la tradition philosophique et juridique occidentale. Elle est au cœur des doctrines du droit naturel<ref>Voir notamment Cicéron, ''De republica'', III, 33 sur la ''lex vera'' ; Thomas d’Aquin, ''Somme théologique'', I-II, q. 94 sur la loi naturelle.</ref>, des théories modernes de la désobéissance civile (Thoreau, Gandhi, Martin Luther King), et des débats contemporains sur la légitimité du droit positif. === La mort et le courage === Le rapport de Socrate à la mort est une pièce maîtresse du texte. Sa thèse est à double face. D’un côté, la mort en elle-même est inconnue : nul ne sait si elle est un mal ou un bien, et la craindre comme un mal certain est la plus répréhensible des ignorances. De l’autre, il y a pire que la mort : la lâcheté, l’injustice, l’abandon de son poste. Le courage socratique n’est donc pas, comme celui des héros homériques, la volonté exaltée de mourir pour l’honneur ; c’est la lucidité sur ce qui est réellement à craindre, non la mort mais le vice. Ce renversement a nourri toute la morale stoïcienne (qui en fait un de ses principes cardinaux : ne rien craindre de ce qui ne dépend pas de nous, donc pas la mort) puis, par des voies détournées, la pensée chrétienne du martyre. Il faut insister sur la finesse de l’analyse socratique. Elle ne dit pas : « la mort est un bien » (affirmation dogmatique contraire à son savoir du non-savoir). Elle ne dit pas non plus : « la mort est indifférente » (comme le diront plus tard les stoïciens). Elle dit : « la mort est inconnue, donc je ne peux la craindre comme un mal certain ; mais l’injustice est un mal certain, donc je peux la craindre ». Le courage n’est pas fondé sur une espérance métaphysique, mais sur une hiérarchie des savoirs : le connu prime sur l’inconnu, et j’organise ma conduite en fonction de ce que je sais. Cette analyse aura un long destin. Épicure la reprendra pour dire : « la mort n’est rien pour nous »<ref>Épicure, ''Lettre à Ménécée'', § 125.</ref>. Les stoïciens la transformeront en doctrine de l’indifférence aux choses externes. Montaigne en fera un objet central de ses ''Essais''<ref>Montaigne, ''Essais'', I, 20 « Que philosopher, c’est apprendre à mourir » ; III, 12 « De la physionomie ».</ref>. Heidegger, au XXᵉ siècle, retournera au Socrate de l’''Apologie'' en interrogeant le rapport authentique à la mort comme condition d’une existence propre<ref>Martin Heidegger, ''Sein und Zeit'' (1927), § 46-53, sur l’''être-pour-la-mort''.</ref>. Chaque fois, c’est le même geste inaugural qui est repris, celui d’une mort désarmée par la pensée. === Philosophie et cité : l’autre politique === L’''Apologie'' esquisse une conception originale de la politique. Socrate se déclare non-politique au sens courant du terme : il n’a pas fréquenté l’assemblée, il n’a pas cherché les magistratures, il n’a pas fait carrière publique. Mais il se présente comme le plus politique des Athéniens au sens profond : il s’est occupé de la cité elle-même (plus que de ses affaires), en se souciant du perfectionnement de ses concitoyens. C’est une autre politique, qui ne passe pas par les institutions officielles (corrompues selon lui par la démagogie et l’ignorance) mais par la conversation privée, par l’interpellation personnelle, par la formation morale. Cette vision a un versant critique radical vis-à-vis de la démocratie athénienne, qui émerge des épisodes des Arginuses et de Léon de Salamine : la démocratie, livrée à ses passions, peut se conduire de manière aussi injuste qu’une tyrannie. Il serait naïf de voir en Socrate un démocrate simple ou un anti-démocrate simple ; il est un critique des deux régimes en tant qu’ils s’éloignent de la justice. Mais sa critique vise, au-delà des régimes, la capacité même des collectivités humaines à délibérer justement : « il n’y a personne au monde qui puisse garder la vie sauve s’il s’oppose loyalement à vous ou à toute autre collectivité » (32a). Ce diagnostic, désabusé, n’est pas tant politique qu’anthropologique : les foules, quelles qu’elles soient, résistent mal à l’examen rationnel. Mais cette critique a aussi un versant constructif. La philosophie, par l’examen qu’elle exerce sur les esprits, prépare la possibilité d’une politique véritablement juste. Platon développera cette intuition dans la ''République'', en allant jusqu’à imaginer une cité où les philosophes seraient rois, mais ce développement excède le cadre de l’''Apologie''. Au moment où Platon écrit ce texte, la cité idéale n’est pas encore pensée ; il y a seulement la dénonciation d’une cité qui a tué son meilleur citoyen, et la promesse implicite d’une pensée qui prolongera la mission interrompue. === L’ironie socratique === Un mot enfin sur l’ironie, qui est omniprésente dans le texte, et qu’il faut distinguer en plusieurs registres. Il y a d’abord l’ironie au sens étroit : dire le contraire de ce que l’on pense, comme lorsque Socrate qualifie Mélétos de « bon citoyen » et « patriote ». Il y a ensuite l’ironie dialectique : amener l’interlocuteur à se contredire lui-même par des questions prétendument naïves, alors que Socrate sait parfaitement où il le mène. Il y a enfin l’ironie existentielle : vivre de telle façon que toute son existence est un démenti de ce qu’on attendrait d’un homme dans sa situation. La proposition d’être nourri au Prytanée relève de cette dernière : Socrate, condamné, se présente comme un bienfaiteur ; il retourne les rôles, fait du tribunal une scène tragicomique. Cette ironie n’est pas seulement un trait de style. Elle est l’expression d’une distance philosophique à l’égard des conventions et des évidences. Celui qui a compris que le savoir commun est illusoire, que la rhétorique est trompeuse, que les hiérarchies sociales reposent sur des malentendus, ne peut plus prendre au sérieux les rituels qui ordonnent la vie ordinaire. L’ironie socratique est le signe visible d’une conscience libre, qui refuse de se soumettre aux attentes. C’est pourquoi Søren Kierkegaard, au XIXᵉ siècle, en a fait dans sa thèse sur ''Le Concept d’ironie'' le trait caractéristique de la subjectivité philosophique naissante : avec Socrate, la conscience se sépare du monde, s’intériorise, devient sujet<ref name="kierkegaard">Søren Kierkegaard, ''Le Concept d’ironie constamment rapporté à Socrate'' (''Om Begrebet Ironi'', 1841), trad. P.-H. Tisseau et E.-M. Jacquet-Tisseau, dans ''Œuvres complètes'', t. II, Paris, Éditions de l’Orante, 1975.</ref>. L’ironie est ce mode de la subjectivité qui se pose en se distinguant de ce qui est. == Questions de lecture et postérité == === Le Socrate de l’''Apologie'' et le Socrate historique === Une question classique est de savoir dans quelle mesure l’''Apologie'' restitue fidèlement le plaidoyer effectivement prononcé par Socrate en 399. Les éléments de réponse sont partiels. D’une part, Platon, jeune témoin du procès (il avait environ vingt-huit ans), avait de puissantes raisons d’être fidèle : la mémoire des jurés qui liraient le texte était fraîche, et toute infidélité flagrante aurait nui à la thèse défensive. D’autre part, Xénophon, dans sa propre ''Apologie'', confirme certains points centraux (l’oracle de Delphes, le refus de préparer sa défense, le rôle du démon, l’attitude provocante devant le tribunal). Les convergences entre Platon et Xénophon, qui écrivent séparément, garantissent la réalité d’un noyau historique. Pour autant, l’''Apologie'' de Platon est une œuvre écrite, composée probablement plusieurs années après le procès, et qui obéit aux lois de la composition littéraire. La structure en trois discours parfaitement articulée, la densité dialectique de l’interrogatoire de Mélétos, la beauté rythmique de certaines périodes (la finale : « moi pour mourir et vous pour vivre… ») relèvent de l’art platonicien. Le témoignage historique et la recréation littéraire ne sont pas dissociables. La question du « Socrate historique » a été débattue à l’infini. On distingue traditionnellement plusieurs Socrate : celui d’Aristophane (caricatural), celui de Xénophon (pragmatique, moraliste de bon sens), celui de Platon (dialectique et idéaliste), celui d’Aristote (prédécesseur des idées, attribuant à Socrate la recherche des définitions universelles et l’induction<ref>Aristote, ''Métaphysique'', XIII, 4, 1078b17-30 : « deux choses peuvent à bon droit être attribuées à Socrate : les raisonnements inductifs et la définition universelle ».</ref>). Lequel est le vrai ? La réponse la plus raisonnable est qu’aucun ne l’est entièrement, mais qu’ils donnent, collectivement, une image composite d’un homme dont la puissance personnelle a dépassé de beaucoup ce que les documents peuvent restituer. L’''Apologie'' est probablement, de tous les textes, celui qui se tient le plus près de la voix réellement entendue, parce que Platon y a choisi, exceptionnellement, de s’effacer devant son maître. Il est usuel, chez les commentateurs, de distinguer dans l’''Apologie'' des couches. Certains éléments semblent historiques au plus haut degré : le cadre procédural, les noms des accusateurs, l’attitude refusant la supplication, la référence à Chéréphon (mort avant le procès, mais dont les héritiers étaient vivants pour confirmer ou démentir), la condamnation et son déroulement. D’autres éléments sont vraisemblablement platoniciens : l’articulation en trois discours parfaitement équilibrés, la méditation finale sur la mort, peut-être la prophétie adressée aux juges condamnateurs. Mais tout cela relève d’appréciations délicates, et Platon lui-même dans la ''Lettre VII'' revendique le droit de penser philosophiquement à partir du Socrate qu’il a connu<ref>Platon, ''Lettre VII'', 324d-326b. Sur la question socratique, voir Louis-André Dorion, ''Socrate'', Paris, PUF, coll. « Que sais-je ? », 2004.</ref>. === La postérité de l’''Apologie'' === [[Fichier:Statues of Plato (left) and Socrates (right) by Leonidas Drosis at the Academy of Athens.jpg|vignette|droite|upright=1.2|Statues de Platon (à gauche) et de Socrate (à droite), par le sculpteur Leonidas Drosis (seconde moitié du XIX{{e}} siècle), à l’entrée de l’Académie d’Athènes. La réunion des deux figures sur le fronton d’une institution savante moderne témoigne de la postérité canonique que le procès aura contribué à instituer.]] L’''Apologie de Socrate'' est l’un des textes les plus lus, les plus imités, les plus commentés de la philosophie occidentale. Sa postérité ne se laisse pas résumer en quelques lignes, mais on peut en indiquer quelques étapes majeures. Dans l’Antiquité, l’''Apologie'' est immédiatement imitée : Xénophon en donne sa version ; des apologies perdues, d’Eschine de Sphettos ou de Lysias, avaient également circulé. Au IIᵉ siècle, Apulée compose une ''Apologie'' (sur son propre procès en magie) qui imite la structure platonicienne<ref>Apulée, ''Apologie ou De la magie'', texte établi et traduit par Paul Vallette, Paris, Les Belles Lettres, 1924.</ref>. Les écoles hellénistiques (stoïciens, cyniques) prennent Socrate comme figure tutélaire du sage qui meurt pour sa vérité : Épictète et Marc Aurèle se réfèrent constamment à lui<ref>Voir notamment Épictète, ''Entretiens'', II, 1, 32 ; II, 2, 8-20 ; et Marc Aurèle, ''Pensées'', VII, 19 ; XI, 25, 28.</ref>. Les cyniques voient en lui le philosophe de la pauvreté et de la provocation, exemple d’une existence libre des conventions. La tradition chrétienne primitive trouve dans Socrate une figure prophétique. Justin martyr, au IIᵉ siècle, fait de Socrate un « chrétien avant le Christ », guidé par le Logos divin<ref>Justin, ''Première Apologie'', 46 ; ''Seconde Apologie'', 10.</ref>. Les Pères de l’Église le mentionnent souvent, tantôt pour s’en réclamer (Clément d’Alexandrie, Origène), tantôt pour le mettre à distance (Tertullien)<ref>Clément d’Alexandrie, ''Stromates'', I, 14 ; Tertullien, ''Apologétique'', 46.</ref>. La mort de Socrate, rapprochée du martyre, fournit un modèle de témoignage pour la vérité. Toute la hagiographie martyrologique se nourrit, en partie, de l’''Apologie''. À la Renaissance, la redécouverte de Platon par Marsile Ficin et l’Académie florentine met l’''Apologie'' au centre du canon philosophique<ref>Marsile Ficin traduit les œuvres complètes de Platon en latin (''Platonis Opera Omnia'', Florence, 1484), rendant l’''Apologie'' accessible à l’Europe savante.</ref>. Montaigne, dans les ''Essais'', lui consacre plusieurs chapitres et voit en Socrate la figure même de la sagesse sans science, du bon sens humain qui vaut mieux que toutes les spéculations. <blockquote>Socrate fait mouvoir son âme d’un mouvement naturel et commun. Ainsi dit un paysan, ainsi dit une femme. (Montaigne, ''Essais'', III, 12)<ref>Montaigne, ''Essais'', III, 12 « De la physionomie », édition Villey-Saulnier, PUF, 1965, p. 1037-1038.</ref></blockquote> À l’époque des Lumières, l’''Apologie'' devient un manifeste anticlérical. Voltaire y voit le procès de l’intolérance religieuse, Diderot une défense de la libre pensée, Rousseau un modèle d’éthique civile. David peint ''La Mort de Socrate'' (1787)<ref>Jacques-Louis David, ''La Mort de Socrate'', 1787, huile sur toile, 129,5 × 196,2 cm, New York, Metropolitan Museum of Art.</ref>, tableau emblématique où le philosophe saisit la coupe avec sérénité tandis que ses disciples se lamentent : image iconique qui influencera durablement l’imaginaire philosophique. Au XIXᵉ siècle, Hegel, Kierkegaard et Nietzsche proposent trois lectures marquantes. Hegel, dans ses ''Leçons sur l’histoire de la philosophie'', voit Socrate comme le moment où la conscience morale s’intériorise, et dans sa condamnation le conflit tragique entre l’ancienne cité et la subjectivité naissante<ref>G. W. F. Hegel, ''Leçons sur l’histoire de la philosophie'', t. II, trad. P. Garniron, Paris, Vrin, 1971, p. 263-328.</ref>. Kierkegaard fait de l’ironie socratique, analysée dans ''Le Concept d’ironie'' (1841), le modèle de la subjectivité existentielle<ref name="kierkegaard" />. Nietzsche, dans ''La Naissance de la tragédie'' (1872), voit au contraire en Socrate le destructeur du tragique grec, l’homme théorique qui substitue la raison critique à la sagesse instinctive, et accomplit ainsi une « monstruosité par défaut »<ref name="nietzsche-nt" />. Les trois lectures sont opposées, mais elles attestent toutes la centralité de Socrate dans la pensée moderne. Au XXᵉ siècle, l’''Apologie'' continue d’être un lieu de lecture privilégié. Hannah Arendt, dans ''La Vie de l’esprit'', en fait le modèle de la pensée responsable<ref>Hannah Arendt, ''La Vie de l’esprit'', trad. L. Lotringer, Paris, PUF, 1981 ; voir aussi « Philosophie et politique », ''Les Cahiers de philosophie'', n° 4, 1987.</ref>. Michel Foucault, dans ses derniers cours au Collège de France (''Le Courage de la vérité'', 1984), y voit le texte fondateur de la ''parrhēsía'', c’est-à-dire du « franc-parler » philosophique qui engage la vie de celui qui parle<ref>Michel Foucault, ''Le Courage de la vérité. Le Gouvernement de soi et des autres II. Cours au Collège de France, 1983-1984'', éd. F. Gros, Paris, Gallimard/Seuil, 2009.</ref>. Leo Strauss et son école ont proposé une relecture « ésotérique » du texte, attentive à ce que Socrate ne dit pas et à ce qu’il suggère entre les lignes<ref>Leo Strauss, ''Studies in Platonic Political Philosophy'', Chicago, University of Chicago Press, 1983 ; ''The City and Man'', Chicago, Rand McNally, 1964.</ref>. Lire l’''Apologie'' aujourd’hui, c’est donc entrer dans un texte sédimenté par vingt-quatre siècles de commentaires, et qui n’a pas encore épuisé sa charge philosophique. Chaque époque y trouve un Socrate à sa mesure, et c’est peut-être la marque des grandes œuvres de pouvoir soutenir cette pluralité indéfinie d’interprétations. == Conclusion == L’''Apologie de Socrate'' n’est pas un plaidoyer ordinaire. Ce n’est même pas, à proprement parler, un plaidoyer au sens technique, puisque Socrate y refuse presque toutes les tactiques qui auraient pu le faire acquitter : il ne se fait pas écrire par un logographe, il ne supplie pas, il ne fait pas paraître ses enfants, il provoque le jury quand il lui faudrait le ménager. C’est une profession de foi philosophique, prononcée au moment où la vie de l’auteur est en jeu, et qui tire précisément de cet enjeu sa gravité unique. L’''Apologie'' est la preuve par la mort d’une thèse que Socrate n’a cessé de soutenir par les mots : la vertu vaut plus que la vie. Trois traits en font un texte fondateur. D’abord, il inaugure la figure du philosophe comme témoin : celui dont la cohérence entre la parole et la vie va jusqu’au sacrifice. Socrate meurt parce qu’il ne veut pas trahir ce qu’il a dit ; et Platon, en écrivant l’''Apologie'', fait de cette mort le sceau de la vérité philosophique. La philosophie, à partir de ce texte, n’est plus seulement une activité intellectuelle : elle est un engagement existentiel, et le philosophe n’est plus seulement celui qui sait, mais celui qui vit ce qu’il dit. Ensuite, le texte définit la philosophie elle-même, en l’opposant à la sophistique, à la rhétorique et à la religion populaire. Non un savoir constitué, mais un examen ; non une éloquence, mais une recherche du vrai ; non un rite, mais un service intérieur du divin. Cette triple opposition structure toute la pensée platonicienne ultérieure et, par voie de conséquence, la pensée occidentale. La philosophie, depuis Socrate, se définit comme ce qui n’est pas sophistique : un savoir désintéressé, inséparable d’une pratique de vérité. Enfin, le texte pose la question politique dans ses termes platoniciens : la cité peut-elle accueillir le philosophe ? La condamnation de Socrate se laisse lire comme une réponse négative qu’Athènes aurait donnée en acte à cette question, et c’est ainsi que Platon semble l’interpréter. L’œuvre de Platon tout entière tentera de penser une cité qui répondrait autrement, depuis les esquisses du ''Gorgias'' et de la ''République'' jusqu’à la construction ultime des ''Lois''. Le procès de Socrate est ainsi, indirectement, à l’origine de la philosophie politique occidentale. Lire l’''Apologie'' aujourd’hui, c’est s’exposer à une exigence intacte. La vie non examinée n’est pas digne d’être vécue ; la vertu vaut plus que l’argent, que la réputation, que la vie même ; la lâcheté est pire que la mort ; le juste préfère subir l’injustice plutôt que la commettre. Ces formules, qui paraissent extrêmes, sont le cœur d’une éthique dont la philosophie occidentale n’a cessé de se réclamer, même quand elle croyait s’en affranchir. Le taon, vingt-quatre siècles plus tard, pique toujours. == Notes et références == {{Références|colonnes = 2}} == Bibliographie sélective == === Éditions et traductions de l’''Apologie'' === * Platon, ''Apologie de Socrate. Criton'', traduction, introduction et notes par Luc Brisson, Paris, GF-Flammarion, 2016. * Platon, ''Apologie de Socrate'', traduction par Luc Brisson, présentation, notes, dossier, répertoire et glossaire par Arnaud Macé, Paris, GF-Flammarion, 2017. * Platon, ''Apologie de Socrate'', traduction, présentation et notes de Bernard et Renée Piettre, Paris, Le Livre de Poche (Librairie générale française), coll. « Libretti », 1997. * Platon, ''Apologie de Socrate'', texte établi et traduit par Maurice Croiset, Paris, Les Belles Lettres, coll. des Universités de France, 1920 (nombreuses rééditions). * Platon, ''Œuvres complètes'', sous la direction de Luc Brisson, Paris, Flammarion, 2008 (contient l’''Apologie''). === Commentaires === * Claude Chrétien, ''Platon, Apologie de Socrate'', Paris, Hatier, coll. « Profil philosophie », 1993. * Paul Allen Miller, Charles Platter, ''Plato’s Apology of Socrates: A Commentary'', Norman, University of Oklahoma Press, 2010. * Émile de Strycker, S. R. Slings, ''Plato’s Apology of Socrates: A Literary and Philosophical Study with a Running Commentary'', édité et complété par S. R. Slings, Leyde, E. J. Brill, coll. « Mnemosyne Supplements » 137, 1994. * Thomas G. West, ''Plato’s Apology of Socrates: An Interpretation with a New Translation'', Ithaca, Cornell University Press, 1979. === Études sur Socrate et l’''Apologie'' === * Louis-André Dorion, ''Socrate'', Paris, PUF, coll. « Que sais-je ? », 2004. * Gregory Vlastos, ''Socrate : ironie et philosophie morale'', trad. C. Dalimier, Paris, Aubier, 1994 (''Socrates: Ironist and Moral Philosopher'', 1991). * Gregory Vlastos, « The Socratic Elenchus », ''Oxford Studies in Ancient Philosophy'', I, 1983, p. 27-58. * Debra Nails, ''The People of Plato: A Prosopography of Plato and Other Socratics'', Indianapolis, Hackett, 2002. * Gabriele Giannantoni, ''Socratis et Socraticorum Reliquiae'', 4 vol., Naples, Bibliopolis, 1990. * Monique Canto-Sperber (dir.), ''Les Paradoxes de la connaissance. Essais sur le Ménon de Platon'', Paris, Odile Jacob, 1991. === Réceptions modernes === * G. W. F. Hegel, ''Leçons sur l’histoire de la philosophie'', t. II, trad. P. Garniron, Paris, Vrin, 1971. * Søren Kierkegaard, ''Le Concept d’ironie constamment rapporté à Socrate'' (1841), trad. P.-H. Tisseau et E.-M. Jacquet-Tisseau, Paris, Éditions de l’Orante, 1975. * Friedrich Nietzsche, ''La Naissance de la tragédie'' (1872), dans ''Œuvres philosophiques complètes'', t. I, Paris, Gallimard, 1977. * E. R. Dodds, ''Les Grecs et l’irrationnel'' (1951), trad. M. Gibson, Paris, Flammarion, coll. « Champs », 1977. * Hannah Arendt, ''La Vie de l’esprit'', trad. L. Lotringer, Paris, PUF, 1981. * Michel Foucault, ''L’Herméneutique du sujet. Cours au Collège de France, 1981-1982'', éd. F. Gros, Paris, Gallimard/Seuil, 2001. * Michel Foucault, ''Le Courage de la vérité. Cours au Collège de France, 1983-1984'', éd. F. Gros, Paris, Gallimard/Seuil, 2009. * Leo Strauss, ''Studies in Platonic Political Philosophy'', Chicago, University of Chicago Press, 1983. === Sources antiques === * Aristophane, ''Les Nuées'', dans ''Théâtre complet'', t. I, trad. V.-H. Debidour, Paris, Gallimard, coll. « Folio », 1965. * Xénophon, ''Apologie de Socrate'' et ''Mémorables'', trad. P. Chambry, Paris, Garnier-Flammarion, 1967. * Diogène Laërce, ''Vies et doctrines des philosophes illustres'', trad. sous la direction de M.-O. Goulet-Cazé, Paris, Le Livre de Poche, coll. « La Pochothèque », 1999. * Plutarque, ''Du démon de Socrate'', dans ''Œuvres morales'', t. VIII, trad. J. Hani, Paris, Les Belles Lettres, 1980. * Aristote, ''Rhétorique'', trad. M. Dufour et A. Wartelle, Paris, Les Belles Lettres, 1938-1973. * Cicéron, ''Tusculanes'', trad. J. Humbert, Paris, Les Belles Lettres, 1931. {{AutoCat}} fuxtiakhcr2p4zpyuau1nnsjb1k6v25 Fonctionnement d'un ordinateur/Le parallélisme mémoire au niveau du cache 0 83830 764636 2026-04-23T13:37:03Z Mewtow 31375 Mewtow a déplacé la page [[Fonctionnement d'un ordinateur/Le parallélisme mémoire au niveau du cache]] vers [[Fonctionnement d'un ordinateur/Le parallélisme mémoire]] : Elargissement du sujet du chapitre 764636 wikitext text/x-wiki #REDIRECTION [[Fonctionnement d'un ordinateur/Le parallélisme mémoire]] mwgp3tyo4nz5vwietrgqp3puyz825v3